Use card (#19)

* the process of using card (uncompleted)

* code style: tab is 2 spaces(not \t or 4 space)

* update lua54.dll to MinGW version(no cygwin1.dll required)

* basic ui logic

* ActiveSkill

* modidy ActiveSkill defaults

* todo: defaultEquipSkill

* client

* send use card to server

* playing phase, equip

Co-authored-by: Ho-spair <linyuy@163.com>
This commit is contained in:
notify 2022-04-30 15:27:56 +08:00 committed by GitHub
parent fd2d7b4d10
commit dedde94643
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 7268 additions and 6314 deletions

View File

@ -3,10 +3,10 @@ cmake_minimum_required(VERSION 3.16)
project(FreeKill VERSION 0.0.1) project(FreeKill VERSION 0.0.1)
find_package(Qt5 REQUIRED COMPONENTS find_package(Qt5 REQUIRED COMPONENTS
Gui Gui
Qml Qml
Network Network
Multimedia Multimedia
) )
find_package(Lua) find_package(Lua)

View File

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 624 B

View File

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

BIN
lib/win/lua54.dll Normal file → Executable file

Binary file not shown.

View File

@ -5,140 +5,140 @@ Client = class('Client')
-- load client classes -- load client classes
ClientPlayer = require "client.clientplayer" ClientPlayer = require "client.clientplayer"
dofile "lua/client/client_util.lua"
fk.client_callback = {} fk.client_callback = {}
function Client:initialize() function Client:initialize()
self.client = fk.ClientInstance self.client = fk.ClientInstance
self.notifyUI = function(self, command, jsonData) self.notifyUI = function(self, command, jsonData)
fk.Backend:emitNotifyUI(command, jsonData) fk.Backend:emitNotifyUI(command, jsonData)
end end
self.client.callback = function(_self, command, jsonData) self.client.callback = function(_self, command, jsonData)
local cb = fk.client_callback[command] local cb = fk.client_callback[command]
if (type(cb) == "function") then if (type(cb) == "function") then
cb(jsonData) cb(jsonData)
else else
self:notifyUI(command, jsonData); self:notifyUI(command, jsonData);
end
end end
end
self.players = {} -- ClientPlayer[] self.players = {} -- ClientPlayer[]
end end
---@param id integer ---@param id integer
---@return ClientPlayer ---@return ClientPlayer
function Client:findPlayer(id) function Client:findPlayer(id)
for _, p in ipairs(self.players) do for _, p in ipairs(self.players) do
if p.player:getId() == id then return p end if p.player:getId() == id then return p end
end end
return nil return nil
end end
fk.client_callback["Setup"] = function(jsonData) fk.client_callback["Setup"] = function(jsonData)
-- jsonData: [ int id, string screenName, string avatar ] -- jsonData: [ int id, string screenName, string avatar ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local id, name, avatar = data[1], data[2], data[3] local id, name, avatar = data[1], data[2], data[3]
local self = fk.Self local self = fk.Self
self:setId(id) self:setId(id)
self:setScreenName(name) self:setScreenName(name)
self:setAvatar(avatar) self:setAvatar(avatar)
Self = ClientPlayer:new(fk.Self) Self = ClientPlayer:new(fk.Self)
table.insert(ClientInstance.players, Self) table.insert(ClientInstance.players, Self)
end end
fk.client_callback["AddPlayer"] = function(jsonData) fk.client_callback["AddPlayer"] = function(jsonData)
-- jsonData: [ int id, string screenName, string avatar ] -- jsonData: [ int id, string screenName, string avatar ]
-- when other player enter the room, we create clientplayer(C and lua) for them -- when other player enter the room, we create clientplayer(C and lua) for them
local data = json.decode(jsonData) local data = json.decode(jsonData)
local id, name, avatar = data[1], data[2], data[3] local id, name, avatar = data[1], data[2], data[3]
local player = fk.ClientInstance:addPlayer(id, name, avatar) local player = fk.ClientInstance:addPlayer(id, name, avatar)
table.insert(ClientInstance.players, ClientPlayer:new(player)) table.insert(ClientInstance.players, ClientPlayer:new(player))
ClientInstance:notifyUI("AddPlayer", jsonData) ClientInstance:notifyUI("AddPlayer", jsonData)
end end
fk.client_callback["RemovePlayer"] = function(jsonData) fk.client_callback["RemovePlayer"] = function(jsonData)
-- jsonData: [ int id ] -- jsonData: [ int id ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local id = data[1] local id = data[1]
for _, p in ipairs(ClientInstance.players) do for _, p in ipairs(ClientInstance.players) do
if p.player:getId() == id then if p.player:getId() == id then
table.removeOne(ClientInstance.players, p) table.removeOne(ClientInstance.players, p)
break break
end
end end
fk.ClientInstance:removePlayer(id) end
ClientInstance:notifyUI("RemovePlayer", jsonData) fk.ClientInstance:removePlayer(id)
ClientInstance:notifyUI("RemovePlayer", jsonData)
end end
fk.client_callback["ArrangeSeats"] = function(jsonData) fk.client_callback["ArrangeSeats"] = function(jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local n = #ClientInstance.players local n = #ClientInstance.players
local players = {} local players = {}
for i = 1, n do for i = 1, n do
table.insert(players, ClientInstance:findPlayer(data[i])) table.insert(players, ClientInstance:findPlayer(data[i]))
end end
ClientInstance.players = players ClientInstance.players = players
ClientInstance:notifyUI("ArrangeSeats", jsonData) ClientInstance:notifyUI("ArrangeSeats", jsonData)
end end
fk.client_callback["PropertyUpdate"] = function(jsonData) fk.client_callback["PropertyUpdate"] = function(jsonData)
-- jsonData: [ int id, string property_name, value ] -- jsonData: [ int id, string property_name, value ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local id, name, value = data[1], data[2], data[3] local id, name, value = data[1], data[2], data[3]
ClientInstance:findPlayer(id)[name] = value ClientInstance:findPlayer(id)[name] = value
ClientInstance:notifyUI("PropertyUpdate", jsonData) ClientInstance:notifyUI("PropertyUpdate", jsonData)
end end
--- separated moves to many moves(one card per move) --- separated moves to many moves(one card per move)
---@param moves CardsMoveStruct[] ---@param moves CardsMoveStruct[]
local function separateMoves(moves) local function separateMoves(moves)
local ret = {} ---@type CardsMoveInfo[] local ret = {} ---@type CardsMoveInfo[]
for _, move in ipairs(moves) do for _, move in ipairs(moves) do
for _, info in ipairs(move.moveInfo) do for _, info in ipairs(move.moveInfo) do
table.insert(ret, { table.insert(ret, {
ids = {info.cardId}, ids = {info.cardId},
from = move.from, from = move.from,
to = move.to, to = move.to,
toArea = move.toArea, toArea = move.toArea,
fromArea = info.fromArea, fromArea = info.fromArea,
}) })
end
end end
return ret end
return ret
end end
--- merge separated moves (one fromArea per move) --- merge separated moves (one fromArea per move)
local function mergeMoves(moves) local function mergeMoves(moves)
local ret = {} local ret = {}
local temp = {} local temp = {}
for _, move in ipairs(moves) do for _, move in ipairs(moves) do
if temp[move.fromArea] == nil then if temp[move.fromArea] == nil then
temp[move.fromArea] = { temp[move.fromArea] = {
ids = {}, ids = {},
from = move.from, from = move.from,
to = move.to, to = move.to,
fromArea = move.fromArea, fromArea = move.fromArea,
toArea = move.toArea toArea = move.toArea
} }
end
table.insert(temp[move.fromArea].ids, move.ids[1])
end end
for _, v in pairs(temp) do table.insert(temp[move.fromArea].ids, move.ids[1])
table.insert(ret, v) end
end for _, v in pairs(temp) do
return ret table.insert(ret, v)
end
return ret
end end
fk.client_callback["MoveCards"] = function(jsonData) fk.client_callback["MoveCards"] = function(jsonData)
-- jsonData: CardsMoveStruct[] -- jsonData: CardsMoveStruct[]
local raw_moves = json.decode(jsonData) local raw_moves = json.decode(jsonData)
local separated = separateMoves(raw_moves) local separated = separateMoves(raw_moves)
local merged = mergeMoves(separated) local merged = mergeMoves(separated)
ClientInstance:notifyUI("MoveCards", json.encode(merged)) ClientInstance:notifyUI("MoveCards", json.encode(merged))
end end
-- Create ClientInstance (used by Lua) -- Create ClientInstance (used by Lua)
ClientInstance = Client:new() ClientInstance = Client:new()
dofile "lua/client/client_util.lua"

View File

@ -1,64 +1,145 @@
-- All functions in this file are used by Qml
function Translate(src) function Translate(src)
return Fk.translations[src] return Fk.translations[src]
end end
function GetGeneralData(name) function GetGeneralData(name)
local general = Fk.generals[name] local general = Fk.generals[name]
if general == nil then general = Fk.generals["diaochan"] end if general == nil then general = Fk.generals["diaochan"] end
return json.encode { return json.encode {
kingdom = general.kingdom, kingdom = general.kingdom,
hp = general.hp, hp = general.hp,
maxHp = general.maxHp maxHp = general.maxHp
} }
end end
local cardSubtypeStrings = {
[Card.SubtypeNone] = "none",
[Card.SubtypeDelayedTrick] = "delayed_trick",
[Card.SubtypeWeapon] = "weapon",
[Card.SubtypeArmor] = "armor",
[Card.SubtypeDefensiveRide] = "defensive_horse",
[Card.SubtypeOffensiveRide] = "offensive_horse",
[Card.SubtypeTreasure] = "treasure",
}
function GetCardData(id) function GetCardData(id)
local card = Fk.cards[id] local card = Fk.cards[id]
if card == nil then return json.encode{ if card == nil then return json.encode{
cid = id, cid = id,
known = false known = false
} end } end
return json.encode{ local ret = {
cid = id, cid = id,
name = card.name, name = card.name,
number = card.number, number = card.number,
suit = card:getSuitString(), suit = card:getSuitString(),
color = card.color, color = card.color,
} subtype = cardSubtypeStrings[card.sub_type]
}
return json.encode(ret)
end end
function GetAllGeneralPack() function GetAllGeneralPack()
local ret = {} local ret = {}
for _, name in ipairs(Fk.package_names) do for _, name in ipairs(Fk.package_names) do
if Fk.packages[name].type == Package.GeneralPack then if Fk.packages[name].type == Package.GeneralPack then
table.insert(ret, name) table.insert(ret, name)
end
end end
return json.encode(ret) end
return json.encode(ret)
end end
function GetGenerals(pack_name) function GetGenerals(pack_name)
local ret = {} local ret = {}
for _, g in ipairs(Fk.packages[pack_name].generals) do for _, g in ipairs(Fk.packages[pack_name].generals) do
table.insert(ret, g.name) table.insert(ret, g.name)
end end
return json.encode(ret) return json.encode(ret)
end end
function GetAllCardPack() function GetAllCardPack()
local ret = {} local ret = {}
for _, name in ipairs(Fk.package_names) do for _, name in ipairs(Fk.package_names) do
if Fk.packages[name].type == Package.CardPack then if Fk.packages[name].type == Package.CardPack then
table.insert(ret, name) table.insert(ret, name)
end
end end
return json.encode(ret) end
return json.encode(ret)
end end
function GetCards(pack_name) function GetCards(pack_name)
local ret = {} local ret = {}
for _, c in ipairs(Fk.packages[pack_name].cards) do for _, c in ipairs(Fk.packages[pack_name].cards) do
table.insert(ret, c.id) table.insert(ret, c.id)
end end
return json.encode(ret) return json.encode(ret)
end
---@param card string | integer
---@param player integer
function CanUseCard(card, player)
local c ---@type Card
if type(card) == "number" then
c = Fk:getCardById(card)
else
error()
end
local ret = c.skill:canUse(ClientInstance:findPlayer(player))
return json.encode(ret)
end
---@param card string | integer
---@param to_select integer @ id of the target
---@param selected integer[] @ ids of selected targets
---@param selected_cards integer[] @ ids of selected cards
function CanUseCardToTarget(card, to_select, selected)
local c ---@type Card
local selected_cards
if type(card) == "number" then
c = Fk:getCardById(card)
selected_cards = {card}
else
error()
end
local ret = c.skill:targetFilter(to_select, selected, selected_cards)
return json.encode(ret)
end
---@param card string | integer
---@param to_select integer @ id of a card not selected
---@param selected integer[] @ ids of selected cards
---@param selected_targets integer[] @ ids of selected players
function CanSelectCardForSkill(card, to_select, selected_targets)
local c ---@type Card
local selected_cards
if type(card) == "number" then
c = Fk:getCardById(card)
selected_cards = {card}
else
error()
end
local ret = c.skill:cardFilter(to_select, selected_cards, selected_targets)
return json.encode(ret)
end
---@param card string | integer
---@param selected integer[] @ ids of selected cards
---@param selected_targets integer[] @ ids of selected players
function CardFeasible(card, selected_targets)
local c ---@type Card
local selected_cards
if type(card) == "number" then
c = Fk:getCardById(card)
selected_cards = {card}
else
error()
end
local ret = c.skill:feasible(selected_cards, selected_targets)
return json.encode(ret)
end end

View File

@ -5,9 +5,9 @@
local ClientPlayer = Player:subclass("ClientPlayer") local ClientPlayer = Player:subclass("ClientPlayer")
function ClientPlayer:initialize(cp) function ClientPlayer:initialize(cp)
self.player = cp self.player = cp
self.handcardNum = 0 self.handcardNum = 0
self.known_cards = {} self.known_cards = {}
end end
return ClientPlayer return ClientPlayer

View File

@ -3,6 +3,7 @@
---@field name string ---@field name string
---@field suit Suit ---@field suit Suit
---@field number integer ---@field number integer
---@field trueName string
---@field color Color ---@field color Color
---@field id integer ---@field id integer
---@field type CardType ---@field type CardType
@ -26,10 +27,9 @@ Card.NoColor = 3
---@alias CardType integer ---@alias CardType integer
Card.TypeSkill = 1 Card.TypeBasic = 1
Card.TypeBasic = 2 Card.TypeTrick = 2
Card.TypeTrick = 3 Card.TypeEquip = 3
Card.TypeEquip = 4
---@alias CardSubtype integer ---@alias CardSubtype integer
@ -54,39 +54,41 @@ Card.DiscardPile = 7
Card.Void = 8 Card.Void = 8
function Card:initialize(name, suit, number, color) function Card:initialize(name, suit, number, color)
self.name = name self.name = name
self.suit = suit or Card.NoSuit self.suit = suit or Card.NoSuit
self.number = number or 0 self.number = number or 0
self.trueName = name
if suit == Card.Spade or suit == Card.Club then if suit == Card.Spade or suit == Card.Club then
self.color = Card.Black self.color = Card.Black
elseif suit == Card.Heart or suit == Card.Diamond then elseif suit == Card.Heart or suit == Card.Diamond then
self.color = Card.Red self.color = Card.Red
elseif color ~= nil then elseif color ~= nil then
self.color = color self.color = color
else else
self.color = Card.NoColor self.color = Card.NoColor
end end
self.package = nil self.package = nil
self.id = 0 self.id = 0
self.type = 0 self.type = 0
self.sub_type = Card.SubTypeNone self.sub_type = Card.SubTypeNone
self.skill = nil
end end
function Card:getSuitString() function Card:getSuitString()
local suit = self.suit local suit = self.suit
if suit == Card.Spade then if suit == Card.Spade then
return "spade" return "spade"
elseif suit == Card.Heart then elseif suit == Card.Heart then
return "heart" return "heart"
elseif suit == Card.Club then elseif suit == Card.Club then
return "club" return "club"
elseif suit == Card.Diamond then elseif suit == Card.Diamond then
return "diamond" return "diamond"
else else
return "unknown" return "unknown"
end end
end end
return Card return Card

View File

@ -2,16 +2,17 @@
local BasicCard = Card:subclass("BasicCard") local BasicCard = Card:subclass("BasicCard")
function BasicCard:initialize(name, suit, number) function BasicCard:initialize(name, suit, number)
Card.initialize(self, name, suit, number) Card.initialize(self, name, suit, number)
self.type = Card.TypeBasic self.type = Card.TypeBasic
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return BasicCard ---@return BasicCard
function BasicCard:clone(suit, number) function BasicCard:clone(suit, number)
local newCard = BasicCard:new(self.name, suit, number) local newCard = BasicCard:new(self.name, suit, number)
return newCard newCard.skill = self.skill
return newCard
end end
return BasicCard return BasicCard

View File

@ -1,90 +1,97 @@
---@class EquipCard : Card ---@class EquipCard : Card
---@field equipSkill Skill
local EquipCard = Card:subclass("EquipCard") local EquipCard = Card:subclass("EquipCard")
function EquipCard:initialize(name, suit, number) function EquipCard:initialize(name, suit, number)
Card.initialize(self, name, suit, number) Card.initialize(self, name, suit, number)
self.type = Card.TypeEquip self.type = Card.TypeEquip
self.equipSkill = nil
end end
---@class Weapon : EquipCard ---@class Weapon : EquipCard
local Weapon = EquipCard:subclass("Weapon") local Weapon = EquipCard:subclass("Weapon")
function Weapon:initialize(name, suit, number, attackRange) function Weapon:initialize(name, suit, number, attackRange)
EquipCard.initialize(self, name, suit, number) EquipCard.initialize(self, name, suit, number)
self.sub_type = Card.SubtypeWeapon self.sub_type = Card.SubtypeWeapon
self.attack_range = attackRange or 1 self.attack_range = attackRange or 1
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return Weapon ---@return Weapon
function Weapon:clone(suit, number) function Weapon:clone(suit, number)
local newCard = Weapon:new(self.name, suit, number, self.attack_range) local newCard = Weapon:new(self.name, suit, number, self.attack_range)
return newCard newCard.skill = self.skill
return newCard
end end
---@class Armor : EquipCard ---@class Armor : EquipCard
local Armor = EquipCard:subclass("armor") local Armor = EquipCard:subclass("armor")
function Armor:initialize(name, suit, number) function Armor:initialize(name, suit, number)
EquipCard.initialize(self, name, suit, number) EquipCard.initialize(self, name, suit, number)
self.sub_type = Card.SubtypeArmor self.sub_type = Card.SubtypeArmor
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return Armor ---@return Armor
function Armor:clone(suit, number) function Armor:clone(suit, number)
local newCard = Armor:new(self.name, suit, number) local newCard = Armor:new(self.name, suit, number)
return newCard newCard.skill = self.skill
return newCard
end end
---@class DefensiveRide : EquipCard ---@class DefensiveRide : EquipCard
local DefensiveRide = EquipCard:subclass("DefensiveRide") local DefensiveRide = EquipCard:subclass("DefensiveRide")
function DefensiveRide:initialize(name, suit, number) function DefensiveRide:initialize(name, suit, number)
EquipCard.initialize(self, name, suit, number) EquipCard.initialize(self, name, suit, number)
self.sub_type = Card.SubtypeDefensiveRide self.sub_type = Card.SubtypeDefensiveRide
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return DefensiveRide ---@return DefensiveRide
function DefensiveRide:clone(suit, number) function DefensiveRide:clone(suit, number)
local newCard = DefensiveRide:new(self.name, suit, number) local newCard = DefensiveRide:new(self.name, suit, number)
return newCard newCard.skill = self.skill
return newCard
end end
---@class OffensiveRide : EquipCard ---@class OffensiveRide : EquipCard
local OffensiveRide = EquipCard:subclass("OffensiveRide") local OffensiveRide = EquipCard:subclass("OffensiveRide")
function OffensiveRide:initialize(name, suit, number) function OffensiveRide:initialize(name, suit, number)
EquipCard.initialize(self, name, suit, number) EquipCard.initialize(self, name, suit, number)
self.sub_type = Card.SubtypeOffensiveRide self.sub_type = Card.SubtypeOffensiveRide
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return OffensiveRide ---@return OffensiveRide
function OffensiveRide:clone(suit, number) function OffensiveRide:clone(suit, number)
local newCard = OffensiveRide:new(self.name, suit, number) local newCard = OffensiveRide:new(self.name, suit, number)
return newCard newCard.skill = self.skill
return newCard
end end
---@class Treasure : EquipCard ---@class Treasure : EquipCard
local Treasure = EquipCard:subclass("Treasure") local Treasure = EquipCard:subclass("Treasure")
function Treasure:initialize(name, suit, number) function Treasure:initialize(name, suit, number)
EquipCard.initialize(self, name, suit, number) EquipCard.initialize(self, name, suit, number)
self.sub_type = Card.SubtypeTreasure self.sub_type = Card.SubtypeTreasure
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return Treasure ---@return Treasure
function Treasure:clone(suit, number) function Treasure:clone(suit, number)
local newCard = Treasure:new(self.name, suit, number) local newCard = Treasure:new(self.name, suit, number)
return newCard newCard.skill = self.skill
return newCard
end end
return { EquipCard, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure } return { EquipCard, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure }

View File

@ -1,9 +0,0 @@
---@class SkillCard : Card
local SkillCard = Card:subclass("SkillCard")
function SkillCard:initialize(name)
Card.initialize(self, name, Card.NoSuit, 0)
self.type = Card.TypeSkill
end
return SkillCard

View File

@ -2,32 +2,36 @@
local TrickCard = Card:subclass("TrickCard") local TrickCard = Card:subclass("TrickCard")
function TrickCard:initialize(name, suit, number) function TrickCard:initialize(name, suit, number)
Card.initialize(self, name, suit, number) Card.initialize(self, name, suit, number)
self.type = Card.TypeTrick self.type = Card.TypeTrick
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return TrickCard ---@return TrickCard
function TrickCard:clone(suit, number) function TrickCard:clone(suit, number)
local newCard = TrickCard:new(self.name, suit, number) local newCard = TrickCard:new(self.name, suit, number)
return newCard
newCard.skill = self.skill
return newCard
end end
---@class DelayedTrickCard : TrickCard ---@class DelayedTrickCard : TrickCard
local DelayedTrickCard = TrickCard:subclass("DelayedTrickCard") local DelayedTrickCard = TrickCard:subclass("DelayedTrickCard")
function DelayedTrickCard:initialize(name, suit, number) function DelayedTrickCard:initialize(name, suit, number)
TrickCard.initialize(self, name, suit, number) TrickCard.initialize(self, name, suit, number)
self.sub_type = Card.SubtypeDelayedTrick self.sub_type = Card.SubtypeDelayedTrick
end end
---@param suit Suit ---@param suit Suit
---@param number integer ---@param number integer
---@return DelayedTrickCard ---@return DelayedTrickCard
function DelayedTrickCard:clone(suit, number) function DelayedTrickCard:clone(suit, number)
local newCard = DelayedTrickCard:new(self.name, suit, number) local newCard = DelayedTrickCard:new(self.name, suit, number)
return newCard newCard.skill = self.skill
return newCard
end end
return { TrickCard, DelayedTrickCard } return { TrickCard, DelayedTrickCard }

View File

@ -3,16 +3,16 @@ inspect = require "inspect"
DebugMode = true DebugMode = true
function PrintWhenMethodCall() function PrintWhenMethodCall()
local info = debug.getinfo(2) local info = debug.getinfo(2)
local name = info.name local name = info.name
local line = info.currentline local line = info.currentline
local namewhat = info.namewhat local namewhat = info.namewhat
local shortsrc = info.short_src local shortsrc = info.short_src
if (namewhat == "method") and if (namewhat == "method") and
(shortsrc ~= "[C]") and (shortsrc ~= "[C]") and
(not string.find(shortsrc, "/lib")) then (not string.find(shortsrc, "/lib")) then
print(shortsrc .. ":" .. line .. ": " .. name) print(shortsrc .. ":" .. line .. ": " .. name)
end end
end end
--debug.sethook(PrintWhenMethodCall, "c") --debug.sethook(PrintWhenMethodCall, "c")

View File

@ -11,126 +11,126 @@
local Engine = class("Engine") local Engine = class("Engine")
function Engine:initialize() function Engine:initialize()
-- Engine should be singleton -- Engine should be singleton
if Fk ~= nil then if Fk ~= nil then
error("Engine has been initialized") error("Engine has been initialized")
return return
end end
Fk = self Fk = self
self.packages = {} -- name --> Package self.packages = {} -- name --> Package
self.package_names = {} self.package_names = {}
self.skills = {} -- name --> Skill self.skills = {} -- name --> Skill
self.related_skills = {} -- skillName --> relatedSkill[] self.related_skills = {} -- skillName --> relatedSkill[]
self.global_trigger = {} self.global_trigger = {}
self.generals = {} -- name --> General self.generals = {} -- name --> General
self.lords = {} -- lordName[] self.lords = {} -- lordName[]
self.cards = {} -- Card[] self.cards = {} -- Card[]
self.translations = {} -- srcText --> translated self.translations = {} -- srcText --> translated
self:loadPackages() self:loadPackages()
end end
---@param pack Package ---@param pack Package
function Engine:loadPackage(pack) function Engine:loadPackage(pack)
assert(pack:isInstanceOf(Package)) assert(pack:isInstanceOf(Package))
if self.packages[pack.name] ~= nil then if self.packages[pack.name] ~= nil then
error(string.format("Duplicate package %s detected", pack.name)) error(string.format("Duplicate package %s detected", pack.name))
end end
self.packages[pack.name] = pack self.packages[pack.name] = pack
table.insert(self.package_names, pack.name) table.insert(self.package_names, pack.name)
-- add cards, generals and skills to Engine -- add cards, generals and skills to Engine
if pack.type == Package.CardPack then if pack.type == Package.CardPack then
self:addCards(pack.cards) self:addCards(pack.cards)
elseif pack.type == Package.GeneralPack then elseif pack.type == Package.GeneralPack then
self:addGenerals(pack.generals) self:addGenerals(pack.generals)
end end
self:addSkills(pack:getSkills()) self:addSkills(pack:getSkills())
end end
function Engine:loadPackages() function Engine:loadPackages()
local directories = FileIO.ls("packages") local directories = FileIO.ls("packages")
-- load standard & standard_cards first -- load standard & standard_cards first
self:loadPackage(require("packages.standard")) self:loadPackage(require("packages.standard"))
self:loadPackage(require("packages.standard_cards")) self:loadPackage(require("packages.standard_cards"))
table.removeOne(directories, "standard") table.removeOne(directories, "standard")
table.removeOne(directories, "standard_cards") table.removeOne(directories, "standard_cards")
for _, dir in ipairs(directories) do for _, dir in ipairs(directories) do
if FileIO.isDir("packages/" .. dir) then if FileIO.isDir("packages/" .. dir) then
local pack = require(string.format("packages.%s", dir)) local pack = require(string.format("packages.%s", dir))
-- Note that instance of Package is a table too -- Note that instance of Package is a table too
-- so dont use type(pack) == "table" here -- so dont use type(pack) == "table" here
if pack[1] ~= nil then if pack[1] ~= nil then
for _, p in ipairs(pack) do for _, p in ipairs(pack) do
self:loadPackage(p) self:loadPackage(p)
end
else
self:loadPackage(pack)
end
end end
else
self:loadPackage(pack)
end
end end
end
end end
---@param t table ---@param t table
function Engine:loadTranslationTable(t) function Engine:loadTranslationTable(t)
assert(type(t) == "table") assert(type(t) == "table")
for k, v in pairs(t) do for k, v in pairs(t) do
self.translations[k] = v self.translations[k] = v
end end
end end
---@param skill Skill ---@param skill Skill
function Engine:addSkill(skill) function Engine:addSkill(skill)
assert(skill.class:isSubclassOf(Skill)) assert(skill.class:isSubclassOf(Skill))
if self.skills[skill.name] ~= nil then if self.skills[skill.name] ~= nil then
error(string.format("Duplicate skill %s detected", skill.name)) error(string.format("Duplicate skill %s detected", skill.name))
end end
self.skills[skill.name] = skill self.skills[skill.name] = skill
end end
---@param skills Skill[] ---@param skills Skill[]
function Engine:addSkills(skills) function Engine:addSkills(skills)
assert(type(skills) == "table") assert(type(skills) == "table")
for _, skill in ipairs(skills) do for _, skill in ipairs(skills) do
self:addSkill(skill) self:addSkill(skill)
end end
end end
---@param general General ---@param general General
function Engine:addGeneral(general) function Engine:addGeneral(general)
assert(general:isInstanceOf(General)) assert(general:isInstanceOf(General))
if self.generals[general.name] ~= nil then if self.generals[general.name] ~= nil then
error(string.format("Duplicate general %s detected", general.name)) error(string.format("Duplicate general %s detected", general.name))
end end
self.generals[general.name] = general self.generals[general.name] = general
end end
---@param generals General[] ---@param generals General[]
function Engine:addGenerals(generals) function Engine:addGenerals(generals)
assert(type(generals) == "table") assert(type(generals) == "table")
for _, general in ipairs(generals) do for _, general in ipairs(generals) do
self:addGeneral(general) self:addGeneral(general)
end end
end end
local cardId = 1 local cardId = 1
---@param card Card ---@param card Card
function Engine:addCard(card) function Engine:addCard(card)
assert(card.class:isSubclassOf(Card)) assert(card.class:isSubclassOf(Card))
card.id = cardId card.id = cardId
cardId = cardId + 1 cardId = cardId + 1
table.insert(self.cards, card) table.insert(self.cards, card)
end end
---@param cards Card[] ---@param cards Card[]
function Engine:addCards(cards) function Engine:addCards(cards)
for _, card in ipairs(cards) do for _, card in ipairs(cards) do
self:addCard(card) self:addCard(card)
end end
end end
---@param num integer ---@param num integer
@ -139,68 +139,68 @@ end
---@param filter function ---@param filter function
---@return General[] ---@return General[]
function Engine:getGeneralsRandomly(num, generalPool, except, filter) function Engine:getGeneralsRandomly(num, generalPool, except, filter)
if filter then if filter then
assert(type(filter) == "function") assert(type(filter) == "function")
end end
generalPool = generalPool or self.generals generalPool = generalPool or self.generals
except = except or {} except = except or {}
local availableGenerals = {} local availableGenerals = {}
for _, general in pairs(generalPool) do for _, general in pairs(generalPool) do
if not table.contains(except, general.name) and not (filter and filter(general)) then if not table.contains(except, general.name) and not (filter and filter(general)) then
table.insert(availableGenerals, general) table.insert(availableGenerals, general)
end
end end
end
if #availableGenerals == 0 then
return {}
end
local result = {}
for i = 1, num do
local randomGeneral = math.random(1, #availableGenerals)
table.insert(result, availableGenerals[randomGeneral])
table.remove(availableGenerals, randomGeneral)
if #availableGenerals == 0 then if #availableGenerals == 0 then
return {} break
end end
end
local result = {} return result
for i = 1, num do
local randomGeneral = math.random(1, #availableGenerals)
table.insert(result, availableGenerals[randomGeneral])
table.remove(availableGenerals, randomGeneral)
if #availableGenerals == 0 then
break
end
end
return result
end end
---@param except General[] ---@param except General[]
---@return General[] ---@return General[]
function Engine:getAllGenerals(except) function Engine:getAllGenerals(except)
local result = {} local result = {}
for _, general in ipairs(self.generals) do for _, general in ipairs(self.generals) do
if not (except and table.contains(except, general)) then if not (except and table.contains(except, general)) then
table.insert(result, general) table.insert(result, general)
end
end end
end
return result return result
end end
---@param except integer[] ---@param except integer[]
---@return integer[] ---@return integer[]
function Engine:getAllCardIds(except) function Engine:getAllCardIds(except)
local result = {} local result = {}
for _, card in ipairs(self.cards) do for _, card in ipairs(self.cards) do
if not (except and table.contains(except, card.id)) then if not (except and table.contains(except, card.id)) then
table.insert(result, card.id) table.insert(result, card.id)
end
end end
end
return result return result
end end
---@param id integer ---@param id integer
---@return Card ---@return Card
function Engine:getCardById(id) function Engine:getCardById(id)
return self.cards[id] return self.cards[id]
end end
return Engine return Engine

View File

@ -15,24 +15,24 @@ General.Male = 1
General.Female = 2 General.Female = 2
function General:initialize(package, name, kingdom, hp, maxHp, gender) function General:initialize(package, name, kingdom, hp, maxHp, gender)
self.package = package self.package = package
self.name = name self.name = name
self.kingdom = kingdom self.kingdom = kingdom
self.hp = hp self.hp = hp
self.maxHp = maxHp or hp self.maxHp = maxHp or hp
self.gender = gender or General.Male self.gender = gender or General.Male
self.skills = {} -- skills first added to this general self.skills = {} -- skills first added to this general
self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde
end end
---@param skill Skill ---@param skill Skill
function General:addSkill(skill) function General:addSkill(skill)
if (type(skill) == "string") then if (type(skill) == "string") then
table.insert(self.other_skills, skill) table.insert(self.other_skills, skill)
elseif (skill.class and skill.class:isSubclassOf(Skill)) then elseif (skill.class and skill.class:isSubclassOf(Skill)) then
table.insert(self.skills, skill) table.insert(self.skills, skill)
end end
end end
return General return General

View File

@ -14,48 +14,48 @@ Package.CardPack = 2
Package.SpecialPack = 3 Package.SpecialPack = 3
function Package:initialize(name, _type) function Package:initialize(name, _type)
assert(type(name) == "string") assert(type(name) == "string")
assert(type(_type) == "nil" or type(_type) == "number") assert(type(_type) == "nil" or type(_type) == "number")
self.name = name self.name = name
self.type = _type or Package.GeneralPack self.type = _type or Package.GeneralPack
self.generals = {} self.generals = {}
self.extra_skills = {} -- skill not belongs to any generals, like "jixi" self.extra_skills = {} -- skill not belongs to any generals, like "jixi"
self.related_skills = {} self.related_skills = {}
self.cards = {} self.cards = {}
end end
---@return Skill[] ---@return Skill[]
function Package:getSkills() function Package:getSkills()
local ret = {table.unpack(self.related_skills)} local ret = {table.unpack(self.related_skills)}
if self.type == Package.GeneralPack then if self.type == Package.GeneralPack then
for _, g in ipairs(self.generals) do for _, g in ipairs(self.generals) do
for _, s in ipairs(g.skills) do for _, s in ipairs(g.skills) do
table.insert(ret, s) table.insert(ret, s)
end end
end
end end
return ret end
return ret
end end
---@param general General ---@param general General
function Package:addGeneral(general) function Package:addGeneral(general)
assert(general.class and general:isInstanceOf(General)) assert(general.class and general:isInstanceOf(General))
table.insert(self.generals, general) table.insert(self.generals, general)
end end
---@param card Card ---@param card Card
function Package:addCard(card) function Package:addCard(card)
assert(card.class and card:isInstanceOf(Card)) assert(card.class and card:isInstanceOf(Card))
card.package = self card.package = self
table.insert(self.cards, card) table.insert(self.cards, card)
end end
---@param cards Card[] ---@param cards Card[]
function Package:addCards(cards) function Package:addCards(cards)
for _, card in ipairs(cards) do for _, card in ipairs(cards) do
self:addCard(card) self:addCard(card)
end end
end end
return Package return Package

View File

@ -19,6 +19,7 @@
---@field mark table<string, integer> ---@field mark table<string, integer>
---@field player_cards table<integer, integer[]> ---@field player_cards table<integer, integer[]>
---@field special_cards table<string, integer[]> ---@field special_cards table<string, integer[]>
---@field cardUsedHistory table<string, integer>
local Player = class("Player") local Player = class("Player")
---@alias Phase integer ---@alias Phase integer
@ -41,184 +42,211 @@ Player.Judge = 3
Player.Special = 4 Player.Special = 4
function Player:initialize() function Player:initialize()
self.id = 114514 self.id = 114514
self.hp = 0 self.hp = 0
self.maxHp = 0 self.maxHp = 0
self.kingdom = "qun" self.kingdom = "qun"
self.role = "" self.role = ""
self.general = "" self.general = ""
self.seat = 0 self.seat = 0
self.phase = Player.PhaseNone self.phase = Player.PhaseNone
self.faceup = true self.faceup = true
self.chained = false self.chained = false
self.dying = false self.dying = false
self.dead = false self.dead = false
self.state = "" self.state = ""
self.player_skills = {} self.player_skills = {}
self.flag = {} self.flag = {}
self.tag = {} self.tag = {}
self.mark = {} self.mark = {}
self.player_cards = { self.player_cards = {
[Player.Hand] = {}, [Player.Hand] = {},
[Player.Equip] = {}, [Player.Equip] = {},
[Player.Judge] = {}, [Player.Judge] = {},
} }
self.special_cards = {} self.special_cards = {}
self.cardUsedHistory = {}
end end
---@param general General ---@param general General
---@param setHp boolean ---@param setHp boolean
---@param addSkills boolean ---@param addSkills boolean
function Player:setGeneral(general, setHp, addSkills) function Player:setGeneral(general, setHp, addSkills)
self.general = general self.general = general
if setHp then if setHp then
self.maxHp = general.maxHp self.maxHp = general.maxHp
self.hp = general.hp self.hp = general.hp
end end
if addSkills then if addSkills then
table.insertTable(self.player_skills, general.skills) table.insertTable(self.player_skills, general.skills)
end end
end end
---@param flag string ---@param flag string
function Player:hasFlag(flag) function Player:hasFlag(flag)
return table.contains(self.flag, flag) return table.contains(self.flag, flag)
end end
---@param flag string ---@param flag string
function Player:setFlag(flag) function Player:setFlag(flag)
if flag == "." then if flag == "." then
self:clearFlags() self:clearFlags()
return return
end end
if flag:sub(1, 1) == "-" then if flag:sub(1, 1) == "-" then
flag = flag:sub(2, #flag) flag = flag:sub(2, #flag)
table.removeOne(self.flag, flag) table.removeOne(self.flag, flag)
return return
end end
if not self:hasFlag(flag) then if not self:hasFlag(flag) then
table.insert(self.flag, flag) table.insert(self.flag, flag)
end end
end end
function Player:clearFlags() function Player:clearFlags()
self.flag = {} self.flag = {}
end end
function Player:addMark(mark, count) function Player:addMark(mark, count)
count = count or 1 count = count or 1
local num = self.mark[mark] local num = self.mark[mark]
num = num or 0 num = num or 0
self:setMark(mark, math.max(num + count, 0)) self:setMark(mark, math.max(num + count, 0))
end end
function Player:removeMark(mark, count) function Player:removeMark(mark, count)
count = count or 1 count = count or 1
local num = self.mark[mark] local num = self.mark[mark]
num = num or 0 num = num or 0
self:setMark(mark, math.max(num - count, 0)) self:setMark(mark, math.max(num - count, 0))
end end
function Player:setMark(mark, count) function Player:setMark(mark, count)
if self.mark[mark] ~= count then if self.mark[mark] ~= count then
self.mark[mark] = count self.mark[mark] = count
end end
end end
function Player:getMark(mark) function Player:getMark(mark)
return (self.mark[mark] or 0) return (self.mark[mark] or 0)
end end
function Player:getMarkNames() function Player:getMarkNames()
local ret = {} local ret = {}
for k, _ in pairs(self.mark) do for k, _ in pairs(self.mark) do
table.insert(ret, k) table.insert(ret, k)
end end
return ret return ret
end end
---@param playerArea PlayerCardArea ---@param playerArea PlayerCardArea
---@param cardIds integer[] ---@param cardIds integer[]
---@param specialName string ---@param specialName string
function Player:addCards(playerArea, cardIds, specialName) function Player:addCards(playerArea, cardIds, specialName)
assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea)) assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea))
assert(playerArea ~= Player.Special or type(specialName) == "string") assert(playerArea ~= Player.Special or type(specialName) == "string")
if playerArea == Player.Special then if playerArea == Player.Special then
self.special_cards[specialName] = self.special_cards[specialName] or {} self.special_cards[specialName] = self.special_cards[specialName] or {}
table.insertTable(self.special_cards[specialName], cardIds) table.insertTable(self.special_cards[specialName], cardIds)
else else
table.insertTable(self.player_cards[playerArea], cardIds) table.insertTable(self.player_cards[playerArea], cardIds)
end end
end end
---@param playerArea PlayerCardArea ---@param playerArea PlayerCardArea
---@param cardIds integer[] ---@param cardIds integer[]
---@param specialName string ---@param specialName string
function Player:removeCards(playerArea, cardIds, specialName) function Player:removeCards(playerArea, cardIds, specialName)
assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea)) assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea))
assert(playerArea ~= Player.Special or type(specialName) == "string") assert(playerArea ~= Player.Special or type(specialName) == "string")
local fromAreaIds = playerArea == Player.Special and self.special_cards[specialName] or self.player_cards[playerArea] local fromAreaIds = playerArea == Player.Special and self.special_cards[specialName] or self.player_cards[playerArea]
if fromAreaIds then if fromAreaIds then
for _, id in ipairs(cardIds) do for _, id in ipairs(cardIds) do
if #fromAreaIds == 0 then if #fromAreaIds == 0 then
break break
end end
table.removeOne(fromAreaIds, id) table.removeOne(fromAreaIds, id)
end
end end
end
end end
---@param playerAreas PlayerCardArea ---@param playerAreas PlayerCardArea
---@param specialName string ---@param specialName string
---@return integer[] ---@return integer[]
function Player:getCardIds(playerAreas, specialName) function Player:getCardIds(playerAreas, specialName)
local rightAreas = { Player.Hand, Player.Equip, Player.Judge } local rightAreas = { Player.Hand, Player.Equip, Player.Judge }
playerAreas = playerAreas or rightAreas playerAreas = playerAreas or rightAreas
assert(type(playerAreas) == "number" or type(playerAreas) == "table") assert(type(playerAreas) == "number" or type(playerAreas) == "table")
local areas = type(playerAreas) == "table" and playerAreas or { playerAreas } local areas = type(playerAreas) == "table" and playerAreas or { playerAreas }
local rightAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special } local rightAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special }
local cardIds = {} local cardIds = {}
for _, area in ipairs(areas) do for _, area in ipairs(areas) do
assert(table.contains(rightAreas, area)) assert(table.contains(rightAreas, area))
assert(area ~= Player.Special or type(specialName) == "string") assert(area ~= Player.Special or type(specialName) == "string")
local currentCardIds = area == Player.Special and self.special_cards[specialName] or self.player_cards[area] local currentCardIds = area == Player.Special and self.special_cards[specialName] or self.player_cards[area]
table.insertTable(cardIds, currentCardIds) table.insertTable(cardIds, currentCardIds)
end
return cardIds
end
---@param cardSubtype CardSubtype
---@return integer|null
function Player:getEquipment(cardSubtype)
for _, cardId in ipairs(self.player_cards[Player.Equip]) do
if Fk:getCardById(cardId).sub_type == cardSubtype then
return cardId
end end
end
return cardIds return nil
end end
function Player:getMaxCards() function Player:getMaxCards()
local baseValue = math.max(self.hp, 0) local baseValue = math.max(self.hp, 0)
return baseValue return baseValue
end end
---@param subtype CardSubtype ---@param subtype CardSubtype
---@return integer|null ---@return integer|null
function Player:getEquipBySubtype(subtype) function Player:getEquipBySubtype(subtype)
local equipId = nil local equipId = nil
for _, id in ipairs(self.player_cards[Player.Equip]) do for _, id in ipairs(self.player_cards[Player.Equip]) do
if Fk.getCardById(id).sub_type == subtype then if Fk:getCardById(id).sub_type == subtype then
equipId = id equipId = id
break break
end
end end
end
return equipId return equipId
end end
function Player:getAttackRange() function Player:getAttackRange()
local weapon = Fk.getCardById(self:getEquipBySubtype(Card.SubtypeWeapon)) local weapon = Fk:getCardById(self:getEquipBySubtype(Card.SubtypeWeapon))
local baseAttackRange = math.max(weapon and weapon.attack_range or 1, 0) local baseAttackRange = math.max(weapon and weapon.attack_range or 1, 0)
return math.max(baseAttackRange, 0) return math.max(baseAttackRange, 0)
end
function Player:addCardUseHistory(cardName, num)
assert(type(num) == "number" and num ~= 0)
self.cardUsedHistory[cardName] = self.cardUsedHistory[cardName] or 0
self.cardUsedHistory[cardName] = self.cardUsedHistory[cardName] + num
end
function Player:resetCardUseHistory(cardName)
if self.cardUsedHistory[cardName] then
self.cardUsedHistory[cardName] = 0
end
end end
return Player return Player

View File

@ -13,10 +13,10 @@ Skill.Limited = 4
Skill.Wake = 5 Skill.Wake = 5
function Skill:initialize(name, frequency) function Skill:initialize(name, frequency)
-- TODO: visible, lord, etc -- TODO: visible, lord, etc
self.name = name self.name = name
self.frequency = frequency self.frequency = frequency
self.visible = true self.visible = true
end end
return Skill return Skill

View File

@ -0,0 +1,57 @@
--- ActiveSkill is a skill type like SkillCard+ViewAsSkill in QSanguosha
---
---@class ActiveSkill : Skill
local ActiveSkill = Skill:subclass("ActiveSkill")
function ActiveSkill:initialize(name)
Skill.initialize(self, name, Skill.NotFrequent)
end
---------
-- Note: these functions are used both client and ai
------- {
--- Determine whether the skill can be used in playing phase
---@param player Player
function ActiveSkill:canUse(player)
return true
end
--- Determine whether a card can be selected by this skill
--- only used in skill of players
---@param to_select integer @ id of a card not selected
---@param selected integer[] @ ids of selected cards
---@param selected_targets integer[] @ ids of selected players
function ActiveSkill:cardFilter(to_select, selected, selected_targets)
return true
end
--- Determine whether a target can be selected by this skill
--- only used in skill of players
---@param to_select integer @ id of the target
---@param selected integer[] @ ids of selected targets
---@param selected_cards integer[] @ ids of selected cards
function ActiveSkill:targetFilter(to_select, selected, selected_cards)
return false
end
--- Determine if selected cards and targets are valid for this skill
--- If returns true, the OK button should be enabled
--- only used in skill of players
---@param selected integer[] @ ids of selected cards
---@param selected_targets integer[] @ ids of selected players
function ActiveSkill:feasible(selected, selected_targets)
return true
end
------- }
---@param room Room
---@param cardUseEvent CardUseStruct
function ActiveSkill:onUse(room, cardUseEvent) end
---@param room Room
---@param cardEffectEvent CardEffectEvent
function ActiveSkill:onEffect(room, cardEffectEvent) end
return ActiveSkill

View File

@ -6,12 +6,12 @@
local TriggerSkill = Skill:subclass("TriggerSkill") local TriggerSkill = Skill:subclass("TriggerSkill")
function TriggerSkill:initialize(name, frequency) function TriggerSkill:initialize(name, frequency)
Skill.initialize(self, name, frequency) Skill.initialize(self, name, frequency)
self.global = false self.global = false
self.events = {} self.events = {}
self.refresh_events = {} self.refresh_events = {}
self.priority_table = {} -- GameEvent --> priority self.priority_table = {} -- GameEvent --> priority
end end
-- Default functions -- Default functions
@ -37,8 +37,8 @@ function TriggerSkill:refresh(event, target, player, data) end
---@param data any @ useful data of the event ---@param data any @ useful data of the event
---@return boolean ---@return boolean
function TriggerSkill:triggerable(event, target, player, data) function TriggerSkill:triggerable(event, target, player, data)
return target and (target == player) return target and (target == player)
and (self.global or (target:isAlive() and target:hasSkill(self))) and (self.global or (target:isAlive() and target:hasSkill(self)))
end end
---Trigger this skill ---Trigger this skill
@ -48,10 +48,10 @@ end
---@param data any @ useful data of the event ---@param data any @ useful data of the event
---@return boolean @ returns true if trigger is broken ---@return boolean @ returns true if trigger is broken
function TriggerSkill:trigger(event, target, player, data) function TriggerSkill:trigger(event, target, player, data)
if player.room:askForSkillInvoke(player, self.name) then if player.room:askForSkillInvoke(player, self.name) then
return self:use(event, target, player, data) return self:use(event, target, player, data)
end end
return false return false
end end
---Use this skill ---Use this skill

View File

@ -1,52 +1,52 @@
-- the iterator of QList object -- the iterator of QList object
local qlist_iterator = function(list, n) local qlist_iterator = function(list, n)
if n < list:length() - 1 then if n < list:length() - 1 then
return n + 1, list:at(n + 1) -- the next element of list return n + 1, list:at(n + 1) -- the next element of list
end end
end end
function fk.qlist(list) function fk.qlist(list)
return qlist_iterator, list, -1 return qlist_iterator, list, -1
end end
function table:contains(element) function table:contains(element)
if #self == 0 or type(self[1]) ~= type(element) then return false end if #self == 0 or type(self[1]) ~= type(element) then return false end
for _, e in ipairs(self) do for _, e in ipairs(self) do
if e == element then return true end if e == element then return true end
end end
end end
function table:shuffle() function table:shuffle()
for i = #self, 2, -1 do for i = #self, 2, -1 do
local j = math.random(i) local j = math.random(i)
self[i], self[j] = self[j], self[i] self[i], self[j] = self[j], self[i]
end end
end end
function table:insertTable(list) function table:insertTable(list)
for _, e in ipairs(list) do for _, e in ipairs(list) do
table.insert(self, e) table.insert(self, e)
end end
end end
function table:indexOf(value, from) function table:indexOf(value, from)
from = from or 1 from = from or 1
for i = from, #self do for i = from, #self do
if self[i] == value then return i end if self[i] == value then return i end
end end
return -1 return -1
end end
function table:removeOne(element) function table:removeOne(element)
if #self == 0 or type(self[1]) ~= type(element) then return false end if #self == 0 or type(self[1]) ~= type(element) then return false end
for i = 1, #self do for i = 1, #self do
if self[i] == element then if self[i] == element then
table.remove(self, i) table.remove(self, i)
return true return true
end end
end end
return false return false
end end
-- Note: only clone key and value, no metatable -- Note: only clone key and value, no metatable
@ -55,57 +55,57 @@ end
---@param self T ---@param self T
---@return T ---@return T
function table.clone(self) function table.clone(self)
local ret = {} local ret = {}
for k, v in pairs(self) do for k, v in pairs(self) do
if type(v) == "table" then if type(v) == "table" then
ret[k] = table.clone(v) ret[k] = table.clone(v)
else else
ret[k] = v ret[k] = v
end end
end end
return ret return ret
end end
---@class Sql ---@class Sql
Sql = { Sql = {
---@param filename string ---@param filename string
open = function(filename) open = function(filename)
return fk.OpenDatabase(filename) return fk.OpenDatabase(filename)
end, end,
---@param db fk.SQLite3 ---@param db fk.SQLite3
close = function(db) close = function(db)
fk.CloseDatabase(db) fk.CloseDatabase(db)
end, end,
--- Execute an SQL statement. --- Execute an SQL statement.
---@param db fk.SQLite3 ---@param db fk.SQLite3
---@param sql string ---@param sql string
exec = function(db, sql) exec = function(db, sql)
fk.ExecSQL(db, sql) fk.ExecSQL(db, sql)
end, end,
--- Execute a `SELECT` SQL statement. --- Execute a `SELECT` SQL statement.
---@param db fk.SQLite3 ---@param db fk.SQLite3
---@param sql string ---@param sql string
---@return table @ { [columnName] --> result : string[] } ---@return table @ { [columnName] --> result : string[] }
exec_select = function(db, sql) exec_select = function(db, sql)
return json.decode(fk.SelectFromDb(db, sql)) return json.decode(fk.SelectFromDb(db, sql))
end, end,
} }
FileIO = { FileIO = {
pwd = fk.QmlBackend_pwd, pwd = fk.QmlBackend_pwd,
ls = function(filename) ls = function(filename)
if filename == nil then if filename == nil then
return fk.QmlBackend_ls(".") return fk.QmlBackend_ls(".")
else else
return fk.QmlBackend_ls(filename) return fk.QmlBackend_ls(filename)
end end
end, end,
cd = fk.QmlBackend_cd, cd = fk.QmlBackend_cd,
exists = fk.QmlBackend_exists, exists = fk.QmlBackend_exists,
isDir = fk.QmlBackend_isDir isDir = fk.QmlBackend_isDir
} }
os.getms = fk.GetMicroSecond os.getms = fk.GetMicroSecond
@ -113,23 +113,23 @@ os.getms = fk.GetMicroSecond
---@class Stack : Object ---@class Stack : Object
Stack = class("Stack") Stack = class("Stack")
function Stack:initialize() function Stack:initialize()
self.t = {} self.t = {}
self.p = 0 self.p = 0
end end
function Stack:push(e) function Stack:push(e)
self.p = self.p + 1 self.p = self.p + 1
self.t[self.p] = e self.t[self.p] = e
end end
function Stack:isEmpty() function Stack:isEmpty()
return self.p == 0 return self.p == 0
end end
function Stack:pop() function Stack:pop()
if self.p == 0 then return nil end if self.p == 0 then return nil end
self.p = self.p - 1 self.p = self.p - 1
return self.t[self.p + 1] return self.t[self.p + 1]
end end
@ -139,15 +139,156 @@ end
---@param table string ---@param table string
---@param enum string[] ---@param enum string[]
function CreateEnum(table, enum) function CreateEnum(table, enum)
local enum_format = "%s.%s = %d" local enum_format = "%s.%s = %d"
for i, v in ipairs(enum) do for i, v in ipairs(enum) do
print(string.format(enum_format, table, v, i)) print(string.format(enum_format, table, v, i))
end end
end end
function switch(param, case_table) function switch(param, case_table)
local case = case_table[param] local case = case_table[param]
if case then return case() end if case then return case() end
local def = case_table["default"] local def = case_table["default"]
return def and def() or nil return def and def() or nil
end end
---@class TargetGroup : Object
local TargetGroup = class("TargetGroup")
function TargetGroup.static:getRealTargets(targetGroup)
if not targetGroup then
return {}
end
local realTargets = {}
for _, targets in ipairs(targetGroup) do
table.insert(realTargets, targets[1])
end
return realTargets
end
function TargetGroup.static:includeRealTargets(targetGroup, playerId)
if not targetGroup then
return false
end
for _, targets in ipairs(targetGroup) do
if targets[1] == playerId then
return true
end
end
return false
end
function TargetGroup.static:removeTarget(targetGroup, playerId)
if not targetGroup then
return
end
for index, targets in ipairs(targetGroup) do
if (targets[1] == playerId) then
table.remove(targetGroup, index)
return
end
end
end
function TargetGroup.static:pushTargets(targetGroup, playerIds)
if not targetGroup then
return
end
if type(playerIds) == "table" then
table.insert(targetGroup, playerIds)
elseif type(playerIds) == "number" then
table.insert(targetGroup, { playerIds })
end
end
---@class AimGroup : Object
local AimGroup = class("AimGroup")
AimGroup.Undone = 1
AimGroup.Done = 2
AimGroup.Cancelled = 3
function AimGroup.static:initAimGroup(playerIds)
return { [AimGroup.Undone] = playerIds, [AimGroup.Done] = {}, [AimGroup.Cancelled] = {} }
end
function AimGroup.static:getAllTargets(aimGroup)
local targets = {}
table.insertTable(targets, aimGroup[AimGroup.Undone])
table.insertTable(targets, aimGroup[AimGroup.Done])
return targets
end
function AimGroup.static:getUndoneOrDoneTargets(aimGroup, done)
return done and aimGroup[AimGroup.Done] or aimGroup[AimGroup.Undone]
end
function AimGroup.static:setTargetDone(aimGroup, playerId)
local index = table.indexOf(aimGroup[AimGroup.Undone], playerId)
if index ~= -1 then
table.remove(aimGroup[AimGroup.Undone], index)
table.insert(aimGroup[AimGroup.Done], playerId)
end
end
function AimGroup.static:addTargets(room, aimEvent, playerIds)
local playerId = type(playerIds) == "table" and playerIds[1] or playerIds
table.insert(aimEvent.tos[AimGroup.Undone], playerId)
room:sortPlayersByAction(aimEvent.tos[AimGroup.Undone])
if aimEvent.targetGroup then
TargetGroup:pushTargets(aimEvent.targetGroup, playerIds)
end
end
function AimGroup.static:cancelTarget(aimEvent, playerId)
local cancelled = false
for status = AimGroup.Undone, AimGroup.Done do
local indexList = {}
for index, pId in ipairs(aimEvent.tos[status]) do
if pId == playerId then
table.insert(indexList, index)
end
end
if #indexList > 0 then
cancelled = true
for i = 1, #indexList do
table.remove(aimEvent.tos[status], indexList[i])
end
end
end
if cancelled then
table.insert(aimEvent.tos[AimGroup.Cancelled], playerId)
if aimEvent.targetGroup then
TargetGroup:removeTarget(aimEvent.targetGroup, playerId)
end
end
end
function AimGroup.static:removeDeadTargets(room, aimEvent)
for index = AimGroup.Undone, AimGroup.Done do
aimEvent.tos[index] = room:deadPlayerFilter(aimEvent.tos[index])
end
if aimEvent.targetGroup then
local targets = TargetGroup:getRealTargets(aimEvent.targetGroup)
for _, target in ipairs(targets) do
if not room:getPlayerById(target):isAlive() then
TargetGroup:removeTarget(aimEvent.targetGroup, target)
end
end
end
end
function AimGroup.static:getCancelledTargets(aimGroup)
return aimGroup[AimGroup.Cancelled]
end
return { TargetGroup, AimGroup }

View File

@ -1,18 +1,16 @@
-- load types for extension -- load types for extension
SkillCard = require "core.card_type.skill" dofile "lua/server/event.lua"
dofile "lua/server/system_enum.lua"
TriggerSkill = require "core.skill_type.trigger"
ActiveSkill = require "core.skill_type.active_skill"
BasicCard = require "core.card_type.basic" BasicCard = require "core.card_type.basic"
local Trick = require "core.card_type.trick" local Trick = require "core.card_type.trick"
TrickCard, DelayedTrickCard = table.unpack(Trick) TrickCard, DelayedTrickCard = table.unpack(Trick)
local Equip = require "core.card_type.equip" local Equip = require "core.card_type.equip"
_, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure = table.unpack(Equip) _, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure = table.unpack(Equip)
dofile "lua/server/event.lua"
dofile "lua/server/system_enum.lua"
TriggerSkill = require "core.skill_type.trigger"
---@class CardSpec: Card
---@class SkillSpec: Skill ---@class SkillSpec: Skill
---@alias TrigFunc fun(self: TriggerSkill, event: Event, target: ServerPlayer, player: ServerPlayer):boolean ---@alias TrigFunc fun(self: TriggerSkill, event: Event, target: ServerPlayer, player: ServerPlayer):boolean
@ -26,166 +24,208 @@ TriggerSkill = require "core.skill_type.trigger"
---@field on_refresh TrigFunc ---@field on_refresh TrigFunc
---@field can_refresh TrigFunc ---@field can_refresh TrigFunc
---@param spec TriggerSkillSpec
---@return TriggerSkill
function fk.CreateTriggerSkill(spec)
assert(type(spec.name) == "string")
--assert(type(spec.on_trigger) == "function")
if spec.frequency then assert(type(spec.frequency) == "number") end
local frequency = spec.frequency or Skill.NotFrequent
local skill = TriggerSkill:new(spec.name, frequency)
if type(spec.events) == "number" then
table.insert(skill.events, spec.events)
elseif type(spec.events) == "table" then
table.insertTable(skill.events, spec.events)
end
if type(spec.refresh_events) == "number" then
table.insert(skill.refresh_events, spec.refresh_events)
elseif type(spec.refresh_events) == "table" then
table.insertTable(skill.refresh_events, spec.refresh_events)
end
if type(spec.global) == "boolean" then skill.global = spec.global end
if spec.on_trigger then skill.trigger = spec.on_trigger end
if spec.can_trigger then
skill.triggerable = spec.can_trigger
end
if spec.can_refresh then
skill.canRefresh = spec.can_refresh
end
if spec.on_refresh then
skill.refresh = spec.on_refresh
end
if not spec.priority then
if frequency == Skill.Wake then
spec.priority = 3
elseif frequency == Skill.Compulsory then
spec.priority = 2
else
spec.priority = 1
end
end
if type(spec.priority) == "number" then
for _, event in ipairs(skill.events) do
skill.priority_table[event] = spec.priority
end
elseif type(spec.priority) == "table" then
for event, priority in pairs(spec.priority) do
skill.priority_table[event] = priority
end
end
return skill
end
---@class ActiveSkillSpec: SkillSpec
---@field can_use fun(self: ActiveSkill, player: Player): boolean
---@field card_filter fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_targets: integer[]): boolean
---@field target_filter fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_cards: integer[]): boolean
---@field feasible fun(self: ActiveSkill, selected: integer[], selected_targets: integer[]): boolean
---@field on_use fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct): boolean
---@field on_effect fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean
---@param spec ActiveSkillSpec
---@return ActiveSkill
function fk.CreateActiveSkill(spec)
assert(type(spec.name) == "string")
local skill = ActiveSkill:new(spec.name)
if spec.can_use then skill.canUse = spec.can_use end
if spec.card_filter then skill.cardFilter = spec.card_filter end
if spec.target_filter then skill.targetFilter = spec.target_filter end
if spec.feasible then skill.feasible = spec.feasible end
if spec.on_use then skill.onUse = spec.on_use end
if spec.on_effect then skill.onEffect = spec.on_effect end
return skill
end
---@class CardSpec: Card
---@field skill Skill
local defaultCardSkill = fk.CreateActiveSkill{
name = "default_card_skill",
on_use = function(self, room, use)
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
use.tos = { { use.from } }
end
end
}
---@param spec CardSpec ---@param spec CardSpec
---@return BasicCard ---@return BasicCard
function fk.CreateBasicCard(spec) function fk.CreateBasicCard(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
local card = BasicCard:new(spec.name, spec.suit, spec.number) local card = BasicCard:new(spec.name, spec.suit, spec.number)
return card card.skill = spec.skill or defaultCardSkill
return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return TrickCard ---@return TrickCard
function fk.CreateTrickCard(spec) function fk.CreateTrickCard(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
local card = TrickCard:new(spec.name, spec.suit, spec.number) local card = TrickCard:new(spec.name, spec.suit, spec.number)
return card card.skill = spec.skill or defaultCardSkill
return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return DelayedTrickCard ---@return DelayedTrickCard
function fk.CreateDelayedTrickCard(spec) function fk.CreateDelayedTrickCard(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number) local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number)
return card card.skill = spec.skill or defaultCardSkill
return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return Weapon ---@return Weapon
function fk.CreateWeapon(spec) function fk.CreateWeapon(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
if spec.attack_range then assert(type(spec.attack_range) == "number" and spec.attack_range >= 0) end if spec.attack_range then assert(type(spec.attack_range) == "number" and spec.attack_range >= 0) end
local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range) local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range)
return card card.skill = spec.skill or defaultCardSkill
return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return Armor ---@return Armor
function fk.CreateArmor(spec) function fk.CreateArmor(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
local card = Armor:new(spec.name, spec.suit, spec.number) local card = Armor:new(spec.name, spec.suit, spec.number)
return card card.skill = spec.skill or defaultCardSkill
return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return DefensiveRide ---@return DefensiveRide
function fk.CreateDefensiveRide(spec) function fk.CreateDefensiveRide(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
local card = DefensiveRide:new(spec.name, spec.suit, spec.number) local card = DefensiveRide:new(spec.name, spec.suit, spec.number)
return card card.skill = spec.skill or defaultCardSkill
return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return OffensiveRide ---@return OffensiveRide
function fk.CreateOffensiveRide(spec) function fk.CreateOffensiveRide(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
local card = OffensiveRide:new(spec.name, spec.suit, spec.number) local card = OffensiveRide:new(spec.name, spec.suit, spec.number)
return card card.skill = spec.skill or defaultCardSkill
return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return Treasure ---@return Treasure
function fk.CreateTreasure(spec) function fk.CreateTreasure(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
local card = Treasure:new(spec.name, spec.suit, spec.number) local card = Treasure:new(spec.name, spec.suit, spec.number)
return card card.skill = spec.skill or defaultCardSkill
end return card
---@param spec TriggerSkillSpec
---@return TriggerSkill
function fk.CreateTriggerSkill(spec)
assert(type(spec.name) == "string")
--assert(type(spec.on_trigger) == "function")
if spec.frequency then assert(type(spec.frequency) == "number") end
local frequency = spec.frequency or Skill.NotFrequent
local skill = TriggerSkill:new(spec.name, frequency)
if type(spec.events) == "number" then
table.insert(skill.events, spec.events)
elseif type(spec.events) == "table" then
table.insertTable(skill.events, spec.events)
end
if type(spec.refresh_events) == "number" then
table.insert(skill.refresh_events, spec.refresh_events)
elseif type(spec.refresh_events) == "table" then
table.insertTable(skill.refresh_events, spec.refresh_events)
end
if type(spec.global) == "boolean" then skill.global = spec.global end
if spec.on_trigger then skill.trigger = spec.on_trigger end
if spec.can_trigger then
skill.triggerable = spec.can_trigger
end
if spec.can_refresh then
skill.canRefresh = spec.can_refresh
end
if spec.on_refresh then
skill.refresh = spec.on_refresh
end
if not spec.priority then
if frequency == Skill.Wake then
spec.priority = 3
elseif frequency == Skill.Compulsory then
spec.priority = 2
else
spec.priority = 1
end
end
if type(spec.priority) == "number" then
for _, event in ipairs(skill.events) do
skill.priority_table[event] = spec.priority
end
elseif type(spec.priority) == "table" then
for event, priority in pairs(spec.priority) do
skill.priority_table[event] = priority
end
end
return skill
end end

View File

@ -10,7 +10,8 @@ class = require "middleclass"
json = require "json" json = require "json"
dofile "lua/lib/sha256.lua" dofile "lua/lib/sha256.lua"
dofile "lua/core/util.lua" local GroupUtils = require "core.util"
TargetGroup, AimGroup = table.unpack(GroupUtils)
dofile "lua/core/debug.lua" dofile "lua/core/debug.lua"
math.randomseed(os.time()) math.randomseed(os.time())

View File

@ -50,4 +50,24 @@ fk.EnterDying = 38
fk.Dying = 39 fk.Dying = 39
fk.AfterDying = 40 fk.AfterDying = 40
fk.NumOfEvents = 41 fk.PreCardUse = 41
fk.AfterCardUseDeclared = 42
fk.AfterCardTargetDeclared = 43
fk.BeforeCardUseEffect = 44
fk.CardUsing = 45
fk.TargetSpecifying = 46
fk.TargetConfirming = 47
fk.TargetSpecified = 48
fk.TargetConfirmed = 49
fk.CardUseFinished = 50
fk.PreCardRespond = 51
fk.CardResponding = 52
fk.CardRespondFinished = 53
fk.PreCardEffect = 54
fk.BeforeCardEffect = 55
fk.CardEffecting = 56
fk.CardEffectFinished = 57
fk.NumOfEvents = 58

View File

@ -8,274 +8,274 @@
local GameLogic = class("GameLogic") local GameLogic = class("GameLogic")
function GameLogic:initialize(room) function GameLogic:initialize(room)
self.room = room self.room = room
self.skill_table = {} -- TriggerEvent --> TriggerSkill[] self.skill_table = {} -- TriggerEvent --> TriggerSkill[]
self.refresh_skill_table = {} self.refresh_skill_table = {}
self.skills = {} -- skillName[] self.skills = {} -- skillName[]
self.event_stack = Stack:new() self.event_stack = Stack:new()
self.role_table = { self.role_table = {
{ "lord" }, { "lord" },
{ "lord", "rebel" }, { "lord", "rebel" },
{ "lord", "rebel", "renegade" }, { "lord", "rebel", "renegade" },
{ "lord", "loyalist", "rebel", "renegade" }, { "lord", "loyalist", "rebel", "renegade" },
{ "lord", "loyalist", "rebel", "rebel", "renegade" }, { "lord", "loyalist", "rebel", "rebel", "renegade" },
{ "lord", "loyalist", "rebel", "rebel", "rebel", "renegade" }, { "lord", "loyalist", "rebel", "rebel", "rebel", "renegade" },
{ "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade" }, { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade" },
{ "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade" }, { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade" },
} }
end end
function GameLogic:run() function GameLogic:run()
-- default logic -- default logic
table.shuffle(self.room.players) table.shuffle(self.room.players)
self:assignRoles() self:assignRoles()
self.room:adjustSeats() self.room:adjustSeats()
self:chooseGenerals() self:chooseGenerals()
self:prepareForStart() self:prepareForStart()
self:action() self:action()
end end
function GameLogic:assignRoles() function GameLogic:assignRoles()
local room = self.room local room = self.room
local n = #room.players local n = #room.players
local roles = self.role_table[n] local roles = self.role_table[n]
table.shuffle(roles) table.shuffle(roles)
for i = 1, n do for i = 1, n do
local p = room.players[i] local p = room.players[i]
p.role = roles[i] p.role = roles[i]
if p.role == "lord" then if p.role == "lord" then
room:broadcastProperty(p, "role") room:broadcastProperty(p, "role")
else else
room:notifyProperty(p, p, "role") room:notifyProperty(p, p, "role")
end
end end
end
end end
function GameLogic:chooseGenerals() function GameLogic:chooseGenerals()
local room = self.room local room = self.room
local function setPlayerGeneral(player, general) local function setPlayerGeneral(player, general)
if Fk.generals[general] == nil then return end if Fk.generals[general] == nil then return end
player.general = general player.general = general
self.room:notifyProperty(player, player, "general") self.room:notifyProperty(player, player, "general")
end end
local lord = room:getLord() local lord = room:getLord()
local lord_general = nil local lord_general = nil
if lord ~= nil then if lord ~= nil then
room.current = lord room.current = lord
local generals = Fk:getGeneralsRandomly(3) local generals = Fk:getGeneralsRandomly(3)
for i = 1, #generals do for i = 1, #generals do
generals[i] = generals[i].name generals[i] = generals[i].name
end
lord_general = room:askForGeneral(lord, generals)
setPlayerGeneral(lord, lord_general)
room:broadcastProperty(lord, "general")
end end
lord_general = room:askForGeneral(lord, generals)
setPlayerGeneral(lord, lord_general)
room:broadcastProperty(lord, "general")
end
local nonlord = room:getOtherPlayers(lord) local nonlord = room:getOtherPlayers(lord)
local generals = Fk:getGeneralsRandomly(#nonlord * 3, Fk.generals, {lord_general}) local generals = Fk:getGeneralsRandomly(#nonlord * 3, Fk.generals, {lord_general})
table.shuffle(generals) table.shuffle(generals)
for _, p in ipairs(nonlord) do for _, p in ipairs(nonlord) do
local arg = { local arg = {
(table.remove(generals, 1)).name, (table.remove(generals, 1)).name,
(table.remove(generals, 1)).name, (table.remove(generals, 1)).name,
(table.remove(generals, 1)).name, (table.remove(generals, 1)).name,
} }
p.request_data = json.encode(arg) p.request_data = json.encode(arg)
p.default_reply = arg[1] p.default_reply = arg[1]
end end
room:doBroadcastRequest("AskForGeneral", nonlord) room:doBroadcastRequest("AskForGeneral", nonlord)
for _, p in ipairs(nonlord) do for _, p in ipairs(nonlord) do
if p.general == "" and p.reply_ready then if p.general == "" and p.reply_ready then
local general = json.decode(p.client_reply)[1] local general = json.decode(p.client_reply)[1]
setPlayerGeneral(p, general) setPlayerGeneral(p, general)
else else
setPlayerGeneral(p, p.default_reply) setPlayerGeneral(p, p.default_reply)
end
p.default_reply = ""
end end
p.default_reply = ""
end
end end
function GameLogic:prepareForStart() function GameLogic:prepareForStart()
local room = self.room local room = self.room
local players = room.players local players = room.players
room.alive_players = {table.unpack(players)} room.alive_players = {table.unpack(players)}
for i = 1, #players - 1 do for i = 1, #players - 1 do
players[i].next = players[i + 1] players[i].next = players[i + 1]
end
players[#players].next = players[1]
for _, p in ipairs(players) do
assert(p.general ~= "")
local general = Fk.generals[p.general]
p.maxHp = general.maxHp
p.hp = general.hp
-- TODO: setup AI here
if p.role ~= "lord" then
room:broadcastProperty(p, "general")
elseif #players >= 5 then
p.maxHp = p.maxHp + 1
p.hp = p.hp + 1
end end
players[#players].next = players[1] room:broadcastProperty(p, "maxHp")
room:broadcastProperty(p, "hp")
for _, p in ipairs(players) do -- TODO: add skills to player
assert(p.general ~= "") end
local general = Fk.generals[p.general]
p.maxHp = general.maxHp
p.hp = general.hp
-- TODO: setup AI here
if p.role ~= "lord" then -- TODO: prepare drawPile
room:broadcastProperty(p, "general") -- TODO: init cards in drawPile
elseif #players >= 5 then local allCardIds = Fk:getAllCardIds()
p.maxHp = p.maxHp + 1 table.shuffle(allCardIds)
p.hp = p.hp + 1 room.draw_pile = allCardIds
end for _, id in ipairs(room.draw_pile) do
room:broadcastProperty(p, "maxHp") self.room:setCardArea(id, Card.DrawPile)
room:broadcastProperty(p, "hp") end
-- TODO: add skills to player self:addTriggerSkill(GameRule)
end for _, trig in ipairs(Fk.global_trigger) do
self:addTriggerSkill(trig)
-- TODO: prepare drawPile end
-- TODO: init cards in drawPile
local allCardIds = Fk:getAllCardIds()
table.shuffle(allCardIds)
room.draw_pile = allCardIds
for _, id in ipairs(room.draw_pile) do
self.room:setCardArea(id, Card.DrawPile)
end
self:addTriggerSkill(GameRule)
for _, trig in ipairs(Fk.global_trigger) do
self:addTriggerSkill(trig)
end
end end
function GameLogic:action() function GameLogic:action()
self:trigger(fk.GameStart) self:trigger(fk.GameStart)
local room = self.room local room = self.room
for _, p in ipairs(room.players) do for _, p in ipairs(room.players) do
self:trigger(fk.DrawInitialCards, p, { num = 4 }) self:trigger(fk.DrawInitialCards, p, { num = 4 })
end end
while true do while true do
self:trigger(fk.TurnStart, room.current) self:trigger(fk.TurnStart, room.current)
if room.game_finished then break end if room.game_finished then break end
room.current = room.current:getNextAlive() room.current = room.current:getNextAlive()
end end
end end
---@param skill TriggerSkill ---@param skill TriggerSkill
function GameLogic:addTriggerSkill(skill) function GameLogic:addTriggerSkill(skill)
if skill == nil or table.contains(self.skills, skill.name) then if skill == nil or table.contains(self.skills, skill.name) then
return return
end end
table.insert(self.skills, skill.name) table.insert(self.skills, skill.name)
for _, event in ipairs(skill.refresh_events) do for _, event in ipairs(skill.refresh_events) do
if self.refresh_skill_table[event] == nil then if self.refresh_skill_table[event] == nil then
self.refresh_skill_table[event] = {} self.refresh_skill_table[event] = {}
end
table.insert(self.refresh_skill_table[event], skill)
end end
table.insert(self.refresh_skill_table[event], skill)
end
for _, event in ipairs(skill.events) do for _, event in ipairs(skill.events) do
if self.skill_table[event] == nil then if self.skill_table[event] == nil then
self.skill_table[event] = {} self.skill_table[event] = {}
end
table.insert(self.skill_table[event], skill)
end end
table.insert(self.skill_table[event], skill)
end
if skill.visible then if skill.visible then
if (Fk.related_skills[skill.name] == nil) then return end if (Fk.related_skills[skill.name] == nil) then return end
for _, s in ipairs(Fk.related_skills[skill.name]) do for _, s in ipairs(Fk.related_skills[skill.name]) do
if (s.class == TriggerSkill) then if (s.class == TriggerSkill) then
self:addTriggerSkill(s) self:addTriggerSkill(s)
end end
end
end end
end
end end
---@param event Event ---@param event Event
---@param target ServerPlayer ---@param target ServerPlayer
---@param data any ---@param data any
function GameLogic:trigger(event, target, data) function GameLogic:trigger(event, target, data)
local room = self.room local room = self.room
local broken = false local broken = false
local skills = self.skill_table[event] or {} local skills = self.skill_table[event] or {}
local skills_to_refresh = self.refresh_skill_table[event] or {} local skills_to_refresh = self.refresh_skill_table[event] or {}
local player = target local player = target
self.event_stack:push({event, target, data}) self.event_stack:push({event, target, data})
if target == nil then if target == nil then
for _, skill in ipairs(skills_to_refresh) do for _, skill in ipairs(skills_to_refresh) do
if skill:canRefresh(event, target, player, data) then if skill:canRefresh(event, target, player, data) then
skill:refresh(event, target, player, data) skill:refresh(event, target, player, data)
end end
end
for _, skill in ipairs(skills) do
if skill:triggerable(event, target, player, data) then
broken = skill:trigger(event, target, player, data)
if broken then break end
end
end
self.event_stack:pop()
return broken
end end
repeat do for _, skill in ipairs(skills) do
-- refresh skills. This should not be broken if skill:triggerable(event, target, player, data) then
for _, skill in ipairs(skills_to_refresh) do broken = skill:trigger(event, target, player, data)
if skill:canRefresh(event, target, player, data) then
skill:refresh(event, target, player, data)
end
end
player = player.next
end until player == target
---@param a TriggerSkill
---@param b TriggerSkill
local compare_func = function (a, b)
return a.priority_table[event] > b.priority_table[event]
end
table.sort(skills, compare_func)
repeat do
local triggerable_skills = {} ---@type table<number, TriggerSkill[]>
local priority_table = {} ---@type number[]
for _, skill in ipairs(skills) do
if skill:triggerable(event, target, player, data) then
local priority = skill.priority_table[event]
if triggerable_skills[priority] == nil then
triggerable_skills[priority] = {}
end
table.insert(triggerable_skills[priority], skill)
if not table.contains(priority_table, priority) then
table.insert(priority_table, priority)
end
end
end
for _, priority in ipairs(priority_table) do
local triggerables = triggerable_skills[priority]
local skill_names = {} ---@type string[]
for _, skill in ipairs(triggerables) do
table.insert(skill_names, skill.name)
end
while #skill_names > 0 do
local skill_name = room:askForChoice(player, skill_names, "trigger")
local skill = triggerables[table.indexOf(skill_names, skill_name)]
broken = skill:trigger(event, target, player, data)
if broken then break end
table.removeOne(skill_names, skill_name)
table.removeOne(triggerables, skill)
end
end
if broken then break end if broken then break end
end
player = player.next end
end until player == target
self.event_stack:pop() self.event_stack:pop()
return broken return broken
end
repeat do
-- refresh skills. This should not be broken
for _, skill in ipairs(skills_to_refresh) do
if skill:canRefresh(event, target, player, data) then
skill:refresh(event, target, player, data)
end
end
player = player.next
end until player == target
---@param a TriggerSkill
---@param b TriggerSkill
local compare_func = function (a, b)
return a.priority_table[event] > b.priority_table[event]
end
table.sort(skills, compare_func)
repeat do
local triggerable_skills = {} ---@type table<number, TriggerSkill[]>
local priority_table = {} ---@type number[]
for _, skill in ipairs(skills) do
if skill:triggerable(event, target, player, data) then
local priority = skill.priority_table[event]
if triggerable_skills[priority] == nil then
triggerable_skills[priority] = {}
end
table.insert(triggerable_skills[priority], skill)
if not table.contains(priority_table, priority) then
table.insert(priority_table, priority)
end
end
end
for _, priority in ipairs(priority_table) do
local triggerables = triggerable_skills[priority]
local skill_names = {} ---@type string[]
for _, skill in ipairs(triggerables) do
table.insert(skill_names, skill.name)
end
while #skill_names > 0 do
local skill_name = room:askForChoice(player, skill_names, "trigger")
local skill = triggerables[table.indexOf(skill_names, skill_name)]
broken = skill:trigger(event, target, player, data)
if broken then break end
table.removeOne(skill_names, skill_name)
table.removeOne(triggerables, skill)
end
end
if broken then break end
player = player.next
end until player == target
self.event_stack:pop()
return broken
end end
return GameLogic return GameLogic

View File

@ -6,63 +6,63 @@ fk.lobby_callback = {}
local db = fk.ServerInstance:getDatabase() local db = fk.ServerInstance:getDatabase()
function Lobby:initialize(_lobby) function Lobby:initialize(_lobby)
self.lobby = _lobby self.lobby = _lobby
self.lobby.callback = function(_self, command, jsonData) self.lobby.callback = function(_self, command, jsonData)
local cb = fk.lobby_callback[command] local cb = fk.lobby_callback[command]
if (type(cb) == "function") then if (type(cb) == "function") then
cb(jsonData) cb(jsonData)
else else
print("Lobby error: Unknown command " .. command); print("Lobby error: Unknown command " .. command);
end
end end
end
end end
fk.lobby_callback["UpdateAvatar"] = function(jsonData) fk.lobby_callback["UpdateAvatar"] = function(jsonData)
-- jsonData: [ int uid, string newavatar ] -- jsonData: [ int uid, string newavatar ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local id, avatar = data[1], data[2] local id, avatar = data[1], data[2]
local sql = "UPDATE userinfo SET avatar='%s' WHERE id=%d;" local sql = "UPDATE userinfo SET avatar='%s' WHERE id=%d;"
Sql.exec(db, string.format(sql, avatar, id)) Sql.exec(db, string.format(sql, avatar, id))
local player = fk.ServerInstance:findPlayer(id) local player = fk.ServerInstance:findPlayer(id)
player:setAvatar(avatar) player:setAvatar(avatar)
player:doNotify("UpdateAvatar", avatar) player:doNotify("UpdateAvatar", avatar)
end end
fk.lobby_callback["UpdatePassword"] = function(jsonData) fk.lobby_callback["UpdatePassword"] = function(jsonData)
-- jsonData: [ int uid, string oldpassword, int newpassword ] -- jsonData: [ int uid, string oldpassword, int newpassword ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local id, old, new = data[1], data[2], data[3] local id, old, new = data[1], data[2], data[3]
local sql_find = "SELECT password FROM userinfo WHERE id=%d;" local sql_find = "SELECT password FROM userinfo WHERE id=%d;"
local sql_update = "UPDATE userinfo SET password='%s' WHERE id=%d;" local sql_update = "UPDATE userinfo SET password='%s' WHERE id=%d;"
local passed = false local passed = false
local result = Sql.exec_select(db, string.format(sql_find, id)) local result = Sql.exec_select(db, string.format(sql_find, id))
passed = (result["password"][1] == sha256(old)) passed = (result["password"][1] == sha256(old))
if passed then if passed then
Sql.exec(db, string.format(sql_update, sha256(new), id)) Sql.exec(db, string.format(sql_update, sha256(new), id))
end end
local player = fk.ServerInstance:findPlayer(tonumber(id)) local player = fk.ServerInstance:findPlayer(tonumber(id))
player:doNotify("UpdatePassword", passed and "1" or "0") player:doNotify("UpdatePassword", passed and "1" or "0")
end end
fk.lobby_callback["CreateRoom"] = function(jsonData) fk.lobby_callback["CreateRoom"] = function(jsonData)
-- jsonData: [ int uid, string name, int capacity ] -- jsonData: [ int uid, string name, int capacity ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local owner = fk.ServerInstance:findPlayer(tonumber(data[1])) local owner = fk.ServerInstance:findPlayer(tonumber(data[1]))
local roomName = data[2] local roomName = data[2]
local capacity = data[3] local capacity = data[3]
fk.ServerInstance:createRoom(owner, roomName, capacity) fk.ServerInstance:createRoom(owner, roomName, capacity)
end end
fk.lobby_callback["EnterRoom"] = function(jsonData) fk.lobby_callback["EnterRoom"] = function(jsonData)
-- jsonData: [ int uid, int roomId ] -- jsonData: [ int uid, int roomId ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local player = fk.ServerInstance:findPlayer(tonumber(data[1])) local player = fk.ServerInstance:findPlayer(tonumber(data[1]))
local room = fk.ServerInstance:findRoom(tonumber(data[2])) local room = fk.ServerInstance:findRoom(tonumber(data[2]))
room:addPlayer(player) room:addPlayer(player)
end end
function CreateRoom(_room) function CreateRoom(_room)
LobbyInstance = Lobby:new(_room) LobbyInstance = Lobby:new(_room)
end end

File diff suppressed because it is too large Load Diff

View File

@ -12,30 +12,30 @@
local ServerPlayer = Player:subclass("ServerPlayer") local ServerPlayer = Player:subclass("ServerPlayer")
function ServerPlayer:initialize(_self) function ServerPlayer:initialize(_self)
Player.initialize(self) Player.initialize(self)
self.serverplayer = _self self.serverplayer = _self
self.id = _self:getId() self.id = _self:getId()
self.room = nil self.room = nil
self.next = nil self.next = nil
-- Below are for doBroadcastRequest -- Below are for doBroadcastRequest
self.request_data = "" self.request_data = ""
self.client_reply = "" self.client_reply = ""
self.default_reply = "" self.default_reply = ""
self.reply_ready = false self.reply_ready = false
self.phases = {} self.phases = {}
end end
---@return integer ---@return integer
function ServerPlayer:getId() function ServerPlayer:getId()
return self.id return self.id
end end
---@param command string ---@param command string
---@param jsonData string ---@param jsonData string
function ServerPlayer:doNotify(command, jsonData) function ServerPlayer:doNotify(command, jsonData)
self.serverplayer:doNotify(command, jsonData) self.serverplayer:doNotify(command, jsonData)
end end
--- Send a request to client, and allow client to reply within *timeout* seconds. --- Send a request to client, and allow client to reply within *timeout* seconds.
@ -45,10 +45,10 @@ end
---@param jsonData string ---@param jsonData string
---@param timeout integer ---@param timeout integer
function ServerPlayer:doRequest(command, jsonData, timeout) function ServerPlayer:doRequest(command, jsonData, timeout)
timeout = timeout or self.room.timeout timeout = timeout or self.room.timeout
self.client_reply = "" self.client_reply = ""
self.reply_ready = false self.reply_ready = false
self.serverplayer:doRequest(command, jsonData, timeout) self.serverplayer:doRequest(command, jsonData, timeout)
end end
--- Wait for at most *timeout* seconds for reply from client. --- Wait for at most *timeout* seconds for reply from client.
@ -57,153 +57,153 @@ end
---@param timeout integer @ seconds to wait ---@param timeout integer @ seconds to wait
---@return string @ JSON data ---@return string @ JSON data
function ServerPlayer:waitForReply(timeout) function ServerPlayer:waitForReply(timeout)
local result = "" local result = ""
if timeout == nil then if timeout == nil then
result = self.serverplayer:waitForReply() result = self.serverplayer:waitForReply()
else else
result = self.serverplayer:waitForReply(timeout) result = self.serverplayer:waitForReply(timeout)
end end
self.request_data = "" self.request_data = ""
self.client_reply = result self.client_reply = result
if result ~= "" then self.reply_ready = true end if result ~= "" then self.reply_ready = true end
return result return result
end end
---@param skill Skill ---@param skill Skill
function ServerPlayer:hasSkill(skill) function ServerPlayer:hasSkill(skill)
return table.contains(self.player_skills, skill) return table.contains(self.player_skills, skill)
end end
function ServerPlayer:isAlive() function ServerPlayer:isAlive()
return self.dead == false return self.dead == false
end end
function ServerPlayer:getNextAlive() function ServerPlayer:getNextAlive()
if #self.room.alive_players == 0 then if #self.room.alive_players == 0 then
return self return self
end end
local ret = self.next local ret = self.next
while ret.dead do while ret.dead do
ret = ret.next ret = ret.next
end end
return ret return ret
end end
function ServerPlayer:turnOver() function ServerPlayer:turnOver()
self.faceup = not self.faceup self.faceup = not self.faceup
self.room:broadcastProperty(self, "faceup") self.room:broadcastProperty(self, "faceup")
-- TODO: log -- TODO: log
self.room.logic:trigger(fk.TurnedOver, self) self.room.logic:trigger(fk.TurnedOver, self)
end end
---@param from_phase Phase ---@param from_phase Phase
---@param to_phase Phase ---@param to_phase Phase
function ServerPlayer:changePhase(from_phase, to_phase) function ServerPlayer:changePhase(from_phase, to_phase)
local room = self.room local room = self.room
local logic = room.logic local logic = room.logic
self.phase = Player.PhaseNone self.phase = Player.PhaseNone
local phase_change = { local phase_change = {
from = from_phase, from = from_phase,
to = to_phase to = to_phase
} }
local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change) local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change)
if skip and to_phase ~= Player.NotActive then if skip and to_phase ~= Player.NotActive then
self.phase = from_phase self.phase = from_phase
return true return true
end end
self.phase = to_phase self.phase = to_phase
room:notifyProperty(self, self, "phase") room:notifyProperty(self, self, "phase")
if #self.phases > 0 then if #self.phases > 0 then
table.remove(self.phases, 1) table.remove(self.phases, 1)
end end
if not logic:trigger(fk.EventPhaseStart, self) then
if self.phase ~= Player.NotActive then
logic:trigger(fk.EventPhaseProceeding, self)
end
end
if not logic:trigger(fk.EventPhaseStart, self) then
if self.phase ~= Player.NotActive then if self.phase ~= Player.NotActive then
logic:trigger(fk.EventPhaseEnd, self) logic:trigger(fk.EventPhaseProceeding, self)
end end
end
return false if self.phase ~= Player.NotActive then
logic:trigger(fk.EventPhaseEnd, self)
end
return false
end end
---@param phase_table Phase[] ---@param phase_table Phase[]
function ServerPlayer:play(phase_table) function ServerPlayer:play(phase_table)
phase_table = phase_table or {} phase_table = phase_table or {}
if #phase_table > 0 then if #phase_table > 0 then
if not table.contains(phase_table, Player.NotActive) then if not table.contains(phase_table, Player.NotActive) then
table.insert(phase_table, Player.NotActive) table.insert(phase_table, Player.NotActive)
end end
else else
phase_table = { phase_table = {
Player.RoundStart, Player.Start, Player.RoundStart, Player.Start,
Player.Judge, Player.Draw, Player.Play, Player.Discard, Player.Judge, Player.Draw, Player.Play, Player.Discard,
Player.Finish, Player.NotActive, Player.Finish, Player.NotActive,
} }
end
self.phases = phase_table
self.phase_state = {}
local phases = self.phases
local phase_state = self.phase_state
local room = self.room
for i = 1, #phases do
phase_state[i] = {
phase = phases[i],
skipped = false
}
end
for i = 1, #phases do
if self.dead then
self:changePhase(self.phase, Player.NotActive)
break
end end
self.phases = phase_table self.phase_index = i
self.phase_state = {} local phase_change = {
from = self.phase,
to = phases[i]
}
local phases = self.phases local logic = self.room.logic
local phase_state = self.phase_state self.phase = Player.PhaseNone
local room = self.room
for i = 1, #phases do local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change)
phase_state[i] = { phases[i] = phase_change.to
phase = phases[i], phase_state[i].phase = phases[i]
skipped = false
} self.phase = phases[i]
room:notifyProperty(self, self, "phase")
local cancel_skip = true
if phases[i] ~= Player.NotActive and (phase_state[i].skipped or skip) then
cancel_skip = logic:trigger(fk.EventPhaseSkipping, self)
end end
for i = 1, #phases do if (not skip) or (cancel_skip) then
if self.dead then if not logic:trigger(fk.EventPhaseStart, self) then
self:changePhase(self.phase, Player.NotActive) if self.phase ~= Player.NotActive then
break logic:trigger(fk.EventPhaseProceeding, self)
end end
end
self.phase_index = i if self.phase ~= Player.NotActive then
local phase_change = { logic:trigger(fk.EventPhaseEnd, self)
from = self.phase, else break end
to = phases[i]
}
local logic = self.room.logic
self.phase = Player.PhaseNone
local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change)
phases[i] = phase_change.to
phase_state[i].phase = phases[i]
self.phase = phases[i]
room:notifyProperty(self, self, "phase")
local cancel_skip = true
if phases[i] ~= Player.NotActive and (phase_state[i].skipped or skip) then
cancel_skip = logic:trigger(fk.EventPhaseSkipping, self)
end
if (not skip) or (cancel_skip) then
if not logic:trigger(fk.EventPhaseStart, self) then
if self.phase ~= Player.NotActive then
logic:trigger(fk.EventPhaseProceeding, self)
end
end
if self.phase ~= Player.NotActive then
logic:trigger(fk.EventPhaseEnd, self)
else break end
end
end end
end
end end
return ServerPlayer return ServerPlayer

View File

@ -10,6 +10,9 @@
---@alias DyingStruct { who: integer, damage: DamageStruct } ---@alias DyingStruct { who: integer, damage: DamageStruct }
---@alias DeathStruct { who: integer, damage: DamageStruct } ---@alias DeathStruct { who: integer, damage: DamageStruct }
---@alias CardUseStruct { from: integer, tos: TargetGroup, cardId: integer, toCardId: integer|null, responseToEvent: CardUseStruct|null, nullifiedTargets: interger[]|null, extraUse: boolean|null, disresponsiveList: integer[]|null, unoffsetableList: integer[]|null, addtionalDamage: integer|null, customFrom: integer|null, cardIdsResponded: integer[]|null }
---@alias AimStruct { from: integer, cardId: integer, tos: AimGroup, to: integer, targetGroup: TargetGroup|null, nullifiedTargets: integer[]|null, firstTarget: boolean, additionalDamage: integer|null, disresponsive: boolean|null, unoffsetableList: boolean|null }
---@alias CardEffectEvent { from: integer, tos: TargetGroup, cardId: integer, toCardId: integer|null, responseToEvent: CardUseStruct|null, nullifiedTargets: interger[]|null, extraUse: boolean|null, disresponsiveList: integer[]|null, unoffsetableList: integer[]|null, addtionalDamage: integer|null, customFrom: integer|null, cardIdsResponded: integer[]|null }
---@alias MoveReason integer ---@alias MoveReason integer
@ -21,6 +24,8 @@ fk.ReasonPut = 5
fk.ReasonPutIntoDiscardPile = 6 fk.ReasonPutIntoDiscardPile = 6
fk.ReasonPrey = 7 fk.ReasonPrey = 7
fk.ReasonExchange = 8 fk.ReasonExchange = 8
fk.ReasonUse = 9
fk.ReasonResonpse = 10
---@alias DamageType integer ---@alias DamageType integer

View File

@ -1,118 +1,133 @@
GameRule = fk.CreateTriggerSkill{ GameRule = fk.CreateTriggerSkill{
name = "game_rule", name = "game_rule",
events = { events = {
fk.GameStart, fk.DrawInitialCards, fk.TurnStart, fk.GameStart, fk.DrawInitialCards, fk.TurnStart,
fk.EventPhaseProceeding, fk.EventPhaseEnd, fk.EventPhaseChanging, fk.EventPhaseProceeding, fk.EventPhaseEnd, fk.EventPhaseChanging,
}, },
priority = 0, priority = 0,
can_trigger = function(self, event, target, player, data) can_trigger = function(self, event, target, player, data)
return (target == player) or (target == nil) return (target == player) or (target == nil)
end, end,
on_trigger = function(self, event, target, player, data) on_trigger = function(self, event, target, player, data)
if RoomInstance.tag["SkipGameRule"] then if RoomInstance.tag["SkipGameRule"] then
RoomInstance.tag["SkipGameRule"] = false RoomInstance.tag["SkipGameRule"] = false
return false return false
end
if target == nil then
if event == fk.GameStart then
print("Game started")
RoomInstance.tag["FirstRound"] = true
end
return false
end
local room = player.room
switch(event, {
[fk.DrawInitialCards] = function()
if data.num > 0 then
-- TODO: need a new function to call the UI
local cardIds = room:getNCards(data.num)
player:addCards(Player.Hand, cardIds)
local move_to_notify = {} ---@type CardsMoveStruct
move_to_notify.toArea = Card.PlayerHand
move_to_notify.to = player:getId()
move_to_notify.moveInfo = {}
for _, id in ipairs(cardIds) do
table.insert(move_to_notify.moveInfo,
{ cardId = id, fromArea = Card.DrawPile })
end
room:notifyMoveCards(room.players, {move_to_notify})
for _, id in ipairs(cardIds) do
room:setCardArea(id, Card.PlayerHand)
end end
if target == nil then room.logic:trigger(fk.AfterDrawInitialCards, player, data)
if event == fk.GameStart then end
print("Game started")
RoomInstance.tag["FirstRound"] = true
end
return false
end
local room = player.room
switch(event, {
[fk.DrawInitialCards] = function()
if data.num > 0 then
-- TODO: need a new function to call the UI
local cardIds = room:getNCards(data.num)
player:addCards(Player.Hand, cardIds)
local move_to_notify = {} ---@type CardsMoveStruct
move_to_notify.toArea = Card.PlayerHand
move_to_notify.to = player:getId()
move_to_notify.moveInfo = {}
for _, id in ipairs(cardIds) do
table.insert(move_to_notify.moveInfo,
{ cardId = id, fromArea = Card.DrawPile })
end
room:notifyMoveCards(room.players, {move_to_notify})
for _, id in ipairs(cardIds) do
room:setCardArea(id, Card.PlayerHand)
end
room.logic:trigger(fk.AfterDrawInitialCards, player, data)
end
end,
[fk.TurnStart] = function()
player = room.current
if room.tag["FirstRound"] == true then
room.tag["FirstRound"] = false
player:setFlag("Global_FirstRound")
end
-- TODO: send log
player:addMark("Global_TurnCount")
if not player.faceup then
player:setFlag("-Global_FirstRound")
player:turnOver()
elseif not player.dead then
player:play()
end
end,
[fk.EventPhaseProceeding] = function()
switch(player.phase, {
[Player.PhaseNone] = function()
error("You should never proceed PhaseNone")
end,
[Player.RoundStart] = function()
end,
[Player.Start] = function()
end,
[Player.Judge] = function()
end,
[Player.Draw] = function()
room:drawCards(player, 2, self.name)
end,
[Player.Play] = function()
room:askForSkillInvoke(player, "rule")
end,
[Player.Discard] = function()
local discardNum = #player:getCardIds(Player.Hand) - player:getMaxCards()
if discardNum > 0 then
room:askForDiscard(player, discardNum, discardNum, false, self.name)
end
end,
[Player.Finish] = function()
end,
[Player.NotActive] = function()
end,
})
end,
[fk.EventPhaseEnd] = function()
if player.phase == Player.Play then
-- TODO: clear history
end
end,
[fk.EventPhaseChanging] = function()
-- TODO: copy but dont copy all
end,
default = function()
print("game_rule: Event=" .. event)
room:askForSkillInvoke(player, "rule")
end,
})
return false
end, end,
[fk.TurnStart] = function()
player = room.current
if room.tag["FirstRound"] == true then
room.tag["FirstRound"] = false
player:setFlag("Global_FirstRound")
end
-- TODO: send log
player:addMark("Global_TurnCount")
if not player.faceup then
player:setFlag("-Global_FirstRound")
player:turnOver()
elseif not player.dead then
player:play()
end
end,
[fk.EventPhaseProceeding] = function()
switch(player.phase, {
[Player.PhaseNone] = function()
error("You should never proceed PhaseNone")
end,
[Player.RoundStart] = function()
end,
[Player.Start] = function()
end,
[Player.Judge] = function()
end,
[Player.Draw] = function()
room:drawCards(player, 2, self.name)
end,
[Player.Play] = function()
while not player.dead do
local result = room:doRequest(player, "PlayCard", player:getId())
if result == "" then break end
local data = json.decode(result)
local card = data.card
local targets = data.targets
local use = {} ---@type CardUseStruct
use.from = player:getId()
use.tos = {}
for _, target in ipairs(targets) do
table.insert(use.tos, { target })
end
use.cardId = card
room:useCard(use)
end
end,
[Player.Discard] = function()
local discardNum = #player:getCardIds(Player.Hand) - player:getMaxCards()
if discardNum > 0 then
room:askForDiscard(player, discardNum, discardNum, false, self.name)
end
end,
[Player.Finish] = function()
end,
[Player.NotActive] = function()
end,
})
end,
[fk.EventPhaseEnd] = function()
if player.phase == Player.Play then
-- TODO: clear history
end
end,
[fk.EventPhaseChanging] = function()
-- TODO: copy but dont copy all
end,
default = function()
print("game_rule: Event=" .. event)
room:askForSkillInvoke(player, "rule")
end,
})
return false
end,
} }

View File

@ -3,161 +3,161 @@ extension.metadata = require "packages.standard.metadata"
dofile "packages/standard/game_rule.lua" dofile "packages/standard/game_rule.lua"
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["standard"] = "标准包", ["standard"] = "标准包",
["wei"] = "", ["wei"] = "",
["shu"] = "", ["shu"] = "",
["wu"] = "", ["wu"] = "",
["qun"] = "", ["qun"] = "",
} }
local caocao = General:new(extension, "caocao", "wei", 4) local caocao = General:new(extension, "caocao", "wei", 4)
extension:addGeneral(caocao) extension:addGeneral(caocao)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["caocao"] = "曹操", ["caocao"] = "曹操",
} }
local simayi = General:new(extension, "simayi", "wei", 3) local simayi = General:new(extension, "simayi", "wei", 3)
extension:addGeneral(simayi) extension:addGeneral(simayi)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["simayi"] = "司马懿", ["simayi"] = "司马懿",
} }
local xiahoudun = General:new(extension, "xiahoudun", "wei", 4) local xiahoudun = General:new(extension, "xiahoudun", "wei", 4)
extension:addGeneral(xiahoudun) extension:addGeneral(xiahoudun)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["xiahoudun"] = "夏侯惇", ["xiahoudun"] = "夏侯惇",
} }
local zhangliao = General:new(extension, "zhangliao", "wei", 4) local zhangliao = General:new(extension, "zhangliao", "wei", 4)
extension:addGeneral(zhangliao) extension:addGeneral(zhangliao)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zhangliao"] = "张辽", ["zhangliao"] = "张辽",
} }
local xuchu = General:new(extension, "xuchu", "wei", 4) local xuchu = General:new(extension, "xuchu", "wei", 4)
extension:addGeneral(xuchu) extension:addGeneral(xuchu)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["xuchu"] = "许褚", ["xuchu"] = "许褚",
} }
local guojia = General:new(extension, "guojia", "wei", 4) local guojia = General:new(extension, "guojia", "wei", 4)
extension:addGeneral(guojia) extension:addGeneral(guojia)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["guojia"] = "郭嘉", ["guojia"] = "郭嘉",
} }
local zhenji = General:new(extension, "zhenji", "wei", 3) local zhenji = General:new(extension, "zhenji", "wei", 3)
extension:addGeneral(zhenji) extension:addGeneral(zhenji)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zhenji"] = "甄姬", ["zhenji"] = "甄姬",
} }
local liubei = General:new(extension, "liubei", "shu", 4) local liubei = General:new(extension, "liubei", "shu", 4)
extension:addGeneral(liubei) extension:addGeneral(liubei)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["liubei"] = "刘备", ["liubei"] = "刘备",
} }
local guanyu = General:new(extension, "guanyu", "shu", 4) local guanyu = General:new(extension, "guanyu", "shu", 4)
extension:addGeneral(guanyu) extension:addGeneral(guanyu)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["guanyu"] = "关羽", ["guanyu"] = "关羽",
} }
local zhangfei = General:new(extension, "zhangfei", "shu", 4) local zhangfei = General:new(extension, "zhangfei", "shu", 4)
extension:addGeneral(zhangfei) extension:addGeneral(zhangfei)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zhangfei"] = "张飞", ["zhangfei"] = "张飞",
} }
local zhugeliang = General:new(extension, "zhugeliang", "shu", 3) local zhugeliang = General:new(extension, "zhugeliang", "shu", 3)
extension:addGeneral(zhugeliang) extension:addGeneral(zhugeliang)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zhugeliang"] = "诸葛亮", ["zhugeliang"] = "诸葛亮",
} }
local zhaoyun = General:new(extension, "zhaoyun", "shu", 4) local zhaoyun = General:new(extension, "zhaoyun", "shu", 4)
extension:addGeneral(zhaoyun) extension:addGeneral(zhaoyun)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zhaoyun"] = "赵云", ["zhaoyun"] = "赵云",
} }
local machao = General:new(extension, "machao", "shu", 4) local machao = General:new(extension, "machao", "shu", 4)
extension:addGeneral(machao) extension:addGeneral(machao)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["machao"] = "马超", ["machao"] = "马超",
} }
local huangyueying = General:new(extension, "huangyueying", "shu", 3) local huangyueying = General:new(extension, "huangyueying", "shu", 3)
extension:addGeneral(huangyueying) extension:addGeneral(huangyueying)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["huangyueying"] = "黄月英", ["huangyueying"] = "黄月英",
} }
local sunquan = General:new(extension, "sunquan", "wu", 4) local sunquan = General:new(extension, "sunquan", "wu", 4)
extension:addGeneral(sunquan) extension:addGeneral(sunquan)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["sunquan"] = "孙权", ["sunquan"] = "孙权",
} }
local ganning = General:new(extension, "ganning", "wu", 4) local ganning = General:new(extension, "ganning", "wu", 4)
extension:addGeneral(ganning) extension:addGeneral(ganning)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["ganning"] = "甘宁", ["ganning"] = "甘宁",
} }
local lvmeng = General:new(extension, "lvmeng", "wu", 4) local lvmeng = General:new(extension, "lvmeng", "wu", 4)
extension:addGeneral(lvmeng) extension:addGeneral(lvmeng)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["lvmeng"] = "吕蒙", ["lvmeng"] = "吕蒙",
} }
local huanggai = General:new(extension, "huanggai", "wu", 4) local huanggai = General:new(extension, "huanggai", "wu", 4)
extension:addGeneral(huanggai) extension:addGeneral(huanggai)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["huanggai"] = "黄盖", ["huanggai"] = "黄盖",
} }
local zhouyu = General:new(extension, "zhouyu", "wu", 3) local zhouyu = General:new(extension, "zhouyu", "wu", 3)
extension:addGeneral(zhouyu) extension:addGeneral(zhouyu)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zhouyu"] = "周瑜", ["zhouyu"] = "周瑜",
} }
local daqiao = General:new(extension, "daqiao", "wu", 3) local daqiao = General:new(extension, "daqiao", "wu", 3)
extension:addGeneral(daqiao) extension:addGeneral(daqiao)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["daqiao"] = "大乔", ["daqiao"] = "大乔",
} }
local luxun = General:new(extension, "luxun", "wu", 3) local luxun = General:new(extension, "luxun", "wu", 3)
extension:addGeneral(luxun) extension:addGeneral(luxun)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["luxun"] = "陆逊", ["luxun"] = "陆逊",
} }
local sunshangxiang = General:new(extension, "sunshangxiang", "wu", 3) local sunshangxiang = General:new(extension, "sunshangxiang", "wu", 3)
extension:addGeneral(sunshangxiang) extension:addGeneral(sunshangxiang)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["sunshangxiang"] = "孙尚香", ["sunshangxiang"] = "孙尚香",
} }
local huatuo = General:new(extension, "huatuo", "qun", 3) local huatuo = General:new(extension, "huatuo", "qun", 3)
extension:addGeneral(huatuo) extension:addGeneral(huatuo)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["huatuo"] = "华佗", ["huatuo"] = "华佗",
} }
local lvbu = General:new(extension, "lvbu", "qun", 4) local lvbu = General:new(extension, "lvbu", "qun", 4)
extension:addGeneral(lvbu) extension:addGeneral(lvbu)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["lvbu"] = "吕布", ["lvbu"] = "吕布",
} }
local diaochan = General:new(extension, "diaochan", "qun", 3) local diaochan = General:new(extension, "diaochan", "qun", 3)
extension:addGeneral(diaochan) extension:addGeneral(diaochan)
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["diaochan"] = "貂蝉", ["diaochan"] = "貂蝉",
} }
return extension return extension

View File

@ -1,14 +1,14 @@
return { return {
name = "standard", name = "standard",
author = "official", author = "official",
description = "", description = "",
collaborators = { collaborators = {
program = {}, program = {},
designer = {}, designer = {},
cv = {}, cv = {},
illustrator = {}, illustrator = {},
}, },
dependencies = {}, dependencies = {},
extra_files = {}, extra_files = {},
} }

View File

@ -2,510 +2,523 @@ local extension = Package:new("standard_cards", Package.CardPack)
extension.metadata = require "packages.standard_cards.metadata" extension.metadata = require "packages.standard_cards.metadata"
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["standard_cards"] = "标+EX" ["standard_cards"] = "标+EX"
} }
local slash = fk.CreateBasicCard{ local slash = fk.CreateBasicCard{
name = "slash", name = "slash",
number = 7, number = 7,
suit = Card.Spade, suit = Card.Spade,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["slash"] = "", ["slash"] = "",
} }
extension:addCards({ extension:addCards({
slash, slash,
slash:clone(Card.Spade, 8), slash:clone(Card.Spade, 8),
slash:clone(Card.Spade, 8), slash:clone(Card.Spade, 8),
slash:clone(Card.Spade, 9), slash:clone(Card.Spade, 9),
slash:clone(Card.Spade, 9), slash:clone(Card.Spade, 9),
slash:clone(Card.Spade, 10), slash:clone(Card.Spade, 10),
slash:clone(Card.Spade, 10), slash:clone(Card.Spade, 10),
slash:clone(Card.Club, 2), slash:clone(Card.Club, 2),
slash:clone(Card.Club, 3), slash:clone(Card.Club, 3),
slash:clone(Card.Club, 4), slash:clone(Card.Club, 4),
slash:clone(Card.Club, 5), slash:clone(Card.Club, 5),
slash:clone(Card.Club, 6), slash:clone(Card.Club, 6),
slash:clone(Card.Club, 7), slash:clone(Card.Club, 7),
slash:clone(Card.Club, 8), slash:clone(Card.Club, 8),
slash:clone(Card.Club, 8), slash:clone(Card.Club, 8),
slash:clone(Card.Club, 9), slash:clone(Card.Club, 9),
slash:clone(Card.Club, 9), slash:clone(Card.Club, 9),
slash:clone(Card.Club, 10), slash:clone(Card.Club, 10),
slash:clone(Card.Club, 10), slash:clone(Card.Club, 10),
slash:clone(Card.Club, 11), slash:clone(Card.Club, 11),
slash:clone(Card.Club, 11), slash:clone(Card.Club, 11),
slash:clone(Card.Heart, 10), slash:clone(Card.Heart, 10),
slash:clone(Card.Heart, 10), slash:clone(Card.Heart, 10),
slash:clone(Card.Heart, 11), slash:clone(Card.Heart, 11),
slash:clone(Card.Diamond, 6), slash:clone(Card.Diamond, 6),
slash:clone(Card.Diamond, 7), slash:clone(Card.Diamond, 7),
slash:clone(Card.Diamond, 8), slash:clone(Card.Diamond, 8),
slash:clone(Card.Diamond, 9), slash:clone(Card.Diamond, 9),
slash:clone(Card.Diamond, 10), slash:clone(Card.Diamond, 10),
slash:clone(Card.Diamond, 13), slash:clone(Card.Diamond, 13),
}) })
local jink = fk.CreateBasicCard{ local jink = fk.CreateBasicCard{
name = "jink", name = "jink",
suit = Card.Heart, suit = Card.Heart,
number = 2, number = 2,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["jink"] = "", ["jink"] = "",
} }
extension:addCards({ extension:addCards({
jink, jink,
jink:clone(Card.Heart, 2), jink:clone(Card.Heart, 2),
jink:clone(Card.Heart, 13), jink:clone(Card.Heart, 13),
jink:clone(Card.Diamond, 2), jink:clone(Card.Diamond, 2),
jink:clone(Card.Diamond, 2), jink:clone(Card.Diamond, 2),
jink:clone(Card.Diamond, 3), jink:clone(Card.Diamond, 3),
jink:clone(Card.Diamond, 4), jink:clone(Card.Diamond, 4),
jink:clone(Card.Diamond, 5), jink:clone(Card.Diamond, 5),
jink:clone(Card.Diamond, 6), jink:clone(Card.Diamond, 6),
jink:clone(Card.Diamond, 7), jink:clone(Card.Diamond, 7),
jink:clone(Card.Diamond, 8), jink:clone(Card.Diamond, 8),
jink:clone(Card.Diamond, 9), jink:clone(Card.Diamond, 9),
jink:clone(Card.Diamond, 10), jink:clone(Card.Diamond, 10),
jink:clone(Card.Diamond, 11), jink:clone(Card.Diamond, 11),
jink:clone(Card.Diamond, 11), jink:clone(Card.Diamond, 11),
}) })
local peach = fk.CreateBasicCard{ local peach = fk.CreateBasicCard{
name = "peach", name = "peach",
suit = Card.Heart, suit = Card.Heart,
number = 3, number = 3,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["peach"] = "", ["peach"] = "",
} }
extension:addCards({ extension:addCards({
peach, peach,
peach:clone(Card.Heart, 4), peach:clone(Card.Heart, 4),
peach:clone(Card.Heart, 6), peach:clone(Card.Heart, 6),
peach:clone(Card.Heart, 7), peach:clone(Card.Heart, 7),
peach:clone(Card.Heart, 8), peach:clone(Card.Heart, 8),
peach:clone(Card.Heart, 9), peach:clone(Card.Heart, 9),
peach:clone(Card.Heart, 12), peach:clone(Card.Heart, 12),
peach:clone(Card.Heart, 12), peach:clone(Card.Heart, 12),
}) })
local dismantlement = fk.CreateTrickCard{ local dismantlement = fk.CreateTrickCard{
name = "dismantlement", name = "dismantlement",
suit = Card.Spade, suit = Card.Spade,
number = 3, number = 3,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["dismantlement"] = "过河拆桥", ["dismantlement"] = "过河拆桥",
} }
extension:addCards({ extension:addCards({
dismantlement, dismantlement,
dismantlement:clone(Card.Spade, 4), dismantlement:clone(Card.Spade, 4),
dismantlement:clone(Card.Spade, 12), dismantlement:clone(Card.Spade, 12),
dismantlement:clone(Card.Club, 3), dismantlement:clone(Card.Club, 3),
dismantlement:clone(Card.Club, 4), dismantlement:clone(Card.Club, 4),
dismantlement:clone(Card.Heart, 12), dismantlement:clone(Card.Heart, 12),
}) })
local snatch = fk.CreateTrickCard{ local snatch = fk.CreateTrickCard{
name = "snatch", name = "snatch",
suit = Card.Spade, suit = Card.Spade,
number = 3, number = 3,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["snatch"] = "顺手牵羊", ["snatch"] = "顺手牵羊",
} }
extension:addCards({ extension:addCards({
snatch, snatch,
snatch:clone(Card.Spade, 4), snatch:clone(Card.Spade, 4),
snatch:clone(Card.Spade, 11), snatch:clone(Card.Spade, 11),
snatch:clone(Card.Diamond, 3), snatch:clone(Card.Diamond, 3),
snatch:clone(Card.Diamond, 4), snatch:clone(Card.Diamond, 4),
}) })
local duel = fk.CreateTrickCard{ local duel = fk.CreateTrickCard{
name = "duel", name = "duel",
suit = Card.Spade, suit = Card.Spade,
number = 1, number = 1,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["duel"] = "决斗", ["duel"] = "决斗",
} }
extension:addCards({ extension:addCards({
duel, duel,
duel:clone(Card.Club, 1), duel:clone(Card.Club, 1),
duel:clone(Card.Diamond, 1), duel:clone(Card.Diamond, 1),
}) })
local collateral = fk.CreateTrickCard{ local collateral = fk.CreateTrickCard{
name = "collateral", name = "collateral",
suit = Card.Club, suit = Card.Club,
number = 12, number = 12,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["collateral"] = "借刀杀人", ["collateral"] = "借刀杀人",
} }
extension:addCards({ extension:addCards({
collateral, collateral,
collateral:clone(Card.Club, 13), collateral:clone(Card.Club, 13),
}) })
local exNihiloSkill = fk.CreateActiveSkill{
name = "ex_nihilo_skill",
on_use = function(self, room, cardUseEvent)
if not cardUseEvent.tos or #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then
cardUseEvent.tos = { { cardUseEvent.from } }
end
end,
on_effect = function(self, room, cardEffectEvent)
room:drawCards(room:getPlayerById(TargetGroup:getRealTargets(cardEffectEvent.tos)[1]), 2, "ex_nihilo")
end
}
local exNihilo = fk.CreateTrickCard{ local exNihilo = fk.CreateTrickCard{
name = "ex_nihilo", name = "ex_nihilo",
suit = Card.Heart, suit = Card.Heart,
number = 7, number = 7,
skill = exNihiloSkill,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["ex_nihilo"] = "无中生有", ["ex_nihilo"] = "无中生有",
} }
extension:addCards({ extension:addCards({
exNihilo, exNihilo,
exNihilo:clone(Card.Heart, 8), exNihilo:clone(Card.Heart, 8),
exNihilo:clone(Card.Heart, 9), exNihilo:clone(Card.Heart, 9),
exNihilo:clone(Card.Heart, 11), exNihilo:clone(Card.Heart, 11),
}) })
local nullification = fk.CreateTrickCard{ local nullification = fk.CreateTrickCard{
name = "nullification", name = "nullification",
suit = Card.Spade, suit = Card.Spade,
number = 11, number = 11,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["nullification"] = "无懈可击", ["nullification"] = "无懈可击",
} }
extension:addCards({ extension:addCards({
nullification, nullification,
nullification:clone(Card.Club, 12), nullification:clone(Card.Club, 12),
nullification:clone(Card.Club, 13), nullification:clone(Card.Club, 13),
nullification:clone(Card.Diamond, 12), nullification:clone(Card.Diamond, 12),
}) })
local savageAssault = fk.CreateTrickCard{ local savageAssault = fk.CreateTrickCard{
name = "savage_assault", name = "savage_assault",
suit = Card.Spade, suit = Card.Spade,
number = 7, number = 7,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["savage_assault"] = "南蛮入侵", ["savage_assault"] = "南蛮入侵",
} }
extension:addCards({ extension:addCards({
savageAssault, savageAssault,
savageAssault:clone(Card.Spade, 13), savageAssault:clone(Card.Spade, 13),
savageAssault:clone(Card.Club, 7), savageAssault:clone(Card.Club, 7),
}) })
local archeryAttack = fk.CreateTrickCard{ local archeryAttack = fk.CreateTrickCard{
name = "archery_attack", name = "archery_attack",
suit = Card.Heart, suit = Card.Heart,
number = 1, number = 1,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["archery_attack"] = "万箭齐发", ["archery_attack"] = "万箭齐发",
} }
extension:addCards({ extension:addCards({
archeryAttack, archeryAttack,
}) })
local godSalvation = fk.CreateTrickCard{ local godSalvation = fk.CreateTrickCard{
name = "god_salvation", name = "god_salvation",
suit = Card.Heart, suit = Card.Heart,
number = 1, number = 1,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["god_salvation"] = "桃园结义", ["god_salvation"] = "桃园结义",
} }
extension:addCards({ extension:addCards({
godSalvation, godSalvation,
}) })
local amazingGrace = fk.CreateTrickCard{ local amazingGrace = fk.CreateTrickCard{
name = "amazing_grace", name = "amazing_grace",
suit = Card.Heart, suit = Card.Heart,
number = 3, number = 3,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["amazing_grace"] = "五谷丰登", ["amazing_grace"] = "五谷丰登",
} }
extension:addCards({ extension:addCards({
amazingGrace, amazingGrace,
amazingGrace:clone(Card.Heart, 4), amazingGrace:clone(Card.Heart, 4),
}) })
local lightning = fk.CreateDelayedTrickCard{ local lightning = fk.CreateDelayedTrickCard{
name = "lightning", name = "lightning",
suit = Card.Spade, suit = Card.Spade,
number = 1, number = 1,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["lightning"] = "闪电", ["lightning"] = "闪电",
} }
extension:addCards({ extension:addCards({
lightning, lightning,
lightning:clone(Card.Heart, 12), lightning:clone(Card.Heart, 12),
}) })
local indulgence = fk.CreateDelayedTrickCard{ local indulgence = fk.CreateDelayedTrickCard{
name = "indulgence", name = "indulgence",
suit = Card.Spade, suit = Card.Spade,
number = 6, number = 6,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["indulgence"] = "乐不思蜀", ["indulgence"] = "乐不思蜀",
} }
extension:addCards({ extension:addCards({
indulgence, indulgence,
indulgence:clone(Card.Club, 6), indulgence:clone(Card.Club, 6),
indulgence:clone(Card.Heart, 6), indulgence:clone(Card.Heart, 6),
}) })
local crossbow = fk.CreateWeapon{ local crossbow = fk.CreateWeapon{
name = "crossbow", name = "crossbow",
suit = Card.Club, suit = Card.Club,
number = 1, number = 1,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["crossbow"] = "诸葛连弩", ["crossbow"] = "诸葛连弩",
} }
extension:addCards({ extension:addCards({
crossbow, crossbow,
crossbow:clone(Card.Diamond, 1), crossbow:clone(Card.Diamond, 1),
}) })
local qingGang = fk.CreateWeapon{ local qingGang = fk.CreateWeapon{
name = "qinggang_sword", name = "qinggang_sword",
suit = Card.Spade, suit = Card.Spade,
number = 6, number = 6,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["qinggang_sword"] = "青釭剑", ["qinggang_sword"] = "青釭剑",
} }
extension:addCards({ extension:addCards({
qingGang, qingGang,
}) })
local iceSword = fk.CreateWeapon{ local iceSword = fk.CreateWeapon{
name = "ice_sword", name = "ice_sword",
suit = Card.Spade, suit = Card.Spade,
number = 2, number = 2,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["ice_sword"] = "寒冰剑", ["ice_sword"] = "寒冰剑",
} }
extension:addCards({ extension:addCards({
iceSword, iceSword,
}) })
local doubleSwords = fk.CreateWeapon{ local doubleSwords = fk.CreateWeapon{
name = "double_swords", name = "double_swords",
suit = Card.Spade, suit = Card.Spade,
number = 2, number = 2,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["double_swords"] = "雌雄双股剑", ["double_swords"] = "雌雄双股剑",
} }
extension:addCards({ extension:addCards({
doubleSwords, doubleSwords,
}) })
local blade = fk.CreateWeapon{ local blade = fk.CreateWeapon{
name = "blade", name = "blade",
suit = Card.Spade, suit = Card.Spade,
number = 5, number = 5,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["blade"] = "青龙偃月刀", ["blade"] = "青龙偃月刀",
} }
extension:addCards({ extension:addCards({
blade, blade,
}) })
local spear = fk.CreateWeapon{ local spear = fk.CreateWeapon{
name = "spear", name = "spear",
suit = Card.Spade, suit = Card.Spade,
number = 12, number = 12,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["spear"] = "丈八蛇矛", ["spear"] = "丈八蛇矛",
} }
extension:addCards({ extension:addCards({
spear, spear,
}) })
local axe = fk.CreateWeapon{ local axe = fk.CreateWeapon{
name = "axe", name = "axe",
suit = Card.Diamond, suit = Card.Diamond,
number = 5, number = 5,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["axe"] = "贯石斧", ["axe"] = "贯石斧",
} }
extension:addCards({ extension:addCards({
axe, axe,
}) })
local halberd = fk.CreateWeapon{ local halberd = fk.CreateWeapon{
name = "halberd", name = "halberd",
suit = Card.Diamond, suit = Card.Diamond,
number = 12, number = 12,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["halberd"] = "方天画戟", ["halberd"] = "方天画戟",
} }
extension:addCards({ extension:addCards({
halberd, halberd,
}) })
local kylinBow = fk.CreateWeapon{ local kylinBow = fk.CreateWeapon{
name = "kylin_bow", name = "kylin_bow",
suit = Card.Heart, suit = Card.Heart,
number = 5, number = 5,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["kylin_bow"] = "麒麟弓", ["kylin_bow"] = "麒麟弓",
} }
extension:addCards({ extension:addCards({
kylinBow, kylinBow,
}) })
local eightDiagram = fk.CreateArmor{ local eightDiagram = fk.CreateArmor{
name = "eight_diagram", name = "eight_diagram",
suit = Card.Spade, suit = Card.Spade,
number = 2, number = 2,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["eight_diagram"] = "八卦阵", ["eight_diagram"] = "八卦阵",
} }
extension:addCards({ extension:addCards({
eightDiagram, eightDiagram,
eightDiagram:clone(Card.Club, 2), eightDiagram:clone(Card.Club, 2),
}) })
local niohShield = fk.CreateArmor{ local niohShield = fk.CreateArmor{
name = "nioh_shield", name = "nioh_shield",
suit = Card.Club, suit = Card.Club,
number = 2, number = 2,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["nioh_shield"] = "仁王盾", ["nioh_shield"] = "仁王盾",
} }
extension:addCards({ extension:addCards({
niohShield, niohShield,
}) })
local diLu = fk.CreateDefensiveRide{ local diLu = fk.CreateDefensiveRide{
name = "dilu", name = "dilu",
suit = Card.Club, suit = Card.Club,
number = 5, number = 5,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["dilu"] = "的卢", ["dilu"] = "的卢",
} }
extension:addCards({ extension:addCards({
diLu, diLu,
}) })
local jueYing = fk.CreateDefensiveRide{ local jueYing = fk.CreateDefensiveRide{
name = "jueying", name = "jueying",
suit = Card.Spade, suit = Card.Spade,
number = 5, number = 5,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["jueying"] = "绝影", ["jueying"] = "绝影",
} }
extension:addCards({ extension:addCards({
jueYing, jueYing,
}) })
local zhuaHuangFeiDian = fk.CreateDefensiveRide{ local zhuaHuangFeiDian = fk.CreateDefensiveRide{
name = "zhuahuangfeidian", name = "zhuahuangfeidian",
suit = Card.Heart, suit = Card.Heart,
number = 13, number = 13,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zhuahuangfeidian"] = "爪黄飞电", ["zhuahuangfeidian"] = "爪黄飞电",
} }
extension:addCards({ extension:addCards({
zhuaHuangFeiDian, zhuaHuangFeiDian,
}) })
local chiTu = fk.CreateOffensiveRide{ local chiTu = fk.CreateOffensiveRide{
name = "chitu", name = "chitu",
suit = Card.Heart, suit = Card.Heart,
number = 5, number = 5,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["chitu"] = "赤兔", ["chitu"] = "赤兔",
} }
extension:addCards({ extension:addCards({
chiTu, chiTu,
}) })
local daYuan = fk.CreateOffensiveRide{ local daYuan = fk.CreateOffensiveRide{
name = "dayuan", name = "dayuan",
suit = Card.Spade, suit = Card.Spade,
number = 13, number = 13,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["dayuan"] = "大宛", ["dayuan"] = "大宛",
} }
extension:addCards({ extension:addCards({
daYuan, daYuan,
}) })
local ziXing = fk.CreateOffensiveRide{ local ziXing = fk.CreateOffensiveRide{
name = "zixing", name = "zixing",
suit = Card.Heart, suit = Card.Heart,
number = 5, number = 5,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["zixing"] = "紫骍", ["zixing"] = "紫骍",
} }
extension:addCards({ extension:addCards({
ziXing, ziXing,
}) })
return extension return extension

View File

@ -1,14 +1,14 @@
return { return {
name = "standard_cards", name = "standard_cards",
author = "official", author = "official",
description = "", description = "",
collaborators = { collaborators = {
program = {}, program = {},
designer = {}, designer = {},
cv = {}, cv = {},
illustrator = {}, illustrator = {},
}, },
dependencies = {}, dependencies = {},
extra_files = {}, extra_files = {},
} }

View File

@ -1,13 +1,13 @@
import QtQuick 2.15 import QtQuick 2.15
QtObject { QtObject {
// Client configuration // Client configuration
// Player property of client // Player property of client
property string screenName: "" property string screenName: ""
property string password: "" property string password: ""
// Client data // Client data
property int roomCapacity: 0 property int roomCapacity: 0
property int roomTimeout: 0 property int roomTimeout: 0
} }

View File

@ -3,62 +3,62 @@ import QtQuick.Controls 2.0
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
Item { Item {
id: root id: root
width: childrenRect.width width: childrenRect.width
height: childrenRect.height height: childrenRect.height
signal finished() signal finished()
ColumnLayout { ColumnLayout {
spacing: 20 spacing: 20
RowLayout { RowLayout {
anchors.rightMargin: 8 anchors.rightMargin: 8
spacing: 16 spacing: 16
Text { Text {
text: "Room Name" text: "Room Name"
} }
TextField { TextField {
id: roomName id: roomName
font.pixelSize: 18 font.pixelSize: 18
text: Self.screenName + "'s Room" text: Self.screenName + "'s Room"
} }
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "Player num"
}
SpinBox {
id: playerNum
from: 2
to: 8
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Button {
text: "OK"
onClicked: {
root.finished();
mainWindow.busy = true;
ClientInstance.notifyServer(
"CreateRoom",
JSON.stringify([roomName.text, playerNum.value])
);
}
}
Button {
text: "Cancel"
onClicked: {
root.finished();
}
}
}
} }
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "Player num"
}
SpinBox {
id: playerNum
from: 2
to: 8
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Button {
text: "OK"
onClicked: {
root.finished();
mainWindow.busy = true;
ClientInstance.notifyServer(
"CreateRoom",
JSON.stringify([roomName.text, playerNum.value])
);
}
}
Button {
text: "Cancel"
onClicked: {
root.finished();
}
}
}
}
} }

View File

@ -3,98 +3,98 @@ import QtQuick.Controls 2.0
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
Item { Item {
id: root id: root
width: childrenRect.width width: childrenRect.width
height: childrenRect.height height: childrenRect.height
signal finished() signal finished()
ColumnLayout { ColumnLayout {
spacing: 20 spacing: 20
RowLayout { RowLayout {
anchors.rightMargin: 8 anchors.rightMargin: 8
spacing: 16 spacing: 16
Text { Text {
text: "Username" text: "Username"
} }
Text { Text {
text: Self.screenName text: Self.screenName
font.pixelSize: 18 font.pixelSize: 18
} }
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "Avatar"
}
TextField {
id: avatarName
font.pixelSize: 18
text: Self.avatar
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "Old Password"
}
TextField {
id: oldPassword
echoMode: TextInput.Password
passwordCharacter: "*"
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "New Password"
}
TextField {
id: newPassword
echoMode: TextInput.Password
passwordCharacter: "*"
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Button {
text: "Update Avatar"
enabled: avatarName.text !== ""
onClicked: {
mainWindow.busy = true;
ClientInstance.notifyServer(
"UpdateAvatar",
JSON.stringify([avatarName.text])
);
}
}
Button {
text: "Update Password"
enabled: oldPassword.text !== "" && newPassword.text !== ""
onClicked: {
mainWindow.busy = true;
ClientInstance.notifyServer(
"UpdatePassword",
JSON.stringify([oldPassword.text, newPassword.text])
);
}
}
Button {
text: "Exit"
onClicked: {
root.finished();
}
}
}
} }
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "Avatar"
}
TextField {
id: avatarName
font.pixelSize: 18
text: Self.avatar
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "Old Password"
}
TextField {
id: oldPassword
echoMode: TextInput.Password
passwordCharacter: "*"
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: "New Password"
}
TextField {
id: newPassword
echoMode: TextInput.Password
passwordCharacter: "*"
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Button {
text: "Update Avatar"
enabled: avatarName.text !== ""
onClicked: {
mainWindow.busy = true;
ClientInstance.notifyServer(
"UpdateAvatar",
JSON.stringify([avatarName.text])
);
}
}
Button {
text: "Update Password"
enabled: oldPassword.text !== "" && newPassword.text !== ""
onClicked: {
mainWindow.busy = true;
ClientInstance.notifyServer(
"UpdatePassword",
JSON.stringify([oldPassword.text, newPassword.text])
);
}
}
Button {
text: "Exit"
onClicked: {
root.finished();
}
}
}
}
} }

View File

@ -1,5 +1,5 @@
import QtQuick 2.0 import QtQuick 2.0
Text { Text {
text: "dsdsd" text: "dsdsd"
} }

View File

@ -1,53 +1,53 @@
var callbacks = {}; var callbacks = {};
callbacks["NetworkDelayTest"] = function(jsonData) { callbacks["NetworkDelayTest"] = function(jsonData) {
ClientInstance.notifyServer("Setup", JSON.stringify([ ClientInstance.notifyServer("Setup", JSON.stringify([
config.screenName, config.screenName,
config.password config.password
])); ]));
} }
callbacks["ErrorMsg"] = function(jsonData) { callbacks["ErrorMsg"] = function(jsonData) {
console.log("ERROR: " + jsonData); console.log("ERROR: " + jsonData);
toast.show(jsonData, 5000); toast.show(jsonData, 5000);
mainWindow.busy = false; mainWindow.busy = false;
} }
callbacks["BackToStart"] = function(jsonData) { callbacks["BackToStart"] = function(jsonData) {
while (mainStack.depth > 1) { while (mainStack.depth > 1) {
mainStack.pop(); mainStack.pop();
} }
} }
callbacks["EnterLobby"] = function(jsonData) { callbacks["EnterLobby"] = function(jsonData) {
// depth == 1 means the lobby page is not present in mainStack // depth == 1 means the lobby page is not present in mainStack
if (mainStack.depth === 1) { if (mainStack.depth === 1) {
mainStack.push(lobby); mainStack.push(lobby);
} else { } else {
mainStack.pop(); mainStack.pop();
} }
mainWindow.busy = false; mainWindow.busy = false;
} }
callbacks["EnterRoom"] = function(jsonData) { callbacks["EnterRoom"] = function(jsonData) {
// jsonData: int capacity, int timeout // jsonData: int capacity, int timeout
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
config.roomCapacity = data[0]; config.roomCapacity = data[0];
config.roomTimeout = data[1]; config.roomTimeout = data[1];
mainStack.push(room); mainStack.push(room);
mainWindow.busy = false; mainWindow.busy = false;
} }
callbacks["UpdateRoomList"] = function(jsonData) { callbacks["UpdateRoomList"] = function(jsonData) {
let current = mainStack.currentItem; // should be lobby let current = mainStack.currentItem; // should be lobby
current.roomModel.clear(); current.roomModel.clear();
JSON.parse(jsonData).forEach(function(room) { JSON.parse(jsonData).forEach(function(room) {
current.roomModel.append({ current.roomModel.append({
roomId: room[0], roomId: room[0],
roomName: room[1], roomName: room[1],
gameMode: room[2], gameMode: room[2],
playerNum: room[3], playerNum: room[3],
capacity: room[4], capacity: room[4],
});
}); });
});
} }

View File

@ -4,49 +4,49 @@ import QtQuick.Controls 2.0
import "RoomElement" import "RoomElement"
Item { Item {
id: root id: root
property bool loaded: false property bool loaded: false
ListView { ListView {
width: Math.floor(root.width / 98) * 98 width: Math.floor(root.width / 98) * 98
height: parent.height height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
ScrollBar.vertical: ScrollBar {} ScrollBar.vertical: ScrollBar {}
model: ListModel { model: ListModel {
id: packages id: packages
} }
delegate: ColumnLayout { delegate: ColumnLayout {
Text { text: Backend.translate(name) } Text { text: Backend.translate(name) }
GridLayout { GridLayout {
columns: root.width / 98 columns: root.width / 98
Repeater { Repeater {
model: JSON.parse(Backend.getCards(name)) model: JSON.parse(Backend.callLuaFunction("GetCards", [name]))
CardItem { CardItem {
autoBack: false autoBack: false
Component.onCompleted: { Component.onCompleted: {
let data = JSON.parse(Backend.getCardData(modelData)); let data = JSON.parse(Backend.callLuaFunction("GetCardData", [modelData]));
setData(data); setData(data);
}
}
}
} }
}
} }
}
} }
}
Button { Button {
text: "Quit" text: "Quit"
anchors.right: parent.right anchors.right: parent.right
onClicked: { onClicked: {
mainStack.pop(); mainStack.pop();
}
} }
}
function loadPackages() { function loadPackages() {
if (loaded) return; if (loaded) return;
let packs = JSON.parse(Backend.getAllCardPack()); let packs = JSON.parse(Backend.callLuaFunction("GetAllCardPack", []));
packs.forEach((name) => packages.append({ name: name })); packs.forEach((name) => packages.append({ name: name }));
loaded = true; loaded = true;
} }
} }

View File

@ -4,50 +4,50 @@ import QtQuick.Controls 2.0
import "RoomElement" import "RoomElement"
Item { Item {
id: root id: root
property bool loaded: false property bool loaded: false
ListView { ListView {
width: Math.floor(root.width / 98) * 98 width: Math.floor(root.width / 98) * 98
height: parent.height height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
ScrollBar.vertical: ScrollBar {} ScrollBar.vertical: ScrollBar {}
model: ListModel { model: ListModel {
id: packages id: packages
} }
delegate: ColumnLayout { delegate: ColumnLayout {
Text { text: Backend.translate(name) } Text { text: Backend.translate(name) }
GridLayout { GridLayout {
columns: root.width / 98 columns: root.width / 98
Repeater { Repeater {
model: JSON.parse(Backend.getGenerals(name)) model: JSON.parse(Backend.callLuaFunction("GetGenerals", [name]))
GeneralCardItem { GeneralCardItem {
autoBack: false autoBack: false
Component.onCompleted: { Component.onCompleted: {
let data = JSON.parse(Backend.getGeneralData(modelData)); let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [modelData]));
name = modelData; name = modelData;
kingdom = data.kingdom; kingdom = data.kingdom;
}
}
}
} }
}
} }
}
} }
}
Button { Button {
text: "Quit" text: "Quit"
anchors.right: parent.right anchors.right: parent.right
onClicked: { onClicked: {
mainStack.pop(); mainStack.pop();
}
} }
}
function loadPackages() { function loadPackages() {
if (loaded) return; if (loaded) return;
let packs = JSON.parse(Backend.getAllGeneralPack()); let packs = JSON.parse(Backend.callLuaFunction("GetAllGeneralPack", []));
packs.forEach((name) => packages.append({ name: name })); packs.forEach((name) => packages.append({ name: name }));
loaded = true; loaded = true;
} }
} }

View File

@ -2,50 +2,50 @@ import QtQuick 2.15
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
Item { Item {
id: root id: root
Frame { Frame {
id: join_server id: join_server
anchors.centerIn: parent anchors.centerIn: parent
Column { Column {
spacing: 8 spacing: 8
TextField { TextField {
id: server_addr id: server_addr
text: "127.0.0.1" text: "127.0.0.1"
} }
TextField { TextField {
id: screenNameEdit id: screenNameEdit
text: "player" text: "player"
} }
/*TextField { /*TextField {
id: avatarEdit id: avatarEdit
text: "liubei" text: "liubei"
}*/ }*/
TextField { TextField {
id: passwordEdit id: passwordEdit
text: "" text: ""
echoMode: TextInput.Password echoMode: TextInput.Password
passwordCharacter: "*" passwordCharacter: "*"
} }
Button { Button {
text: "Join Server" text: "Join Server"
onClicked: { onClicked: {
config.screenName = screenNameEdit.text; config.screenName = screenNameEdit.text;
config.password = passwordEdit.text; config.password = passwordEdit.text;
mainWindow.busy = true; mainWindow.busy = true;
Backend.joinServer(server_addr.text); Backend.joinServer(server_addr.text);
}
}
Button {
text: "Console start"
onClicked: {
config.screenName = screenNameEdit.text;
config.password = passwordEdit.text;
mainWindow.busy = true;
Backend.startServer(9527);
Backend.joinServer("127.0.0.1");
}
}
} }
}
Button {
text: "Console start"
onClicked: {
config.screenName = screenNameEdit.text;
config.password = passwordEdit.text;
mainWindow.busy = true;
Backend.startServer(9527);
Backend.joinServer("127.0.0.1");
}
}
} }
}
} }

View File

@ -5,154 +5,154 @@ import QtQuick.Layouts 1.15
import "Logic.js" as Logic import "Logic.js" as Logic
Item { Item {
id: root id: root
property alias roomModel: roomModel property alias roomModel: roomModel
Component { Component {
id: roomDelegate id: roomDelegate
RowLayout {
width: roomList.width * 0.9
spacing: 16
Text {
text: roomId
}
Text {
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
text: roomName
}
Text {
text: gameMode
}
Text {
color: (playerNum == capacity) ? "red" : "black"
text: playerNum + "/" + capacity
}
Text {
text: "Enter"
font.underline: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: { parent.color = "blue" }
onExited: { parent.color = "black" }
onClicked: {
mainWindow.busy = true;
ClientInstance.notifyServer(
"EnterRoom",
JSON.stringify([roomId])
);
}
}
}
}
}
ListModel {
id: roomModel
}
RowLayout { RowLayout {
anchors.fill: parent width: roomList.width * 0.9
Rectangle { spacing: 16
Layout.preferredWidth: root.width * 0.7 Text {
Layout.fillHeight: true text: roomId
color: "#e2e2e1" }
radius: 4
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: "Room List"
}
ListView {
height: parent.height * 0.9
width: parent.width * 0.95
contentHeight: roomDelegate.height * count
ScrollBar.vertical: ScrollBar {}
anchors.centerIn: parent
id: roomList
delegate: roomDelegate
model: roomModel
}
}
ColumnLayout { Text {
Button { horizontalAlignment: Text.AlignHCenter
text: "Edit Profile" Layout.fillWidth: true
onClicked: { text: roomName
globalPopup.source = "EditProfile.qml"; }
globalPopup.open();
} Text {
} text: gameMode
Button { }
text: "Create Room"
onClicked: { Text {
globalPopup.source = "CreateRoom.qml"; color: (playerNum == capacity) ? "red" : "black"
globalPopup.open(); text: playerNum + "/" + capacity
} }
}
Button { Text {
text: "Generals Overview" text: "Enter"
onClicked: { font.underline: true
mainStack.push(generalsOverview); MouseArea {
mainStack.currentItem.loadPackages(); anchors.fill: parent
} hoverEnabled: true
} onEntered: { parent.color = "blue" }
Button { onExited: { parent.color = "black" }
text: "Cards Overview" onClicked: {
onClicked: { mainWindow.busy = true;
mainStack.push(cardsOverview); ClientInstance.notifyServer(
mainStack.currentItem.loadPackages(); "EnterRoom",
} JSON.stringify([roomId])
} );
Button { }
text: "Scenarios Overview"
}
Button {
text: "About"
}
Button {
text: "Exit Lobby"
onClicked: {
toast.show("Goodbye.");
Backend.quitLobby();
mainStack.pop();
}
}
} }
}
}
}
ListModel {
id: roomModel
}
RowLayout {
anchors.fill: parent
Rectangle {
Layout.preferredWidth: root.width * 0.7
Layout.fillHeight: true
color: "#e2e2e1"
radius: 4
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: "Room List"
}
ListView {
height: parent.height * 0.9
width: parent.width * 0.95
contentHeight: roomDelegate.height * count
ScrollBar.vertical: ScrollBar {}
anchors.centerIn: parent
id: roomList
delegate: roomDelegate
model: roomModel
}
} }
Loader { ColumnLayout {
id: lobby_dialog Button {
z: 1000 text: "Edit Profile"
onSourceChanged: { onClicked: {
if (item === null) globalPopup.source = "EditProfile.qml";
return; globalPopup.open();
item.finished.connect(function(){
source = "";
});
item.widthChanged.connect(function(){
lobby_dialog.moveToCenter();
});
item.heightChanged.connect(function(){
lobby_dialog.moveToCenter();
});
moveToCenter();
} }
}
Button {
text: "Create Room"
onClicked: {
globalPopup.source = "CreateRoom.qml";
globalPopup.open();
}
}
Button {
text: "Generals Overview"
onClicked: {
mainStack.push(generalsOverview);
mainStack.currentItem.loadPackages();
}
}
Button {
text: "Cards Overview"
onClicked: {
mainStack.push(cardsOverview);
mainStack.currentItem.loadPackages();
}
}
Button {
text: "Scenarios Overview"
}
Button {
text: "About"
}
Button {
text: "Exit Lobby"
onClicked: {
toast.show("Goodbye.");
Backend.quitLobby();
mainStack.pop();
}
}
}
}
function moveToCenter() Loader {
{ id: lobby_dialog
item.x = Math.round((root.width - item.width) / 2); z: 1000
item.y = Math.round(root.height * 0.67 - item.height / 2); onSourceChanged: {
} if (item === null)
return;
item.finished.connect(function(){
source = "";
});
item.widthChanged.connect(function(){
lobby_dialog.moveToCenter();
});
item.heightChanged.connect(function(){
lobby_dialog.moveToCenter();
});
moveToCenter();
} }
Component.onCompleted: { function moveToCenter()
toast.show("Welcome to FreeKill lobby!"); {
item.x = Math.round((root.width - item.width) / 2);
item.y = Math.round(root.height * 0.67 - item.height / 2);
} }
}
Component.onCompleted: {
toast.show("Welcome to FreeKill lobby!");
}
} }

View File

@ -1,13 +1,13 @@
callbacks["UpdateAvatar"] = function(jsonData) { callbacks["UpdateAvatar"] = function(jsonData) {
mainWindow.busy = false; mainWindow.busy = false;
Self.avatar = jsonData; Self.avatar = jsonData;
toast.show("Update avatar done."); toast.show("Update avatar done.");
} }
callbacks["UpdatePassword"] = function(jsonData) { callbacks["UpdatePassword"] = function(jsonData) {
mainWindow.busy = false; mainWindow.busy = false;
if (jsonData === "1") if (jsonData === "1")
toast.show("Update password done."); toast.show("Update password done.");
else else
toast.show("Old password wrong!", 5000); toast.show("Old password wrong!", 5000);
} }

View File

@ -1,69 +1,69 @@
import QtQuick 2.15 import QtQuick 2.15
Item { Item {
property bool enabled: true property bool enabled: true
property alias text: title.text property alias text: title.text
property alias textColor: title.color property alias textColor: title.color
property alias textFont: title.font property alias textFont: title.font
property alias backgroundColor: bg.color property alias backgroundColor: bg.color
property alias border: bg.border property alias border: bg.border
property alias iconSource: icon.source property alias iconSource: icon.source
property int padding: 5 property int padding: 5
signal clicked signal clicked
id: button id: button
width: icon.width + title.implicitWidth + padding * 2 width: icon.width + title.implicitWidth + padding * 2
height: Math.max(icon.height, title.implicitHeight) + padding * 2 height: Math.max(icon.height, title.implicitHeight) + padding * 2
Rectangle { Rectangle {
id: bg id: bg
anchors.fill: parent anchors.fill: parent
color: "black" color: "black"
border.width: 2 border.width: 2
border.color: "white" border.color: "white"
opacity: 0.8 opacity: 0.8
}
states: [
State {
name: "hovered"; when: mouse.containsMouse
PropertyChanges { target: bg; color: "white" }
PropertyChanges { target: title; color: "black" }
},
State {
name: "disabled"; when: !enabled
PropertyChanges { target: button; opacity: 0.2 }
}
]
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: parent.enabled
onReleased: if (parent.enabled) parent.clicked()
}
Row {
x: padding
y: padding
anchors.centerIn: parent
spacing: 5
Image {
id: icon
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
} }
states: [ Text {
State { id: title
name: "hovered"; when: mouse.containsMouse font.pixelSize: 18
PropertyChanges { target: bg; color: "white" } // font.family: "WenQuanYi Micro Hei"
PropertyChanges { target: title; color: "black" } anchors.verticalCenter: parent.verticalCenter
}, text: ""
State { color: "white"
name: "disabled"; when: !enabled
PropertyChanges { target: button; opacity: 0.2 }
}
]
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: parent.enabled
onReleased: if (parent.enabled) parent.clicked()
}
Row {
x: padding
y: padding
anchors.centerIn: parent
spacing: 5
Image {
id: icon
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
}
Text {
id: title
font.pixelSize: 18
// font.family: "WenQuanYi Micro Hei"
anchors.verticalCenter: parent.verticalCenter
text: ""
color: "white"
}
} }
}
} }

View File

@ -5,325 +5,347 @@ import "RoomElement"
import "RoomLogic.js" as Logic import "RoomLogic.js" as Logic
Item { Item {
id: roomScene id: roomScene
property int playerNum: 0 property int playerNum: 0
property var dashboardModel property var dashboardModel
property bool isOwner: false property bool isOwner: false
property bool isStarted: false property bool isStarted: false
property alias popupBox: popupBox property alias popupBox: popupBox
property alias promptText: prompt.text property alias promptText: prompt.text
property var selected_targets: []
// tmp
Row {
Button{text:"摸1牌"
onClicked:{
Logic.moveCards([{
from:Logic.Player.DrawPile,
to:Logic.Player.PlaceHand,
cards:[1],
}])
}}
Button{text:"弃1牌"
onClicked:{Logic.moveCards([{
to:Logic.Player.DrawPile,
from:Logic.Player.PlaceHand,
cards:[1],
}])}}
}
Button {
text: "quit"
anchors.top: parent.top
anchors.right: parent.right
onClicked: {
ClientInstance.clearPlayers();
ClientInstance.notifyServer("QuitRoom", "[]");
}
}
Button {
text: "add robot"
visible: dashboardModel.isOwner && !isStarted
anchors.centerIn: parent
onClicked: {
ClientInstance.notifyServer("AddRobot", "[]");
}
}
states: [
State { name: "notactive" }, // Normal status
State { name: "playing" }, // Playing cards in playing phase
State { name: "responding" }, // all requests need to operate dashboard
State { name: "replying" } // requests only operate a popup window
]
state: "notactive"
transitions: [
Transition {
from: "*"; to: "notactive"
ScriptAction {
script: {
promptText = "";
progress.visible = false;
okCancel.visible = false;
endPhaseButton.visible = false;
dashboard.disableAllCards();
if (dashboard.pending_skill !== "")
dashboard.stopPending();
selected_targets = [];
if (popupBox.item != null) {
popupBox.item.finished();
}
}
}
},
Transition {
from: "*"; to: "playing"
ScriptAction {
script: {
dashboard.enableCards();
progress.visible = true;
okCancel.visible = true;
endPhaseButton.visible = true;
}
}
},
Transition {
from: "*"; to: "responding"
ScriptAction {
script: {
progress.visible = true;
okCancel.visible = true;
}
}
},
Transition {
from: "*"; to: "replying"
ScriptAction {
script: {
progress.visible = true;
}
}
}
]
/* Layout:
* +---------------------+
* | Photos, get more |
* | in arrangePhotos() |
* | tablePile |
* | progress,prompt,btn |
* +---------------------+
* | dashboard |
* +---------------------+
*/
ListModel {
id: photoModel
}
Item {
id: roomArea
width: roomScene.width
height: roomScene.height - dashboard.height
Repeater {
id: photos
model: photoModel
Photo {
playerid: model.id
general: model.general
screenName: model.screenName
role: model.role
kingdom: model.kingdom
netstate: model.netstate
maxHp: model.maxHp
hp: model.hp
seatNumber: model.seatNumber
isDead: model.isDead
dying: model.dying
faceup: model.faceup
chained: model.chained
drank: model.drank
isOwner: model.isOwner
onSelectedChanged: {
Logic.updateSelectedTargets(playerid, selected);
}
}
}
onWidthChanged: Logic.arrangePhotos();
onHeightChanged: Logic.arrangePhotos();
InvisibleCardArea {
id: drawPile
x: parent.width / 2
y: roomScene.height / 2
}
TablePile {
id: tablePile
width: parent.width * 0.6
height: 150
x: parent.width * 0.2
y: parent.height * 0.6
}
}
Dashboard {
id: dashboard
width: roomScene.width
anchors.top: roomArea.bottom
self.playerid: dashboardModel.id
self.general: dashboardModel.general
self.screenName: dashboardModel.screenName
self.role: dashboardModel.role
self.kingdom: dashboardModel.kingdom
self.netstate: dashboardModel.netstate
self.maxHp: dashboardModel.maxHp
self.hp: dashboardModel.hp
self.seatNumber: dashboardModel.seatNumber
self.isDead: dashboardModel.isDead
self.dying: dashboardModel.dying
self.faceup: dashboardModel.faceup
self.chained: dashboardModel.chained
self.drank: dashboardModel.drank
self.isOwner: dashboardModel.isOwner
onSelectedChanged: {
Logic.updateSelectedTargets(self.playerid, selected);
}
onCardSelected: {
Logic.enableTargets(card);
}
}
Item {
id: controls
anchors.bottom: dashboard.top
anchors.bottomMargin: -40
width: roomScene.width
Text {
id: prompt
visible: progress.visible
anchors.bottom: progress.top
anchors.bottomMargin: 8
anchors.horizontalCenter: progress.horizontalCenter
}
ProgressBar {
id: progress
width: parent.width * 0.6
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: okCancel.top
anchors.bottomMargin: 8
from: 0.0
to: 100.0
visible: false
NumberAnimation on value {
running: progress.visible
from: 100.0
to: 0.0
duration: config.roomTimeout * 1000
onFinished: {
roomScene.state = "notactive"
}
}
}
// tmp
Row { Row {
Button{text:"摸1牌" id: okCancel
onClicked:{ anchors.bottom: parent.bottom
Logic.moveCards([{ anchors.horizontalCenter: progress.horizontalCenter
from:Logic.Player.DrawPile, spacing: 20
to:Logic.Player.PlaceHand, visible: false
cards:[1],
}]) Button {
}} id: okButton
Button{text:"弃1牌" text: "OK"
onClicked:{Logic.moveCards([{ onClicked: Logic.doOkButton();
to:Logic.Player.DrawPile, }
from:Logic.Player.PlaceHand,
cards:[1], Button {
}])}} id: cancelButton
text: "Cancel"
onClicked: Logic.doCancelButton();
}
} }
Button { Button {
text: "quit" id: endPhaseButton
anchors.top: parent.top text: "End"
anchors.right: parent.right anchors.bottom: parent.bottom
onClicked: { anchors.bottomMargin: 40
ClientInstance.clearPlayers(); anchors.right: parent.right
ClientInstance.notifyServer("QuitRoom", "[]"); anchors.rightMargin: 30
} visible: false;
onClicked: Logic.doCancelButton();
} }
Button { }
text: "add robot"
visible: dashboardModel.isOwner && !isStarted Loader {
anchors.centerIn: parent id: popupBox
onClicked: { onSourceChanged: {
ClientInstance.notifyServer("AddRobot", "[]"); if (item === null)
} return;
item.finished.connect(function(){
source = "";
});
item.widthChanged.connect(function(){
popupBox.moveToCenter();
});
item.heightChanged.connect(function(){
popupBox.moveToCenter();
});
moveToCenter();
} }
states: [ function moveToCenter()
State { name: "notactive" }, // Normal status {
State { name: "playing" }, // Playing cards in playing phase item.x = Math.round((roomArea.width - item.width) / 2);
State { name: "responding" }, // all requests need to operate dashboard item.y = Math.round(roomArea.height * 0.67 - item.height / 2);
State { name: "replying" } // requests only operate a popup window }
] }
state: "notactive"
transitions: [
Transition {
from: "*"; to: "notactive"
ScriptAction {
script: {
promptText = "";
progress.visible = false;
okCancel.visible = false;
endPhaseButton.visible = false;
if (popupBox.item != null) { Component.onCompleted: {
popupBox.item.finished(); toast.show("Sucesessfully entered room.");
}
}
}
},
Transition { dashboardModel = {
from: "*"; to: "playing" id: Self.id,
ScriptAction { general: Self.avatar,
script: { screenName: Self.screenName,
progress.visible = true; role: "unknown",
okCancel.visible = true; kingdom: "qun",
endPhaseButton.visible = true; netstate: "online",
} maxHp: 0,
} hp: 0,
}, seatNumber: 1,
isDead: false,
Transition { dying: false,
from: "*"; to: "responding" faceup: true,
ScriptAction { chained: false,
script: { drank: false,
progress.visible = true; isOwner: false
okCancel.visible = true;
}
}
},
Transition {
from: "*"; to: "replying"
ScriptAction {
script: {
progress.visible = true;
}
}
}
]
/* Layout:
* +---------------------+
* | Photos, get more |
* | in arrangePhotos() |
* | tablePile |
* | progress,prompt,btn |
* +---------------------+
* | dashboard |
* +---------------------+
*/
ListModel {
id: photoModel
} }
Item { playerNum = config.roomCapacity;
id: roomArea
width: roomScene.width
height: roomScene.height - dashboard.height
Repeater { let i;
id: photos for (i = 1; i < playerNum; i++) {
model: photoModel photoModel.append({
Photo { id: -1,
general: model.general index: i - 1, // For animating seat swap
screenName: model.screenName general: "",
role: model.role screenName: "",
kingdom: model.kingdom role: "unknown",
netstate: model.netstate kingdom: "qun",
maxHp: model.maxHp netstate: "online",
hp: model.hp maxHp: 0,
seatNumber: model.seatNumber hp: 0,
isDead: model.isDead seatNumber: i + 1,
dying: model.dying isDead: false,
faceup: model.faceup dying: false,
chained: model.chained faceup: true,
drank: model.drank chained: false,
isOwner: model.isOwner drank: false,
} isOwner: false
} });
onWidthChanged: Logic.arrangePhotos();
onHeightChanged: Logic.arrangePhotos();
InvisibleCardArea {
id: drawPile
x: parent.width / 2
y: roomScene.height / 2
}
TablePile {
id: tablePile
width: parent.width * 0.6
height: 150
x: parent.width * 0.2
y: parent.height * 0.6
}
} }
Dashboard { Logic.arrangePhotos();
id: dashboard }
width: roomScene.width
anchors.top: roomArea.bottom
self.general: dashboardModel.general
self.screenName: dashboardModel.screenName
self.role: dashboardModel.role
self.kingdom: dashboardModel.kingdom
self.netstate: dashboardModel.netstate
self.maxHp: dashboardModel.maxHp
self.hp: dashboardModel.hp
self.seatNumber: dashboardModel.seatNumber
self.isDead: dashboardModel.isDead
self.dying: dashboardModel.dying
self.faceup: dashboardModel.faceup
self.chained: dashboardModel.chained
self.drank: dashboardModel.drank
self.isOwner: dashboardModel.isOwner
}
Item {
id: controls
anchors.bottom: dashboard.top
anchors.bottomMargin: -40
width: roomScene.width
Text {
id: prompt
visible: progress.visible
anchors.bottom: progress.top
anchors.bottomMargin: 8
anchors.horizontalCenter: progress.horizontalCenter
}
ProgressBar {
id: progress
width: parent.width * 0.6
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: okCancel.top
anchors.bottomMargin: 8
from: 0.0
to: 100.0
visible: false
NumberAnimation on value {
running: progress.visible
from: 100.0
to: 0.0
duration: config.roomTimeout * 1000
onFinished: {
roomScene.state = "notactive"
}
}
}
Row {
id: okCancel
anchors.bottom: parent.bottom
anchors.horizontalCenter: progress.horizontalCenter
spacing: 20
visible: false
Button {
id: okButton
text: "OK"
onClicked: Logic.doOkButton();
}
Button {
id: cancelButton
text: "Cancel"
onClicked: Logic.doCancelButton();
}
}
Button {
id: endPhaseButton
text: "End"
anchors.bottom: parent.bottom
anchors.bottomMargin: 40
anchors.right: parent.right
anchors.rightMargin: 30
visible: false;
onClicked: Logic.doCancelButton();
}
}
Loader {
id: popupBox
onSourceChanged: {
if (item === null)
return;
item.finished.connect(function(){
source = "";
});
item.widthChanged.connect(function(){
popupBox.moveToCenter();
});
item.heightChanged.connect(function(){
popupBox.moveToCenter();
});
moveToCenter();
}
function moveToCenter()
{
item.x = Math.round((roomArea.width - item.width) / 2);
item.y = Math.round(roomArea.height * 0.67 - item.height / 2);
}
}
Component.onCompleted: {
toast.show("Sucesessfully entered room.");
dashboardModel = {
id: Self.id,
general: Self.avatar,
screenName: Self.screenName,
role: "unknown",
kingdom: "qun",
netstate: "online",
maxHp: 0,
hp: 0,
seatNumber: 1,
isDead: false,
dying: false,
faceup: true,
chained: false,
drank: false,
isOwner: false
}
playerNum = config.roomCapacity;
let i;
for (i = 1; i < playerNum; i++) {
photoModel.append({
id: -1,
index: i - 1, // For animating seat swap
general: "",
screenName: "",
role: "unknown",
kingdom: "qun",
netstate: "online",
maxHp: 0,
hp: 0,
seatNumber: i + 1,
isDead: false,
dying: false,
faceup: true,
chained: false,
drank: false,
isOwner: false
});
}
Logic.arrangePhotos();
}
} }

View File

@ -3,75 +3,75 @@ import QtQuick 2.15
// CardArea stores CardItem. // CardArea stores CardItem.
Item { Item {
property var cards: [] property var cards: []
property int length: 0 property int length: 0
id: root id: root
function add(inputs) function add(inputs)
{ {
if (inputs instanceof Array) { if (inputs instanceof Array) {
cards.push(...inputs); cards.push(...inputs);
} else { } else {
cards.push(inputs); cards.push(inputs);
}
length = cards.length;
}
function remove(outputs)
{
let result = [];
for (let i = 0; i < cards.length; i++) {
for (let j = 0; j < outputs.length; j++) {
if (outputs[j] === cards[i].cid) {
result.push(cards[i]);
cards.splice(i, 1);
i--;
break;
} }
length = cards.length; }
}
length = cards.length;
return result;
}
function updateCardPosition(animated)
{
let i, card;
let overflow = false;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX = i * card.width;
if (card.origX + card.width >= root.width) {
overflow = true;
break;
}
card.origY = 0;
} }
function remove(outputs) if (overflow) {
{ // TODO: Adjust cards in multiple lines if there are too many cards
let result = []; let xLimit = root.width - card.width;
for (let i = 0; i < cards.length; i++) { let spacing = xLimit / (cards.length - 1);
for (let j = 0; j < outputs.length; j++) { for (i = 0; i < cards.length; i++) {
if (outputs[j] === cards[i].cid) { card = cards[i];
result.push(cards[i]); card.origX = i * spacing;
cards.splice(i, 1); card.origY = 0;
i--; }
break;
}
}
}
length = cards.length;
return result;
} }
function updateCardPosition(animated) let parentPos = roomScene.mapFromItem(root, 0, 0);
{ for (i = 0; i < cards.length; i++) {
let i, card; card = cards[i];
card.origX += parentPos.x;
let overflow = false; card.origY += parentPos.y;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX = i * card.width;
if (card.origX + card.width >= root.width) {
overflow = true;
break;
}
card.origY = 0;
}
if (overflow) {
// TODO: Adjust cards in multiple lines if there are too many cards
let xLimit = root.width - card.width;
let spacing = xLimit / (cards.length - 1);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX = i * spacing;
card.origY = 0;
}
}
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX += parentPos.x;
card.origY += parentPos.y;
}
if (animated) {
for (i = 0; i < cards.length; i++)
cards[i].goBack(true);
}
} }
if (animated) {
for (i = 0; i < cards.length; i++)
cards[i].goBack(true);
}
}
} }

View File

@ -13,242 +13,242 @@ import "../skin-bank.js" as SkinBank
*/ */
Item { Item {
id: root id: root
width: 93 width: 93
height: 130 height: 130
// properties for the view // properties for the view
property string suit: "club" property string suit: "club"
property int number: 7 property int number: 7
property string name: "slash" property string name: "slash"
property string subtype: "" property string subtype: ""
property string color: "" // only use when suit is empty property string color: "" // only use when suit is empty
property string footnote: "" // footnote, e.g. "A use card to B" property string footnote: "" // footnote, e.g. "A use card to B"
property bool footnoteVisible: true property bool footnoteVisible: true
property bool known: true // if false it only show a card back property bool known: true // if false it only show a card back
property bool enabled: true // if false the card will be grey property bool enabled: true // if false the card will be grey
property alias card: cardItem property alias card: cardItem
property alias glow: glowItem property alias glow: glowItem
function getColor() { function getColor() {
if (suit != "") if (suit != "")
return (suit == "heart" || suit == "diamond") ? "red" : "black"; return (suit == "heart" || suit == "diamond") ? "red" : "black";
else return color; else return color;
}
// properties for animation and game system
property int cid: 0
property bool selectable: true
property bool selected: false
property bool draggable: false
property bool autoBack: true
property int origX: 0
property int origY: 0
property real origOpacity: 1
property bool isClicked: false
property bool moveAborted: false
property alias goBackAnim: goBackAnimation
property int goBackDuration: 500
signal toggleDiscards()
signal clicked()
signal doubleClicked()
signal thrown()
signal released()
signal entered()
signal exited()
signal moveFinished()
signal generalChanged() // For choose general freely
signal hoverChanged(bool enter)
RectangularGlow {
id: glowItem
anchors.fill: parent
glowRadius: 8
spread: 0
color: "#88FFFFFF"
cornerRadius: 8
visible: false
}
Image {
id: cardItem
source: known ? (name != "" ? SkinBank.CARD_DIR + name : "")
: (SkinBank.CARD_DIR + "card-back")
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
}
Image {
id: suitItem
visible: known
source: suit != "" ? SkinBank.CARD_SUIT_DIR + suit : ""
x: 3
y: 19
width: 21
height: 17
}
Image {
id: numberItem
visible: known
source: (suit != "" && number > 0) ? SkinBank.CARD_DIR
+ "number/" + root.getColor() + "/" + number : ""
x: 0
y: 0
width: 27
height: 28
}
Image {
id: colorItem
visible: known && suit == ""
source: (visible && color != "") ? SkinBank.CARD_SUIT_DIR + "/" + color : ""
x: 1
}
GlowText {
id: footnoteItem
text: footnote
x: 6
y: parent.height - height - 6
width: root.width - x * 2
color: "#E4D5A0"
visible: footnoteVisible
wrapMode: Text.WrapAnywhere
horizontalAlignment: Text.AlignHCenter
font.family: "FZLiBian-S02"
font.pixelSize: 14
glow.color: "black"
glow.spread: 1
glow.radius: 1
glow.samples: 12
}
Rectangle {
visible: !root.selectable
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: 0.7
}
MouseArea {
anchors.fill: parent
drag.target: draggable ? parent : undefined
drag.axis: Drag.XAndYAxis
hoverEnabled: true
onReleased: {
root.isClicked = mouse.isClick;
parent.released();
if (autoBack)
goBackAnimation.start();
} }
// properties for animation and game system onEntered: {
property int cid: 0 parent.entered();
property bool selectable: true if (draggable) {
property bool selected: false glow.visible = true;
property bool draggable: false root.z++;
property bool autoBack: true }
property int origX: 0
property int origY: 0
property real origOpacity: 1
property bool isClicked: false
property bool moveAborted: false
property alias goBackAnim: goBackAnimation
property int goBackDuration: 500
signal toggleDiscards()
signal clicked()
signal doubleClicked()
signal thrown()
signal released()
signal entered()
signal exited()
signal moveFinished()
signal generalChanged() // For choose general freely
signal hoverChanged(bool enter)
RectangularGlow {
id: glowItem
anchors.fill: parent
glowRadius: 8
spread: 0
color: "#88FFFFFF"
cornerRadius: 8
visible: false
} }
Image { onExited: {
id: cardItem parent.exited();
source: known ? (name != "" ? SkinBank.CARD_DIR + name : "") if (draggable) {
: (SkinBank.CARD_DIR + "card-back") glow.visible = false;
anchors.fill: parent root.z--;
fillMode: Image.PreserveAspectCrop }
} }
Image { onClicked: {
id: suitItem selected = selectable ? !selected : false;
visible: known parent.clicked();
source: suit != "" ? SkinBank.CARD_SUIT_DIR + suit : "" }
x: 3 }
y: 19
width: 21 ParallelAnimation {
height: 17 id: goBackAnimation
PropertyAnimation {
target: root
property: "x"
to: origX
easing.type: Easing.OutQuad
duration: goBackDuration
} }
Image { PropertyAnimation {
id: numberItem target: root
visible: known property: "y"
source: (suit != "" && number > 0) ? SkinBank.CARD_DIR to: origY
+ "number/" + root.getColor() + "/" + number : "" easing.type: Easing.OutQuad
x: 0 duration: goBackDuration
y: 0
width: 27
height: 28
} }
Image { SequentialAnimation {
id: colorItem PropertyAnimation {
visible: known && suit == "" target: root
source: (visible && color != "") ? SkinBank.CARD_SUIT_DIR + "/" + color : "" property: "opacity"
x: 1 to: 1
easing.type: Easing.OutQuad
duration: goBackDuration * 0.8
}
PropertyAnimation {
target: root
property: "opacity"
to: origOpacity
easing.type: Easing.OutQuad
duration: goBackDuration * 0.2
}
} }
GlowText { onStopped: {
id: footnoteItem if (!moveAborted)
text: footnote root.moveFinished();
x: 6
y: parent.height - height - 6
width: root.width - x * 2
color: "#E4D5A0"
visible: footnoteVisible
wrapMode: Text.WrapAnywhere
horizontalAlignment: Text.AlignHCenter
font.family: "FZLiBian-S02"
font.pixelSize: 14
glow.color: "black"
glow.spread: 1
glow.radius: 1
glow.samples: 12
} }
}
Rectangle { function setData(data)
visible: !root.selectable {
anchors.fill: parent cid = data.cid;
color: Qt.rgba(0, 0, 0, 0.5) name = data.name;
opacity: 0.7 suit = data.suit;
number = data.number;
color = data.color;
}
function toData()
{
let data = {
cid: cid,
name: name,
suit: suit,
number: number,
color: color
};
return data;
}
function goBack(animated)
{
if (animated) {
moveAborted = true;
goBackAnimation.stop();
moveAborted = false;
goBackAnimation.start();
} else {
x = origX;
y = origY;
opacity = origOpacity;
} }
}
MouseArea { function destroyOnStop()
anchors.fill: parent {
drag.target: draggable ? parent : undefined root.moveFinished.connect(function(){
drag.axis: Drag.XAndYAxis root.destroy();
hoverEnabled: true });
}
onReleased: {
root.isClicked = mouse.isClick;
parent.released();
if (autoBack)
goBackAnimation.start();
}
onEntered: {
parent.entered();
if (draggable) {
glow.visible = true;
root.z++;
}
}
onExited: {
parent.exited();
if (draggable) {
glow.visible = false;
root.z--;
}
}
onClicked: {
selected = selectable ? !selected : false;
parent.clicked();
}
}
ParallelAnimation {
id: goBackAnimation
PropertyAnimation {
target: root
property: "x"
to: origX
easing.type: Easing.OutQuad
duration: goBackDuration
}
PropertyAnimation {
target: root
property: "y"
to: origY
easing.type: Easing.OutQuad
duration: goBackDuration
}
SequentialAnimation {
PropertyAnimation {
target: root
property: "opacity"
to: 1
easing.type: Easing.OutQuad
duration: goBackDuration * 0.8
}
PropertyAnimation {
target: root
property: "opacity"
to: origOpacity
easing.type: Easing.OutQuad
duration: goBackDuration * 0.2
}
}
onStopped: {
if (!moveAborted)
root.moveFinished();
}
}
function setData(data)
{
cid = data.cid;
name = data.name;
suit = data.suit;
number = data.number;
color = data.color;
}
function toData()
{
let data = {
cid: cid,
name: name,
suit: suit,
number: number,
color: color
};
return data;
}
function goBack(animated)
{
if (animated) {
moveAborted = true;
goBackAnimation.stop();
moveAborted = false;
goBackAnimation.start();
} else {
x = origX;
y = origY;
opacity = origOpacity;
}
}
function destroyOnStop()
{
root.moveFinished.connect(function(){
root.destroy();
});
}
} }

View File

@ -2,33 +2,33 @@ import QtQuick 2.15
import ".." import ".."
GraphicsBox { GraphicsBox {
property var options: [] property var options: []
property string skill_name: "" property string skill_name: ""
property int result property int result
id: root id: root
title.text: skill_name + ": Please choose" title.text: skill_name + ": Please choose"
width: Math.max(140, body.width + 20) width: Math.max(140, body.width + 20)
height: body.height + title.height + 20 height: body.height + title.height + 20
Column { Column {
id: body id: body
x: 10 x: 10
y: title.height + 5 y: title.height + 5
spacing: 10 spacing: 10
Repeater { Repeater {
model: options model: options
MetroButton { MetroButton {
text: modelData text: modelData
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
onClicked: { onClicked: {
result = index; result = index;
root.close(); root.close();
}
}
} }
}
} }
}
} }

View File

@ -3,175 +3,175 @@ import ".."
import "../skin-bank.js" as SkinBank import "../skin-bank.js" as SkinBank
GraphicsBox { GraphicsBox {
property alias generalList: generalList property alias generalList: generalList
// property var generalList: [] // property var generalList: []
property int choiceNum: 1 property int choiceNum: 1
property var choices: [] property var choices: []
property var selectedItem: [] property var selectedItem: []
property bool loaded: false property bool loaded: false
ListModel { ListModel {
id: generalList id: generalList
}
id: root
title.text: qsTr("Please choose ") + choiceNum + qsTr(" general(s)")
width: generalArea.width + body.anchors.leftMargin + body.anchors.rightMargin
height: body.implicitHeight + body.anchors.topMargin + body.anchors.bottomMargin
Column {
id: body
anchors.fill: parent
anchors.margins: 40
anchors.bottomMargin: 20
Item {
id: generalArea
width: (generalList.count >= 5 ? Math.ceil(generalList.count / 2) : Math.max(3, generalList.count)) * 97
height: generalList.count >= 5 ? 290 : 150
z: 1
Repeater {
id: generalMagnetList
model: generalList.count
Item {
width: 93
height: 130
x: (index % Math.ceil(generalList.count / (generalList.count >= 5 ? 2 : 1))) * 98 + (generalList.count >= 5 && index > generalList.count / 2 && generalList.count % 2 == 1 ? 50 : 0)
y: generalList.count < 5 ? 0 : (index < generalList.count / 2 ? 0 : 135)
}
}
} }
id: root Item {
title.text: qsTr("Please choose ") + choiceNum + qsTr(" general(s)") id: splitLine
width: generalArea.width + body.anchors.leftMargin + body.anchors.rightMargin width: parent.width - 80
height: body.implicitHeight + body.anchors.topMargin + body.anchors.bottomMargin height: 6
anchors.horizontalCenter: parent.horizontalCenter
Column { clip: true
id: body
anchors.fill: parent
anchors.margins: 40
anchors.bottomMargin: 20
Item {
id: generalArea
width: (generalList.count >= 5 ? Math.ceil(generalList.count / 2) : Math.max(3, generalList.count)) * 97
height: generalList.count >= 5 ? 290 : 150
z: 1
Repeater {
id: generalMagnetList
model: generalList.count
Item {
width: 93
height: 130
x: (index % Math.ceil(generalList.count / (generalList.count >= 5 ? 2 : 1))) * 98 + (generalList.count >= 5 && index > generalList.count / 2 && generalList.count % 2 == 1 ? 50 : 0)
y: generalList.count < 5 ? 0 : (index < generalList.count / 2 ? 0 : 135)
}
}
}
Item {
id: splitLine
width: parent.width - 80
height: 6
anchors.horizontalCenter: parent.horizontalCenter
clip: true
}
Item {
width: parent.width
height: 165
Row {
id: resultArea
anchors.centerIn: parent
spacing: 10
Repeater {
id: resultList
model: choiceNum
Rectangle {
color: "#1D1E19"
radius: 3
width: 93
height: 130
}
}
}
}
Item {
id: buttonArea
width: parent.width
height: 40
MetroButton {
id: fightButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
text: qsTr("Fight")
width: 120
height: 35
enabled: false
onClicked: close();
}
}
} }
Repeater { Item {
id: generalCardList width: parent.width
model: generalList height: 165
GeneralCardItem { Row {
name: model.name id: resultArea
selectable: true anchors.centerIn: parent
draggable: true spacing: 10
onClicked: { Repeater {
let toSelect = true; id: resultList
for (let i = 0; i < selectedItem.length; i++) { model: choiceNum
if (selectedItem[i] === this) {
toSelect = false;
selectedItem.splice(i, 1);
}
}
if (toSelect && selectedItem.length < choiceNum)
selectedItem.push(this);
updatePosition();
}
onReleased: { Rectangle {
if (!isClicked) color: "#1D1E19"
arrangeCards(); radius: 3
} width: 93
height: 130
}
} }
}
} }
function arrangeCards() Item {
{ id: buttonArea
let item, i; width: parent.width
height: 40
selectedItem = []; MetroButton {
for (i = 0; i < generalList.count; i++) { id: fightButton
item = generalCardList.itemAt(i); anchors.horizontalCenter: parent.horizontalCenter
if (item.y > splitLine.y) anchors.bottom: parent.bottom
selectedItem.push(item); text: qsTr("Fight")
width: 120
height: 35
enabled: false
onClicked: close();
}
}
}
Repeater {
id: generalCardList
model: generalList
GeneralCardItem {
name: model.name
selectable: true
draggable: true
onClicked: {
let toSelect = true;
for (let i = 0; i < selectedItem.length; i++) {
if (selectedItem[i] === this) {
toSelect = false;
selectedItem.splice(i, 1);
}
} }
if (toSelect && selectedItem.length < choiceNum)
selectedItem.sort((a, b) => a.x - b.x); selectedItem.push(this);
if (selectedItem.length > choiceNum)
selectedItem.splice(choiceNum, selectedItem.length - choiceNum);
updatePosition(); updatePosition();
}
onReleased: {
if (!isClicked)
arrangeCards();
}
}
}
function arrangeCards()
{
let item, i;
selectedItem = [];
for (i = 0; i < generalList.count; i++) {
item = generalCardList.itemAt(i);
if (item.y > splitLine.y)
selectedItem.push(item);
} }
function updatePosition() selectedItem.sort((a, b) => a.x - b.x);
{
choices = [];
let item, magnet, pos, i;
for (i = 0; i < selectedItem.length && i < resultList.count; i++) {
item = selectedItem[i];
choices.push(item.name);
magnet = resultList.itemAt(i);
pos = root.mapFromItem(resultArea, magnet.x, magnet.y);
if (item.origX !== pos.x || item.origY !== item.y) {
item.origX = pos.x;
item.origY = pos.y;
item.goBack(true);
}
}
fightButton.enabled = (choices.length == choiceNum); if (selectedItem.length > choiceNum)
selectedItem.splice(choiceNum, selectedItem.length - choiceNum);
for (i = 0; i < generalCardList.count; i++) { updatePosition();
item = generalCardList.itemAt(i); }
if (selectedItem.indexOf(item) != -1)
continue;
magnet = generalMagnetList.itemAt(i); function updatePosition()
pos = root.mapFromItem(generalMagnetList.parent, magnet.x, magnet.y); {
if (item.origX !== pos.x || item.origY !== item.y) { choices = [];
item.origX = pos.x; let item, magnet, pos, i;
item.origY = pos.y; for (i = 0; i < selectedItem.length && i < resultList.count; i++) {
item.goBack(true); item = selectedItem[i];
} choices.push(item.name);
} magnet = resultList.itemAt(i);
pos = root.mapFromItem(resultArea, magnet.x, magnet.y);
if (item.origX !== pos.x || item.origY !== item.y) {
item.origX = pos.x;
item.origY = pos.y;
item.goBack(true);
}
} }
fightButton.enabled = (choices.length == choiceNum);
for (i = 0; i < generalCardList.count; i++) {
item = generalCardList.itemAt(i);
if (selectedItem.indexOf(item) != -1)
continue;
magnet = generalMagnetList.itemAt(i);
pos = root.mapFromItem(generalMagnetList.parent, magnet.x, magnet.y);
if (item.origX !== pos.x || item.origY !== item.y) {
item.origX = pos.x;
item.origY = pos.y;
item.goBack(true);
}
}
}
} }

View File

@ -3,29 +3,166 @@ import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
RowLayout { RowLayout {
id: root id: root
property alias self: selfPhoto property alias self: selfPhoto
property alias handcardArea: handcardAreaItem property alias handcardArea: handcardAreaItem
property alias equipArea: selfPhoto.equipArea property alias equipArea: selfPhoto.equipArea
property alias delayedTrickArea: selfPhoto.delayedTrickArea property alias delayedTrickArea: selfPhoto.delayedTrickArea
property alias specialArea: selfPhoto.specialArea property alias specialArea: selfPhoto.specialArea
Item { property bool selected: selfPhoto.selected
width: 40
property bool is_pending: false
property string pending_skill: ""
property var pending_card
property var pendings: [] // int[], store cid
property int selected_card: -1
signal cardSelected(var card)
Item {
width: 40
}
HandcardArea {
id: handcardAreaItem
Layout.fillWidth: true
Layout.preferredHeight: 130
Layout.alignment: Qt.AlignVCenter
}
Photo {
id: selfPhoto
handcards: handcardAreaItem.length
}
Item { width: 5 }
Connections {
target: handcardAreaItem
function onCardSelected(cardId, selected) {
dashboard.selectCard(cardId, selected);
}
}
function disableAllCards() {
handcardAreaItem.enableCards([]);
}
function unSelectAll(expectId) {
handcardAreaItem.unselectAll(expectId);
}
function enableCards() {
// TODO: expand pile
let ids = [], cards = handcardAreaItem.cards;
for (let i = 0; i < cards.length; i++) {
if (JSON.parse(Backend.callLuaFunction("CanUseCard", [cards[i].cid, Self.id])))
ids.push(cards[i].cid);
}
handcardAreaItem.enableCards(ids)
}
function selectCard(cardId, selected) {
if (pending_skill !== "") {
if (selected) {
pendings.push(cardId);
} else {
pendings.splice(pendings.indexOf(cardId), 1);
}
updatePending();
} else {
if (selected) {
handcardAreaItem.unselectAll(cardId);
selected_card = cardId;
} else {
handcardAreaItem.unselectAll();
selected_card = -1;
}
cardSelected(selected_card);
}
}
function getSelectedCard() {
if (pending_skill !== "") {
return JSON.stringify({
skill: pending_skill,
subcards: pendings
});
} else {
return selected_card;
}
}
function updatePending() {
if (pending_skill === "") return;
let enabled_cards = [];
handcardAreaItem.cards.forEach(function(card) {
if (card.selected || Router.vs_view_filter(pending_skill, pendings, card.cid))
enabled_cards.push(card.cid);
});
handcardAreaItem.enableCards(enabled_cards);
let equip;
for (let i = 0; i < 5; i++) {
equip = equipAreaItem.equips.itemAt(i);
if (equip.selected || equip.cid !== -1 &&
Router.vs_view_filter(pending_skill, pendings, equip.cid))
enabled_cards.push(equip.cid);
}
equipAreaItem.enableCards(enabled_cards);
if (Router.vs_can_view_as(pending_skill, pendings)) {
pending_card = {
skill: pending_skill,
subcards: pendings
};
cardSelected(JSON.stringify(pending_card));
} else {
pending_card = -1;
cardSelected(pending_card);
}
}
function startPending(skill_name) {
pending_skill = skill_name;
pendings = [];
handcardAreaItem.unselectAll();
// TODO: expand pile
// TODO: equipment
updatePending();
}
function deactivateSkillButton() {
for (let i = 0; i < headSkills.length; i++) {
headSkillButtons.itemAt(i).pressed = false;
}
}
function stopPending() {
pending_skill = "";
pending_card = -1;
// TODO: expand pile
let equip;
for (let i = 0; i < 5; i++) {
equip = equipAreaItem.equips.itemAt(i);
if (equip.name !== "") {
equip.selected = false;
equip.selectable = false;
}
} }
HandcardArea { pendings = [];
id: handcardAreaItem handcardAreaItem.adjustCards();
Layout.fillWidth: true cardSelected(-1);
Layout.preferredHeight: 130 }
Layout.alignment: Qt.AlignVCenter
}
Photo {
id: selfPhoto
handcards: handcardAreaItem.length
}
Item { width: 5 }
} }

View File

@ -13,12 +13,12 @@ import "../skin-bank.js" as SkinBank
*/ */
CardItem { CardItem {
property string kingdom: "qun" property string kingdom: "qun"
name: "caocao" name: "caocao"
// description: Sanguosha.getGeneralDescription(name) // description: Sanguosha.getGeneralDescription(name)
suit: "" suit: ""
number: 0 number: 0
footnote: "" footnote: ""
card.source: SkinBank.GENERAL_DIR + name card.source: SkinBank.GENERAL_DIR + name
glow.color: "white" //Engine.kingdomColor[kingdom] glow.color: "white" //Engine.kingdomColor[kingdom]
} }

View File

@ -2,29 +2,29 @@ import QtQuick 2.15
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
Item { Item {
property alias text: textItem.text property alias text: textItem.text
property alias color: textItem.color property alias color: textItem.color
property alias font: textItem.font property alias font: textItem.font
property alias fontSizeMode: textItem.fontSizeMode property alias fontSizeMode: textItem.fontSizeMode
property alias horizontalAlignment: textItem.horizontalAlignment property alias horizontalAlignment: textItem.horizontalAlignment
property alias verticalAlignment: textItem.verticalAlignment property alias verticalAlignment: textItem.verticalAlignment
property alias style: textItem.style property alias style: textItem.style
property alias styleColor: textItem.styleColor property alias styleColor: textItem.styleColor
property alias wrapMode: textItem.wrapMode property alias wrapMode: textItem.wrapMode
property alias lineHeight: textItem.lineHeight property alias lineHeight: textItem.lineHeight
property alias glow: glowItem property alias glow: glowItem
width: textItem.implicitWidth width: textItem.implicitWidth
height: textItem.implicitHeight height: textItem.implicitHeight
Text { Text {
id: textItem id: textItem
anchors.fill: parent anchors.fill: parent
} }
Glow { Glow {
id: glowItem id: glowItem
source: textItem source: textItem
anchors.fill: textItem anchors.fill: textItem
} }
} }

View File

@ -2,52 +2,52 @@ import QtQuick 2.15
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
Item { Item {
property alias title: titleItem property alias title: titleItem
signal accepted() //Read result signal accepted() //Read result
signal finished() //Close the box signal finished() //Close the box
id: root id: root
Rectangle { Rectangle {
id: background id: background
anchors.fill: parent anchors.fill: parent
color: "#B0000000" color: "#B0000000"
radius: 5 radius: 5
border.color: "#A6967A" border.color: "#A6967A"
border.width: 1 border.width: 1
} }
DropShadow { DropShadow {
source: background source: background
anchors.fill: background anchors.fill: background
color: "#B0000000" color: "#B0000000"
radius: 5 radius: 5
samples: 12 samples: 12
spread: 0.2 spread: 0.2
horizontalOffset: 5 horizontalOffset: 5
verticalOffset: 4 verticalOffset: 4
transparentBorder: true transparentBorder: true
} }
Text { Text {
id: titleItem id: titleItem
color: "#E4D5A0" color: "#E4D5A0"
font.pixelSize: 18 font.pixelSize: 18
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 4 anchors.topMargin: 4
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
drag.target: parent drag.target: parent
drag.axis: Drag.XAndYAxis drag.axis: Drag.XAndYAxis
} }
function close() function close()
{ {
accepted(); accepted();
finished(); finished();
} }
} }

View File

@ -2,130 +2,130 @@ import QtQuick 2.15
import "../../util.js" as Utility import "../../util.js" as Utility
Item { Item {
property alias cards: cardArea.cards property alias cards: cardArea.cards
property alias length: cardArea.length property alias length: cardArea.length
property var selectedCards: [] property var selectedCards: []
signal cardSelected(int cardId, bool selected) signal cardSelected(int cardId, bool selected)
id: area id: area
CardArea { CardArea {
anchors.fill: parent anchors.fill: parent
id: cardArea id: cardArea
onLengthChanged: area.updateCardPosition(true); onLengthChanged: area.updateCardPosition(true);
}
function add(inputs)
{
cardArea.add(inputs);
if (inputs instanceof Array) {
for (let i = 0; i < inputs.length; i++)
filterInputCard(inputs[i]);
} else {
filterInputCard(inputs);
}
}
function filterInputCard(card)
{
card.autoBack = true;
card.draggable = true;
card.selectable = false;
card.clicked.connect(adjustCards);
}
function remove(outputs)
{
let result = cardArea.remove(outputs);
let card;
for (let i = 0; i < result.length; i++) {
card = result[i];
card.draggable = false;
card.selectable = false;
card.selectedChanged.disconnect(adjustCards);
}
return result;
}
function enableCards(cardIds)
{
let card, i;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.selectable = cardIds.contains(card.cid);
if (!card.selectable) {
card.selected = false;
unselectCard(card);
}
}
}
function updateCardPosition(animated)
{
cardArea.updateCardPosition(false);
let i, card;
for (i = 0; i < cards.length; i++) {
card = cards[i];
if (card.selected)
card.origY -= 20;
} }
function add(inputs) if (animated) {
{ for (i = 0; i < cards.length; i++)
cardArea.add(inputs); cards[i].goBack(true)
if (inputs instanceof Array) {
for (let i = 0; i < inputs.length; i++)
filterInputCard(inputs[i]);
} else {
filterInputCard(inputs);
}
} }
}
function filterInputCard(card) function adjustCards()
{ {
card.autoBack = true; area.updateCardPosition(true);
card.draggable = true;
card.selectable = false; for (let i = 0; i < cards.length; i++) {
card.clicked.connect(adjustCards); let card = cards[i];
if (card.selected) {
if (!selectedCards.contains(card))
selectCard(card);
} else {
if (selectedCards.contains(card))
unselectCard(card);
}
} }
}
function remove(outputs) function selectCard(card)
{ {
let result = cardArea.remove(outputs); selectedCards.push(card);
let card; cardSelected(card.cid, true);
for (let i = 0; i < result.length; i++) { }
card = result[i];
card.draggable = false; function unselectCard(card)
card.selectable = false; {
card.selectedChanged.disconnect(adjustCards); for (let i = 0; i < selectedCards.length; i++) {
} if (selectedCards[i] === card) {
return result; selectedCards.splice(i, 1);
cardSelected(card.cid, false);
break;
}
} }
}
function enableCards(cardIds) function unselectAll(exceptId) {
{ let card = undefined;
let card, i; for (let i = 0; i < selectedCards.length; i++) {
for (i = 0; i < cards.length; i++) { if (selectedCards[i].cid !== exceptId) {
card = cards[i]; selectedCards[i].selected = false;
card.selectable = cardIds.contains(card.cid); } else {
if (!card.selectable) { card = selectedCards[i];
card.selected = false; card.selected = true;
unselectCard(card); }
}
}
} }
if (card === undefined) {
function updateCardPosition(animated) selectedCards = [];
{ } else {
cardArea.updateCardPosition(false); selectedCards = [card];
let i, card;
for (i = 0; i < cards.length; i++) {
card = cards[i];
if (card.selected)
card.origY -= 20;
}
if (animated) {
for (i = 0; i < cards.length; i++)
cards[i].goBack(true)
}
}
function adjustCards()
{
area.updateCardPosition(true);
for (let i = 0; i < cards.length; i++) {
let card = cards[i];
if (card.selected) {
if (!selectedCards.contains(card))
selectCard(card);
} else {
if (selectedCards.contains(card))
unselectCard(card);
}
}
}
function selectCard(card)
{
selectedCards.push(card);
cardSelected(card.cid, true);
}
function unselectCard(card)
{
for (let i = 0; i < selectedCards.length; i++) {
if (selectedCards[i] === card) {
selectedCards.splice(i, 1);
cardSelected(card.cid, false);
break;
}
}
}
function unselectAll(exceptId) {
let card = undefined;
for (let i = 0; i < selectedCards.length; i++) {
if (selectedCards[i].cid !== exceptId) {
selectedCards[i].selected = false;
} else {
card = selectedCards[i];
card.selected = true;
}
}
if (card === undefined) {
selectedCards = [];
} else {
selectedCards = [card];
}
updateCardPosition(true);
} }
updateCardPosition(true);
}
} }

View File

@ -1,101 +1,101 @@
import QtQuick 2.15 import QtQuick 2.15
Item { Item {
property point start: Qt.point(0, 0) property point start: Qt.point(0, 0)
property var end: [] property var end: []
property alias running: pointToAnimation.running property alias running: pointToAnimation.running
property color color: "#96943D" property color color: "#96943D"
property real ratio: 0 property real ratio: 0
property int lineWidth: 6 property int lineWidth: 6
signal finished() signal finished()
id: root id: root
anchors.fill: parent anchors.fill: parent
Repeater { Repeater {
model: end model: end
Rectangle { Rectangle {
width: 6 width: 6
height: Math.sqrt(Math.pow(modelData.x - start.x, 2) + Math.pow(modelData.y - start.y, 2)) * ratio height: Math.sqrt(Math.pow(modelData.x - start.x, 2) + Math.pow(modelData.y - start.y, 2)) * ratio
x: start.x x: start.x
y: start.y y: start.y
antialiasing: true antialiasing: true
gradient: Gradient { gradient: Gradient {
GradientStop { GradientStop {
position: 0 position: 0
color: Qt.rgba(255, 255, 255, 0) color: Qt.rgba(255, 255, 255, 0)
}
GradientStop {
position: 1
color: Qt.rgba(200, 200, 200, 0.12)
}
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 3
height: parent.height
antialiasing: true
gradient: Gradient {
GradientStop {
position: 0
color: Qt.rgba(255, 255, 255, 0)
}
GradientStop {
position: 1
color: Qt.lighter(root.color)
}
}
}
transform: Rotation {
angle: 0
Component.onCompleted: {
var dx = modelData.x - start.x;
var dy = modelData.y - start.y;
if (dx > 0) {
angle = Math.atan2(dy, dx) / Math.PI * 180 - 90;
} else if (dx < 0) {
angle = Math.atan2(dy, dx) / Math.PI * 180 + 270;
} else if (dy < 0) {
angle = 180;
}
}
}
} }
GradientStop {
position: 1
color: Qt.rgba(200, 200, 200, 0.12)
}
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 3
height: parent.height
antialiasing: true
gradient: Gradient {
GradientStop {
position: 0
color: Qt.rgba(255, 255, 255, 0)
}
GradientStop {
position: 1
color: Qt.lighter(root.color)
}
}
}
transform: Rotation {
angle: 0
Component.onCompleted: {
var dx = modelData.x - start.x;
var dy = modelData.y - start.y;
if (dx > 0) {
angle = Math.atan2(dy, dx) / Math.PI * 180 - 90;
} else if (dx < 0) {
angle = Math.atan2(dy, dx) / Math.PI * 180 + 270;
} else if (dy < 0) {
angle = 180;
}
}
}
}
}
SequentialAnimation {
id: pointToAnimation
PropertyAnimation {
target: root
property: "ratio"
to: 1
easing.type: Easing.OutCubic
duration: 200
} }
SequentialAnimation { PauseAnimation {
id: pointToAnimation duration: 200
PropertyAnimation {
target: root
property: "ratio"
to: 1
easing.type: Easing.OutCubic
duration: 200
}
PauseAnimation {
duration: 200
}
PropertyAnimation {
target: root
property: "opacity"
to: 0
easing.type: Easing.InQuart
duration: 300
}
onStopped: {
root.visible = false;
root.finished();
}
} }
PropertyAnimation {
target: root
property: "opacity"
to: 0
easing.type: Easing.InQuart
duration: 300
}
onStopped: {
root.visible = false;
root.finished();
}
}
} }

View File

@ -1,111 +1,111 @@
import QtQuick 2.15 import QtQuick 2.15
Item { Item {
property var cards: [] property var cards: []
property int length: 0 property int length: 0
property var pendingInput: [] property var pendingInput: []
property bool checkExisting: false property bool checkExisting: false
id: root id: root
function add(inputs) function add(inputs)
{
let card;
if (inputs instanceof Array) {
for (let i = 0; i < inputs.length; i++) {
card = inputs[i];
pendingInput.push(card);
cards.push(card.toData());
}
if (checkExisting)
length = cards.length;
else
length += inputs.length;
} else {
pendingInput.push(inputs);
cards.push(inputs.toData());
if (checkExisting)
length = cards.length;
else
length++;
}
}
function _contains(cid)
{
if (!checkExisting)
return true;
for (let i = 0; i < cards.length; i++)
{ {
let card; if (cards[i].cid === cid)
if (inputs instanceof Array) { return true;
for (let i = 0; i < inputs.length; i++) { }
card = inputs[i]; return false;
pendingInput.push(card); }
cards.push(card.toData());
}
if (checkExisting) function remove(outputs)
length = cards.length; {
else let component = Qt.createComponent("CardItem.qml");
length += inputs.length; if (component.status !== Component.Ready)
} else { return [];
pendingInput.push(inputs);
cards.push(inputs.toData());
if (checkExisting) let parentPos = roomScene.mapFromItem(root, 0, 0);
length = cards.length; let card;
else let items = [];
length++; for (let i = 0; i < outputs.length; i++) {
if (_contains(outputs[i])) {
let state = JSON.parse(Backend.callLuaFunction("GetCardData", [outputs[i]]))
state.x = parentPos.x;
state.y = parentPos.y;
state.opacity = 0;
card = component.createObject(roomScene, state);
card.x -= card.width / 2;
card.x += (i - outputs.length / 2) * 15;
card.y -= card.height / 2;
items.push(card);
if (checkExisting) {
//@to-do: remove it from cards
cards.splice(i, 1);
i--;
} }
}
}
if (checkExisting)
length = cards.length;
else
length -= outputs.length;
return items;
}
function updateCardPosition(animated)
{
let i, card;
if (animated) {
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < pendingInput.length; i++) {
card = pendingInput[i];
card.origX = parentPos.x - card.width / 2 + ((i - pendingInput.length / 2) * 15);
card.origY = parentPos.y - card.height / 2;
card.origOpacity = 0;
card.destroyOnStop();
}
for (i = 0; i < pendingInput.length; i++)
pendingInput[i].goBack(true);
} else {
for (i = 0; i < pendingInput.length; i++) {
card = pendingInput[i];
card.x = parentPos.x - card.width / 2;
card.y = parentPos.y - card.height / 2;
card.opacity = 1;
card.destroy();
}
} }
function _contains(cid) pendingInput = [];
{ }
if (!checkExisting)
return true;
for (let i = 0; i < cards.length; i++)
{
if (cards[i].cid === cid)
return true;
}
return false;
}
function remove(outputs)
{
let component = Qt.createComponent("CardItem.qml");
if (component.status !== Component.Ready)
return [];
let parentPos = roomScene.mapFromItem(root, 0, 0);
let card;
let items = [];
for (let i = 0; i < outputs.length; i++) {
if (_contains(outputs[i])) {
let state = JSON.parse(Backend.getCardData(outputs[i]))
state.x = parentPos.x;
state.y = parentPos.y;
state.opacity = 0;
card = component.createObject(roomScene, state);
card.x -= card.width / 2;
card.x += (i - outputs.length / 2) * 15;
card.y -= card.height / 2;
items.push(card);
if (checkExisting) {
//@to-do: remove it from cards
cards.splice(i, 1);
i--;
}
}
}
if (checkExisting)
length = cards.length;
else
length -= outputs.length;
return items;
}
function updateCardPosition(animated)
{
let i, card;
if (animated) {
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < pendingInput.length; i++) {
card = pendingInput[i];
card.origX = parentPos.x - card.width / 2 + ((i - pendingInput.length / 2) * 15);
card.origY = parentPos.y - card.height / 2;
card.origOpacity = 0;
card.destroyOnStop();
}
for (i = 0; i < pendingInput.length; i++)
pendingInput[i].goBack(true);
} else {
for (i = 0; i < pendingInput.length; i++) {
card = pendingInput[i];
card.x = parentPos.x - card.width / 2;
card.y = parentPos.y - card.height / 2;
card.opacity = 1;
card.destroy();
}
}
pendingInput = [];
}
} }

View File

@ -5,309 +5,377 @@ import "PhotoElement"
import "../skin-bank.js" as SkinBank import "../skin-bank.js" as SkinBank
Item { Item {
id: root id: root
width: 175 width: 175
height: 233 height: 233
scale: 0.8 scale: 0.8
property string general: "" property int playerid
property string screenName: "" property string general: ""
property string role: "unknown" property string screenName: ""
property string kingdom: "qun" property string role: "unknown"
property string netstate: "online" property string kingdom: "qun"
property alias handcards: handcardAreaItem.length property string netstate: "online"
property int maxHp: 0 property alias handcards: handcardAreaItem.length
property int hp: 0 property int maxHp: 0
property int seatNumber: 1 property int hp: 0
property bool isDead: false property int seatNumber: 1
property bool dying: false property bool isDead: false
property bool faceup: true property bool dying: false
property bool chained: false property bool faceup: true
property bool drank: false property bool chained: false
property bool isOwner: false property bool drank: false
property string status: "normal" property bool isOwner: false
property string status: "normal"
property alias handcardArea: handcardAreaItem property alias handcardArea: handcardAreaItem
property alias equipArea: equipAreaItem property alias equipArea: equipAreaItem
property alias delayedTrickArea: delayedTrickAreaItem property alias delayedTrickArea: delayedTrickAreaItem
property alias specialArea: handcardAreaItem property alias specialArea: handcardAreaItem
property alias progressBar: progressBar property alias progressBar: progressBar
property alias progressTip: progressTip.text property alias progressTip: progressTip.text
property bool selectable: false property bool selectable: false
property bool selected: false property bool selected: false
Behavior on x { Behavior on x {
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad } NumberAnimation { duration: 600; easing.type: Easing.InOutQuad }
}
Behavior on y {
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad }
}
states: [
State { name: "normal" },
State { name: "candidate" },
State { name: "playing" }
//State { name: "responding" },
//State { name: "sos" }
]
state: "normal"
transitions: [
Transition {
from: "*"; to: "normal"
ScriptAction {
script: {
animPlaying.stop();
animSelectable.stop();
animSelected.stop();
}
}
},
Transition {
from: "*"; to: "playing"
ScriptAction {
script: {
animPlaying.start();
}
}
},
Transition {
from: "*"; to: "candidate"
ScriptAction {
script: {
animSelectable.start();
animSelected.start();
}
}
} }
]
Behavior on y { PixmapAnimation {
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad } id: animPlaying
} source: "playing"
anchors.centerIn: parent
loop: true
scale: 1.1
visible: root.state === "playing"
}
PixmapAnimation { PixmapAnimation {
id: animFrame id: animSelected
source: "selected" source: "selected"
anchors.centerIn: parent anchors.centerIn: parent
loop: true loop: true
scale: 1.1 scale: 1.1
} visible: root.state === "candidate" && selected
}
Image { Image {
id: back id: back
source: SkinBank.PHOTO_BACK_DIR + root.kingdom source: SkinBank.PHOTO_BACK_DIR + root.kingdom
} }
Text {
id: generalName
x: 5
y: 28
font.family: "FZLiBian-S02"
font.pixelSize: 22
opacity: 0.7
horizontalAlignment: Text.AlignHCenter
lineHeight: 18
lineHeightMode: Text.FixedHeight
color: "white"
width: 24
wrapMode: Text.WordWrap
text: ""
}
HpBar {
id: hp
x: 8
value: root.hp
maxValue: root.maxHp
anchors.bottom: parent.bottom
anchors.bottomMargin: 36
}
Image {
id: generalImage
width: 138
height: 222
smooth: true
visible: false
fillMode: Image.PreserveAspectCrop
source: (general != "") ? SkinBank.GENERAL_DIR + general : ""
}
Rectangle {
id: photoMask
x: 31
y: 5
width: 138
height: 222
radius: 8
visible: false
}
OpacityMask {
anchors.fill: photoMask
source: generalImage
maskSource: photoMask
}
Colorize {
anchors.fill: photoMask
source: generalImage
saturation: 0
visible: root.isDead
}
Image {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: 8
anchors.rightMargin: 4
source: SkinBank.PHOTO_DIR + (isOwner ? "owner" : "ready")
visible: screenName != "" && !roomScene.isStarted
}
Image {
visible: equipAreaItem.length > 0
source: SkinBank.PHOTO_DIR + "equipbg"
x: 31
y: 121
}
Image {
source: root.status != "normal" ? SkinBank.STATUS_DIR + root.status : ""
x: -6
}
Image {
id: turnedOver
visible: !root.faceup
source: SkinBank.PHOTO_DIR + "faceturned"
x: 29; y: 5
}
EquipArea {
id: equipAreaItem
x: 31
y: 139
}
Image {
id: chain
visible: root.chained
source: SkinBank.PHOTO_DIR + "chain"
anchors.horizontalCenter: parent.horizontalCenter
y: 72
}
Image {
// id: saveme
visible: root.isDead || root.dying
source: SkinBank.DEATH_DIR + (root.isDead ? root.role : "saveme")
anchors.centerIn: photoMask
}
Image {
id: netstat
source: SkinBank.STATE_DIR + root.netstate
x: photoMask.x
y: photoMask.y
}
Image {
id: handcardNum
source: SkinBank.PHOTO_DIR + "handcard"
anchors.bottom: parent.bottom
anchors.bottomMargin: -6
x: -6
Text { Text {
id: generalName text: root.handcards
x: 5 font.family: "FZLiBian-S02"
y: 28 font.pixelSize: 32
font.family: "FZLiBian-S02" //font.weight: 30
font.pixelSize: 22 color: "white"
opacity: 0.7 anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter anchors.bottom: parent.bottom
lineHeight: 18 anchors.bottomMargin: 4
lineHeightMode: Text.FixedHeight style: Text.Outline
color: "white"
width: 24
wrapMode: Text.WordWrap
text: ""
} }
}
HpBar { MouseArea {
id: hp anchors.fill: parent
x: 8 onClicked: {
value: root.hp if (parent.state != "candidate" || !parent.selectable)
maxValue: root.maxHp return;
anchors.bottom: parent.bottom parent.selected = !parent.selected;
anchors.bottomMargin: 36
} }
}
Image { RoleComboBox {
id: generalImage id: role
width: 138 value: root.role
height: 222 anchors.top: parent.top
smooth: true anchors.topMargin: -4
visible: false anchors.right: parent.right
fillMode: Image.PreserveAspectCrop anchors.rightMargin: -4
source: (general != "") ? SkinBank.GENERAL_DIR + general : "" }
Image {
visible: root.state === "candidate" && !selectable && !selected
source: SkinBank.PHOTO_DIR + "disable"
x: 31; y: -21
}
GlowText {
id: seatNum
visible: !progressBar.visible
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: -32
property var seatChr: ["一", "二", "三", "四", "五", "六", "七", "八"]
font.family: "FZLiShu II-S06S"
font.pixelSize: 32
text: seatChr[seatNumber - 1]
glow.color: "brown"
glow.spread: 0.2
glow.radius: 8
glow.samples: 12
}
SequentialAnimation {
id: trembleAnimation
running: false
PropertyAnimation {
target: root
property: "x"
to: root.x - 20
easing.type: Easing.InQuad
duration: 100
} }
PropertyAnimation {
Rectangle { target: root
id: photoMask property: "x"
x: 31 to: root.x
y: 5 easing.type: Easing.OutQuad
width: 138 duration: 100
height: 222
radius: 8
visible: false
} }
}
function tremble() {
trembleAnimation.start()
}
OpacityMask { ProgressBar {
anchors.fill: photoMask id: progressBar
source: generalImage width: parent.width
maskSource: photoMask height: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: -4
from: 0.0
to: 100.0
visible: false
NumberAnimation on value {
running: progressBar.visible
from: 100.0
to: 0.0
duration: config.roomTimeout * 1000
onFinished: {
progressBar.visible = false;
root.progressTip = "";
}
} }
}
Colorize { Image {
anchors.fill: photoMask anchors.top: progressBar.bottom
source: generalImage anchors.topMargin: 1
saturation: 0 source: SkinBank.PHOTO_DIR + "control/tip"
visible: root.isDead visible: progressTip.text != ""
Text {
id: progressTip
font.family: "FZLiBian-S02"
font.pixelSize: 18
x: 18
color: "white"
text: ""
} }
}
Image { PixmapAnimation {
anchors.bottom: parent.bottom id: animSelectable
anchors.right: parent.right source: "selectable"
anchors.bottomMargin: 8 anchors.centerIn: parent
anchors.rightMargin: 4 loop: true
source: SkinBank.PHOTO_DIR + (isOwner ? "owner" : "ready") visible: root.state === "candidate" && selectable
visible: screenName != "" && !roomScene.isStarted }
}
Image { InvisibleCardArea {
visible: equipAreaItem.length > 0 id: handcardAreaItem
source: SkinBank.PHOTO_DIR + "equipbg" anchors.centerIn: parent
x: 31 }
y: 121
}
Image { DelayedTrickArea {
source: root.status != "normal" ? SkinBank.STATUS_DIR + root.status : "" id: delayedTrickAreaItem
x: -6 rows: 1
} anchors.bottom: parent.bottom
anchors.bottomMargin: 8
}
Image { InvisibleCardArea {
id: turnedOver id: defaultArea
visible: !root.faceup anchors.centerIn: parent
source: SkinBank.PHOTO_DIR + "faceturned" }
x: 29; y: 5
}
EquipArea { onGeneralChanged: {
id: equipAreaItem if (!roomScene.isStarted) return;
generalName.text = Backend.translate(general);
x: 31 let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [general]));
y: 139 kingdom = data.kingdom;
} }
Image {
id: chain
visible: root.chained
source: SkinBank.PHOTO_DIR + "chain"
anchors.horizontalCenter: parent.horizontalCenter
y: 72
}
Image {
// id: saveme
visible: root.isDead || root.dying
source: SkinBank.DEATH_DIR + (root.isDead ? root.role : "saveme")
anchors.centerIn: photoMask
}
Image {
id: netstat
source: SkinBank.STATE_DIR + root.netstate
x: photoMask.x
y: photoMask.y
}
Image {
id: handcardNum
source: SkinBank.PHOTO_DIR + "handcard"
anchors.bottom: parent.bottom
anchors.bottomMargin: -6
x: -6
Text {
text: root.handcards
font.family: "FZLiBian-S02"
font.pixelSize: 32
//font.weight: 30
color: "white"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
style: Text.Outline
}
}
RoleComboBox {
id: role
value: root.role
anchors.top: parent.top
anchors.topMargin: -4
anchors.right: parent.right
anchors.rightMargin: -4
}
GlowText {
id: seatNum
visible: !progressBar.visible
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: -32
property var seatChr: ["一", "二", "三", "四", "五", "六", "七", "八"]
font.family: "FZLiShu II-S06S"
font.pixelSize: 32
text: seatChr[seatNumber - 1]
glow.color: "brown"
glow.spread: 0.2
glow.radius: 8
glow.samples: 12
}
SequentialAnimation {
id: trembleAnimation
running: false
PropertyAnimation {
target: root
property: "x"
to: root.x - 20
easing.type: Easing.InQuad
duration: 100
}
PropertyAnimation {
target: root
property: "x"
to: root.x
easing.type: Easing.OutQuad
duration: 100
}
}
function tremble() {
trembleAnimation.start()
}
ProgressBar {
id: progressBar
width: parent.width
height: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: -4
from: 0.0
to: 100.0
visible: false
NumberAnimation on value {
running: progressBar.visible
from: 100.0
to: 0.0
duration: config.roomTimeout * 1000
onFinished: {
progressBar.visible = false;
root.progressTip = "";
}
}
}
Image {
anchors.top: progressBar.bottom
anchors.topMargin: 1
source: SkinBank.PHOTO_DIR + "control/tip"
visible: progressTip.text != ""
Text {
id: progressTip
font.family: "FZLiBian-S02"
font.pixelSize: 18
x: 18
color: "white"
text: ""
}
}
PixmapAnimation {
id: animSelectable
source: "selectable"
anchors.centerIn: parent
loop: true
}
InvisibleCardArea {
id: handcardAreaItem
anchors.centerIn: parent
}
DelayedTrickArea {
id: delayedTrickAreaItem
rows: 1
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
}
InvisibleCardArea {
id: defaultArea
anchors.centerIn: parent
}
onGeneralChanged: {
if (!roomScene.isStarted) return;
generalName.text = Backend.translate(general);
let data = JSON.parse(Backend.getGeneralData(general));
kingdom = data.kingdom;
}
} }

View File

@ -4,62 +4,62 @@ import ".."
import "../../skin-bank.js" as SkinBank import "../../skin-bank.js" as SkinBank
Item { Item {
property alias rows: grid.rows property alias rows: grid.rows
property alias columns: grid.columns property alias columns: grid.columns
InvisibleCardArea { InvisibleCardArea {
id: area id: area
checkExisting: true checkExisting: true
}
ListModel {
id: cards
}
Grid {
id: grid
anchors.fill: parent
rows: 100
columns: 100
Repeater {
model: cards
Image {
source: SkinBank.DELAYED_TRICK_DIR + name
}
} }
}
ListModel { function add(inputs)
id: cards {
area.add(inputs);
if (inputs instanceof Array) {
cards.append(...inputs);
} else {
cards.append(inputs);
} }
}
Grid { function remove(outputs)
id: grid {
anchors.fill: parent let result = area.remove(outputs);
rows: 100 for (let i = 0; i < result.length; i++) {
columns: 100 let item = result[i];
for (let j = 0; j < cards.count; j++) {
Repeater { let icon = cards.get(j);
model: cards if (icon.cid === item.cid) {
cards.remove(j, 1);
Image { break;
source: SkinBank.DELAYED_TRICK_DIR + name
}
} }
}
} }
function add(inputs) return result;
{ }
area.add(inputs);
if (inputs instanceof Array) {
cards.append(...inputs);
} else {
cards.append(inputs);
}
}
function remove(outputs) function updateCardPosition(animated)
{ {
let result = area.remove(outputs); area.updateCardPosition(animated);
for (let i = 0; i < result.length; i++) { }
let item = result[i];
for (let j = 0; j < cards.count; j++) {
let icon = cards.get(j);
if (icon.cid === item.cid) {
cards.remove(j, 1);
break;
}
}
}
return result;
}
function updateCardPosition(animated)
{
area.updateCardPosition(animated);
}
} }

View File

@ -11,110 +11,110 @@ import "../../skin-bank.js" as SkinBank
*/ */
Column { Column {
height: 88 height: 88
width: 138 width: 138
property int itemHeight: Math.floor(height / 4) property int itemHeight: Math.floor(height / 4)
property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem] property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem]
property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"] property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"]
property int length: area.length property int length: area.length
InvisibleCardArea { InvisibleCardArea {
id: area id: area
checkExisting: true checkExisting: true
} }
EquipItem { EquipItem {
id: treasureItem id: treasureItem
width: parent.width
height: itemHeight
opacity: 0
}
EquipItem {
id: weaponItem
width: parent.width
height: itemHeight
opacity: 0
}
EquipItem {
id: armorItem
width: parent.width
height: itemHeight
opacity: 0
}
Row {
width: parent.width
height: itemHeight
Item {
width: Math.ceil(parent.width / 2)
height: itemHeight
EquipItem {
id: defensiveHorseItem
width: parent.width width: parent.width
height: itemHeight height: itemHeight
icon: "horse"
opacity: 0 opacity: 0
}
} }
EquipItem { Item {
id: weaponItem width: Math.floor(parent.width / 2)
height: itemHeight
EquipItem {
id: offensiveHorseItem
width: parent.width width: parent.width
height: itemHeight height: itemHeight
icon: "horse"
opacity: 0 opacity: 0
}
} }
}
EquipItem { function add(inputs)
id: armorItem {
width: parent.width area.add(inputs);
height: itemHeight
opacity: 0 let card, item;
if (inputs instanceof Array) {
for (let i = 0; i < inputs.length; i++) {
card = inputs[i];
item = items[subtypes.indexOf(card.subtype)];
item.setCard(card);
item.show();
}
} else {
card = inputs;
item = items[subtypes.indexOf(card.subtype)];
item.setCard(card);
item.show();
} }
}
Row { function remove(outputs)
width: parent.width {
height: itemHeight let result = area.remove(outputs);
for (let i = 0; i < result.length; i++) {
Item { let card = result[i];
width: Math.ceil(parent.width / 2) for (let j = 0; j < items.length; j++) {
height: itemHeight let item = items[j];
if (item.cid === card.cid) {
EquipItem { item.reset();
id: defensiveHorseItem item.hide();
width: parent.width
height: itemHeight
icon: "horse"
opacity: 0
}
}
Item {
width: Math.floor(parent.width / 2)
height: itemHeight
EquipItem {
id: offensiveHorseItem
width: parent.width
height: itemHeight
icon: "horse"
opacity: 0
}
} }
}
} }
function add(inputs) return result;
{ }
area.add(inputs);
let card, item; function updateCardPosition(animated)
if (inputs instanceof Array) { {
for (let i = 0; i < inputs.length; i++) { area.updateCardPosition(animated);
card = inputs[i]; }
item = items[subtypes.indexOf(card.subtype)];
item.setCard(card);
item.show();
}
} else {
card = inputs;
item = items[subtypes.indexOf(card.subtype)];
item.setCard(card);
item.show();
}
}
function remove(outputs)
{
let result = area.remove(outputs);
for (let i = 0; i < result.length; i++) {
let card = result[i];
for (let j = 0; j < items.length; j++) {
let item = items[j];
if (item.cid === card.cid) {
item.reset();
item.hide();
}
}
}
return result;
}
function updateCardPosition(animated)
{
area.updateCardPosition(animated);
}
} }

View File

@ -4,139 +4,139 @@ import "../../../util.js" as Utility
import "../../skin-bank.js" as SkinBank import "../../skin-bank.js" as SkinBank
Item { Item {
property int cid: 0 property int cid: 0
property string name: "" property string name: ""
property string suit: "" property string suit: ""
property int number: 0 property int number: 0
property string icon: "" property string icon: ""
property alias text: textItem.text property alias text: textItem.text
id: root id: root
Image { Image {
id: iconItem id: iconItem
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: 3 x: 3
source: icon ? SkinBank.EQUIP_ICON_DIR + icon : "" source: icon ? SkinBank.EQUIP_ICON_DIR + icon : ""
}
Image {
id: suitItem
anchors.right: parent.right
source: suit ? SkinBank.CARD_SUIT_DIR + suit : ""
width: implicitWidth / implicitHeight * height
height: 16
}
GlowText {
id: numberItem
visible: number > 0 && number < 14
text: Utility.convertNumber(number)
color: "white"
font.family: "FZLiBian-S02"
font.pixelSize: 16
glow.color: "black"
glow.spread: 0.75
glow.radius: 2
glow.samples: 4
x: parent.width - 24
y: 1
}
GlowText {
id: textItem
font.family: "FZLiBian-S02"
color: "white"
font.pixelSize: 18
glow.color: "black"
glow.spread: 0.9
glow.radius: 2
glow.samples: 6
anchors.left: iconItem.right
anchors.leftMargin: -8
verticalAlignment: Text.AlignVCenter
}
ParallelAnimation {
id: showAnime
NumberAnimation {
target: root
property: "x"
duration: 200
easing.type: Easing.InOutQuad
from: 10
to: 0
} }
Image { NumberAnimation {
id: suitItem target: root
anchors.right: parent.right property: "opacity"
source: suit ? SkinBank.CARD_SUIT_DIR + suit : "" duration: 200
width: implicitWidth / implicitHeight * height easing.type: Easing.InOutQuad
height: 16 from: 0
to: 1
}
}
ParallelAnimation {
id: hideAnime
NumberAnimation {
target: root
property: "x"
duration: 200
easing.type: Easing.InOutQuad
from: 0
to: 10
} }
GlowText { NumberAnimation {
id: numberItem target: root
visible: number > 0 && number < 14 property: "opacity"
text: Utility.convertNumber(number) duration: 200
color: "white" easing.type: Easing.InOutQuad
font.family: "FZLiBian-S02" from: 1
font.pixelSize: 16 to: 0
glow.color: "black"
glow.spread: 0.75
glow.radius: 2
glow.samples: 4
x: parent.width - 24
y: 1
} }
}
GlowText { function reset()
id: textItem {
font.family: "FZLiBian-S02" cid = 0;
color: "white" name = "";
font.pixelSize: 18 suit = "";
glow.color: "black" number = 0;
glow.spread: 0.9 text = "";
glow.radius: 2 }
glow.samples: 6
anchors.left: iconItem.right function setCard(card)
anchors.leftMargin: -8 {
verticalAlignment: Text.AlignVCenter cid = card.cid;
name = card.name;
suit = card.suit;
number = card.number;
if (card.subtype === "defensive_horse") {
text = "+1";
icon = "horse";
} else if (card.subtype === "offensive_horse") {
text = "-1"
icon = "horse";
} else {
text = Backend.translate(name);
icon = name;
} }
}
ParallelAnimation { function show()
id: showAnime {
showAnime.start();
}
NumberAnimation { function hide()
target: root {
property: "x" hideAnime.start();
duration: 200 }
easing.type: Easing.InOutQuad
from: 10
to: 0
}
NumberAnimation {
target: root
property: "opacity"
duration: 200
easing.type: Easing.InOutQuad
from: 0
to: 1
}
}
ParallelAnimation {
id: hideAnime
NumberAnimation {
target: root
property: "x"
duration: 200
easing.type: Easing.InOutQuad
from: 0
to: 10
}
NumberAnimation {
target: root
property: "opacity"
duration: 200
easing.type: Easing.InOutQuad
from: 1
to: 0
}
}
function reset()
{
cid = 0;
name = "";
suit = "";
number = 0;
text = "";
}
function setCard(card)
{
cid = card.cid;
name = card.name;
suit = card.suit;
number = card.number;
if (card.subtype === "defensive_horse") {
text = "+1";
icon = "horse";
} else if (card.subtype === "offensive_horse") {
text = "-1"
icon = "horse";
} else {
text = name;
icon = name;
}
}
function show()
{
showAnime.start();
}
function hide()
{
hideAnime.start();
}
} }

View File

@ -2,71 +2,71 @@ import QtQuick 2.15
import ".." import ".."
Column { Column {
id: root id: root
property int maxValue: 4 property int maxValue: 4
property int value: 4 property int value: 4
property var colors: ["#F4180E", "#F4180E", "#E3B006", "#25EC27"] property var colors: ["#F4180E", "#F4180E", "#E3B006", "#25EC27"]
Repeater { Repeater {
id: repeater id: repeater
model: maxValue <= 4 ? maxValue : 0 model: maxValue <= 4 ? maxValue : 0
Magatama { Magatama {
state: (maxValue - 1 - index) >= value ? 0 : (value >= 3 || value >= maxValue ? 3 : (value <= 0 ? 0 : value)) state: (maxValue - 1 - index) >= value ? 0 : (value >= 3 || value >= maxValue ? 3 : (value <= 0 ? 0 : value))
} }
}
Column {
visible: maxValue > 4
spacing: -4
Magatama {
state: (value >= 3 || value >= maxValue) ? 3 : (value <= 0 ? 0 : value)
} }
Column { GlowText {
visible: maxValue > 4 id: hpItem
spacing: -4 width: root.width
text: value
color: root.colors[(value >= 3 || value >= maxValue) ? 3 : (value <= 0 ? 0 : value)]
font.family: "FZLiBian-S02"
font.pixelSize: 22
font.bold: true
horizontalAlignment: Text.AlignHCenter
Magatama { glow.color: "#3E3F47"
state: (value >= 3 || value >= maxValue) ? 3 : (value <= 0 ? 0 : value) glow.spread: 0.8
} glow.radius: 8
glow.samples: 12
GlowText {
id: hpItem
width: root.width
text: value
color: root.colors[(value >= 3 || value >= maxValue) ? 3 : (value <= 0 ? 0 : value)]
font.family: "FZLiBian-S02"
font.pixelSize: 22
font.bold: true
horizontalAlignment: Text.AlignHCenter
glow.color: "#3E3F47"
glow.spread: 0.8
glow.radius: 8
glow.samples: 12
}
GlowText {
id: splitter
width: root.width
text: "/"
z: -10
color: hpItem.color
font: hpItem.font
horizontalAlignment: hpItem.horizontalAlignment
glow.color: hpItem.glow.color
glow.spread: hpItem.glow.spread
glow.radius: hpItem.glow.radius
glow.samples: hpItem.glow.samples
}
GlowText {
id: maxHpItem
width: root.width
text: maxValue
color: hpItem.color
font: hpItem.font
horizontalAlignment: hpItem.horizontalAlignment
glow.color: hpItem.glow.color
glow.spread: hpItem.glow.spread
glow.radius: hpItem.glow.radius
glow.samples: hpItem.glow.samples
}
} }
GlowText {
id: splitter
width: root.width
text: "/"
z: -10
color: hpItem.color
font: hpItem.font
horizontalAlignment: hpItem.horizontalAlignment
glow.color: hpItem.glow.color
glow.spread: hpItem.glow.spread
glow.radius: hpItem.glow.radius
glow.samples: hpItem.glow.samples
}
GlowText {
id: maxHpItem
width: root.width
text: maxValue
color: hpItem.color
font: hpItem.font
horizontalAlignment: hpItem.horizontalAlignment
glow.color: hpItem.glow.color
glow.spread: hpItem.glow.spread
glow.radius: hpItem.glow.radius
glow.samples: hpItem.glow.samples
}
}
} }

View File

@ -2,57 +2,57 @@ import QtQuick 2.15
import "../../skin-bank.js" as SkinBank import "../../skin-bank.js" as SkinBank
Image { Image {
source: SkinBank.MAGATAMA_DIR + "0" source: SkinBank.MAGATAMA_DIR + "0"
state: "3" state: "3"
states: [ states: [
State { State {
name: "3" name: "3"
PropertyChanges { PropertyChanges {
target: main target: main
source: SkinBank.MAGATAMA_DIR + "3" source: SkinBank.MAGATAMA_DIR + "3"
opacity: 1 opacity: 1
scale: 1 scale: 1
} }
}, },
State { State {
name: "2" name: "2"
PropertyChanges { PropertyChanges {
target: main target: main
source: SkinBank.MAGATAMA_DIR + "2" source: SkinBank.MAGATAMA_DIR + "2"
opacity: 1 opacity: 1
scale: 1 scale: 1
} }
}, },
State { State {
name: "1" name: "1"
PropertyChanges { PropertyChanges {
target: main target: main
source: SkinBank.MAGATAMA_DIR + "1" source: SkinBank.MAGATAMA_DIR + "1"
opacity: 1 opacity: 1
scale: 1 scale: 1
} }
}, },
State { State {
name: "0" name: "0"
PropertyChanges { PropertyChanges {
target: main target: main
source: SkinBank.MAGATAMA_DIR + "0" source: SkinBank.MAGATAMA_DIR + "0"
opacity: 0 opacity: 0
scale: 4 scale: 4
} }
}
]
transitions: Transition {
PropertyAnimation {
properties: "opacity,scale"
}
} }
]
Image { transitions: Transition {
id: main PropertyAnimation {
anchors.centerIn: parent properties: "opacity,scale"
} }
}
Image {
id: main
anchors.centerIn: parent
}
} }

View File

@ -3,45 +3,45 @@ import QtQuick 2.15
import "../../skin-bank.js" as SkinBank import "../../skin-bank.js" as SkinBank
Image { Image {
property string value: "unknown"
property var options: ["unknown", "loyalist", "rebel", "renegade"]
id: root
source: visible ? SkinBank.ROLE_DIR + value : ""
visible: value != "hidden"
Image {
property string value: "unknown" property string value: "unknown"
property var options: ["unknown", "loyalist", "rebel", "renegade"]
id: root id: assumptionBox
source: visible ? SkinBank.ROLE_DIR + value : "" source: SkinBank.ROLE_DIR + value
visible: value != "hidden" visible: root.value == "unknown"
Image { MouseArea {
property string value: "unknown" anchors.fill: parent
onClicked: optionPopupBox.visible = true;
}
}
id: assumptionBox Column {
source: SkinBank.ROLE_DIR + value id: optionPopupBox
visible: root.value == "unknown" visible: false
spacing: 2
Repeater {
model: options
Image {
source: SkinBank.ROLE_DIR + modelData
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: optionPopupBox.visible = true; onClicked: {
} optionPopupBox.visible = false;
} assumptionBox.value = modelData;
}
Column {
id: optionPopupBox
visible: false
spacing: 2
Repeater {
model: options
Image {
source: SkinBank.ROLE_DIR + modelData
MouseArea {
anchors.fill: parent
onClicked: {
optionPopupBox.visible = false;
assumptionBox.value = modelData;
}
}
}
} }
}
} }
}
} }

View File

@ -3,87 +3,87 @@ import Qt.labs.folderlistmodel 2.15
import "../skin-bank.js" as SkinBank import "../skin-bank.js" as SkinBank
Item { Item {
property string source: "" property string source: ""
property int currentFrame: 0 property int currentFrame: 0
property alias interval: timer.interval property alias interval: timer.interval
property int loadedFrameCount: 0 property int loadedFrameCount: 0
property bool autoStart: false property bool autoStart: false
property bool loop: false property bool loop: false
signal loaded() signal loaded()
signal started() signal started()
signal finished() signal finished()
id: root id: root
width: childrenRect.width width: childrenRect.width
height: childrenRect.height height: childrenRect.height
FolderListModel { FolderListModel {
id: fileModel id: fileModel
folder: SkinBank.PIXANIM_DIR + source folder: SkinBank.PIXANIM_DIR + source
nameFilters: ["*.png"] nameFilters: ["*.png"]
showDirs: false showDirs: false
} }
Repeater { Repeater {
id: frames id: frames
model: fileModel model: fileModel
Image { Image {
source: SkinBank.PIXANIM_DIR + root.source + "/" + index source: SkinBank.PIXANIM_DIR + root.source + "/" + index
visible: false visible: false
onStatusChanged: { onStatusChanged: {
if (status == Image.Ready) { if (status == Image.Ready) {
loadedFrameCount++; loadedFrameCount++;
if (loadedFrameCount == fileModel.count) if (loadedFrameCount == fileModel.count)
root.loaded(); root.loaded();
}
}
} }
}
} }
}
onLoaded: { onLoaded: {
if (autoStart) if (autoStart)
timer.start(); timer.start();
} }
Timer { Timer {
id: timer id: timer
interval: 50 interval: 50
repeat: true repeat: true
onTriggered: { onTriggered: {
if (currentFrame >= fileModel.count) { if (currentFrame >= fileModel.count) {
frames.itemAt(fileModel.count - 1).visible = false; frames.itemAt(fileModel.count - 1).visible = false;
if (loop) { if (loop) {
currentFrame = 0; currentFrame = 0;
} else {
timer.stop();
root.finished();
return;
}
}
if (currentFrame > 0)
frames.itemAt(currentFrame - 1).visible = false;
frames.itemAt(currentFrame).visible = true;
currentFrame++;
}
}
function start()
{
if (loadedFrameCount == fileModel.count) {
timer.start();
} else { } else {
root.loaded.connect(function(){ timer.stop();
timer.start(); root.finished();
}); return;
} }
} }
function stop() if (currentFrame > 0)
{ frames.itemAt(currentFrame - 1).visible = false;
timer.stop(); frames.itemAt(currentFrame).visible = true;
currentFrame++;
} }
}
function start()
{
if (loadedFrameCount == fileModel.count) {
timer.start();
} else {
root.loaded.connect(function(){
timer.start();
});
}
}
function stop()
{
timer.stop();
}
} }

View File

@ -1,5 +1,5 @@
import QtQuick 2.15 import QtQuick 2.15
Flickable { Flickable {
id: root id: root
} }

View File

@ -1,135 +1,135 @@
import QtQuick 2.15 import QtQuick 2.15
Item { Item {
property var discardedCards: [] property var discardedCards: []
property alias cards: area.cards property alias cards: area.cards
property bool toVanish: false property bool toVanish: false
id: root id: root
CardArea { CardArea {
id: area id: area
} }
InvisibleCardArea { InvisibleCardArea {
id: invisibleArea id: invisibleArea
} }
Timer { Timer {
id: vanishTimer id: vanishTimer
interval: 1500 interval: 1500
repeat: true repeat: true
running: true running: true
triggeredOnStart: true triggeredOnStart: true
onTriggered: { onTriggered: {
let i, card; let i, card;
if (toVanish) { if (toVanish) {
for (i = 0; i < discardedCards.length; i++) { for (i = 0; i < discardedCards.length; i++) {
card = discardedCards[i]; card = discardedCards[i];
card.origOpacity = 0; card.origOpacity = 0;
card.goBack(true); card.goBack(true);
card.destroyOnStop() card.destroyOnStop()
}
cards.splice(0, discardedCards.length);
updateCardPosition(true);
discardedCards = new Array(cards.length);
for (i = 0; i < cards.length; i++)
discardedCards[i] = cards[i];
toVanish = false
} else {
for (i = 0; i < discardedCards.length; i++) {
discardedCards[i].selectable = false
}
toVanish = true
}
} }
}
function add(inputs) cards.splice(0, discardedCards.length);
{
area.add(inputs);
// if (!inputs instanceof Array)
for (let i = 0; i < inputs.length; i++) {
inputs[i].footnoteVisible = true
inputs[i].selectable = true
}
}
function remove(outputs)
{
let i, j;
let result = area.remove(outputs);
let vanished = [];
if (result.length < outputs.length) {
for (i = 0; i < outputs.length; i++) {
let exists = false;
for (j = 0; j < result.length; j++) {
if (result[j].cid === outputs[i]) {
exists = true;
break;
}
}
if (!exists)
vanished.push(outputs[i]);
}
}
result = result.concat(invisibleArea.remove(vanished));
for (i = 0; i < result.length; i++) {
for (j = 0; j < discardedCards.length; j++) {
if (result[i].cid === discardedCards[j].cid) {
discardedCards.splice(j, 1);
break;
}
}
}
updateCardPosition(true); updateCardPosition(true);
return result;
discardedCards = new Array(cards.length);
for (i = 0; i < cards.length; i++)
discardedCards[i] = cards[i];
toVanish = false
} else {
for (i = 0; i < discardedCards.length; i++) {
discardedCards[i].selectable = false
}
toVanish = true
}
}
}
function add(inputs)
{
area.add(inputs);
// if (!inputs instanceof Array)
for (let i = 0; i < inputs.length; i++) {
inputs[i].footnoteVisible = true
inputs[i].selectable = true
}
}
function remove(outputs)
{
let i, j;
let result = area.remove(outputs);
let vanished = [];
if (result.length < outputs.length) {
for (i = 0; i < outputs.length; i++) {
let exists = false;
for (j = 0; j < result.length; j++) {
if (result[j].cid === outputs[i]) {
exists = true;
break;
}
}
if (!exists)
vanished.push(outputs[i]);
}
}
result = result.concat(invisibleArea.remove(vanished));
for (i = 0; i < result.length; i++) {
for (j = 0; j < discardedCards.length; j++) {
if (result[i].cid === discardedCards[j].cid) {
discardedCards.splice(j, 1);
break;
}
}
}
updateCardPosition(true);
return result;
}
function updateCardPosition(animated)
{
if (cards.length <= 0)
return;
let i, card;
let overflow = false;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX = i * card.width;
if (card.origX + card.width >= root.width) {
overflow = true;
break;
}
card.origY = 0;
} }
function updateCardPosition(animated) if (overflow) {
{ //@to-do: Adjust cards in multiple lines if there are too many cards
if (cards.length <= 0) let xLimit = root.width - card.width;
return; let spacing = xLimit / (cards.length - 1);
for (i = 0; i < cards.length; i++) {
let i, card; card = cards[i];
card.origX = i * spacing;
let overflow = false; card.origY = 0;
for (i = 0; i < cards.length; i++) { }
card = cards[i];
card.origX = i * card.width;
if (card.origX + card.width >= root.width) {
overflow = true;
break;
}
card.origY = 0;
}
if (overflow) {
//@to-do: Adjust cards in multiple lines if there are too many cards
let xLimit = root.width - card.width;
let spacing = xLimit / (cards.length - 1);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX = i * spacing;
card.origY = 0;
}
}
let offsetX = Math.max(0, (root.width - cards.length * card.width) / 2);
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX += parentPos.x + offsetX;
card.origY += parentPos.y;
}
if (animated) {
for (i = 0; i < cards.length; i++)
cards[i].goBack(true)
}
} }
let offsetX = Math.max(0, (root.width - cards.length * card.width) / 2);
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX += parentPos.x + offsetX;
card.origY += parentPos.y;
}
if (animated) {
for (i = 0; i < cards.length; i++)
cards[i].goBack(true)
}
}
} }

View File

@ -1,377 +1,465 @@
var Card = { var Card = {
Unknown : 0, Unknown : 0,
PlayerHand : 1, PlayerHand : 1,
PlayerEquip : 2, PlayerEquip : 2,
PlayerJudge : 3, PlayerJudge : 3,
PlayerSpecial : 4, PlayerSpecial : 4,
Processing : 5, Processing : 5,
DrawPile : 6, DrawPile : 6,
DiscardPile : 7, DiscardPile : 7,
Void : 8 Void : 8
} }
function arrangePhotos() { function arrangePhotos() {
/* Layout of photos: /* Layout of photos:
* +---------------+ * +---------------+
* | 6 5 4 3 2 | * | 6 5 4 3 2 |
* | 7 1 | * | 7 1 |
* | dashboard | * | dashboard |
* +---------------+ * +---------------+
*/ */
const photoWidth = 175; const photoWidth = 175;
const roomAreaPadding = 10; const roomAreaPadding = 10;
let verticalPadding = Math.max(10, roomArea.width * 0.01); let verticalPadding = Math.max(10, roomArea.width * 0.01);
let horizontalSpacing = Math.max(30, roomArea.height * 0.1); let horizontalSpacing = Math.max(30, roomArea.height * 0.1);
let verticalSpacing = (roomArea.width - photoWidth * 7 - verticalPadding * 2) / 6; let verticalSpacing = (roomArea.width - photoWidth * 7 - verticalPadding * 2) / 6;
// Position 1-7 // Position 1-7
const regions = [ const regions = [
{ x: verticalPadding + (photoWidth + verticalSpacing) * 6, y: roomAreaPadding + horizontalSpacing * 2 }, { x: verticalPadding + (photoWidth + verticalSpacing) * 6, y: roomAreaPadding + horizontalSpacing * 2 },
{ x: verticalPadding + (photoWidth + verticalSpacing) * 5, y: roomAreaPadding + horizontalSpacing }, { x: verticalPadding + (photoWidth + verticalSpacing) * 5, y: roomAreaPadding + horizontalSpacing },
{ x: verticalPadding + (photoWidth + verticalSpacing) * 4, y: roomAreaPadding }, { x: verticalPadding + (photoWidth + verticalSpacing) * 4, y: roomAreaPadding },
{ x: verticalPadding + (photoWidth + verticalSpacing) * 3, y: roomAreaPadding }, { x: verticalPadding + (photoWidth + verticalSpacing) * 3, y: roomAreaPadding },
{ x: verticalPadding + (photoWidth + verticalSpacing) * 2, y: roomAreaPadding }, { x: verticalPadding + (photoWidth + verticalSpacing) * 2, y: roomAreaPadding },
{ x: verticalPadding + photoWidth + verticalSpacing, y: roomAreaPadding + horizontalSpacing }, { x: verticalPadding + photoWidth + verticalSpacing, y: roomAreaPadding + horizontalSpacing },
{ x: verticalPadding, y: roomAreaPadding + horizontalSpacing * 2 }, { x: verticalPadding, y: roomAreaPadding + horizontalSpacing * 2 },
]; ];
const regularSeatIndex = [ const regularSeatIndex = [
[4], [4],
[3, 5], [3, 5],
[1, 4, 7], [1, 4, 7],
[1, 3, 5, 7], [1, 3, 5, 7],
[1, 3, 4, 5, 7], [1, 3, 4, 5, 7],
[1, 2, 3, 5, 6, 7], [1, 2, 3, 5, 6, 7],
[1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7],
]; ];
let seatIndex = regularSeatIndex[playerNum - 2]; let seatIndex = regularSeatIndex[playerNum - 2];
let item, region, i; let item, region, i;
for (i = 0; i < playerNum - 1; i++) { for (i = 0; i < playerNum - 1; i++) {
item = photos.itemAt(i); item = photos.itemAt(i);
if (!item) if (!item)
continue; continue;
region = regions[seatIndex[photoModel.get(i).index] - 1]; region = regions[seatIndex[photoModel.get(i).index] - 1];
item.x = region.x; item.x = region.x;
item.y = region.y; item.y = region.y;
} }
} }
function doOkButton() { function doOkButton() {
replyToServer("1"); if (roomScene.state == "playing") {
replyToServer(JSON.stringify(
{
card: dashboard.getSelectedCard(),
targets: selected_targets
}
));
return;
}
replyToServer("1");
} }
function doCancelButton() { function doCancelButton() {
replyToServer(""); replyToServer("");
} }
function replyToServer(jsonData) { function replyToServer(jsonData) {
roomScene.state = "notactive"; roomScene.state = "notactive";
ClientInstance.replyToServer("", jsonData); ClientInstance.replyToServer("", jsonData);
} }
function getPhotoModel(id) { function getPhotoModel(id) {
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i); let item = photoModel.get(i);
if (item.id === id) { if (item.id === id) {
return item; return item;
}
} }
return undefined; }
return undefined;
} }
function getPhoto(id) { function getPhoto(id) {
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i); let item = photoModel.get(i);
if (item.id === id) { if (item.id === id) {
return photos.itemAt(i); return photos.itemAt(i);
}
} }
return undefined; }
return undefined;
} }
function getPhotoOrDashboard(id) { function getPhotoOrDashboard(id) {
let photo = getPhoto(id); let photo = getPhoto(id);
if (!photo) { if (!photo) {
if (id === Self.id) if (id === Self.id)
return dashboard; return dashboard;
} }
return photo; return photo;
} }
function getAreaItem(area, id) { function getAreaItem(area, id) {
if (area === Card.DrawPile) { if (area === Card.DrawPile) {
return drawPile; return drawPile;
} else if (area === Card.DiscardPile || area === Card.Processing) { } else if (area === Card.DiscardPile || area === Card.Processing) {
return tablePile; return tablePile;
} else if (area === Card.AG) { } else if (area === Card.AG) {
return popupBox.item; return popupBox.item;
} }
let photo = getPhotoOrDashboard(id);
if (!photo) {
return null;
}
if (area === Card.PlayerHand) {
return photo.handcardArea;
} else if (area === Card.PlayerEquip)
return photo.equipArea;
else if (area === Card.PlayerJudge)
return photo.delayedTrickArea;
else if (area === Card.PlayerSpecial)
return photo.specialArea;
let photo = getPhotoOrDashboard(id);
if (!photo) {
return null; return null;
}
if (area === Card.PlayerHand) {
return photo.handcardArea;
} else if (area === Card.PlayerEquip)
return photo.equipArea;
else if (area === Card.PlayerJudge)
return photo.delayedTrickArea;
else if (area === Card.PlayerSpecial)
return photo.specialArea;
return null;
} }
function moveCards(moves) { function moveCards(moves) {
for (let i = 0; i < moves.length; i++) { for (let i = 0; i < moves.length; i++) {
let move = moves[i]; let move = moves[i];
let from = getAreaItem(move.fromArea, move.from); let from = getAreaItem(move.fromArea, move.from);
let to = getAreaItem(move.toArea, move.to); let to = getAreaItem(move.toArea, move.to);
if (!from || !to || from === to) if (!from || !to || from === to)
continue; continue;
let items = from.remove(move.ids); let items = from.remove(move.ids);
if (items.length > 0) if (items.length > 0)
to.add(items); to.add(items);
to.updateCardPosition(true); to.updateCardPosition(true);
} }
} }
function setEmotion(id, emotion) { function setEmotion(id, emotion) {
let component = Qt.createComponent("RoomElement/PixmapAnimation.qml"); let component = Qt.createComponent("RoomElement/PixmapAnimation.qml");
if (component.status !== Component.Ready) if (component.status !== Component.Ready)
return; return;
let photo = getPhoto(id); let photo = getPhoto(id);
if (!photo) { if (!photo) {
if (id === dashboardModel.id) { if (id === dashboardModel.id) {
photo = dashboard.self; photo = dashboard.self;
} else { } else {
return null; return null;
}
} }
}
let animation = component.createObject(photo, {source: emotion, anchors: {centerIn: photo}}); let animation = component.createObject(photo, {source: emotion, anchors: {centerIn: photo}});
animation.finished.connect(() => animation.destroy()); animation.finished.connect(() => animation.destroy());
animation.start(); animation.start();
} }
function changeHp(id, delta, losthp) { function changeHp(id, delta, losthp) {
let photo = getPhoto(id); let photo = getPhoto(id);
if (!photo) { if (!photo) {
if (id === dashboardModel.id) { if (id === dashboardModel.id) {
photo = dashboard.self; photo = dashboard.self;
} else { } else {
return null; return null;
}
} }
if (delta < 0) { }
if (!losthp) { if (delta < 0) {
setEmotion(id, "damage") if (!losthp) {
photo.tremble() setEmotion(id, "damage")
} photo.tremble()
} }
}
} }
function doIndicate(from, tos) { function doIndicate(from, tos) {
let component = Qt.createComponent("RoomElement/IndicatorLine.qml"); let component = Qt.createComponent("RoomElement/IndicatorLine.qml");
if (component.status !== Component.Ready) if (component.status !== Component.Ready)
return; return;
let fromItem = getPhotoOrDashboard(from); let fromItem = getPhotoOrDashboard(from);
let fromPos = mapFromItem(fromItem, fromItem.width / 2, fromItem.height / 2); let fromPos = mapFromItem(fromItem, fromItem.width / 2, fromItem.height / 2);
let end = []; let end = [];
for (let i = 0; i < tos.length; i++) { for (let i = 0; i < tos.length; i++) {
if (from === tos[i]) if (from === tos[i])
continue; continue;
let toItem = getPhotoOrDashboard(tos[i]); let toItem = getPhotoOrDashboard(tos[i]);
let toPos = mapFromItem(toItem, toItem.width / 2, toItem.height / 2); let toPos = mapFromItem(toItem, toItem.width / 2, toItem.height / 2);
end.push(toPos); end.push(toPos);
} }
let color = "#96943D"; let color = "#96943D";
let line = component.createObject(roomScene, {start: fromPos, end: end, color: color}); let line = component.createObject(roomScene, {start: fromPos, end: end, color: color});
line.finished.connect(() => line.destroy()); line.finished.connect(() => line.destroy());
line.running = true; line.running = true;
} }
callbacks["AddPlayer"] = function(jsonData) { callbacks["AddPlayer"] = function(jsonData) {
// jsonData: int id, string screenName, string avatar // jsonData: int id, string screenName, string avatar
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i); let item = photoModel.get(i);
if (item.id === -1) { if (item.id === -1) {
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
let uid = data[0]; let uid = data[0];
let name = data[1]; let name = data[1];
let avatar = data[2]; let avatar = data[2];
item.id = uid; item.id = uid;
item.screenName = name; item.screenName = name;
item.general = avatar; item.general = avatar;
return; return;
}
} }
}
}
function enableTargets(card) { // card: int | { skill: string, subcards: int[] }
let i = 0;
let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string";
let all_photos = [dashboard.self];
for (i = 0; i < playerNum - 1; i++) {
all_photos.push(photos.itemAt(i))
}
selected_targets = [];
for (i = 0; i < playerNum; i++) {
all_photos[i].selected = false;
}
if (candidate) {
let data = {
ok_enabled: false,
enabled_targets: []
}
all_photos.forEach(photo => {
photo.state = "candidate";
let id = photo.playerid;
let ret = JSON.parse(Backend.callLuaFunction(
"CanUseCardToTarget",
[card, id, selected_targets]
));
photo.selectable = ret;
})
okButton.enabled = JSON.parse(Backend.callLuaFunction(
"CardFeasible", [card, selected_targets]
));
} else {
all_photos.forEach(photo => {
photo.state = "normal";
photo.selected = false;
});
okButton.enabled = false;
}
}
function updateSelectedTargets(playerid, selected) {
let i = 0;
let card = dashboard.getSelectedCard();
let all_photos = [dashboard.self]
for (i = 0; i < playerNum - 1; i++) {
all_photos.push(photos.itemAt(i))
}
if (selected) {
selected_targets.push(playerid);
} else {
selected_targets.splice(selected_targets.indexOf(playerid), 1);
}
all_photos.forEach(photo => {
if (photo.selected) return;
let id = photo.playerid;
let ret = JSON.parse(Backend.callLuaFunction(
"CanUseCardToTarget",
[card, id, selected_targets]
));
photo.selectable = ret;
})
okButton.enabled = JSON.parse(Backend.callLuaFunction(
"CardFeasible", [card, selected_targets]
));
} }
callbacks["RemovePlayer"] = function(jsonData) { callbacks["RemovePlayer"] = function(jsonData) {
// jsonData: int uid // jsonData: int uid
let uid = JSON.parse(jsonData)[0]; let uid = JSON.parse(jsonData)[0];
let model = getPhotoModel(uid); let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") { if (typeof(model) !== "undefined") {
model.id = -1; model.id = -1;
model.screenName = ""; model.screenName = "";
model.general = ""; model.general = "";
} }
} }
callbacks["RoomOwner"] = function(jsonData) { callbacks["RoomOwner"] = function(jsonData) {
// jsonData: int uid of the owner // jsonData: int uid of the owner
let uid = JSON.parse(jsonData)[0]; let uid = JSON.parse(jsonData)[0];
if (dashboardModel.id === uid) { if (dashboardModel.id === uid) {
dashboardModel.isOwner = true; dashboardModel.isOwner = true;
roomScene.dashboardModelChanged(); roomScene.dashboardModelChanged();
return; return;
} }
let model = getPhotoModel(uid); let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") { if (typeof(model) !== "undefined") {
model.isOwner = true; model.isOwner = true;
} }
} }
callbacks["PropertyUpdate"] = function(jsonData) { callbacks["PropertyUpdate"] = function(jsonData) {
// jsonData: int id, string property_name, value // jsonData: int id, string property_name, value
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
let uid = data[0]; let uid = data[0];
let property_name = data[1]; let property_name = data[1];
let value = data[2]; let value = data[2];
if (Self.id === uid) { if (Self.id === uid) {
dashboardModel[property_name] = value; dashboardModel[property_name] = value;
roomScene.dashboardModelChanged(); roomScene.dashboardModelChanged();
return; return;
} }
let model = getPhotoModel(uid); let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") { if (typeof(model) !== "undefined") {
model[property_name] = value; model[property_name] = value;
} }
} }
callbacks["ArrangeSeats"] = function(jsonData) { callbacks["ArrangeSeats"] = function(jsonData) {
// jsonData: seat order // jsonData: seat order
let order = JSON.parse(jsonData); let order = JSON.parse(jsonData);
roomScene.isStarted = true; roomScene.isStarted = true;
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i); let item = photoModel.get(i);
item.seatNumber = order.indexOf(item.id) + 1; item.seatNumber = order.indexOf(item.id) + 1;
} }
dashboardModel.seatNumber = order.indexOf(Self.id) + 1; dashboardModel.seatNumber = order.indexOf(Self.id) + 1;
roomScene.dashboardModelChanged(); roomScene.dashboardModelChanged();
// make Self to the first of list, then reorder photomodel // make Self to the first of list, then reorder photomodel
let selfIndex = order.indexOf(Self.id); let selfIndex = order.indexOf(Self.id);
let after = order.splice(selfIndex); let after = order.splice(selfIndex);
after.push(...order); after.push(...order);
let photoOrder = after.slice(1); let photoOrder = after.slice(1);
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i); let item = photoModel.get(i);
item.index = photoOrder.indexOf(item.id); item.index = photoOrder.indexOf(item.id);
} }
arrangePhotos(); arrangePhotos();
} }
function cancelAllFocus() { function cancelAllFocus() {
let item; let item;
for (let i = 0; i < playerNum - 1; i++) { for (let i = 0; i < playerNum - 1; i++) {
item = photos.itemAt(i); item = photos.itemAt(i);
item.progressBar.visible = false; item.progressBar.visible = false;
item.progressTip = ""; item.progressTip = "";
} }
} }
callbacks["MoveFocus"] = function(jsonData) { callbacks["MoveFocus"] = function(jsonData) {
// jsonData: int[] focuses, string command // jsonData: int[] focuses, string command
cancelAllFocus(); cancelAllFocus();
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
let focuses = data[0]; let focuses = data[0];
let command = data[1]; let command = data[1];
let item, model; let item, model;
for (let i = 0; i < playerNum - 1; i++) { for (let i = 0; i < playerNum - 1; i++) {
model = photoModel.get(i); model = photoModel.get(i);
if (focuses.indexOf(model.id) != -1) { if (focuses.indexOf(model.id) != -1) {
item = photos.itemAt(i); item = photos.itemAt(i);
item.progressBar.visible = true; item.progressBar.visible = true;
item.progressTip = command + " thinking..."; item.progressTip = command + " thinking...";
}
} }
}
} }
callbacks["PlayerRunned"] = function(jsonData) { callbacks["PlayerRunned"] = function(jsonData) {
// jsonData: int runner, int robot // jsonData: int runner, int robot
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
let runner = data[0]; let runner = data[0];
let robot = data[1]; let robot = data[1];
let model = getPhotoModel(runner); let model = getPhotoModel(runner);
if (typeof(model) !== "undefined") { if (typeof(model) !== "undefined") {
model.id = robot; model.id = robot;
} }
} }
callbacks["AskForGeneral"] = function(jsonData) { callbacks["AskForGeneral"] = function(jsonData) {
// jsonData: string[] Generals // jsonData: string[] Generals
// TODO: choose multiple generals // TODO: choose multiple generals
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
roomScene.promptText = "Please choose 1 general"; roomScene.promptText = "Please choose 1 general";
roomScene.state = "replying"; roomScene.state = "replying";
roomScene.popupBox.source = "RoomElement/ChooseGeneralBox.qml"; roomScene.popupBox.source = "RoomElement/ChooseGeneralBox.qml";
let box = roomScene.popupBox.item; let box = roomScene.popupBox.item;
box.choiceNum = 1; box.choiceNum = 1;
box.accepted.connect(() => { box.accepted.connect(() => {
replyToServer(JSON.stringify([box.choices[0]])); replyToServer(JSON.stringify([box.choices[0]]));
}); });
for (let i = 0; i < data.length; i++) for (let i = 0; i < data.length; i++)
box.generalList.append({ "name": data[i] }); box.generalList.append({ "name": data[i] });
box.updatePosition(); box.updatePosition();
} }
callbacks["AskForSkillInvoke"] = function(jsonData) { callbacks["AskForSkillInvoke"] = function(jsonData) {
// jsonData: string name // jsonData: string name
roomScene.promptText = "Do you want to invoke '" + jsonData + "' ?"; roomScene.promptText = "Do you want to invoke '" + jsonData + "' ?";
roomScene.state = "responding"; roomScene.state = "responding";
} }
callbacks["AskForChoice"] = function(jsonData) { callbacks["AskForChoice"] = function(jsonData) {
// jsonData: [ string[] choices, string skill ] // jsonData: [ string[] choices, string skill ]
// TODO: multiple choices, e.g. benxi_ol // TODO: multiple choices, e.g. benxi_ol
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
let choices = data[0]; let choices = data[0];
let skill_name = data[1]; let skill_name = data[1];
roomScene.promptText = skill_name + ": Please make choice"; roomScene.promptText = skill_name + ": Please make choice";
roomScene.state = "replying"; roomScene.state = "replying";
roomScene.popupBox.source = "RoomElement/ChoiceBox.qml"; roomScene.popupBox.source = "RoomElement/ChoiceBox.qml";
let box = roomScene.popupBox.item; let box = roomScene.popupBox.item;
box.options = choices; box.options = choices;
box.skill_name = skill_name; box.skill_name = skill_name;
box.accepted.connect(() => { box.accepted.connect(() => {
replyToServer(choices[box.result]); replyToServer(choices[box.result]);
}); });
} }
callbacks["MoveCards"] = function(jsonData) { callbacks["MoveCards"] = function(jsonData) {
// jsonData: merged moves // jsonData: merged moves
let moves = JSON.parse(jsonData); let moves = JSON.parse(jsonData);
moveCards(moves); moveCards(moves);
}
callbacks["PlayCard"] = function(jsonData) {
// jsonData: int playerId
let playerId = parseInt(jsonData);
if (playerId == Self.id) {
roomScene.promptText = "Please use a card";
roomScene.state = "playing";
}
} }

View File

@ -1,56 +1,56 @@
import QtQuick 2.15 import QtQuick 2.15
Rectangle { Rectangle {
function show(text, duration) { function show(text, duration) {
message.text = text; message.text = text;
time = Math.max(duration, 2 * fadeTime); time = Math.max(duration, 2 * fadeTime);
animation.start(); animation.start();
}
id: root
readonly property real defaultTime: 3000
property real time: defaultTime
readonly property real fadeTime: 300
anchors.horizontalCenter: parent != null ? parent.horizontalCenter : undefined
height: message.height + 20
width: message.width + 40
radius: 16
opacity: 0
color: "#F2808A87"
Text {
id: message
color: "white"
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}
SequentialAnimation on opacity {
id: animation
running: false
NumberAnimation {
to: .9
duration: fadeTime
} }
id: root PauseAnimation {
duration: time - 2 * fadeTime
readonly property real defaultTime: 3000
property real time: defaultTime
readonly property real fadeTime: 300
anchors.horizontalCenter: parent != null ? parent.horizontalCenter : undefined
height: message.height + 20
width: message.width + 40
radius: 16
opacity: 0
color: "#F2808A87"
Text {
id: message
color: "white"
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
} }
SequentialAnimation on opacity { NumberAnimation {
id: animation to: 0
running: false duration: fadeTime
NumberAnimation {
to: .9
duration: fadeTime
}
PauseAnimation {
duration: time - 2 * fadeTime
}
NumberAnimation {
to: 0
duration: fadeTime
}
onRunningChanged: {
if (!running) {
toast.model.remove(index);
}
}
} }
onRunningChanged: {
if (!running) {
toast.model.remove(index);
}
}
}
} }

View File

@ -3,35 +3,35 @@ import QtQuick 2.15
// copy from https://gist.github.com/jonmcclung/bae669101d17b103e94790341301c129 // copy from https://gist.github.com/jonmcclung/bae669101d17b103e94790341301c129
// and modified some code // and modified some code
ListView { ListView {
function show(text, duration) { function show(text, duration) {
if (duration === undefined) { if (duration === undefined) {
duration = 3000; duration = 3000;
}
model.insert(0, {text: text, duration: duration});
} }
model.insert(0, {text: text, duration: duration});
}
id: root id: root
z: Infinity z: Infinity
spacing: 5 spacing: 5
anchors.fill: parent anchors.fill: parent
anchors.bottomMargin: 10 anchors.bottomMargin: 10
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
interactive: false interactive: false
displaced: Transition { displaced: Transition {
NumberAnimation { NumberAnimation {
properties: "y" properties: "y"
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
}
} }
}
delegate: Toast {
Component.onCompleted: { delegate: Toast {
show(text, duration); Component.onCompleted: {
} show(text, duration);
} }
}
model: ListModel {id: model} model: ListModel {id: model}
} }

View File

@ -5,116 +5,116 @@ import "Logic.js" as Logic
import "Pages" import "Pages"
Window { Window {
id: mainWindow id: mainWindow
visible: true visible: true
width: 720 width: 720
height: 480 height: 480
property var callbacks: Logic.callbacks property var callbacks: Logic.callbacks
StackView { StackView {
id: mainStack id: mainStack
visible: !mainWindow.busy visible: !mainWindow.busy
initialItem: init initialItem: init
anchors.fill: parent anchors.fill: parent
}
Component { id: init; Init {} }
Component { id: lobby; Lobby {} }
Component { id: generalsOverview; GeneralsOverview {} }
Component { id: cardsOverview; CardsOverview {} }
Component { id: room; Room {} }
property bool busy: false
BusyIndicator {
running: true
anchors.centerIn: parent
visible: mainWindow.busy === true
}
Config {
id: config
}
// global popup. it is modal and just lower than toast
Rectangle {
id: globalPopupDim
anchors.fill: parent
color: "black"
opacity: 0
visible: !mainWindow.busy
property bool stateVisible: false
states: [
State {
when: globalPopupDim.stateVisible
PropertyChanges { target: globalPopupDim; opacity: 0.5 }
},
State {
when: !globalPopupDim.stateVisible
PropertyChanges { target: globalPopupDim; opacity: 0.0 }
}
]
transitions: Transition {
NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad }
}
}
Popup {
id: globalPopup
property string source: ""
modal: true
dim: false // cannot animate the dim
focus: true
opacity: mainWindow.busy ? 0 : 1
closePolicy: Popup.CloseOnEscape
anchors.centerIn: parent
onAboutToShow: {
globalPopupDim.stateVisible = true
} }
Component { id: init; Init {} } enter: Transition {
Component { id: lobby; Lobby {} } NumberAnimation { properties: "opacity"; from: 0; to: 1 }
Component { id: generalsOverview; GeneralsOverview {} } NumberAnimation { properties: "scale"; from: 0.4; to: 1 }
Component { id: cardsOverview; CardsOverview {} }
Component { id: room; Room {} }
property bool busy: false
BusyIndicator {
running: true
anchors.centerIn: parent
visible: mainWindow.busy === true
} }
Config { onAboutToHide: {
id: config globalPopupDim.stateVisible = false
} }
// global popup. it is modal and just lower than toast exit: Transition {
Rectangle { NumberAnimation { properties: "opacity"; from: 1; to: 0 }
id: globalPopupDim NumberAnimation { properties: "scale"; from: 1; to: 0.4 }
anchors.fill: parent
color: "black"
opacity: 0
visible: !mainWindow.busy
property bool stateVisible: false
states: [
State {
when: globalPopupDim.stateVisible
PropertyChanges { target: globalPopupDim; opacity: 0.5 }
},
State {
when: !globalPopupDim.stateVisible
PropertyChanges { target: globalPopupDim; opacity: 0.0 }
}
]
transitions: Transition {
NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad }
}
} }
Popup { Loader {
id: globalPopup visible: !mainWindow.busy
property string source: "" source: globalPopup.source === "" ? "" : "GlobalPopups/" + globalPopup.source
modal: true onSourceChanged: {
dim: false // cannot animate the dim if (item === null)
focus: true return;
opacity: mainWindow.busy ? 0 : 1 item.finished.connect(() => {
closePolicy: Popup.CloseOnEscape globalPopup.close();
anchors.centerIn: parent globalPopup.source = "";
});
onAboutToShow: { }
globalPopupDim.stateVisible = true
}
enter: Transition {
NumberAnimation { properties: "opacity"; from: 0; to: 1 }
NumberAnimation { properties: "scale"; from: 0.4; to: 1 }
}
onAboutToHide: {
globalPopupDim.stateVisible = false
}
exit: Transition {
NumberAnimation { properties: "opacity"; from: 1; to: 0 }
NumberAnimation { properties: "scale"; from: 1; to: 0.4 }
}
Loader {
visible: !mainWindow.busy
source: globalPopup.source === "" ? "" : "GlobalPopups/" + globalPopup.source
onSourceChanged: {
if (item === null)
return;
item.finished.connect(() => {
globalPopup.close();
globalPopup.source = "";
});
}
}
} }
}
ToastManager { ToastManager {
id: toast id: toast
} }
Connections { Connections {
target: Backend target: Backend
function onNotifyUI(command, jsonData) { function onNotifyUI(command, jsonData) {
let cb = callbacks[command] let cb = callbacks[command]
if (typeof(cb) === "function") { if (typeof(cb) === "function") {
cb(jsonData); cb(jsonData);
} else { } else {
callbacks["ErrorMsg"]("Unknown command " + command + "!"); callbacks["ErrorMsg"]("Unknown command " + command + "!");
} }
}
} }
}
} }

View File

@ -1,21 +1,21 @@
.pragma library .pragma library
function convertNumber(number) { function convertNumber(number) {
if (number === 1) if (number === 1)
return "A"; return "A";
if (number >= 2 && number <= 10) if (number >= 2 && number <= 10)
return number; return number;
if (number >= 11 && number <= 13) { if (number >= 11 && number <= 13) {
const strs = ["J", "Q", "K"]; const strs = ["J", "Q", "K"];
return strs[number - 11]; return strs[number - 11];
} }
return ""; return "";
} }
Array.prototype.contains = function(element) { Array.prototype.contains = function(element) {
return this.indexOf(element) != -1; return this.indexOf(element) != -1;
} }
Array.prototype.prepend = function() { Array.prototype.prepend = function() {
this.splice(0, 0, ...arguments); this.splice(0, 0, ...arguments);
} }

View File

@ -1,12 +1,12 @@
CREATE TABLE userinfo ( CREATE TABLE userinfo (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255), name VARCHAR(255),
password CHAR(64), password CHAR(64),
avatar VARCHAR(64), avatar VARCHAR(64),
lastLoginIp VARCHAR(64), lastLoginIp VARCHAR(64),
banned BOOLEAN banned BOOLEAN
); );
CREATE TABLE banip ( CREATE TABLE banip (
ip VARCHAR(64) ip VARCHAR(64)
); );

View File

@ -1,39 +1,39 @@
set(freekill_SRCS set(freekill_SRCS
"main.cpp" "main.cpp"
"core/player.cpp" "core/player.cpp"
"core/util.cpp" "core/util.cpp"
"network/server_socket.cpp" "network/server_socket.cpp"
"network/client_socket.cpp" "network/client_socket.cpp"
"network/router.cpp" "network/router.cpp"
"server/server.cpp" "server/server.cpp"
"server/serverplayer.cpp" "server/serverplayer.cpp"
"server/room.cpp" "server/room.cpp"
"client/client.cpp" "client/client.cpp"
"client/clientplayer.cpp" "client/clientplayer.cpp"
"ui/qmlbackend.cpp" "ui/qmlbackend.cpp"
"swig/freekill-wrap.cxx" "swig/freekill-wrap.cxx"
) )
set(freekill_HEADERS set(freekill_HEADERS
"core/util.h" "core/util.h"
"core/player.h" "core/player.h"
"network/server_socket.h" "network/server_socket.h"
"network/client_socket.h" "network/client_socket.h"
"network/router.h" "network/router.h"
"server/server.h" "server/server.h"
"server/serverplayer.h" "server/serverplayer.h"
"server/room.h" "server/room.h"
"client/client.h" "client/client.h"
"client/clientplayer.h" "client/clientplayer.h"
"ui/qmlbackend.h" "ui/qmlbackend.h"
) )
if (WIN32) if (WIN32)
set(LUA_LIB ${PROJECT_SOURCE_DIR}/lib/win/lua54.dll) set(LUA_LIB ${PROJECT_SOURCE_DIR}/lib/win/lua54.dll)
set(SQLITE3_LIB ${PROJECT_SOURCE_DIR}/lib/win/sqlite3.dll) set(SQLITE3_LIB ${PROJECT_SOURCE_DIR}/lib/win/sqlite3.dll)
else () else ()
set(LUA_LIB lua5.4) set(LUA_LIB lua5.4)
set(SQLITE3_LIB sqlite3) set(SQLITE3_LIB sqlite3)
endif () endif ()
source_group("Include" FILES ${freekill_HEADERS}) source_group("Include" FILES ${freekill_HEADERS})
@ -42,10 +42,10 @@ target_precompile_headers(FreeKill PRIVATE "pch.h")
target_link_libraries(FreeKill ${LUA_LIB} ${SQLITE3_LIB} Qt5::Qml Qt5::Gui Qt5::Network Qt5::Multimedia) target_link_libraries(FreeKill ${LUA_LIB} ${SQLITE3_LIB} Qt5::Qml Qt5::Gui Qt5::Network Qt5::Multimedia)
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i") file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
add_custom_command( add_custom_command(
OUTPUT ${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx OUTPUT ${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx
DEPENDS ${SWIG_FILES} DEPENDS ${SWIG_FILES}
COMMENT "Generating freekill-wrap.cxx" COMMENT "Generating freekill-wrap.cxx"
COMMAND swig -c++ -lua -Wall -o COMMAND swig -c++ -lua -Wall -o
${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx ${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx
${PROJECT_SOURCE_DIR}/src/swig/freekill.i ${PROJECT_SOURCE_DIR}/src/swig/freekill.i
) )

View File

@ -7,67 +7,67 @@ Client *ClientInstance;
ClientPlayer *Self; ClientPlayer *Self;
Client::Client(QObject* parent) Client::Client(QObject* parent)
: QObject(parent), callback(0) : QObject(parent), callback(0)
{ {
ClientInstance = this; ClientInstance = this;
Self = new ClientPlayer(0, this); Self = new ClientPlayer(0, this);
QQmlApplicationEngine *engine = Backend->getEngine(); QQmlApplicationEngine *engine = Backend->getEngine();
engine->rootContext()->setContextProperty("ClientInstance", ClientInstance); engine->rootContext()->setContextProperty("ClientInstance", ClientInstance);
engine->rootContext()->setContextProperty("Self", Self); engine->rootContext()->setContextProperty("Self", Self);
ClientSocket *socket = new ClientSocket; ClientSocket *socket = new ClientSocket;
connect(socket, &ClientSocket::error_message, this, &Client::error_message); connect(socket, &ClientSocket::error_message, this, &Client::error_message);
router = new Router(this, socket, Router::TYPE_CLIENT); router = new Router(this, socket, Router::TYPE_CLIENT);
L = CreateLuaState(); L = CreateLuaState();
DoLuaScript(L, "lua/freekill.lua"); DoLuaScript(L, "lua/freekill.lua");
DoLuaScript(L, "lua/client/client.lua"); DoLuaScript(L, "lua/client/client.lua");
} }
Client::~Client() Client::~Client()
{ {
ClientInstance = nullptr; ClientInstance = nullptr;
lua_close(L); lua_close(L);
router->getSocket()->disconnectFromHost(); router->getSocket()->disconnectFromHost();
router->getSocket()->deleteLater(); router->getSocket()->deleteLater();
} }
void Client::connectToHost(const QHostAddress& server, ushort port) void Client::connectToHost(const QHostAddress& server, ushort port)
{ {
router->getSocket()->connectToHost(server, port); router->getSocket()->connectToHost(server, port);
} }
void Client::replyToServer(const QString& command, const QString& jsonData) void Client::replyToServer(const QString& command, const QString& jsonData)
{ {
int type = Router::TYPE_REPLY | Router::SRC_CLIENT | Router::DEST_SERVER; int type = Router::TYPE_REPLY | Router::SRC_CLIENT | Router::DEST_SERVER;
router->reply(type, command, jsonData); router->reply(type, command, jsonData);
} }
void Client::notifyServer(const QString& command, const QString& jsonData) void Client::notifyServer(const QString& command, const QString& jsonData)
{ {
int type = Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER; int type = Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER;
router->notify(type, command, jsonData); router->notify(type, command, jsonData);
} }
ClientPlayer *Client::addPlayer(int id, const QString &name, const QString &avatar) { ClientPlayer *Client::addPlayer(int id, const QString &name, const QString &avatar) {
ClientPlayer *player = new ClientPlayer(id); ClientPlayer *player = new ClientPlayer(id);
player->setScreenName(name); player->setScreenName(name);
player->setAvatar(avatar); player->setAvatar(avatar);
players[id] = player; players[id] = player;
return player; return player;
} }
void Client::removePlayer(int id) { void Client::removePlayer(int id) {
ClientPlayer *p = players[id]; ClientPlayer *p = players[id];
p->deleteLater(); p->deleteLater();
players[id] = nullptr; players[id] = nullptr;
} }
void Client::clearPlayers() { void Client::clearPlayers() {
players.clear(); players.clear();
} }
lua_State *Client::getLuaState() { lua_State *Client::getLuaState() {
return L; return L;
} }

View File

@ -6,33 +6,33 @@
#include "qmlbackend.h" #include "qmlbackend.h"
class Client : public QObject { class Client : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Client(QObject *parent = nullptr); Client(QObject *parent = nullptr);
~Client(); ~Client();
void connectToHost(const QHostAddress &server, ushort port); void connectToHost(const QHostAddress &server, ushort port);
Q_INVOKABLE void replyToServer(const QString &command, const QString &jsonData); Q_INVOKABLE void replyToServer(const QString &command, const QString &jsonData);
Q_INVOKABLE void notifyServer(const QString &command, const QString &jsonData); Q_INVOKABLE void notifyServer(const QString &command, const QString &jsonData);
Q_INVOKABLE void callLua(const QString &command, const QString &jsonData); Q_INVOKABLE void callLua(const QString &command, const QString &jsonData);
LuaFunction callback; LuaFunction callback;
ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar);
void removePlayer(int id); void removePlayer(int id);
Q_INVOKABLE void clearPlayers(); Q_INVOKABLE void clearPlayers();
lua_State *getLuaState(); lua_State *getLuaState();
signals: signals:
void error_message(const QString &msg); void error_message(const QString &msg);
private: private:
Router *router; Router *router;
QMap<int, ClientPlayer *> players; QMap<int, ClientPlayer *> players;
lua_State *L; lua_State *L;
}; };
extern Client *ClientInstance; extern Client *ClientInstance;

View File

@ -1,9 +1,9 @@
#include "clientplayer.h" #include "clientplayer.h"
ClientPlayer::ClientPlayer(int id, QObject* parent) ClientPlayer::ClientPlayer(int id, QObject* parent)
: Player(parent) : Player(parent)
{ {
setId(id); setId(id);
} }
ClientPlayer::~ClientPlayer() ClientPlayer::~ClientPlayer()

View File

@ -4,23 +4,23 @@
#include "player.h" #include "player.h"
class ClientPlayer : public Player { class ClientPlayer : public Player {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int id READ getId CONSTANT) Q_PROPERTY(int id READ getId CONSTANT)
Q_PROPERTY(QString screenName Q_PROPERTY(QString screenName
READ getScreenName READ getScreenName
WRITE setScreenName WRITE setScreenName
NOTIFY screenNameChanged NOTIFY screenNameChanged
) )
Q_PROPERTY(QString avatar Q_PROPERTY(QString avatar
READ getAvatar READ getAvatar
WRITE setAvatar WRITE setAvatar
NOTIFY avatarChanged NOTIFY avatarChanged
) )
public: public:
ClientPlayer(int id, QObject *parent = nullptr); ClientPlayer(int id, QObject *parent = nullptr);
~ClientPlayer(); ~ClientPlayer();
private: private:
}; };

View File

@ -1,10 +1,10 @@
#include "player.h" #include "player.h"
Player::Player(QObject* parent) Player::Player(QObject* parent)
: QObject(parent) : QObject(parent)
, id(0) , id(0)
, state(Player::Invalid) , state(Player::Invalid)
, ready(false) , ready(false)
{ {
} }
@ -14,85 +14,85 @@ Player::~Player()
int Player::getId() const int Player::getId() const
{ {
return id; return id;
} }
void Player::setId(int id) void Player::setId(int id)
{ {
this->id = id; this->id = id;
} }
QString Player::getScreenName() const QString Player::getScreenName() const
{ {
return screenName; return screenName;
} }
void Player::setScreenName(const QString& name) void Player::setScreenName(const QString& name)
{ {
this->screenName = name; this->screenName = name;
emit screenNameChanged(); emit screenNameChanged();
} }
QString Player::getAvatar() const QString Player::getAvatar() const
{ {
return avatar; return avatar;
} }
void Player::setAvatar(const QString& avatar) void Player::setAvatar(const QString& avatar)
{ {
this->avatar = avatar; this->avatar = avatar;
emit avatarChanged(); emit avatarChanged();
} }
Player::State Player::getState() const Player::State Player::getState() const
{ {
return state; return state;
} }
QString Player::getStateString() const QString Player::getStateString() const
{ {
switch (state) { switch (state) {
case Online: case Online:
return QStringLiteral("online"); return QStringLiteral("online");
case Trust: case Trust:
return QStringLiteral("trust"); return QStringLiteral("trust");
case Robot: case Robot:
return QStringLiteral("robot"); return QStringLiteral("robot");
case Offline: case Offline:
return QStringLiteral("offline"); return QStringLiteral("offline");
default: default:
return QStringLiteral("invalid"); return QStringLiteral("invalid");
} }
} }
void Player::setState(Player::State state) void Player::setState(Player::State state)
{ {
this->state = state; this->state = state;
emit stateChanged(); emit stateChanged();
} }
void Player::setStateString(const QString &state) void Player::setStateString(const QString &state)
{ {
if (state == QStringLiteral("online")) if (state == QStringLiteral("online"))
setState(Online); setState(Online);
else if (state == QStringLiteral("trust")) else if (state == QStringLiteral("trust"))
setState(Trust); setState(Trust);
else if (state == QStringLiteral("robot")) else if (state == QStringLiteral("robot"))
setState(Robot); setState(Robot);
else if (state == QStringLiteral("offline")) else if (state == QStringLiteral("offline"))
setState(Offline); setState(Offline);
else else
setState(Invalid); setState(Invalid);
} }
bool Player::isReady() const bool Player::isReady() const
{ {
return ready; return ready;
} }
void Player::setReady(bool ready) void Player::setReady(bool ready)
{ {
this->ready = ready; this->ready = ready;
emit readyChanged(); emit readyChanged();
} }

View File

@ -4,49 +4,49 @@
// Common part of ServerPlayer and ClientPlayer // Common part of ServerPlayer and ClientPlayer
// dont initialize it directly // dont initialize it directly
class Player : public QObject { class Player : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum State{ enum State{
Invalid, Invalid,
Online, Online,
Trust, // Trust or run Trust, // Trust or run
Robot, // only for real robot Robot, // only for real robot
Offline Offline
}; };
explicit Player(QObject *parent = nullptr); explicit Player(QObject *parent = nullptr);
~Player(); ~Player();
int getId() const; int getId() const;
void setId(int id); void setId(int id);
QString getScreenName() const; QString getScreenName() const;
void setScreenName(const QString &name); void setScreenName(const QString &name);
QString getAvatar() const; QString getAvatar() const;
void setAvatar(const QString &avatar); void setAvatar(const QString &avatar);
State getState() const; State getState() const;
QString getStateString() const; QString getStateString() const;
void setState(State state); void setState(State state);
void setStateString(const QString &state); void setStateString(const QString &state);
bool isReady() const; bool isReady() const;
void setReady(bool ready); void setReady(bool ready);
signals: signals:
void screenNameChanged(); void screenNameChanged();
void avatarChanged(); void avatarChanged();
void stateChanged(); void stateChanged();
void readyChanged(); void readyChanged();
private: private:
int id; int id;
QString screenName; // screenName should not be same. QString screenName; // screenName should not be same.
QString avatar; QString avatar;
State state; State state;
bool ready; bool ready;
}; };
#endif // _PLAYER_H #endif // _PLAYER_H

View File

@ -1,123 +1,123 @@
#include "util.h" #include "util.h"
extern "C" { extern "C" {
int luaopen_fk(lua_State *); int luaopen_fk(lua_State *);
} }
lua_State *CreateLuaState() lua_State *CreateLuaState()
{ {
lua_State *L = luaL_newstate(); lua_State *L = luaL_newstate();
luaL_openlibs(L); luaL_openlibs(L);
luaopen_fk(L); luaopen_fk(L);
return L; return L;
} }
bool DoLuaScript(lua_State *L, const char *script) bool DoLuaScript(lua_State *L, const char *script)
{ {
lua_getglobal(L, "debug"); lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback"); lua_getfield(L, -1, "traceback");
lua_replace(L, -2); lua_replace(L, -2);
luaL_loadfile(L, script); luaL_loadfile(L, script);
int error = lua_pcall(L, 0, LUA_MULTRET, -2); int error = lua_pcall(L, 0, LUA_MULTRET, -2);
if (error) { if (error) {
const char *error_msg = lua_tostring(L, -1); const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg; qDebug() << error_msg;
lua_pop(L, 2); lua_pop(L, 2);
return false; return false;
} }
lua_pop(L, 1); lua_pop(L, 1);
return true; return true;
} }
// For Lua debugging // For Lua debugging
void Dumpstack(lua_State *L) void Dumpstack(lua_State *L)
{ {
int top = lua_gettop(L); int top = lua_gettop(L);
for (int i = 1; i <= top; i++) { for (int i = 1; i <= top; i++) {
printf("%d\t%s\t", i, luaL_typename(L, i)); printf("%d\t%s\t", i, luaL_typename(L, i));
switch (lua_type(L, i)) { switch (lua_type(L, i)) {
case LUA_TNUMBER: case LUA_TNUMBER:
printf("%g\n",lua_tonumber(L, i)); printf("%g\n",lua_tonumber(L, i));
break; break;
case LUA_TSTRING: case LUA_TSTRING:
printf("%s\n",lua_tostring(L, i)); printf("%s\n",lua_tostring(L, i));
break; break;
case LUA_TBOOLEAN: case LUA_TBOOLEAN:
printf("%s\n", (lua_toboolean(L, i) ? "true" : "false")); printf("%s\n", (lua_toboolean(L, i) ? "true" : "false"));
break; break;
case LUA_TNIL: case LUA_TNIL:
printf("%s\n", "nil"); printf("%s\n", "nil");
break; break;
default: default:
printf("%p\n",lua_topointer(L, i)); printf("%p\n",lua_topointer(L, i));
break; break;
}
} }
}
} }
sqlite3 *OpenDatabase(const QString &filename) sqlite3 *OpenDatabase(const QString &filename)
{ {
sqlite3 *ret; sqlite3 *ret;
int rc; int rc;
if (!QFile::exists(filename)) { if (!QFile::exists(filename)) {
QFile file("./server/init.sql"); QFile file("./server/init.sql");
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "cannot open init.sql. Quit now."; qDebug() << "cannot open init.sql. Quit now.";
qApp->exit(1); qApp->exit(1);
}
QTextStream in(&file);
char *err_msg;
sqlite3_open(filename.toLatin1().data(), &ret);
rc = sqlite3_exec(ret, in.readAll().toLatin1().data(), nullptr, nullptr, &err_msg);
if (rc != SQLITE_OK ) {
qDebug() << "sqlite error:" << err_msg;
sqlite3_free(err_msg);
sqlite3_close(ret);
qApp->exit(1);
}
} else {
rc = sqlite3_open(filename.toLatin1().data(), &ret);
if (rc != SQLITE_OK) {
qDebug() << "Cannot open database:" << sqlite3_errmsg(ret);
sqlite3_close(ret);
qApp->exit(1);
}
} }
return ret;
QTextStream in(&file);
char *err_msg;
sqlite3_open(filename.toLatin1().data(), &ret);
rc = sqlite3_exec(ret, in.readAll().toLatin1().data(), nullptr, nullptr, &err_msg);
if (rc != SQLITE_OK ) {
qDebug() << "sqlite error:" << err_msg;
sqlite3_free(err_msg);
sqlite3_close(ret);
qApp->exit(1);
}
} else {
rc = sqlite3_open(filename.toLatin1().data(), &ret);
if (rc != SQLITE_OK) {
qDebug() << "Cannot open database:" << sqlite3_errmsg(ret);
sqlite3_close(ret);
qApp->exit(1);
}
}
return ret;
} }
// callback for handling SELECT expression // callback for handling SELECT expression
static int callback(void *jsonDoc, int argc, char **argv, char **cols) { static int callback(void *jsonDoc, int argc, char **argv, char **cols) {
QJsonObject obj; QJsonObject obj;
for (int i = 0; i < argc; i++) { for (int i = 0; i < argc; i++) {
QJsonArray arr = obj[QString(cols[i])].toArray(); QJsonArray arr = obj[QString(cols[i])].toArray();
arr << QString(argv[i] ? argv[i] : "#null"); arr << QString(argv[i] ? argv[i] : "#null");
obj[QString(cols[i])] = arr; obj[QString(cols[i])] = arr;
} }
((QJsonObject *)jsonDoc)->swap(obj); ((QJsonObject *)jsonDoc)->swap(obj);
return 0; return 0;
} }
QJsonObject SelectFromDatabase(sqlite3 *db, const QString &sql) { QJsonObject SelectFromDatabase(sqlite3 *db, const QString &sql) {
QJsonObject obj; QJsonObject obj;
sqlite3_exec(db, sql.toUtf8().data(), callback, (void *)&obj, nullptr); sqlite3_exec(db, sql.toUtf8().data(), callback, (void *)&obj, nullptr);
return obj; return obj;
} }
QString SelectFromDb(sqlite3 *db, const QString &sql) { QString SelectFromDb(sqlite3 *db, const QString &sql) {
QJsonObject obj = SelectFromDatabase(db, sql); QJsonObject obj = SelectFromDatabase(db, sql);
return QJsonDocument(obj).toJson(); return QJsonDocument(obj).toJson();
} }
void ExecSQL(sqlite3 *db, const QString &sql) { void ExecSQL(sqlite3 *db, const QString &sql) {
sqlite3_exec(db, sql.toUtf8().data(), nullptr, nullptr, nullptr); sqlite3_exec(db, sql.toUtf8().data(), nullptr, nullptr, nullptr);
} }
void CloseDatabase(sqlite3 *db) { void CloseDatabase(sqlite3 *db) {
sqlite3_close(db); sqlite3_close(db);
} }

View File

@ -3,59 +3,61 @@
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication *app; QCoreApplication *app;
QCoreApplication::setApplicationName("FreeKill"); QCoreApplication::setApplicationName("FreeKill");
QCoreApplication::setApplicationVersion("Alpha 0.0.1"); QCoreApplication::setApplicationVersion("Alpha 0.0.1");
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription("FreeKill server"); parser.setApplicationDescription("FreeKill server");
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
parser.addOption({{"s", "server"}, "start server at <port>", "port"}); parser.addOption({{"s", "server"}, "start server at <port>", "port"});
QStringList cliOptions; QStringList cliOptions;
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
cliOptions << argv[i]; cliOptions << argv[i];
parser.parse(cliOptions); parser.parse(cliOptions);
bool startServer = parser.isSet("server"); bool startServer = parser.isSet("server");
ushort serverPort = 9527; ushort serverPort = 9527;
if (startServer) { if (startServer) {
app = new QCoreApplication(argc, argv); app = new QCoreApplication(argc, argv);
bool ok = false; bool ok = false;
if (parser.value("server").toInt(&ok) && ok) if (parser.value("server").toInt(&ok) && ok)
serverPort = parser.value("server").toInt(); serverPort = parser.value("server").toInt();
Server *server = new Server; Server *server = new Server;
if (!server->listen(QHostAddress::Any, serverPort)) { if (!server->listen(QHostAddress::Any, serverPort)) {
fprintf(stderr, "cannot listen on port %d!\n", serverPort); fprintf(stderr, "cannot listen on port %d!\n", serverPort);
app->exit(1); app->exit(1);
}
return app->exec();
} }
return app->exec();
}
app = new QGuiApplication(argc, argv); app = new QGuiApplication(argc, argv);
QQmlApplicationEngine *engine = new QQmlApplicationEngine; QQmlApplicationEngine *engine = new QQmlApplicationEngine;
QmlBackend backend; QmlBackend backend;
backend.setEngine(engine); backend.setEngine(engine);
engine->rootContext()->setContextProperty("Backend", &backend); engine->rootContext()->setContextProperty("Backend", &backend);
engine->rootContext()->setContextProperty("AppPath", QUrl::fromLocalFile(QDir::currentPath())); engine->rootContext()->setContextProperty("AppPath", QUrl::fromLocalFile(QDir::currentPath()));
#ifdef QT_DEBUG #ifdef QT_DEBUG
bool debugging = true; bool debugging = true;
#else #else
bool debugging = false; bool debugging = false;
#endif #endif
engine->rootContext()->setContextProperty("Debugging", debugging); engine->rootContext()->setContextProperty("Debugging", debugging);
engine->load("qml/main.qml"); engine->load("qml/main.qml");
if (engine->rootObjects().isEmpty())
return -1;
int ret = app->exec(); int ret = app->exec();
// delete the engine first // delete the engine first
// to avoid "TypeError: Cannot read property 'xxx' of null" // to avoid "TypeError: Cannot read property 'xxx' of null"
delete engine; delete engine;
return ret; return ret;
} }

View File

@ -2,94 +2,94 @@
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) ClientSocket::ClientSocket() : socket(new QTcpSocket(this))
{ {
init(); init();
} }
ClientSocket::ClientSocket(QTcpSocket* socket) ClientSocket::ClientSocket(QTcpSocket* socket)
{ {
socket->setParent(this); socket->setParent(this);
this->socket = socket; this->socket = socket;
timerSignup.setSingleShot(true); timerSignup.setSingleShot(true);
connect(&timerSignup, &QTimer::timeout, this, &ClientSocket::disconnectFromHost); connect(&timerSignup, &QTimer::timeout, this, &ClientSocket::disconnectFromHost);
init(); init();
} }
void ClientSocket::init() void ClientSocket::init()
{ {
connect(socket, &QTcpSocket::connected, connect(socket, &QTcpSocket::connected,
this, &ClientSocket::connected); this, &ClientSocket::connected);
connect(socket, &QTcpSocket::disconnected, connect(socket, &QTcpSocket::disconnected,
this, &ClientSocket::disconnected); this, &ClientSocket::disconnected);
connect(socket, &QTcpSocket::readyRead, connect(socket, &QTcpSocket::readyRead,
this, &ClientSocket::getMessage); this, &ClientSocket::getMessage);
connect(socket, &QTcpSocket::errorOccurred, connect(socket, &QTcpSocket::errorOccurred,
this, &ClientSocket::raiseError); this, &ClientSocket::raiseError);
} }
void ClientSocket::connectToHost(const QHostAddress &address, ushort port) void ClientSocket::connectToHost(const QHostAddress &address, ushort port)
{ {
socket->connectToHost(address, port); socket->connectToHost(address, port);
} }
void ClientSocket::getMessage() void ClientSocket::getMessage()
{ {
while (socket->canReadLine()) { while (socket->canReadLine()) {
char msg[16000]; // buffer char msg[16000]; // buffer
socket->readLine(msg, sizeof(msg)); socket->readLine(msg, sizeof(msg));
emit message_got(msg); emit message_got(msg);
} }
} }
void ClientSocket::disconnectFromHost() void ClientSocket::disconnectFromHost()
{ {
socket->disconnectFromHost(); socket->disconnectFromHost();
} }
void ClientSocket::send(const QByteArray &msg) void ClientSocket::send(const QByteArray &msg)
{ {
socket->write(msg); socket->write(msg);
if (!msg.endsWith("\n")) if (!msg.endsWith("\n"))
socket->write("\n"); socket->write("\n");
socket->flush(); socket->flush();
} }
bool ClientSocket::isConnected() const bool ClientSocket::isConnected() const
{ {
return socket->state() == QTcpSocket::ConnectedState; return socket->state() == QTcpSocket::ConnectedState;
} }
QString ClientSocket::peerName() const QString ClientSocket::peerName() const
{ {
QString peer_name = socket->peerName(); QString peer_name = socket->peerName();
if (peer_name.isEmpty()) if (peer_name.isEmpty())
peer_name = QString("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort()); peer_name = QString("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort());
return peer_name; return peer_name;
} }
QString ClientSocket::peerAddress() const QString ClientSocket::peerAddress() const
{ {
return socket->peerAddress().toString(); return socket->peerAddress().toString();
} }
void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error) void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error)
{ {
// translate error message // translate error message
QString reason; QString reason;
switch (socket_error) { switch (socket_error) {
case QAbstractSocket::ConnectionRefusedError: case QAbstractSocket::ConnectionRefusedError:
reason = tr("Connection was refused or timeout"); break; reason = tr("Connection was refused or timeout"); break;
case QAbstractSocket::RemoteHostClosedError: case QAbstractSocket::RemoteHostClosedError:
reason = tr("Remote host close this connection"); break; reason = tr("Remote host close this connection"); break;
case QAbstractSocket::HostNotFoundError: case QAbstractSocket::HostNotFoundError:
reason = tr("Host not found"); break; reason = tr("Host not found"); break;
case QAbstractSocket::SocketAccessError: case QAbstractSocket::SocketAccessError:
reason = tr("Socket access error"); break; reason = tr("Socket access error"); break;
case QAbstractSocket::NetworkError: case QAbstractSocket::NetworkError:
return; // this error is ignored ... return; // this error is ignored ...
default: reason = tr("Unknow error"); break; default: reason = tr("Unknow error"); break;
} }
emit error_message(tr("Connection failed, error code = %1\n reason: %2") emit error_message(tr("Connection failed, error code = %1\n reason: %2")
.arg(socket_error).arg(reason)); .arg(socket_error).arg(reason));
} }

View File

@ -2,34 +2,34 @@
#define _CLIENT_SOCKET_H #define _CLIENT_SOCKET_H
class ClientSocket : public QObject { class ClientSocket : public QObject {
Q_OBJECT Q_OBJECT
public: public:
ClientSocket(); ClientSocket();
// For server use // For server use
ClientSocket(QTcpSocket *socket); ClientSocket(QTcpSocket *socket);
void connectToHost(const QHostAddress &address = QHostAddress::LocalHost, ushort port = 9527u); void connectToHost(const QHostAddress &address = QHostAddress::LocalHost, ushort port = 9527u);
void disconnectFromHost(); void disconnectFromHost();
void send(const QByteArray& msg); void send(const QByteArray& msg);
bool isConnected() const; bool isConnected() const;
QString peerName() const; QString peerName() const;
QString peerAddress() const; QString peerAddress() const;
QTimer timerSignup; QTimer timerSignup;
signals: signals:
void message_got(const QByteArray& msg); void message_got(const QByteArray& msg);
void error_message(const QString &msg); void error_message(const QString &msg);
void disconnected(); void disconnected();
void connected(); void connected();
private slots: private slots:
void getMessage(); void getMessage();
void raiseError(QAbstractSocket::SocketError error); void raiseError(QAbstractSocket::SocketError error);
private: private:
QTcpSocket *socket; QTcpSocket *socket;
void init(); void init();
}; };
#endif // _CLIENT_SOCKET_H #endif // _CLIENT_SOCKET_H

View File

@ -5,200 +5,200 @@
#include "serverplayer.h" #include "serverplayer.h"
Router::Router(QObject *parent, ClientSocket *socket, RouterType type) Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
: QObject(parent) : QObject(parent)
{ {
this->type = type; this->type = type;
this->socket = nullptr; this->socket = nullptr;
setSocket(socket); setSocket(socket);
expectedReplyId = -1; expectedReplyId = -1;
replyTimeout = 0; replyTimeout = 0;
extraReplyReadySemaphore = nullptr; extraReplyReadySemaphore = nullptr;
} }
Router::~Router() Router::~Router()
{ {
abortRequest(); abortRequest();
} }
ClientSocket* Router::getSocket() const ClientSocket* Router::getSocket() const
{ {
return socket; return socket;
} }
void Router::setSocket(ClientSocket *socket) void Router::setSocket(ClientSocket *socket)
{ {
if (this->socket != nullptr) { if (this->socket != nullptr) {
this->socket->disconnect(this); this->socket->disconnect(this);
disconnect(this->socket); disconnect(this->socket);
this->socket->deleteLater(); this->socket->deleteLater();
} }
this->socket = nullptr; this->socket = nullptr;
if (socket != nullptr) { if (socket != nullptr) {
connect(this, &Router::messageReady, socket, &ClientSocket::send); connect(this, &Router::messageReady, socket, &ClientSocket::send);
connect(socket, &ClientSocket::message_got, this, &Router::handlePacket); connect(socket, &ClientSocket::message_got, this, &Router::handlePacket);
connect(socket, &ClientSocket::disconnected, this, &Router::abortRequest); connect(socket, &ClientSocket::disconnected, this, &Router::abortRequest);
socket->setParent(this); socket->setParent(this);
this->socket = socket; this->socket = socket;
} }
} }
void Router::setReplyReadySemaphore(QSemaphore *semaphore) void Router::setReplyReadySemaphore(QSemaphore *semaphore)
{ {
extraReplyReadySemaphore = semaphore; extraReplyReadySemaphore = semaphore;
} }
void Router::request(int type, const QString& command, void Router::request(int type, const QString& command,
const QString& jsonData, int timeout) const QString& jsonData, int timeout)
{ {
// In case a request is called without a following waitForReply call // In case a request is called without a following waitForReply call
if (replyReadySemaphore.available() > 0) if (replyReadySemaphore.available() > 0)
replyReadySemaphore.acquire(replyReadySemaphore.available()); replyReadySemaphore.acquire(replyReadySemaphore.available());
static int requestId = 0; static int requestId = 0;
requestId++; requestId++;
replyMutex.lock(); replyMutex.lock();
expectedReplyId = requestId; expectedReplyId = requestId;
replyTimeout = timeout; replyTimeout = timeout;
requestStartTime = QDateTime::currentDateTime(); requestStartTime = QDateTime::currentDateTime();
m_reply = QString(); m_reply = QString();
replyMutex.unlock(); replyMutex.unlock();
QJsonArray body; QJsonArray body;
body << requestId; body << requestId;
body << type; body << type;
body << command; body << command;
body << jsonData; body << jsonData;
body << timeout; body << timeout;
emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact)); emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact));
} }
void Router::reply(int type, const QString& command, const QString& jsonData) void Router::reply(int type, const QString& command, const QString& jsonData)
{ {
QJsonArray body; QJsonArray body;
body << this->requestId; body << this->requestId;
body << type; body << type;
body << command; body << command;
body << jsonData; body << jsonData;
emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact)); emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact));
} }
void Router::notify(int type, const QString& command, const QString& jsonData) void Router::notify(int type, const QString& command, const QString& jsonData)
{ {
QJsonArray body; QJsonArray body;
body << -2; // requestId = -2 mean this is for notification body << -2; // requestId = -2 mean this is for notification
body << type; body << type;
body << command; body << command;
body << jsonData; body << jsonData;
emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact)); emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact));
} }
int Router::getTimeout() const int Router::getTimeout() const
{ {
return requestTimeout; return requestTimeout;
} }
// cancel last request from the sender // cancel last request from the sender
void Router::cancelRequest() void Router::cancelRequest()
{ {
replyMutex.lock(); replyMutex.lock();
expectedReplyId = -1; expectedReplyId = -1;
replyTimeout = 0; replyTimeout = 0;
extraReplyReadySemaphore = nullptr; extraReplyReadySemaphore = nullptr;
replyMutex.unlock(); replyMutex.unlock();
if (replyReadySemaphore.available() > 0) if (replyReadySemaphore.available() > 0)
replyReadySemaphore.acquire(replyReadySemaphore.available()); replyReadySemaphore.acquire(replyReadySemaphore.available());
} }
QString Router::waitForReply() QString Router::waitForReply()
{ {
replyReadySemaphore.acquire(); replyReadySemaphore.acquire();
return m_reply; return m_reply;
} }
QString Router::waitForReply(int timeout) QString Router::waitForReply(int timeout)
{ {
replyReadySemaphore.tryAcquire(1, timeout * 1000); replyReadySemaphore.tryAcquire(1, timeout * 1000);
return m_reply; return m_reply;
} }
void Router::abortRequest() void Router::abortRequest()
{ {
replyMutex.lock(); replyMutex.lock();
if (expectedReplyId != -1) { if (expectedReplyId != -1) {
replyReadySemaphore.release(); replyReadySemaphore.release();
if (extraReplyReadySemaphore) if (extraReplyReadySemaphore)
extraReplyReadySemaphore->release(); extraReplyReadySemaphore->release();
expectedReplyId = -1; expectedReplyId = -1;
extraReplyReadySemaphore = nullptr; extraReplyReadySemaphore = nullptr;
} }
replyMutex.unlock(); replyMutex.unlock();
} }
void Router::handlePacket(const QByteArray& rawPacket) void Router::handlePacket(const QByteArray& rawPacket)
{ {
QJsonDocument packet = QJsonDocument::fromJson(rawPacket); QJsonDocument packet = QJsonDocument::fromJson(rawPacket);
if (packet.isNull() || !packet.isArray()) if (packet.isNull() || !packet.isArray())
return; return;
int requestId = packet[0].toInt(); int requestId = packet[0].toInt();
int type = packet[1].toInt(); int type = packet[1].toInt();
QString command = packet[2].toString(); QString command = packet[2].toString();
QString jsonData = packet[3].toString(); QString jsonData = packet[3].toString();
if (type & TYPE_NOTIFICATION) { if (type & TYPE_NOTIFICATION) {
if (type & DEST_CLIENT) { if (type & DEST_CLIENT) {
ClientInstance->callLua(command, jsonData); ClientInstance->callLua(command, jsonData);
} else { } else {
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent()); ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
// Add the uid of sender to jsonData // Add the uid of sender to jsonData
QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array(); QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array();
arr.prepend(player->getId()); arr.prepend(player->getId());
Room *room = player->getRoom(); Room *room = player->getRoom();
room->lockLua(__FUNCTION__); room->lockLua(__FUNCTION__);
room->callLua(command, QJsonDocument(arr).toJson()); room->callLua(command, QJsonDocument(arr).toJson());
room->unlockLua(__FUNCTION__); room->unlockLua(__FUNCTION__);
}
} }
else if (type & TYPE_REQUEST) { }
this->requestId = requestId; else if (type & TYPE_REQUEST) {
this->requestTimeout = packet[4].toInt(); this->requestId = requestId;
this->requestTimeout = packet[4].toInt();
if (type & DEST_CLIENT) { if (type & DEST_CLIENT) {
qobject_cast<Client *>(parent())->callLua(command, jsonData); qobject_cast<Client *>(parent())->callLua(command, jsonData);
} else { } else {
// requesting server is not allowed // requesting server is not allowed
Q_ASSERT(false); Q_ASSERT(false);
}
} }
else if (type & TYPE_REPLY) { }
QMutexLocker locker(&replyMutex); else if (type & TYPE_REPLY) {
QMutexLocker locker(&replyMutex);
if (requestId != this->expectedReplyId) if (requestId != this->expectedReplyId)
return; return;
this->expectedReplyId = -1; this->expectedReplyId = -1;
if (replyTimeout >= 0 && replyTimeout < if (replyTimeout >= 0 && replyTimeout <
requestStartTime.secsTo(QDateTime::currentDateTime())) requestStartTime.secsTo(QDateTime::currentDateTime()))
return; return;
m_reply = jsonData; m_reply = jsonData;
// TODO: callback? // TODO: callback?
replyReadySemaphore.release(); replyReadySemaphore.release();
if (extraReplyReadySemaphore) { if (extraReplyReadySemaphore) {
extraReplyReadySemaphore->release(); extraReplyReadySemaphore->release();
extraReplyReadySemaphore = nullptr; extraReplyReadySemaphore = nullptr;
}
locker.unlock();
emit replyReady();
} }
locker.unlock();
emit replyReady();
}
} }

View File

@ -4,75 +4,75 @@
class ClientSocket; class ClientSocket;
class Router : public QObject { class Router : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum PacketType { enum PacketType {
TYPE_REQUEST = 0x100, TYPE_REQUEST = 0x100,
TYPE_REPLY = 0x200, TYPE_REPLY = 0x200,
TYPE_NOTIFICATION = 0x400, TYPE_NOTIFICATION = 0x400,
SRC_CLIENT = 0x010, SRC_CLIENT = 0x010,
SRC_SERVER = 0x020, SRC_SERVER = 0x020,
SRC_LOBBY = 0x040, SRC_LOBBY = 0x040,
DEST_CLIENT = 0x001, DEST_CLIENT = 0x001,
DEST_SERVER = 0x002, DEST_SERVER = 0x002,
DEST_LOBBY = 0x004 DEST_LOBBY = 0x004
}; };
enum RouterType { enum RouterType {
TYPE_SERVER, TYPE_SERVER,
TYPE_CLIENT TYPE_CLIENT
}; };
Router(QObject *parent, ClientSocket *socket, RouterType type); Router(QObject *parent, ClientSocket *socket, RouterType type);
~Router(); ~Router();
ClientSocket *getSocket() const; ClientSocket *getSocket() const;
void setSocket(ClientSocket *socket); void setSocket(ClientSocket *socket);
void setReplyReadySemaphore(QSemaphore *semaphore); void setReplyReadySemaphore(QSemaphore *semaphore);
void request(int type, const QString &command, void request(int type, const QString &command,
const QString &jsonData, int timeout); const QString &jsonData, int timeout);
void reply(int type, const QString &command, const QString &jsonData); void reply(int type, const QString &command, const QString &jsonData);
void notify(int type, const QString &command, const QString &jsonData); void notify(int type, const QString &command, const QString &jsonData);
int getTimeout() const; int getTimeout() const;
void cancelRequest(); void cancelRequest();
void abortRequest(); void abortRequest();
QString waitForReply(); QString waitForReply();
QString waitForReply(int timeout); QString waitForReply(int timeout);
signals: signals:
void messageReady(const QByteArray &message); void messageReady(const QByteArray &message);
void unknownPacket(const QByteArray &packet); void unknownPacket(const QByteArray &packet);
void replyReady(); void replyReady();
protected: protected:
void handlePacket(const QByteArray &rawPacket); void handlePacket(const QByteArray &rawPacket);
private: private:
ClientSocket *socket; ClientSocket *socket;
RouterType type; RouterType type;
// For sender // For sender
int requestId; int requestId;
int requestTimeout; int requestTimeout;
// For receiver // For receiver
QDateTime requestStartTime; QDateTime requestStartTime;
QMutex replyMutex; QMutex replyMutex;
int expectedReplyId; int expectedReplyId;
int replyTimeout; int replyTimeout;
QString m_reply; // should be json string QString m_reply; // should be json string
QSemaphore replyReadySemaphore; QSemaphore replyReadySemaphore;
QSemaphore *extraReplyReadySemaphore; QSemaphore *extraReplyReadySemaphore;
// Two Lua global table for callbacks and interactions // Two Lua global table for callbacks and interactions
// stored in the lua_State of the sender // stored in the lua_State of the sender
// LuaTable interactions; // LuaTable interactions;
// LuaTable callbacks; // LuaTable callbacks;
}; };
#endif // _ROUTER_H #endif // _ROUTER_H

View File

@ -3,23 +3,23 @@
ServerSocket::ServerSocket() ServerSocket::ServerSocket()
{ {
server = new QTcpServer(this); server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, connect(server, &QTcpServer::newConnection,
this, &ServerSocket::processNewConnection); this, &ServerSocket::processNewConnection);
} }
bool ServerSocket::listen(const QHostAddress &address, ushort port) bool ServerSocket::listen(const QHostAddress &address, ushort port)
{ {
return server->listen(address, port); return server->listen(address, port);
} }
void ServerSocket::processNewConnection() void ServerSocket::processNewConnection()
{ {
QTcpSocket *socket = server->nextPendingConnection(); QTcpSocket *socket = server->nextPendingConnection();
ClientSocket *connection = new ClientSocket(socket); ClientSocket *connection = new ClientSocket(socket);
connect(connection, &ClientSocket::disconnected, this, [connection](){ connect(connection, &ClientSocket::disconnected, this, [connection](){
connection->deleteLater(); connection->deleteLater();
}); });
emit new_connection(connection); emit new_connection(connection);
} }

View File

@ -4,21 +4,21 @@
class ClientSocket; class ClientSocket;
class ServerSocket : public QObject { class ServerSocket : public QObject {
Q_OBJECT Q_OBJECT
public: public:
ServerSocket(); ServerSocket();
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u); bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
signals: signals:
void new_connection(ClientSocket *socket); void new_connection(ClientSocket *socket);
private slots: private slots:
void processNewConnection(); void processNewConnection();
private: private:
QTcpServer *server; QTcpServer *server;
}; };
#endif // _SERVER_SOCKET_H #endif // _SERVER_SOCKET_H

View File

@ -1,5 +0,0 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>qml/main.qml</file>
</qresource>
</RCC>

View File

@ -5,306 +5,306 @@
Room::Room(Server* server) Room::Room(Server* server)
{ {
id = server->nextRoomId; id = server->nextRoomId;
server->nextRoomId++; server->nextRoomId++;
this->server = server; this->server = server;
setParent(server); setParent(server);
owner = nullptr; owner = nullptr;
gameStarted = false; gameStarted = false;
robot_id = -1; robot_id = -1;
timeout = 15; timeout = 15;
if (!isLobby()) { if (!isLobby()) {
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer); connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer); connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
} }
L = CreateLuaState(); L = CreateLuaState();
DoLuaScript(L, "lua/freekill.lua"); DoLuaScript(L, "lua/freekill.lua");
if (isLobby()) { if (isLobby()) {
DoLuaScript(L, "lua/server/lobby.lua"); DoLuaScript(L, "lua/server/lobby.lua");
} else { } else {
DoLuaScript(L, "lua/server/room.lua"); DoLuaScript(L, "lua/server/room.lua");
} }
initLua(); initLua();
} }
Room::~Room() Room::~Room()
{ {
// TODO // TODO
if (isRunning()) { if (isRunning()) {
callLua("RoomDeleted", ""); callLua("RoomDeleted", "");
unlockLua(__FUNCTION__); unlockLua(__FUNCTION__);
wait(); wait();
} }
lua_close(L); lua_close(L);
} }
Server *Room::getServer() const Server *Room::getServer() const
{ {
return server; return server;
} }
int Room::getId() const int Room::getId() const
{ {
return id; return id;
} }
bool Room::isLobby() const bool Room::isLobby() const
{ {
return id == 0; return id == 0;
} }
QString Room::getName() const QString Room::getName() const
{ {
return name; return name;
} }
void Room::setName(const QString &name) void Room::setName(const QString &name)
{ {
this->name = name; this->name = name;
} }
int Room::getCapacity() const int Room::getCapacity() const
{ {
return capacity; return capacity;
} }
void Room::setCapacity(int capacity) void Room::setCapacity(int capacity)
{ {
this->capacity = capacity; this->capacity = capacity;
} }
bool Room::isFull() const bool Room::isFull() const
{ {
return players.count() == capacity; return players.count() == capacity;
} }
bool Room::isAbandoned() const bool Room::isAbandoned() const
{ {
if (players.isEmpty()) if (players.isEmpty())
return true;
foreach (ServerPlayer *p, players) {
if (p->getState() == Player::Online)
return false;
}
return true; return true;
foreach (ServerPlayer *p, players) {
if (p->getState() == Player::Online)
return false;
}
return true;
} }
ServerPlayer *Room::getOwner() const ServerPlayer *Room::getOwner() const
{ {
return owner; return owner;
} }
void Room::setOwner(ServerPlayer *owner) void Room::setOwner(ServerPlayer *owner)
{ {
this->owner = owner; this->owner = owner;
QJsonArray jsonData; QJsonArray jsonData;
jsonData << owner->getId(); jsonData << owner->getId();
doBroadcastNotify(players, "RoomOwner", QJsonDocument(jsonData).toJson()); doBroadcastNotify(players, "RoomOwner", QJsonDocument(jsonData).toJson());
} }
void Room::addPlayer(ServerPlayer *player) void Room::addPlayer(ServerPlayer *player)
{ {
if (!player) return; if (!player) return;
if (isFull() || gameStarted) { if (isFull() || gameStarted) {
player->doNotify("ErrorMsg", "Room is full or already started!"); player->doNotify("ErrorMsg", "Room is full or already started!");
if (runned_players.contains(player->getId())) { if (runned_players.contains(player->getId())) {
player->doNotify("ErrorMsg", "Running away is shameful."); player->doNotify("ErrorMsg", "Running away is shameful.");
} }
return; return;
}
QJsonArray jsonData;
// First, notify other players the new player is entering
if (!isLobby()) {
jsonData << player->getId();
jsonData << player->getScreenName();
jsonData << player->getAvatar();
doBroadcastNotify(getPlayers(), "AddPlayer", QJsonDocument(jsonData).toJson());
}
players.append(player);
player->setRoom(this);
if (isLobby()) {
player->doNotify("EnterLobby", "[]");
} else {
// Second, let the player enter room and add other players
jsonData = QJsonArray();
jsonData << this->capacity;
jsonData << this->timeout;
player->doNotify("EnterRoom", QJsonDocument(jsonData).toJson());
foreach (ServerPlayer *p, getOtherPlayers(player)) {
jsonData = QJsonArray();
jsonData << p->getId();
jsonData << p->getScreenName();
jsonData << p->getAvatar();
player->doNotify("AddPlayer", QJsonDocument(jsonData).toJson());
} }
QJsonArray jsonData; if (this->owner != nullptr) {
jsonData = QJsonArray();
// First, notify other players the new player is entering jsonData << this->owner->getId();
if (!isLobby()) { player->doNotify("RoomOwner", QJsonDocument(jsonData).toJson());
jsonData << player->getId();
jsonData << player->getScreenName();
jsonData << player->getAvatar();
doBroadcastNotify(getPlayers(), "AddPlayer", QJsonDocument(jsonData).toJson());
} }
players.append(player); if (isFull() && !gameStarted)
player->setRoom(this); start();
if (isLobby()) { }
player->doNotify("EnterLobby", "[]"); emit playerAdded(player);
} else {
// Second, let the player enter room and add other players
jsonData = QJsonArray();
jsonData << this->capacity;
jsonData << this->timeout;
player->doNotify("EnterRoom", QJsonDocument(jsonData).toJson());
foreach (ServerPlayer *p, getOtherPlayers(player)) {
jsonData = QJsonArray();
jsonData << p->getId();
jsonData << p->getScreenName();
jsonData << p->getAvatar();
player->doNotify("AddPlayer", QJsonDocument(jsonData).toJson());
}
if (this->owner != nullptr) {
jsonData = QJsonArray();
jsonData << this->owner->getId();
player->doNotify("RoomOwner", QJsonDocument(jsonData).toJson());
}
if (isFull() && !gameStarted)
start();
}
emit playerAdded(player);
} }
void Room::addRobot(ServerPlayer *player) void Room::addRobot(ServerPlayer *player)
{ {
if (player != owner || isFull()) return; if (player != owner || isFull()) return;
ServerPlayer *robot = new ServerPlayer(this); ServerPlayer *robot = new ServerPlayer(this);
robot->setState(Player::Robot); robot->setState(Player::Robot);
robot->setId(robot_id); robot->setId(robot_id);
robot->setAvatar("guanyu"); robot->setAvatar("guanyu");
robot->setScreenName(QString("COMP-%1").arg(robot_id)); robot->setScreenName(QString("COMP-%1").arg(robot_id));
robot_id--; robot_id--;
addPlayer(robot); addPlayer(robot);
} }
void Room::removePlayer(ServerPlayer *player) void Room::removePlayer(ServerPlayer *player)
{ {
players.removeOne(player); players.removeOne(player);
emit playerRemoved(player); emit playerRemoved(player);
if (isLobby()) return; if (isLobby()) return;
if (gameStarted) { if (gameStarted) {
// TODO: if the player is died.. // TODO: if the player is died..
// create robot first // create robot first
ServerPlayer *robot = new ServerPlayer(this); ServerPlayer *robot = new ServerPlayer(this);
robot->setState(Player::Robot); robot->setState(Player::Robot);
robot->setId(robot_id); robot->setId(robot_id);
robot->setAvatar(player->getAvatar()); robot->setAvatar(player->getAvatar());
robot->setScreenName(QString("COMP-%1").arg(robot_id)); robot->setScreenName(QString("COMP-%1").arg(robot_id));
robot_id--; robot_id--;
players.append(robot); players.append(robot);
// tell lua & clients // tell lua & clients
QJsonArray jsonData; QJsonArray jsonData;
jsonData << player->getId(); jsonData << player->getId();
jsonData << robot->getId(); jsonData << robot->getId();
callLua("PlayerRunned", QJsonDocument(jsonData).toJson()); callLua("PlayerRunned", QJsonDocument(jsonData).toJson());
doBroadcastNotify(getPlayers(), "PlayerRunned", QJsonDocument(jsonData).toJson()); doBroadcastNotify(getPlayers(), "PlayerRunned", QJsonDocument(jsonData).toJson());
runned_players << player->getId(); runned_players << player->getId();
// FIXME: abortRequest here will result crash // FIXME: abortRequest here will result crash
// but if dont abort and room is abandoned, the main thread will wait until replyed // but if dont abort and room is abandoned, the main thread will wait until replyed
// player->abortRequest(); // player->abortRequest();
} else { } else {
QJsonArray jsonData; QJsonArray jsonData;
jsonData << player->getId(); jsonData << player->getId();
doBroadcastNotify(getPlayers(), "RemovePlayer", QJsonDocument(jsonData).toJson()); doBroadcastNotify(getPlayers(), "RemovePlayer", QJsonDocument(jsonData).toJson());
} }
if (isAbandoned()) { if (isAbandoned()) {
// FIXME: do not delete room here // FIXME: do not delete room here
// create a new thread and delete the room // create a new thread and delete the room
emit abandoned(); emit abandoned();
} else if (player == owner) { } else if (player == owner) {
setOwner(players.first()); setOwner(players.first());
} }
} }
QList<ServerPlayer *> Room::getPlayers() const QList<ServerPlayer *> Room::getPlayers() const
{ {
return players; return players;
} }
QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer* expect) const QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer* expect) const
{ {
QList<ServerPlayer *> others = getPlayers(); QList<ServerPlayer *> others = getPlayers();
others.removeOne(expect); others.removeOne(expect);
return others; return others;
} }
ServerPlayer *Room::findPlayer(int id) const ServerPlayer *Room::findPlayer(int id) const
{ {
foreach (ServerPlayer *p, players) { foreach (ServerPlayer *p, players) {
if (p->getId() == id) if (p->getId() == id)
return p; return p;
} }
return nullptr; return nullptr;
} }
int Room::getTimeout() const int Room::getTimeout() const
{ {
return timeout; return timeout;
} }
void Room::setTimeout(int timeout) void Room::setTimeout(int timeout)
{ {
this->timeout = timeout; this->timeout = timeout;
} }
bool Room::isStarted() const bool Room::isStarted() const
{ {
return gameStarted; return gameStarted;
} }
void Room::doRequest(const QList<ServerPlayer *> targets, int timeout) void Room::doRequest(const QList<ServerPlayer *> targets, int timeout)
{ {
// TODO // TODO
} }
void Room::doNotify(const QList<ServerPlayer *> targets, int timeout) void Room::doNotify(const QList<ServerPlayer *> targets, int timeout)
{ {
// TODO // TODO
} }
void Room::doBroadcastNotify(const QList<ServerPlayer *> targets, void Room::doBroadcastNotify(const QList<ServerPlayer *> targets,
const QString& command, const QString& jsonData) const QString& command, const QString& jsonData)
{ {
foreach (ServerPlayer *p, targets) { foreach (ServerPlayer *p, targets) {
p->doNotify(command, jsonData); p->doNotify(command, jsonData);
} }
} }
void Room::gameOver() void Room::gameOver()
{ {
gameStarted = false; gameStarted = false;
runned_players.clear(); runned_players.clear();
// clean not online players // clean not online players
foreach (ServerPlayer *p, players) { foreach (ServerPlayer *p, players) {
if (p->getState() != Player::Online) { if (p->getState() != Player::Online) {
p->deleteLater(); p->deleteLater();
}
} }
}
} }
void Room::lockLua(const QString &caller) void Room::lockLua(const QString &caller)
{ {
if (!gameStarted) return; if (!gameStarted) return;
lua_mutex.lock(); lua_mutex.lock();
#ifdef QT_DEBUG #ifdef QT_DEBUG
//qDebug() << caller << "=> room->L is locked."; //qDebug() << caller << "=> room->L is locked.";
#endif #endif
} }
void Room::unlockLua(const QString &caller) void Room::unlockLua(const QString &caller)
{ {
if (!gameStarted) return; if (!gameStarted) return;
lua_mutex.unlock(); lua_mutex.unlock();
#ifdef QT_DEBUG #ifdef QT_DEBUG
//qDebug() << caller << "=> room->L is unlocked."; //qDebug() << caller << "=> room->L is unlocked.";
#endif #endif
} }
void Room::run() void Room::run()
{ {
gameStarted = true; gameStarted = true;
lockLua(__FUNCTION__); lockLua(__FUNCTION__);
roomStart(); roomStart();
unlockLua(__FUNCTION__); unlockLua(__FUNCTION__);
} }

View File

@ -5,86 +5,86 @@ class Server;
class ServerPlayer; class ServerPlayer;
class Room : public QThread { class Room : public QThread {
Q_OBJECT Q_OBJECT
public: public:
explicit Room(Server *m_server); explicit Room(Server *m_server);
~Room(); ~Room();
// Property reader & setter // Property reader & setter
// ==================================={ // ==================================={
Server *getServer() const; Server *getServer() const;
int getId() const; int getId() const;
bool isLobby() const; bool isLobby() const;
QString getName() const; QString getName() const;
void setName(const QString &name); void setName(const QString &name);
int getCapacity() const; int getCapacity() const;
void setCapacity(int capacity); void setCapacity(int capacity);
bool isFull() const; bool isFull() const;
bool isAbandoned() const; bool isAbandoned() const;
ServerPlayer *getOwner() const; ServerPlayer *getOwner() const;
void setOwner(ServerPlayer *owner); void setOwner(ServerPlayer *owner);
void addPlayer(ServerPlayer *player); void addPlayer(ServerPlayer *player);
void addRobot(ServerPlayer *player); void addRobot(ServerPlayer *player);
void removePlayer(ServerPlayer *player); void removePlayer(ServerPlayer *player);
QList<ServerPlayer*> getPlayers() const; QList<ServerPlayer*> getPlayers() const;
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const; QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
ServerPlayer *findPlayer(int id) const; ServerPlayer *findPlayer(int id) const;
int getTimeout() const; int getTimeout() const;
void setTimeout(int timeout); void setTimeout(int timeout);
bool isStarted() const; bool isStarted() const;
// ====================================} // ====================================}
void doRequest(const QList<ServerPlayer *> targets, int timeout); void doRequest(const QList<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout); void doNotify(const QList<ServerPlayer *> targets, int timeout);
void doBroadcastNotify( void doBroadcastNotify(
const QList<ServerPlayer *> targets, const QList<ServerPlayer *> targets,
const QString &command, const QString &command,
const QString &jsonData const QString &jsonData
); );
void gameOver(); void gameOver();
void initLua(); void initLua();
void callLua(const QString &command, const QString &jsonData); void callLua(const QString &command, const QString &jsonData);
LuaFunction callback; LuaFunction callback;
void roomStart(); void roomStart();
LuaFunction startGame; LuaFunction startGame;
void lockLua(const QString &caller); void lockLua(const QString &caller);
void unlockLua(const QString &caller); void unlockLua(const QString &caller);
signals: signals:
void abandoned(); void abandoned();
void playerAdded(ServerPlayer *player); void playerAdded(ServerPlayer *player);
void playerRemoved(ServerPlayer *player); void playerRemoved(ServerPlayer *player);
protected: protected:
virtual void run(); virtual void run();
private: private:
Server *server; Server *server;
int id; // Lobby's id is 0 int id; // Lobby's id is 0
QString name; // “阴间大乱斗” QString name; // “阴间大乱斗”
int capacity; // by default is 5, max is 8 int capacity; // by default is 5, max is 8
bool m_abandoned; // If room is empty, delete it bool m_abandoned; // If room is empty, delete it
ServerPlayer *owner; // who created this room? ServerPlayer *owner; // who created this room?
QList<ServerPlayer *> players; QList<ServerPlayer *> players;
QList<int> runned_players; QList<int> runned_players;
int robot_id; int robot_id;
bool gameStarted; bool gameStarted;
int timeout; int timeout;
lua_State *L; lua_State *L;
QMutex lua_mutex; QMutex lua_mutex;
}; };
#endif // _ROOM_H #endif // _ROOM_H

View File

@ -9,253 +9,253 @@
Server *ServerInstance; Server *ServerInstance;
Server::Server(QObject* parent) Server::Server(QObject* parent)
: QObject(parent) : QObject(parent)
{ {
ServerInstance = this; ServerInstance = this;
db = OpenDatabase(); db = OpenDatabase();
server = new ServerSocket(); server = new ServerSocket();
server->setParent(this); server->setParent(this);
connect(server, &ServerSocket::new_connection, connect(server, &ServerSocket::new_connection,
this, &Server::processNewConnection); this, &Server::processNewConnection);
// create lobby // create lobby
nextRoomId = 0; nextRoomId = 0;
createRoom(nullptr, "Lobby", INT32_MAX); createRoom(nullptr, "Lobby", INT32_MAX);
connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList); connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList);
connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList); connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList);
} }
Server::~Server() Server::~Server()
{ {
ServerInstance = nullptr; ServerInstance = nullptr;
m_lobby->deleteLater(); m_lobby->deleteLater();
sqlite3_close(db); sqlite3_close(db);
} }
bool Server::listen(const QHostAddress& address, ushort port) bool Server::listen(const QHostAddress& address, ushort port)
{ {
return server->listen(address, port); return server->listen(address, port);
} }
void Server::createRoom(ServerPlayer* owner, const QString &name, int capacity) void Server::createRoom(ServerPlayer* owner, const QString &name, int capacity)
{ {
Room *room = new Room(this); Room *room = new Room(this);
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned); connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
if (room->isLobby()) if (room->isLobby())
m_lobby = room; m_lobby = room;
else else
rooms.insert(room->getId(), room); rooms.insert(room->getId(), room);
room->setName(name); room->setName(name);
room->setCapacity(capacity); room->setCapacity(capacity);
room->addPlayer(owner); room->addPlayer(owner);
if (!room->isLobby()) room->setOwner(owner); if (!room->isLobby()) room->setOwner(owner);
} }
Room *Server::findRoom(int id) const Room *Server::findRoom(int id) const
{ {
return rooms.value(id); return rooms.value(id);
} }
Room *Server::lobby() const Room *Server::lobby() const
{ {
return m_lobby; return m_lobby;
} }
ServerPlayer *Server::findPlayer(int id) const ServerPlayer *Server::findPlayer(int id) const
{ {
return players.value(id); return players.value(id);
} }
void Server::removePlayer(int id) { void Server::removePlayer(int id) {
players.remove(id); players.remove(id);
} }
void Server::updateRoomList() void Server::updateRoomList()
{ {
QJsonArray arr; QJsonArray arr;
foreach (Room *room, rooms) { foreach (Room *room, rooms) {
QJsonArray obj; QJsonArray obj;
obj << room->getId(); // roomId obj << room->getId(); // roomId
obj << room->getName(); // roomName obj << room->getName(); // roomName
obj << "Role"; // gameMode obj << "Role"; // gameMode
obj << room->getPlayers().count(); // playerNum obj << room->getPlayers().count(); // playerNum
obj << room->getCapacity(); // capacity obj << room->getCapacity(); // capacity
arr << obj; arr << obj;
} }
lobby()->doBroadcastNotify( lobby()->doBroadcastNotify(
lobby()->getPlayers(), lobby()->getPlayers(),
"UpdateRoomList", "UpdateRoomList",
QJsonDocument(arr).toJson() QJsonDocument(arr).toJson()
); );
} }
sqlite3 *Server::getDatabase() { sqlite3 *Server::getDatabase() {
return db; return db;
} }
void Server::processNewConnection(ClientSocket* client) void Server::processNewConnection(ClientSocket* client)
{ {
qDebug() << client->peerAddress() << "connected"; qDebug() << client->peerAddress() << "connected";
// version check, file check, ban IP, reconnect, etc // version check, file check, ban IP, reconnect, etc
connect(client, &ClientSocket::disconnected, this, [client](){ connect(client, &ClientSocket::disconnected, this, [client](){
qDebug() << client->peerAddress() << "disconnected"; qDebug() << client->peerAddress() << "disconnected";
}); });
// network delay test // network delay test
QJsonArray body; QJsonArray body;
body << -2; body << -2;
body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT);
body << "NetworkDelayTest"; body << "NetworkDelayTest";
body << "[]"; body << "[]";
client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); client->send(QJsonDocument(body).toJson(QJsonDocument::Compact));
// Note: the client should send a setup string next // Note: the client should send a setup string next
connect(client, &ClientSocket::message_got, this, &Server::processRequest); connect(client, &ClientSocket::message_got, this, &Server::processRequest);
client->timerSignup.start(30000); client->timerSignup.start(30000);
} }
void Server::processRequest(const QByteArray& msg) void Server::processRequest(const QByteArray& msg)
{ {
ClientSocket *client = qobject_cast<ClientSocket *>(sender()); ClientSocket *client = qobject_cast<ClientSocket *>(sender());
client->disconnect(this, SLOT(processRequest(const QByteArray &))); client->disconnect(this, SLOT(processRequest(const QByteArray &)));
client->timerSignup.stop(); client->timerSignup.stop();
bool valid = true; bool valid = true;
QJsonDocument doc = QJsonDocument::fromJson(msg); QJsonDocument doc = QJsonDocument::fromJson(msg);
if (doc.isNull() || !doc.isArray()) { if (doc.isNull() || !doc.isArray()) {
valid = false; valid = false;
} else { } else {
if (doc.array().size() != 4 if (doc.array().size() != 4
|| doc[0] != -2 || doc[0] != -2
|| doc[1] != (Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER) || doc[1] != (Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER)
|| doc[2] != "Setup" || doc[2] != "Setup"
) )
valid = false; valid = false;
else else
valid = (QJsonDocument::fromJson(doc[3].toString().toUtf8()).array().size() == 2); valid = (QJsonDocument::fromJson(doc[3].toString().toUtf8()).array().size() == 2);
} }
if (!valid) { if (!valid) {
qDebug() << "Invalid setup string:" << msg; qDebug() << "Invalid setup string:" << msg;
QJsonArray body; QJsonArray body;
body << -2; body << -2;
body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT);
body << "ErrorMsg"; body << "ErrorMsg";
body << "INVALID SETUP STRING"; body << "INVALID SETUP STRING";
client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); client->send(QJsonDocument(body).toJson(QJsonDocument::Compact));
client->disconnectFromHost(); client->disconnectFromHost();
return; return;
} }
QJsonArray arr = QJsonDocument::fromJson(doc[3].toString().toUtf8()).array(); QJsonArray arr = QJsonDocument::fromJson(doc[3].toString().toUtf8()).array();
handleNameAndPassword(client, arr[0].toString(), arr[1].toString()); handleNameAndPassword(client, arr[0].toString(), arr[1].toString());
} }
void Server::handleNameAndPassword(ClientSocket *client, const QString& name, const QString& password) void Server::handleNameAndPassword(ClientSocket *client, const QString& name, const QString& password)
{ {
// First check the name and password // First check the name and password
// Matches a string that does not contain special characters // Matches a string that does not contain special characters
QRegExp nameExp("[^\\0000-\\0057\\0072-\\0100\\0133-\\0140\\0173-\\0177]+"); QRegExp nameExp("[^\\0000-\\0057\\0072-\\0100\\0133-\\0140\\0173-\\0177]+");
QByteArray passwordHash = QCryptographicHash::hash(password.toLatin1(), QCryptographicHash::Sha256).toHex(); QByteArray passwordHash = QCryptographicHash::hash(password.toLatin1(), QCryptographicHash::Sha256).toHex();
bool passed = false; bool passed = false;
QString error_msg; QString error_msg;
QJsonObject result; QJsonObject result;
if (nameExp.exactMatch(name)) { if (nameExp.exactMatch(name)) {
// Then we check the database, // Then we check the database,
QString sql_find = QString("SELECT * FROM userinfo \ QString sql_find = QString("SELECT * FROM userinfo \
WHERE name='%1';").arg(name); WHERE name='%1';").arg(name);
result = SelectFromDatabase(db, sql_find); result = SelectFromDatabase(db, sql_find);
QJsonArray arr = result["password"].toArray(); QJsonArray arr = result["password"].toArray();
if (arr.isEmpty()) { if (arr.isEmpty()) {
// not present in database, register // not present in database, register
QString sql_reg = QString("INSERT INTO userinfo (name,password,\ QString sql_reg = QString("INSERT INTO userinfo (name,password,\
avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4',%5);") avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4',%5);")
.arg(name) .arg(name)
.arg(QString(passwordHash)) .arg(QString(passwordHash))
.arg("liubei") .arg("liubei")
.arg(client->peerAddress()) .arg(client->peerAddress())
.arg("FALSE"); .arg("FALSE");
ExecSQL(db, sql_reg); ExecSQL(db, sql_reg);
result = SelectFromDatabase(db, sql_find); // refresh result result = SelectFromDatabase(db, sql_find); // refresh result
passed = true; passed = true;
} else {
// check if this username already login
int id = result["id"].toArray()[0].toString().toInt();
if (!players.value(id)) {
// check if password is the same
passed = (passwordHash == arr[0].toString());
if (!passed) error_msg = "username or password error";
} else {
// TODO: reconnect here
error_msg = "others logged in with this name";
}
}
} else { } else {
error_msg = "invalid user name"; // check if this username already login
int id = result["id"].toArray()[0].toString().toInt();
if (!players.value(id)) {
// check if password is the same
passed = (passwordHash == arr[0].toString());
if (!passed) error_msg = "username or password error";
} else {
// TODO: reconnect here
error_msg = "others logged in with this name";
}
} }
} else {
error_msg = "invalid user name";
}
if (passed) { if (passed) {
// create new ServerPlayer and setup // create new ServerPlayer and setup
ServerPlayer *player = new ServerPlayer(lobby()); ServerPlayer *player = new ServerPlayer(lobby());
player->setSocket(client); player->setSocket(client);
client->disconnect(this); client->disconnect(this);
connect(player, &ServerPlayer::disconnected, this, &Server::onUserDisconnected); connect(player, &ServerPlayer::disconnected, this, &Server::onUserDisconnected);
connect(player, &Player::stateChanged, this, &Server::onUserStateChanged); connect(player, &Player::stateChanged, this, &Server::onUserStateChanged);
player->setScreenName(name); player->setScreenName(name);
player->setAvatar(result["avatar"].toArray()[0].toString()); player->setAvatar(result["avatar"].toArray()[0].toString());
player->setId(result["id"].toArray()[0].toString().toInt()); player->setId(result["id"].toArray()[0].toString().toInt());
players.insert(player->getId(), player); players.insert(player->getId(), player);
// tell the lobby player's basic property // tell the lobby player's basic property
QJsonArray arr; QJsonArray arr;
arr << player->getId(); arr << player->getId();
arr << player->getScreenName(); arr << player->getScreenName();
arr << player->getAvatar(); arr << player->getAvatar();
player->doNotify("Setup", QJsonDocument(arr).toJson()); player->doNotify("Setup", QJsonDocument(arr).toJson());
lobby()->addPlayer(player); lobby()->addPlayer(player);
} else { } else {
qDebug() << client->peerAddress() << "lost connection:" << error_msg; qDebug() << client->peerAddress() << "lost connection:" << error_msg;
QJsonArray body; QJsonArray body;
body << -2; body << -2;
body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT);
body << "ErrorMsg"; body << "ErrorMsg";
body << error_msg; body << error_msg;
client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); client->send(QJsonDocument(body).toJson(QJsonDocument::Compact));
client->disconnectFromHost(); client->disconnectFromHost();
return; return;
} }
} }
void Server::onRoomAbandoned() void Server::onRoomAbandoned()
{ {
Room *room = qobject_cast<Room *>(sender()); Room *room = qobject_cast<Room *>(sender());
room->gameOver(); room->gameOver();
rooms.remove(room->getId()); rooms.remove(room->getId());
updateRoomList(); updateRoomList();
room->deleteLater(); room->deleteLater();
} }
void Server::onUserDisconnected() void Server::onUserDisconnected()
{ {
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender()); ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
qDebug() << "Player" << player->getId() << "disconnected"; qDebug() << "Player" << player->getId() << "disconnected";
Room *room = player->getRoom(); Room *room = player->getRoom();
if (room->isStarted()) { if (room->isStarted()) {
player->setState(Player::Offline); player->setState(Player::Offline);
// TODO: add a robot // TODO: add a robot
} else { } else {
player->deleteLater(); player->deleteLater();
} }
} }
void Server::onUserStateChanged() void Server::onUserStateChanged()
{ {
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender()); ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
QJsonArray arr; QJsonArray arr;
arr << player->getId(); arr << player->getId();
arr << player->getStateString(); arr << player->getStateString();
player->getRoom()->callLua("PlayerStateChanged", QJsonDocument(arr).toJson()); player->getRoom()->callLua("PlayerStateChanged", QJsonDocument(arr).toJson());
} }

View File

@ -8,49 +8,49 @@ class ServerPlayer;
#include "room.h" #include "room.h"
class Server : public QObject { class Server : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit Server(QObject *parent = nullptr); explicit Server(QObject *parent = nullptr);
~Server(); ~Server();
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u); bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
void createRoom(ServerPlayer *owner, const QString &name, int capacity); void createRoom(ServerPlayer *owner, const QString &name, int capacity);
Room *findRoom(int id) const; Room *findRoom(int id) const;
Room *lobby() const; Room *lobby() const;
ServerPlayer *findPlayer(int id) const; ServerPlayer *findPlayer(int id) const;
void removePlayer(int id); void removePlayer(int id);
void updateRoomList(); void updateRoomList();
sqlite3 *getDatabase(); sqlite3 *getDatabase();
signals: signals:
void roomCreated(Room *room); void roomCreated(Room *room);
void playerAdded(ServerPlayer *player); void playerAdded(ServerPlayer *player);
void playerRemoved(ServerPlayer *player); void playerRemoved(ServerPlayer *player);
public slots: public slots:
void processNewConnection(ClientSocket *client); void processNewConnection(ClientSocket *client);
void processRequest(const QByteArray &msg); void processRequest(const QByteArray &msg);
void onRoomAbandoned(); void onRoomAbandoned();
void onUserDisconnected(); void onUserDisconnected();
void onUserStateChanged(); void onUserStateChanged();
private: private:
ServerSocket *server; ServerSocket *server;
Room *m_lobby; Room *m_lobby;
QMap<int, Room *> rooms; QMap<int, Room *> rooms;
int nextRoomId; int nextRoomId;
friend Room::Room(Server *server); friend Room::Room(Server *server);
QHash<int, ServerPlayer *> players; QHash<int, ServerPlayer *> players;
sqlite3 *db; sqlite3 *db;
void handleNameAndPassword(ClientSocket *client, const QString &name, const QString &password); void handleNameAndPassword(ClientSocket *client, const QString &name, const QString &password);
}; };
extern Server *ServerInstance; extern Server *ServerInstance;

View File

@ -6,111 +6,111 @@
ServerPlayer::ServerPlayer(Room *room) ServerPlayer::ServerPlayer(Room *room)
{ {
socket = nullptr; socket = nullptr;
router = new Router(this, socket, Router::TYPE_SERVER); router = new Router(this, socket, Router::TYPE_SERVER);
setState(Player::Online); setState(Player::Online);
this->room = room; this->room = room;
server = room->getServer(); server = room->getServer();
} }
ServerPlayer::~ServerPlayer() ServerPlayer::~ServerPlayer()
{ {
// clean up, quit room and server // clean up, quit room and server
room->removePlayer(this);
if (room != nullptr) {
// now we are in lobby, so quit lobby
room->removePlayer(this); room->removePlayer(this);
if (room != nullptr) { }
// now we are in lobby, so quit lobby server->removePlayer(getId());
room->removePlayer(this); router->deleteLater();
}
server->removePlayer(getId());
router->deleteLater();
} }
void ServerPlayer::setSocket(ClientSocket *socket) void ServerPlayer::setSocket(ClientSocket *socket)
{ {
if (this->socket != nullptr) { if (this->socket != nullptr) {
this->socket->disconnect(this); this->socket->disconnect(this);
disconnect(this->socket); disconnect(this->socket);
this->socket->deleteLater(); this->socket->deleteLater();
} }
this->socket = nullptr; this->socket = nullptr;
if (socket != nullptr) { if (socket != nullptr) {
connect(socket, &ClientSocket::disconnected, this, &ServerPlayer::disconnected); connect(socket, &ClientSocket::disconnected, this, &ServerPlayer::disconnected);
this->socket = socket; this->socket = socket;
} }
router->setSocket(socket); router->setSocket(socket);
} }
Server *ServerPlayer::getServer() const Server *ServerPlayer::getServer() const
{ {
return server; return server;
} }
Room *ServerPlayer::getRoom() const Room *ServerPlayer::getRoom() const
{ {
return room; return room;
} }
void ServerPlayer::setRoom(Room* room) void ServerPlayer::setRoom(Room* room)
{ {
this->room = room; this->room = room;
} }
void ServerPlayer::speak(const QString& message) void ServerPlayer::speak(const QString& message)
{ {
; ;
} }
void ServerPlayer::doRequest(const QString& command, const QString& jsonData, int timeout) void ServerPlayer::doRequest(const QString& command, const QString& jsonData, int timeout)
{ {
if (getState() != Player::Online) return; if (getState() != Player::Online) return;
int type = Router::TYPE_REQUEST | Router::SRC_SERVER | Router::DEST_CLIENT; int type = Router::TYPE_REQUEST | Router::SRC_SERVER | Router::DEST_CLIENT;
router->request(type, command, jsonData, timeout); router->request(type, command, jsonData, timeout);
} }
void ServerPlayer::abortRequest() void ServerPlayer::abortRequest()
{ {
router->abortRequest(); router->abortRequest();
} }
QString ServerPlayer::waitForReply() QString ServerPlayer::waitForReply()
{ {
room->unlockLua(__FUNCTION__); room->unlockLua(__FUNCTION__);
QString ret; QString ret;
if (getState() != Player::Online) { if (getState() != Player::Online) {
QThread::sleep(1); QThread::sleep(1);
ret = ""; ret = "";
} else { } else {
ret = router->waitForReply(); ret = router->waitForReply();
} }
room->lockLua(__FUNCTION__); room->lockLua(__FUNCTION__);
return ret; return ret;
} }
QString ServerPlayer::waitForReply(int timeout) QString ServerPlayer::waitForReply(int timeout)
{ {
room->unlockLua(__FUNCTION__); room->unlockLua(__FUNCTION__);
QString ret; QString ret;
if (getState() != Player::Online) { if (getState() != Player::Online) {
QThread::sleep(1); QThread::sleep(1);
ret = ""; ret = "";
} else { } else {
ret = router->waitForReply(timeout); ret = router->waitForReply(timeout);
} }
room->lockLua(__FUNCTION__); room->lockLua(__FUNCTION__);
return ret; return ret;
} }
void ServerPlayer::doNotify(const QString& command, const QString& jsonData) void ServerPlayer::doNotify(const QString& command, const QString& jsonData)
{ {
if (getState() != Player::Online) return; if (getState() != Player::Online) return;
int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT; int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT;
router->notify(type, command, jsonData); router->notify(type, command, jsonData);
} }
void ServerPlayer::prepareForRequest(const QString& command, const QString& data) void ServerPlayer::prepareForRequest(const QString& command, const QString& data)
{ {
requestCommand = command; requestCommand = command;
requestData = data; requestData = data;
} }

View File

@ -9,40 +9,40 @@ class Server;
class Room; class Room;
class ServerPlayer : public Player { class ServerPlayer : public Player {
Q_OBJECT Q_OBJECT
public: public:
explicit ServerPlayer(Room *room); explicit ServerPlayer(Room *room);
~ServerPlayer(); ~ServerPlayer();
void setSocket(ClientSocket *socket); void setSocket(ClientSocket *socket);
Server *getServer() const; Server *getServer() const;
Room *getRoom() const; Room *getRoom() const;
void setRoom(Room *room); void setRoom(Room *room);
void speak(const QString &message); void speak(const QString &message);
void doRequest(const QString &command, void doRequest(const QString &command,
const QString &jsonData, int timeout = -1); const QString &jsonData, int timeout = -1);
void abortRequest(); void abortRequest();
QString waitForReply(int timeout); QString waitForReply(int timeout);
QString waitForReply(); QString waitForReply();
void doNotify(const QString &command, const QString &jsonData); void doNotify(const QString &command, const QString &jsonData);
void prepareForRequest(const QString &command, void prepareForRequest(const QString &command,
const QString &data); const QString &data);
signals: signals:
void disconnected(); void disconnected();
private: private:
ClientSocket *socket; // socket for communicating with client ClientSocket *socket; // socket for communicating with client
Router *router; Router *router;
Server *server; Server *server;
Room *room; // Room that player is in, maybe lobby Room *room; // Room that player is in, maybe lobby
QString requestCommand; QString requestCommand;
QString requestData; QString requestData;
}; };
#endif // _SERVERPLAYER_H #endif // _SERVERPLAYER_H

View File

@ -2,13 +2,13 @@
%nodefaultdtor QmlBackend; %nodefaultdtor QmlBackend;
class QmlBackend : public QObject { class QmlBackend : public QObject {
public: public:
void emitNotifyUI(const QString &command, const QString &json_data); void emitNotifyUI(const QString &command, const QString &json_data);
static void cd(const QString &path); static void cd(const QString &path);
static QStringList ls(const QString &dir); static QStringList ls(const QString &dir);
static QString pwd(); static QString pwd();
static bool exists(const QString &file); static bool exists(const QString &file);
static bool isDir(const QString &file); static bool isDir(const QString &file);
}; };
extern QmlBackend *Backend; extern QmlBackend *Backend;
@ -17,13 +17,13 @@ extern QmlBackend *Backend;
%nodefaultdtor Client; %nodefaultdtor Client;
class Client : public QObject { class Client : public QObject {
public: public:
void replyToServer(const QString &command, const QString &json_data); void replyToServer(const QString &command, const QString &json_data);
void notifyServer(const QString &command, const QString &json_data); void notifyServer(const QString &command, const QString &json_data);
LuaFunction callback; LuaFunction callback;
ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar);
void removePlayer(int id); void removePlayer(int id);
}; };
extern Client *ClientInstance; extern Client *ClientInstance;
@ -31,24 +31,24 @@ extern Client *ClientInstance;
%{ %{
void Client::callLua(const QString& command, const QString& json_data) void Client::callLua(const QString& command, const QString& json_data)
{ {
Q_ASSERT(callback); Q_ASSERT(callback);
lua_getglobal(L, "debug"); lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback"); lua_getfield(L, -1, "traceback");
lua_replace(L, -2); lua_replace(L, -2);
lua_rawgeti(L, LUA_REGISTRYINDEX, callback); lua_rawgeti(L, LUA_REGISTRYINDEX, callback);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Client, 0); SWIG_NewPointerObj(L, this, SWIGTYPE_p_Client, 0);
lua_pushstring(L, command.toUtf8()); lua_pushstring(L, command.toUtf8());
lua_pushstring(L, json_data.toUtf8()); lua_pushstring(L, json_data.toUtf8());
int error = lua_pcall(L, 3, 0, -5); int error = lua_pcall(L, 3, 0, -5);
if (error) { if (error) {
const char *error_msg = lua_tostring(L, -1); const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg; qDebug() << error_msg;
lua_pop(L, 2); lua_pop(L, 2);
} }
lua_pop(L, 1); lua_pop(L, 1);
} }
%} %}

View File

@ -7,10 +7,10 @@
%typemap(in) LuaFunction %typemap(in) LuaFunction
%{ %{
if (lua_isfunction(L, $input)) { if (lua_isfunction(L, $input)) {
lua_pushvalue(L, $input); lua_pushvalue(L, $input);
$1 = luaL_ref(L, LUA_REGISTRYINDEX); $1 = luaL_ref(L, LUA_REGISTRYINDEX);
} else { } else {
$1 = 0; $1 = 0;
} }
%} %}
@ -35,8 +35,8 @@ SWIG_arg ++;
%typemap(in, checkfn = "lua_isstring") QString const & %typemap(in, checkfn = "lua_isstring") QString const &
%{ %{
$1_str = QString::fromUtf8(lua_tostring(L, $input)); $1_str = QString::fromUtf8(lua_tostring(L, $input));
$1 = &$1_str; $1 = &$1_str;
%} %}
%typemap(out) QString const & %typemap(out) QString const &
@ -48,10 +48,10 @@ SWIG_arg ++;
%typemap(in, checkfn = "lua_istable") QStringList %typemap(in, checkfn = "lua_istable") QStringList
%{ %{
for (size_t i = 0; i < lua_rawlen(L, $input); ++i) { for (size_t i = 0; i < lua_rawlen(L, $input); ++i) {
lua_rawgeti(L, $input, i + 1); lua_rawgeti(L, $input, i + 1);
const char *elem = luaL_checkstring(L, -1); const char *elem = luaL_checkstring(L, -1);
$1 << QString::fromUtf8(QByteArray(elem)); $1 << QString::fromUtf8(QByteArray(elem));
lua_pop(L, 1); lua_pop(L, 1);
} }
%} %}
@ -60,9 +60,9 @@ for (size_t i = 0; i < lua_rawlen(L, $input); ++i) {
lua_createtable(L, $1.length(), 0); lua_createtable(L, $1.length(), 0);
for (int i = 0; i < $1.length(); i++) { for (int i = 0; i < $1.length(); i++) {
QString str = $1.at(i); QString str = $1.at(i);
lua_pushstring(L, str.toUtf8().constData()); lua_pushstring(L, str.toUtf8().constData());
lua_rawseti(L, -2, i + 1); lua_rawseti(L, -2, i + 1);
} }
SWIG_arg++; SWIG_arg++;
@ -70,7 +70,7 @@ SWIG_arg++;
%typemap(typecheck) QStringList %typemap(typecheck) QStringList
%{ %{
$1 = lua_istable(L, $input) ? 1 : 0; $1 = lua_istable(L, $input) ? 1 : 0;
%} %}

View File

@ -2,29 +2,29 @@
%nodefaultdtor Player; %nodefaultdtor Player;
class Player : public QObject { class Player : public QObject {
public: public:
enum State{ enum State{
Invalid, Invalid,
Online, Online,
Trust, Trust,
Offline Offline
}; };
int getId() const; int getId() const;
void setId(int id); void setId(int id);
QString getScreenName() const; QString getScreenName() const;
void setScreenName(const QString &name); void setScreenName(const QString &name);
QString getAvatar() const; QString getAvatar() const;
void setAvatar(const QString &avatar); void setAvatar(const QString &avatar);
State getState() const; State getState() const;
QString getStateString() const; QString getStateString() const;
void setState(State state); void setState(State state);
void setStateString(const QString &state); void setStateString(const QString &state);
bool isReady() const; bool isReady() const;
void setReady(bool ready); void setReady(bool ready);
}; };
%nodefaultctor ClientPlayer; %nodefaultctor ClientPlayer;
@ -39,17 +39,17 @@ extern ClientPlayer *Self;
%nodefaultdtor ServerPlayer; %nodefaultdtor ServerPlayer;
class ServerPlayer : public Player { class ServerPlayer : public Player {
public: public:
Server *getServer() const; Server *getServer() const;
Room *getRoom() const; Room *getRoom() const;
void setRoom(Room *room); void setRoom(Room *room);
void speak(const QString &message); void speak(const QString &message);
void doRequest(const QString &command, void doRequest(const QString &command,
const QString &json_data, int timeout); const QString &json_data, int timeout);
QString waitForReply(); QString waitForReply();
QString waitForReply(int timeout); QString waitForReply(int timeout);
void doNotify(const QString &command, const QString &json_data); void doNotify(const QString &command, const QString &json_data);
void prepareForRequest(const QString &command, const QString &data); void prepareForRequest(const QString &command, const QString &data);
}; };

View File

@ -5,29 +5,29 @@ class QThread {};
template <class T> template <class T>
class QList { class QList {
public: public:
QList(); QList();
~QList(); ~QList();
int length() const; int length() const;
void append(const T &elem); void append(const T &elem);
void prepend(const T &elem); void prepend(const T &elem);
bool isEmpty() const; bool isEmpty() const;
bool contains(const T &value) const; bool contains(const T &value) const;
T first() const; T first() const;
T last() const; T last() const;
void removeAt(int i); void removeAt(int i);
int removeAll(const T &value); int removeAll(const T &value);
bool removeOne(const T &value); bool removeOne(const T &value);
QList<T> mid(int pos, int length = -1) const; QList<T> mid(int pos, int length = -1) const;
int indexOf(const T &value, int from = 0); int indexOf(const T &value, int from = 0);
void replace(int i, const T &value); void replace(int i, const T &value);
void swapItemsAt(int i, int j); void swapItemsAt(int i, int j);
}; };
%extend QList { %extend QList {
T at(int i) const T at(int i) const
{ {
return $self->value(i); return $self->value(i);
} }
} }
%template(SPlayerList) QList<ServerPlayer *>; %template(SPlayerList) QList<ServerPlayer *>;
@ -39,10 +39,10 @@ public:
%{ %{
#include <sys/time.h> #include <sys/time.h>
static int GetMicroSecond(lua_State *L) { static int GetMicroSecond(lua_State *L) {
struct timeval tv; struct timeval tv;
gettimeofday(&tv, nullptr); gettimeofday(&tv, nullptr);
long microsecond = tv.tv_sec * 1000000 + tv.tv_usec; long microsecond = tv.tv_sec * 1000000 + tv.tv_usec;
lua_pushnumber(L, microsecond); lua_pushnumber(L, microsecond);
return 1; return 1;
} }
%} %}

View File

@ -2,12 +2,12 @@
%nodefaultdtor Server; %nodefaultdtor Server;
class Server : public QObject { class Server : public QObject {
public: public:
Room *lobby() const; Room *lobby() const;
void createRoom(ServerPlayer *owner, const QString &name, int capacity); void createRoom(ServerPlayer *owner, const QString &name, int capacity);
Room *findRoom(int id) const; Room *findRoom(int id) const;
ServerPlayer *findPlayer(int id) const; ServerPlayer *findPlayer(int id) const;
sqlite3 *getDatabase(); sqlite3 *getDatabase();
}; };
extern Server *ServerInstance; extern Server *ServerInstance;
@ -16,104 +16,104 @@ extern Server *ServerInstance;
%nodefaultdtor Room; %nodefaultdtor Room;
class Room : public QThread { class Room : public QThread {
public: public:
// Property reader & setter // Property reader & setter
// ==================================={ // ==================================={
Server *getServer() const; Server *getServer() const;
int getId() const; int getId() const;
bool isLobby() const; bool isLobby() const;
QString getName() const; QString getName() const;
void setName(const QString &name); void setName(const QString &name);
int getCapacity() const; int getCapacity() const;
void setCapacity(int capacity); void setCapacity(int capacity);
bool isFull() const; bool isFull() const;
bool isAbandoned() const; bool isAbandoned() const;
ServerPlayer *getOwner() const; ServerPlayer *getOwner() const;
void setOwner(ServerPlayer *owner); void setOwner(ServerPlayer *owner);
void addPlayer(ServerPlayer *player); void addPlayer(ServerPlayer *player);
void addRobot(ServerPlayer *player); void addRobot(ServerPlayer *player);
void removePlayer(ServerPlayer *player); void removePlayer(ServerPlayer *player);
QList<ServerPlayer *> getPlayers() const; QList<ServerPlayer *> getPlayers() const;
ServerPlayer *findPlayer(int id) const; ServerPlayer *findPlayer(int id) const;
int getTimeout() const; int getTimeout() const;
bool isStarted() const; bool isStarted() const;
// ====================================} // ====================================}
void doRequest(const QList<ServerPlayer *> targets, int timeout); void doRequest(const QList<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout); void doNotify(const QList<ServerPlayer *> targets, int timeout);
void doBroadcastNotify( void doBroadcastNotify(
const QList<ServerPlayer *> targets, const QList<ServerPlayer *> targets,
const QString &command, const QString &command,
const QString &jsonData const QString &jsonData
); );
void gameOver(); void gameOver();
LuaFunction callback; LuaFunction callback;
LuaFunction startGame; LuaFunction startGame;
}; };
%{ %{
void Room::initLua() void Room::initLua()
{ {
lua_getglobal(L, "debug"); lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback"); lua_getfield(L, -1, "traceback");
lua_replace(L, -2); lua_replace(L, -2);
lua_getglobal(L, "CreateRoom"); lua_getglobal(L, "CreateRoom");
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
int error = lua_pcall(L, 1, 0, -2); int error = lua_pcall(L, 1, 0, -2);
lua_pop(L, 1); lua_pop(L, 1);
if (error) { if (error) {
const char *error_msg = lua_tostring(L, -1); const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg; qDebug() << error_msg;
} }
} }
void Room::callLua(const QString& command, const QString& json_data) void Room::callLua(const QString& command, const QString& json_data)
{ {
Q_ASSERT(callback); Q_ASSERT(callback);
lua_getglobal(L, "debug"); lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback"); lua_getfield(L, -1, "traceback");
lua_replace(L, -2); lua_replace(L, -2);
lua_rawgeti(L, LUA_REGISTRYINDEX, callback); lua_rawgeti(L, LUA_REGISTRYINDEX, callback);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
lua_pushstring(L, command.toUtf8()); lua_pushstring(L, command.toUtf8());
lua_pushstring(L, json_data.toUtf8()); lua_pushstring(L, json_data.toUtf8());
int error = lua_pcall(L, 3, 0, -5); int error = lua_pcall(L, 3, 0, -5);
if (error) { if (error) {
const char *error_msg = lua_tostring(L, -1); const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg; qDebug() << error_msg;
lua_pop(L, 2); lua_pop(L, 2);
} }
lua_pop(L, 1); lua_pop(L, 1);
} }
void Room::roomStart() { void Room::roomStart() {
Q_ASSERT(startGame); Q_ASSERT(startGame);
lua_getglobal(L, "debug"); lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback"); lua_getfield(L, -1, "traceback");
lua_replace(L, -2); lua_replace(L, -2);
lua_rawgeti(L, LUA_REGISTRYINDEX, startGame); lua_rawgeti(L, LUA_REGISTRYINDEX, startGame);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
int error = lua_pcall(L, 1, 0, -3); int error = lua_pcall(L, 1, 0, -3);
if (error) { if (error) {
const char *error_msg = lua_tostring(L, -1); const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg; qDebug() << error_msg;
lua_pop(L, 2); lua_pop(L, 2);
} }
lua_pop(L, 1); lua_pop(L, 1);
} }
%} %}

Some files were not shown because too many files have changed in this diff Show More