diff --git a/CMakeLists.txt b/CMakeLists.txt index a856a565..9fc0e38e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,10 @@ cmake_minimum_required(VERSION 3.16) project(FreeKill VERSION 0.0.1) find_package(Qt5 REQUIRED COMPONENTS - Gui - Qml - Network - Multimedia + Gui + Qml + Network + Multimedia ) find_package(Lua) diff --git a/image/card/equipIcon/double_sword.png b/image/card/equipIcon/double_swords.png similarity index 100% rename from image/card/equipIcon/double_sword.png rename to image/card/equipIcon/double_swords.png diff --git a/image/card/equipIcon/renwang_shield.png b/image/card/equipIcon/nioh_shield.png similarity index 100% rename from image/card/equipIcon/renwang_shield.png rename to image/card/equipIcon/nioh_shield.png diff --git a/lib/win/lua54.dll b/lib/win/lua54.dll old mode 100644 new mode 100755 index 77ff13fd..fe6e1baf Binary files a/lib/win/lua54.dll and b/lib/win/lua54.dll differ diff --git a/lua/client/client.lua b/lua/client/client.lua index 19f6dfe5..8d0dfea4 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -5,140 +5,140 @@ Client = class('Client') -- load client classes ClientPlayer = require "client.clientplayer" -dofile "lua/client/client_util.lua" fk.client_callback = {} function Client:initialize() - self.client = fk.ClientInstance - self.notifyUI = function(self, command, jsonData) - fk.Backend:emitNotifyUI(command, jsonData) - end - self.client.callback = function(_self, command, jsonData) - local cb = fk.client_callback[command] - if (type(cb) == "function") then - cb(jsonData) - else - self:notifyUI(command, jsonData); - end + self.client = fk.ClientInstance + self.notifyUI = function(self, command, jsonData) + fk.Backend:emitNotifyUI(command, jsonData) + end + self.client.callback = function(_self, command, jsonData) + local cb = fk.client_callback[command] + if (type(cb) == "function") then + cb(jsonData) + else + self:notifyUI(command, jsonData); end + end - self.players = {} -- ClientPlayer[] + self.players = {} -- ClientPlayer[] end ---@param id integer ---@return ClientPlayer function Client:findPlayer(id) - for _, p in ipairs(self.players) do - if p.player:getId() == id then return p end - end - return nil + for _, p in ipairs(self.players) do + if p.player:getId() == id then return p end + end + return nil end fk.client_callback["Setup"] = function(jsonData) - -- jsonData: [ int id, string screenName, string avatar ] - local data = json.decode(jsonData) - local id, name, avatar = data[1], data[2], data[3] - local self = fk.Self - self:setId(id) - self:setScreenName(name) - self:setAvatar(avatar) - Self = ClientPlayer:new(fk.Self) - table.insert(ClientInstance.players, Self) + -- jsonData: [ int id, string screenName, string avatar ] + local data = json.decode(jsonData) + local id, name, avatar = data[1], data[2], data[3] + local self = fk.Self + self:setId(id) + self:setScreenName(name) + self:setAvatar(avatar) + Self = ClientPlayer:new(fk.Self) + table.insert(ClientInstance.players, Self) end fk.client_callback["AddPlayer"] = function(jsonData) - -- jsonData: [ int id, string screenName, string avatar ] - -- when other player enter the room, we create clientplayer(C and lua) for them - local data = json.decode(jsonData) - local id, name, avatar = data[1], data[2], data[3] - local player = fk.ClientInstance:addPlayer(id, name, avatar) - table.insert(ClientInstance.players, ClientPlayer:new(player)) - ClientInstance:notifyUI("AddPlayer", jsonData) + -- jsonData: [ int id, string screenName, string avatar ] + -- when other player enter the room, we create clientplayer(C and lua) for them + local data = json.decode(jsonData) + local id, name, avatar = data[1], data[2], data[3] + local player = fk.ClientInstance:addPlayer(id, name, avatar) + table.insert(ClientInstance.players, ClientPlayer:new(player)) + ClientInstance:notifyUI("AddPlayer", jsonData) end fk.client_callback["RemovePlayer"] = function(jsonData) - -- jsonData: [ int id ] - local data = json.decode(jsonData) - local id = data[1] - for _, p in ipairs(ClientInstance.players) do - if p.player:getId() == id then - table.removeOne(ClientInstance.players, p) - break - end + -- jsonData: [ int id ] + local data = json.decode(jsonData) + local id = data[1] + for _, p in ipairs(ClientInstance.players) do + if p.player:getId() == id then + table.removeOne(ClientInstance.players, p) + break end - fk.ClientInstance:removePlayer(id) - ClientInstance:notifyUI("RemovePlayer", jsonData) + end + fk.ClientInstance:removePlayer(id) + ClientInstance:notifyUI("RemovePlayer", jsonData) end fk.client_callback["ArrangeSeats"] = function(jsonData) - local data = json.decode(jsonData) - local n = #ClientInstance.players - local players = {} + local data = json.decode(jsonData) + local n = #ClientInstance.players + local players = {} - for i = 1, n do - table.insert(players, ClientInstance:findPlayer(data[i])) - end - ClientInstance.players = players + for i = 1, n do + table.insert(players, ClientInstance:findPlayer(data[i])) + end + ClientInstance.players = players - ClientInstance:notifyUI("ArrangeSeats", jsonData) + ClientInstance:notifyUI("ArrangeSeats", jsonData) end fk.client_callback["PropertyUpdate"] = function(jsonData) - -- jsonData: [ int id, string property_name, value ] - local data = json.decode(jsonData) - local id, name, value = data[1], data[2], data[3] - ClientInstance:findPlayer(id)[name] = value - ClientInstance:notifyUI("PropertyUpdate", jsonData) + -- jsonData: [ int id, string property_name, value ] + local data = json.decode(jsonData) + local id, name, value = data[1], data[2], data[3] + ClientInstance:findPlayer(id)[name] = value + ClientInstance:notifyUI("PropertyUpdate", jsonData) end --- separated moves to many moves(one card per move) ---@param moves CardsMoveStruct[] local function separateMoves(moves) - local ret = {} ---@type CardsMoveInfo[] - for _, move in ipairs(moves) do - for _, info in ipairs(move.moveInfo) do - table.insert(ret, { - ids = {info.cardId}, - from = move.from, - to = move.to, - toArea = move.toArea, - fromArea = info.fromArea, - }) - end + local ret = {} ---@type CardsMoveInfo[] + for _, move in ipairs(moves) do + for _, info in ipairs(move.moveInfo) do + table.insert(ret, { + ids = {info.cardId}, + from = move.from, + to = move.to, + toArea = move.toArea, + fromArea = info.fromArea, + }) end - return ret + end + return ret end --- merge separated moves (one fromArea per move) local function mergeMoves(moves) - local ret = {} - local temp = {} - for _, move in ipairs(moves) do - if temp[move.fromArea] == nil then - temp[move.fromArea] = { - ids = {}, - from = move.from, - to = move.to, - fromArea = move.fromArea, - toArea = move.toArea - } - end - table.insert(temp[move.fromArea].ids, move.ids[1]) + local ret = {} + local temp = {} + for _, move in ipairs(moves) do + if temp[move.fromArea] == nil then + temp[move.fromArea] = { + ids = {}, + from = move.from, + to = move.to, + fromArea = move.fromArea, + toArea = move.toArea + } end - for _, v in pairs(temp) do - table.insert(ret, v) - end - return ret + table.insert(temp[move.fromArea].ids, move.ids[1]) + end + for _, v in pairs(temp) do + table.insert(ret, v) + end + return ret end fk.client_callback["MoveCards"] = function(jsonData) - -- jsonData: CardsMoveStruct[] - local raw_moves = json.decode(jsonData) - local separated = separateMoves(raw_moves) - local merged = mergeMoves(separated) - ClientInstance:notifyUI("MoveCards", json.encode(merged)) + -- jsonData: CardsMoveStruct[] + local raw_moves = json.decode(jsonData) + local separated = separateMoves(raw_moves) + local merged = mergeMoves(separated) + ClientInstance:notifyUI("MoveCards", json.encode(merged)) end -- Create ClientInstance (used by Lua) ClientInstance = Client:new() +dofile "lua/client/client_util.lua" diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 182332d9..6e607adb 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -1,64 +1,145 @@ +-- All functions in this file are used by Qml + function Translate(src) - return Fk.translations[src] + return Fk.translations[src] end function GetGeneralData(name) - local general = Fk.generals[name] - if general == nil then general = Fk.generals["diaochan"] end - return json.encode { - kingdom = general.kingdom, - hp = general.hp, - maxHp = general.maxHp - } + local general = Fk.generals[name] + if general == nil then general = Fk.generals["diaochan"] end + return json.encode { + kingdom = general.kingdom, + hp = general.hp, + maxHp = general.maxHp + } 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) - local card = Fk.cards[id] - if card == nil then return json.encode{ - cid = id, - known = false - } end - return json.encode{ - cid = id, - name = card.name, - number = card.number, - suit = card:getSuitString(), - color = card.color, - } + local card = Fk.cards[id] + if card == nil then return json.encode{ + cid = id, + known = false + } end + local ret = { + cid = id, + name = card.name, + number = card.number, + suit = card:getSuitString(), + color = card.color, + subtype = cardSubtypeStrings[card.sub_type] + } + return json.encode(ret) end function GetAllGeneralPack() - local ret = {} - for _, name in ipairs(Fk.package_names) do - if Fk.packages[name].type == Package.GeneralPack then - table.insert(ret, name) - end + local ret = {} + for _, name in ipairs(Fk.package_names) do + if Fk.packages[name].type == Package.GeneralPack then + table.insert(ret, name) end - return json.encode(ret) + end + return json.encode(ret) end function GetGenerals(pack_name) - local ret = {} - for _, g in ipairs(Fk.packages[pack_name].generals) do - table.insert(ret, g.name) - end - return json.encode(ret) + local ret = {} + for _, g in ipairs(Fk.packages[pack_name].generals) do + table.insert(ret, g.name) + end + return json.encode(ret) end function GetAllCardPack() - local ret = {} - for _, name in ipairs(Fk.package_names) do - if Fk.packages[name].type == Package.CardPack then - table.insert(ret, name) - end + local ret = {} + for _, name in ipairs(Fk.package_names) do + if Fk.packages[name].type == Package.CardPack then + table.insert(ret, name) end - return json.encode(ret) + end + return json.encode(ret) end function GetCards(pack_name) - local ret = {} - for _, c in ipairs(Fk.packages[pack_name].cards) do - table.insert(ret, c.id) - end - return json.encode(ret) + local ret = {} + for _, c in ipairs(Fk.packages[pack_name].cards) do + table.insert(ret, c.id) + end + 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 diff --git a/lua/client/clientplayer.lua b/lua/client/clientplayer.lua index e2c575d6..b611ae43 100644 --- a/lua/client/clientplayer.lua +++ b/lua/client/clientplayer.lua @@ -5,9 +5,9 @@ local ClientPlayer = Player:subclass("ClientPlayer") function ClientPlayer:initialize(cp) - self.player = cp - self.handcardNum = 0 - self.known_cards = {} + self.player = cp + self.handcardNum = 0 + self.known_cards = {} end return ClientPlayer diff --git a/lua/core/card.lua b/lua/core/card.lua index c288c61b..bc1261ee 100644 --- a/lua/core/card.lua +++ b/lua/core/card.lua @@ -3,6 +3,7 @@ ---@field name string ---@field suit Suit ---@field number integer +---@field trueName string ---@field color Color ---@field id integer ---@field type CardType @@ -26,10 +27,9 @@ Card.NoColor = 3 ---@alias CardType integer -Card.TypeSkill = 1 -Card.TypeBasic = 2 -Card.TypeTrick = 3 -Card.TypeEquip = 4 +Card.TypeBasic = 1 +Card.TypeTrick = 2 +Card.TypeEquip = 3 ---@alias CardSubtype integer @@ -54,39 +54,41 @@ Card.DiscardPile = 7 Card.Void = 8 function Card:initialize(name, suit, number, color) - self.name = name - self.suit = suit or Card.NoSuit - self.number = number or 0 + self.name = name + self.suit = suit or Card.NoSuit + self.number = number or 0 + self.trueName = name - if suit == Card.Spade or suit == Card.Club then - self.color = Card.Black - elseif suit == Card.Heart or suit == Card.Diamond then - self.color = Card.Red - elseif color ~= nil then - self.color = color - else - self.color = Card.NoColor - end + if suit == Card.Spade or suit == Card.Club then + self.color = Card.Black + elseif suit == Card.Heart or suit == Card.Diamond then + self.color = Card.Red + elseif color ~= nil then + self.color = color + else + self.color = Card.NoColor + end - self.package = nil - self.id = 0 - self.type = 0 - self.sub_type = Card.SubTypeNone + self.package = nil + self.id = 0 + self.type = 0 + self.sub_type = Card.SubTypeNone + self.skill = nil end function Card:getSuitString() - local suit = self.suit - if suit == Card.Spade then - return "spade" - elseif suit == Card.Heart then - return "heart" - elseif suit == Card.Club then - return "club" - elseif suit == Card.Diamond then - return "diamond" - else - return "unknown" - end + local suit = self.suit + if suit == Card.Spade then + return "spade" + elseif suit == Card.Heart then + return "heart" + elseif suit == Card.Club then + return "club" + elseif suit == Card.Diamond then + return "diamond" + else + return "unknown" + end end return Card diff --git a/lua/core/card_type/basic.lua b/lua/core/card_type/basic.lua index 06f0ca48..330811cc 100644 --- a/lua/core/card_type/basic.lua +++ b/lua/core/card_type/basic.lua @@ -2,16 +2,17 @@ local BasicCard = Card:subclass("BasicCard") function BasicCard:initialize(name, suit, number) - Card.initialize(self, name, suit, number) - self.type = Card.TypeBasic + Card.initialize(self, name, suit, number) + self.type = Card.TypeBasic end ---@param suit Suit ---@param number integer ---@return BasicCard function BasicCard:clone(suit, number) - local newCard = BasicCard:new(self.name, suit, number) - return newCard + local newCard = BasicCard:new(self.name, suit, number) + newCard.skill = self.skill + return newCard end return BasicCard diff --git a/lua/core/card_type/equip.lua b/lua/core/card_type/equip.lua index 9ac303b2..2b072065 100644 --- a/lua/core/card_type/equip.lua +++ b/lua/core/card_type/equip.lua @@ -1,90 +1,97 @@ ---@class EquipCard : Card +---@field equipSkill Skill local EquipCard = Card:subclass("EquipCard") function EquipCard:initialize(name, suit, number) - Card.initialize(self, name, suit, number) - self.type = Card.TypeEquip + Card.initialize(self, name, suit, number) + self.type = Card.TypeEquip + self.equipSkill = nil end ---@class Weapon : EquipCard local Weapon = EquipCard:subclass("Weapon") function Weapon:initialize(name, suit, number, attackRange) - EquipCard.initialize(self, name, suit, number) - self.sub_type = Card.SubtypeWeapon - self.attack_range = attackRange or 1 + EquipCard.initialize(self, name, suit, number) + self.sub_type = Card.SubtypeWeapon + self.attack_range = attackRange or 1 end ---@param suit Suit ---@param number integer ---@return Weapon function Weapon:clone(suit, number) - local newCard = Weapon:new(self.name, suit, number, self.attack_range) - return newCard + local newCard = Weapon:new(self.name, suit, number, self.attack_range) + newCard.skill = self.skill + return newCard end ---@class Armor : EquipCard local Armor = EquipCard:subclass("armor") function Armor:initialize(name, suit, number) - EquipCard.initialize(self, name, suit, number) - self.sub_type = Card.SubtypeArmor + EquipCard.initialize(self, name, suit, number) + self.sub_type = Card.SubtypeArmor end ---@param suit Suit ---@param number integer ---@return Armor function Armor:clone(suit, number) - local newCard = Armor:new(self.name, suit, number) - return newCard + local newCard = Armor:new(self.name, suit, number) + newCard.skill = self.skill + return newCard end ---@class DefensiveRide : EquipCard local DefensiveRide = EquipCard:subclass("DefensiveRide") function DefensiveRide:initialize(name, suit, number) - EquipCard.initialize(self, name, suit, number) - self.sub_type = Card.SubtypeDefensiveRide + EquipCard.initialize(self, name, suit, number) + self.sub_type = Card.SubtypeDefensiveRide end ---@param suit Suit ---@param number integer ---@return DefensiveRide function DefensiveRide:clone(suit, number) - local newCard = DefensiveRide:new(self.name, suit, number) - return newCard + local newCard = DefensiveRide:new(self.name, suit, number) + newCard.skill = self.skill + return newCard end ---@class OffensiveRide : EquipCard local OffensiveRide = EquipCard:subclass("OffensiveRide") function OffensiveRide:initialize(name, suit, number) - EquipCard.initialize(self, name, suit, number) - self.sub_type = Card.SubtypeOffensiveRide + EquipCard.initialize(self, name, suit, number) + self.sub_type = Card.SubtypeOffensiveRide end ---@param suit Suit ---@param number integer ---@return OffensiveRide function OffensiveRide:clone(suit, number) - local newCard = OffensiveRide:new(self.name, suit, number) - return newCard + local newCard = OffensiveRide:new(self.name, suit, number) + newCard.skill = self.skill + return newCard end ---@class Treasure : EquipCard local Treasure = EquipCard:subclass("Treasure") function Treasure:initialize(name, suit, number) - EquipCard.initialize(self, name, suit, number) - self.sub_type = Card.SubtypeTreasure + EquipCard.initialize(self, name, suit, number) + self.sub_type = Card.SubtypeTreasure end ---@param suit Suit ---@param number integer ---@return Treasure function Treasure:clone(suit, number) - local newCard = Treasure:new(self.name, suit, number) - return newCard + local newCard = Treasure:new(self.name, suit, number) + newCard.skill = self.skill + return newCard end return { EquipCard, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure } diff --git a/lua/core/card_type/skill.lua b/lua/core/card_type/skill.lua deleted file mode 100644 index a3064d13..00000000 --- a/lua/core/card_type/skill.lua +++ /dev/null @@ -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 diff --git a/lua/core/card_type/trick.lua b/lua/core/card_type/trick.lua index 13106541..2afc0f72 100644 --- a/lua/core/card_type/trick.lua +++ b/lua/core/card_type/trick.lua @@ -2,32 +2,36 @@ local TrickCard = Card:subclass("TrickCard") function TrickCard:initialize(name, suit, number) - Card.initialize(self, name, suit, number) - self.type = Card.TypeTrick + Card.initialize(self, name, suit, number) + self.type = Card.TypeTrick end ---@param suit Suit ---@param number integer ---@return TrickCard function TrickCard:clone(suit, number) - local newCard = TrickCard:new(self.name, suit, number) - return newCard + local newCard = TrickCard:new(self.name, suit, number) + + newCard.skill = self.skill + + return newCard end ---@class DelayedTrickCard : TrickCard local DelayedTrickCard = TrickCard:subclass("DelayedTrickCard") function DelayedTrickCard:initialize(name, suit, number) - TrickCard.initialize(self, name, suit, number) - self.sub_type = Card.SubtypeDelayedTrick + TrickCard.initialize(self, name, suit, number) + self.sub_type = Card.SubtypeDelayedTrick end ---@param suit Suit ---@param number integer ---@return DelayedTrickCard function DelayedTrickCard:clone(suit, number) - local newCard = DelayedTrickCard:new(self.name, suit, number) - return newCard + local newCard = DelayedTrickCard:new(self.name, suit, number) + newCard.skill = self.skill + return newCard end return { TrickCard, DelayedTrickCard } diff --git a/lua/core/debug.lua b/lua/core/debug.lua index 638dee0a..63d4de33 100644 --- a/lua/core/debug.lua +++ b/lua/core/debug.lua @@ -3,16 +3,16 @@ inspect = require "inspect" DebugMode = true function PrintWhenMethodCall() - local info = debug.getinfo(2) - local name = info.name - local line = info.currentline - local namewhat = info.namewhat - local shortsrc = info.short_src - if (namewhat == "method") and - (shortsrc ~= "[C]") and - (not string.find(shortsrc, "/lib")) then - print(shortsrc .. ":" .. line .. ": " .. name) - end + local info = debug.getinfo(2) + local name = info.name + local line = info.currentline + local namewhat = info.namewhat + local shortsrc = info.short_src + if (namewhat == "method") and + (shortsrc ~= "[C]") and + (not string.find(shortsrc, "/lib")) then + print(shortsrc .. ":" .. line .. ": " .. name) + end end --debug.sethook(PrintWhenMethodCall, "c") diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 6e0fe1c3..497c1165 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -11,126 +11,126 @@ local Engine = class("Engine") function Engine:initialize() - -- Engine should be singleton - if Fk ~= nil then - error("Engine has been initialized") - return - end + -- Engine should be singleton + if Fk ~= nil then + error("Engine has been initialized") + return + end - Fk = self + Fk = self - self.packages = {} -- name --> Package - self.package_names = {} - self.skills = {} -- name --> Skill - self.related_skills = {} -- skillName --> relatedSkill[] - self.global_trigger = {} - self.generals = {} -- name --> General - self.lords = {} -- lordName[] - self.cards = {} -- Card[] - self.translations = {} -- srcText --> translated + self.packages = {} -- name --> Package + self.package_names = {} + self.skills = {} -- name --> Skill + self.related_skills = {} -- skillName --> relatedSkill[] + self.global_trigger = {} + self.generals = {} -- name --> General + self.lords = {} -- lordName[] + self.cards = {} -- Card[] + self.translations = {} -- srcText --> translated - self:loadPackages() + self:loadPackages() end ---@param pack Package function Engine:loadPackage(pack) - assert(pack:isInstanceOf(Package)) - if self.packages[pack.name] ~= nil then - error(string.format("Duplicate package %s detected", pack.name)) - end - self.packages[pack.name] = pack - table.insert(self.package_names, pack.name) + assert(pack:isInstanceOf(Package)) + if self.packages[pack.name] ~= nil then + error(string.format("Duplicate package %s detected", pack.name)) + end + self.packages[pack.name] = pack + table.insert(self.package_names, pack.name) - -- add cards, generals and skills to Engine - if pack.type == Package.CardPack then - self:addCards(pack.cards) - elseif pack.type == Package.GeneralPack then - self:addGenerals(pack.generals) - end - self:addSkills(pack:getSkills()) + -- add cards, generals and skills to Engine + if pack.type == Package.CardPack then + self:addCards(pack.cards) + elseif pack.type == Package.GeneralPack then + self:addGenerals(pack.generals) + end + self:addSkills(pack:getSkills()) end function Engine:loadPackages() - local directories = FileIO.ls("packages") + local directories = FileIO.ls("packages") - -- load standard & standard_cards first - self:loadPackage(require("packages.standard")) - self:loadPackage(require("packages.standard_cards")) - table.removeOne(directories, "standard") - table.removeOne(directories, "standard_cards") + -- load standard & standard_cards first + self:loadPackage(require("packages.standard")) + self:loadPackage(require("packages.standard_cards")) + table.removeOne(directories, "standard") + table.removeOne(directories, "standard_cards") - for _, dir in ipairs(directories) do - if FileIO.isDir("packages/" .. dir) then - local pack = require(string.format("packages.%s", dir)) - -- Note that instance of Package is a table too - -- so dont use type(pack) == "table" here - if pack[1] ~= nil then - for _, p in ipairs(pack) do - self:loadPackage(p) - end - else - self:loadPackage(pack) - end + for _, dir in ipairs(directories) do + if FileIO.isDir("packages/" .. dir) then + local pack = require(string.format("packages.%s", dir)) + -- Note that instance of Package is a table too + -- so dont use type(pack) == "table" here + if pack[1] ~= nil then + for _, p in ipairs(pack) do + self:loadPackage(p) end + else + self:loadPackage(pack) + end end + end end ---@param t table function Engine:loadTranslationTable(t) - assert(type(t) == "table") - for k, v in pairs(t) do - self.translations[k] = v - end + assert(type(t) == "table") + for k, v in pairs(t) do + self.translations[k] = v + end end ---@param skill Skill function Engine:addSkill(skill) - assert(skill.class:isSubclassOf(Skill)) - if self.skills[skill.name] ~= nil then - error(string.format("Duplicate skill %s detected", skill.name)) - end - self.skills[skill.name] = skill + assert(skill.class:isSubclassOf(Skill)) + if self.skills[skill.name] ~= nil then + error(string.format("Duplicate skill %s detected", skill.name)) + end + self.skills[skill.name] = skill end ---@param skills Skill[] function Engine:addSkills(skills) - assert(type(skills) == "table") - for _, skill in ipairs(skills) do - self:addSkill(skill) - end + assert(type(skills) == "table") + for _, skill in ipairs(skills) do + self:addSkill(skill) + end end ---@param general General function Engine:addGeneral(general) - assert(general:isInstanceOf(General)) - if self.generals[general.name] ~= nil then - error(string.format("Duplicate general %s detected", general.name)) - end - self.generals[general.name] = general + assert(general:isInstanceOf(General)) + if self.generals[general.name] ~= nil then + error(string.format("Duplicate general %s detected", general.name)) + end + self.generals[general.name] = general end ---@param generals General[] function Engine:addGenerals(generals) - assert(type(generals) == "table") - for _, general in ipairs(generals) do - self:addGeneral(general) - end + assert(type(generals) == "table") + for _, general in ipairs(generals) do + self:addGeneral(general) + end end local cardId = 1 ---@param card Card function Engine:addCard(card) - assert(card.class:isSubclassOf(Card)) - card.id = cardId - cardId = cardId + 1 - table.insert(self.cards, card) + assert(card.class:isSubclassOf(Card)) + card.id = cardId + cardId = cardId + 1 + table.insert(self.cards, card) end ---@param cards Card[] function Engine:addCards(cards) - for _, card in ipairs(cards) do - self:addCard(card) - end + for _, card in ipairs(cards) do + self:addCard(card) + end end ---@param num integer @@ -139,68 +139,68 @@ end ---@param filter function ---@return General[] function Engine:getGeneralsRandomly(num, generalPool, except, filter) - if filter then - assert(type(filter) == "function") - end + if filter then + assert(type(filter) == "function") + end - generalPool = generalPool or self.generals - except = except or {} + generalPool = generalPool or self.generals + except = except or {} - local availableGenerals = {} - for _, general in pairs(generalPool) do - if not table.contains(except, general.name) and not (filter and filter(general)) then - table.insert(availableGenerals, general) - end + local availableGenerals = {} + for _, general in pairs(generalPool) do + if not table.contains(except, general.name) and not (filter and filter(general)) then + table.insert(availableGenerals, general) 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 - return {} + break end + 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 - break - end - end - - return result + return result end ---@param except General[] ---@return General[] function Engine:getAllGenerals(except) - local result = {} - for _, general in ipairs(self.generals) do - if not (except and table.contains(except, general)) then - table.insert(result, general) - end + local result = {} + for _, general in ipairs(self.generals) do + if not (except and table.contains(except, general)) then + table.insert(result, general) end + end - return result + return result end ---@param except integer[] ---@return integer[] function Engine:getAllCardIds(except) - local result = {} - for _, card in ipairs(self.cards) do - if not (except and table.contains(except, card.id)) then - table.insert(result, card.id) - end + local result = {} + for _, card in ipairs(self.cards) do + if not (except and table.contains(except, card.id)) then + table.insert(result, card.id) end + end - return result + return result end ---@param id integer ---@return Card function Engine:getCardById(id) - return self.cards[id] + return self.cards[id] end return Engine diff --git a/lua/core/general.lua b/lua/core/general.lua index de52e89a..dd06d163 100644 --- a/lua/core/general.lua +++ b/lua/core/general.lua @@ -15,24 +15,24 @@ General.Male = 1 General.Female = 2 function General:initialize(package, name, kingdom, hp, maxHp, gender) - self.package = package - self.name = name - self.kingdom = kingdom - self.hp = hp - self.maxHp = maxHp or hp - self.gender = gender or General.Male + self.package = package + self.name = name + self.kingdom = kingdom + self.hp = hp + self.maxHp = maxHp or hp + self.gender = gender or General.Male - self.skills = {} -- skills first added to this general - self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde + self.skills = {} -- skills first added to this general + self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde end ---@param skill Skill function General:addSkill(skill) - if (type(skill) == "string") then - table.insert(self.other_skills, skill) - elseif (skill.class and skill.class:isSubclassOf(Skill)) then - table.insert(self.skills, skill) - end + if (type(skill) == "string") then + table.insert(self.other_skills, skill) + elseif (skill.class and skill.class:isSubclassOf(Skill)) then + table.insert(self.skills, skill) + end end return General diff --git a/lua/core/package.lua b/lua/core/package.lua index 1e64b2c1..b5180a0a 100644 --- a/lua/core/package.lua +++ b/lua/core/package.lua @@ -14,48 +14,48 @@ Package.CardPack = 2 Package.SpecialPack = 3 function Package:initialize(name, _type) - assert(type(name) == "string") - assert(type(_type) == "nil" or type(_type) == "number") - self.name = name - self.type = _type or Package.GeneralPack + assert(type(name) == "string") + assert(type(_type) == "nil" or type(_type) == "number") + self.name = name + self.type = _type or Package.GeneralPack - self.generals = {} - self.extra_skills = {} -- skill not belongs to any generals, like "jixi" - self.related_skills = {} - self.cards = {} + self.generals = {} + self.extra_skills = {} -- skill not belongs to any generals, like "jixi" + self.related_skills = {} + self.cards = {} end ---@return Skill[] function Package:getSkills() - local ret = {table.unpack(self.related_skills)} - if self.type == Package.GeneralPack then - for _, g in ipairs(self.generals) do - for _, s in ipairs(g.skills) do - table.insert(ret, s) - end - end + local ret = {table.unpack(self.related_skills)} + if self.type == Package.GeneralPack then + for _, g in ipairs(self.generals) do + for _, s in ipairs(g.skills) do + table.insert(ret, s) + end end - return ret + end + return ret end ---@param general General function Package:addGeneral(general) - assert(general.class and general:isInstanceOf(General)) - table.insert(self.generals, general) + assert(general.class and general:isInstanceOf(General)) + table.insert(self.generals, general) end ---@param card Card function Package:addCard(card) - assert(card.class and card:isInstanceOf(Card)) - card.package = self - table.insert(self.cards, card) + assert(card.class and card:isInstanceOf(Card)) + card.package = self + table.insert(self.cards, card) end ---@param cards Card[] function Package:addCards(cards) - for _, card in ipairs(cards) do - self:addCard(card) - end + for _, card in ipairs(cards) do + self:addCard(card) + end end return Package diff --git a/lua/core/player.lua b/lua/core/player.lua index 6e35c55d..11b2a5d6 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -19,6 +19,7 @@ ---@field mark table ---@field player_cards table ---@field special_cards table +---@field cardUsedHistory table local Player = class("Player") ---@alias Phase integer @@ -41,184 +42,211 @@ Player.Judge = 3 Player.Special = 4 function Player:initialize() - self.id = 114514 - self.hp = 0 - self.maxHp = 0 - self.kingdom = "qun" - self.role = "" - self.general = "" - self.seat = 0 - self.phase = Player.PhaseNone - self.faceup = true - self.chained = false - self.dying = false - self.dead = false - self.state = "" + self.id = 114514 + self.hp = 0 + self.maxHp = 0 + self.kingdom = "qun" + self.role = "" + self.general = "" + self.seat = 0 + self.phase = Player.PhaseNone + self.faceup = true + self.chained = false + self.dying = false + self.dead = false + self.state = "" - self.player_skills = {} - self.flag = {} - self.tag = {} - self.mark = {} - self.player_cards = { - [Player.Hand] = {}, - [Player.Equip] = {}, - [Player.Judge] = {}, - } - self.special_cards = {} + self.player_skills = {} + self.flag = {} + self.tag = {} + self.mark = {} + self.player_cards = { + [Player.Hand] = {}, + [Player.Equip] = {}, + [Player.Judge] = {}, + } + self.special_cards = {} + + self.cardUsedHistory = {} end ---@param general General ---@param setHp boolean ---@param addSkills boolean function Player:setGeneral(general, setHp, addSkills) - self.general = general - if setHp then - self.maxHp = general.maxHp - self.hp = general.hp - end + self.general = general + if setHp then + self.maxHp = general.maxHp + self.hp = general.hp + end - if addSkills then - table.insertTable(self.player_skills, general.skills) - end + if addSkills then + table.insertTable(self.player_skills, general.skills) + end end ---@param flag string function Player:hasFlag(flag) - return table.contains(self.flag, flag) + return table.contains(self.flag, flag) end ---@param flag string function Player:setFlag(flag) - if flag == "." then - self:clearFlags() - return - end - if flag:sub(1, 1) == "-" then - flag = flag:sub(2, #flag) - table.removeOne(self.flag, flag) - return - end - if not self:hasFlag(flag) then - table.insert(self.flag, flag) - end + if flag == "." then + self:clearFlags() + return + end + if flag:sub(1, 1) == "-" then + flag = flag:sub(2, #flag) + table.removeOne(self.flag, flag) + return + end + if not self:hasFlag(flag) then + table.insert(self.flag, flag) + end end function Player:clearFlags() - self.flag = {} + self.flag = {} end function Player:addMark(mark, count) - count = count or 1 - local num = self.mark[mark] - num = num or 0 - self:setMark(mark, math.max(num + count, 0)) + count = count or 1 + local num = self.mark[mark] + num = num or 0 + self:setMark(mark, math.max(num + count, 0)) end function Player:removeMark(mark, count) - count = count or 1 - local num = self.mark[mark] - num = num or 0 - self:setMark(mark, math.max(num - count, 0)) + count = count or 1 + local num = self.mark[mark] + num = num or 0 + self:setMark(mark, math.max(num - count, 0)) end function Player:setMark(mark, count) - if self.mark[mark] ~= count then - self.mark[mark] = count - end + if self.mark[mark] ~= count then + self.mark[mark] = count + end end function Player:getMark(mark) - return (self.mark[mark] or 0) + return (self.mark[mark] or 0) end function Player:getMarkNames() - local ret = {} - for k, _ in pairs(self.mark) do - table.insert(ret, k) - end - return ret + local ret = {} + for k, _ in pairs(self.mark) do + table.insert(ret, k) + end + return ret end ---@param playerArea PlayerCardArea ---@param cardIds integer[] ---@param specialName string function Player:addCards(playerArea, cardIds, specialName) - assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea)) - assert(playerArea ~= Player.Special or type(specialName) == "string") + assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea)) + assert(playerArea ~= Player.Special or type(specialName) == "string") - if playerArea == Player.Special then - self.special_cards[specialName] = self.special_cards[specialName] or {} - table.insertTable(self.special_cards[specialName], cardIds) - else - table.insertTable(self.player_cards[playerArea], cardIds) - end + if playerArea == Player.Special then + self.special_cards[specialName] = self.special_cards[specialName] or {} + table.insertTable(self.special_cards[specialName], cardIds) + else + table.insertTable(self.player_cards[playerArea], cardIds) + end end ---@param playerArea PlayerCardArea ---@param cardIds integer[] ---@param specialName string function Player:removeCards(playerArea, cardIds, specialName) - assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea)) - assert(playerArea ~= Player.Special or type(specialName) == "string") + assert(table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, playerArea)) + assert(playerArea ~= Player.Special or type(specialName) == "string") - local fromAreaIds = playerArea == Player.Special and self.special_cards[specialName] or self.player_cards[playerArea] - if fromAreaIds then - for _, id in ipairs(cardIds) do - if #fromAreaIds == 0 then - break - end + local fromAreaIds = playerArea == Player.Special and self.special_cards[specialName] or self.player_cards[playerArea] + if fromAreaIds then + for _, id in ipairs(cardIds) do + if #fromAreaIds == 0 then + break + end - table.removeOne(fromAreaIds, id) - end + table.removeOne(fromAreaIds, id) end + end end ---@param playerAreas PlayerCardArea ---@param specialName string ---@return integer[] function Player:getCardIds(playerAreas, specialName) - local rightAreas = { Player.Hand, Player.Equip, Player.Judge } - playerAreas = playerAreas or rightAreas - assert(type(playerAreas) == "number" or type(playerAreas) == "table") - local areas = type(playerAreas) == "table" and playerAreas or { playerAreas } + local rightAreas = { Player.Hand, Player.Equip, Player.Judge } + playerAreas = playerAreas or rightAreas + assert(type(playerAreas) == "number" or type(playerAreas) == "table") + local areas = type(playerAreas) == "table" and playerAreas or { playerAreas } - local rightAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special } - local cardIds = {} - for _, area in ipairs(areas) do - assert(table.contains(rightAreas, area)) - assert(area ~= Player.Special or type(specialName) == "string") - local currentCardIds = area == Player.Special and self.special_cards[specialName] or self.player_cards[area] - table.insertTable(cardIds, currentCardIds) + local rightAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special } + local cardIds = {} + for _, area in ipairs(areas) do + assert(table.contains(rightAreas, area)) + assert(area ~= Player.Special or type(specialName) == "string") + local currentCardIds = area == Player.Special and self.special_cards[specialName] or self.player_cards[area] + 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 - return cardIds + return nil end function Player:getMaxCards() - local baseValue = math.max(self.hp, 0) + local baseValue = math.max(self.hp, 0) - return baseValue + return baseValue end ---@param subtype CardSubtype ---@return integer|null function Player:getEquipBySubtype(subtype) - local equipId = nil - for _, id in ipairs(self.player_cards[Player.Equip]) do - if Fk.getCardById(id).sub_type == subtype then - equipId = id - break - end + local equipId = nil + for _, id in ipairs(self.player_cards[Player.Equip]) do + if Fk:getCardById(id).sub_type == subtype then + equipId = id + break end + end - return equipId + return equipId end function Player:getAttackRange() - local weapon = Fk.getCardById(self:getEquipBySubtype(Card.SubtypeWeapon)) - local baseAttackRange = math.max(weapon and weapon.attack_range or 1, 0) + local weapon = Fk:getCardById(self:getEquipBySubtype(Card.SubtypeWeapon)) + 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 return Player diff --git a/lua/core/skill.lua b/lua/core/skill.lua index 337308d8..8a051b73 100644 --- a/lua/core/skill.lua +++ b/lua/core/skill.lua @@ -13,10 +13,10 @@ Skill.Limited = 4 Skill.Wake = 5 function Skill:initialize(name, frequency) - -- TODO: visible, lord, etc - self.name = name - self.frequency = frequency - self.visible = true + -- TODO: visible, lord, etc + self.name = name + self.frequency = frequency + self.visible = true end return Skill diff --git a/lua/core/skill_type/active_skill.lua b/lua/core/skill_type/active_skill.lua new file mode 100644 index 00000000..158ffe45 --- /dev/null +++ b/lua/core/skill_type/active_skill.lua @@ -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 diff --git a/lua/core/skill_type/trigger.lua b/lua/core/skill_type/trigger.lua index 55924ab8..48a4a339 100644 --- a/lua/core/skill_type/trigger.lua +++ b/lua/core/skill_type/trigger.lua @@ -6,12 +6,12 @@ local TriggerSkill = Skill:subclass("TriggerSkill") function TriggerSkill:initialize(name, frequency) - Skill.initialize(self, name, frequency) + Skill.initialize(self, name, frequency) - self.global = false - self.events = {} - self.refresh_events = {} - self.priority_table = {} -- GameEvent --> priority + self.global = false + self.events = {} + self.refresh_events = {} + self.priority_table = {} -- GameEvent --> priority end -- Default functions @@ -37,8 +37,8 @@ function TriggerSkill:refresh(event, target, player, data) end ---@param data any @ useful data of the event ---@return boolean function TriggerSkill:triggerable(event, target, player, data) - return target and (target == player) - and (self.global or (target:isAlive() and target:hasSkill(self))) + return target and (target == player) + and (self.global or (target:isAlive() and target:hasSkill(self))) end ---Trigger this skill @@ -48,10 +48,10 @@ end ---@param data any @ useful data of the event ---@return boolean @ returns true if trigger is broken function TriggerSkill:trigger(event, target, player, data) - if player.room:askForSkillInvoke(player, self.name) then - return self:use(event, target, player, data) - end - return false + if player.room:askForSkillInvoke(player, self.name) then + return self:use(event, target, player, data) + end + return false end ---Use this skill diff --git a/lua/core/util.lua b/lua/core/util.lua index 13b0cbdc..434cc8b8 100644 --- a/lua/core/util.lua +++ b/lua/core/util.lua @@ -1,52 +1,52 @@ -- the iterator of QList object local qlist_iterator = function(list, n) - if n < list:length() - 1 then - return n + 1, list:at(n + 1) -- the next element of list - end + if n < list:length() - 1 then + return n + 1, list:at(n + 1) -- the next element of list + end end function fk.qlist(list) - return qlist_iterator, list, -1 + return qlist_iterator, list, -1 end function table:contains(element) - if #self == 0 or type(self[1]) ~= type(element) then return false end - for _, e in ipairs(self) do - if e == element then return true end - end + if #self == 0 or type(self[1]) ~= type(element) then return false end + for _, e in ipairs(self) do + if e == element then return true end + end end function table:shuffle() - for i = #self, 2, -1 do - local j = math.random(i) - self[i], self[j] = self[j], self[i] - end + for i = #self, 2, -1 do + local j = math.random(i) + self[i], self[j] = self[j], self[i] + end end function table:insertTable(list) - for _, e in ipairs(list) do - table.insert(self, e) - end + for _, e in ipairs(list) do + table.insert(self, e) + end end function table:indexOf(value, from) - from = from or 1 - for i = from, #self do - if self[i] == value then return i end - end - return -1 + from = from or 1 + for i = from, #self do + if self[i] == value then return i end + end + return -1 end 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 - if self[i] == element then - table.remove(self, i) - return true - end - end - return false + for i = 1, #self do + if self[i] == element then + table.remove(self, i) + return true + end + end + return false end -- Note: only clone key and value, no metatable @@ -55,57 +55,57 @@ end ---@param self T ---@return T function table.clone(self) - local ret = {} - for k, v in pairs(self) do - if type(v) == "table" then - ret[k] = table.clone(v) - else - ret[k] = v - end - end - return ret + local ret = {} + for k, v in pairs(self) do + if type(v) == "table" then + ret[k] = table.clone(v) + else + ret[k] = v + end + end + return ret end ---@class Sql Sql = { - ---@param filename string - open = function(filename) - return fk.OpenDatabase(filename) - end, + ---@param filename string + open = function(filename) + return fk.OpenDatabase(filename) + end, - ---@param db fk.SQLite3 - close = function(db) - fk.CloseDatabase(db) - end, + ---@param db fk.SQLite3 + close = function(db) + fk.CloseDatabase(db) + end, - --- Execute an SQL statement. - ---@param db fk.SQLite3 - ---@param sql string - exec = function(db, sql) - fk.ExecSQL(db, sql) - end, + --- Execute an SQL statement. + ---@param db fk.SQLite3 + ---@param sql string + exec = function(db, sql) + fk.ExecSQL(db, sql) + end, - --- Execute a `SELECT` SQL statement. - ---@param db fk.SQLite3 - ---@param sql string - ---@return table @ { [columnName] --> result : string[] } - exec_select = function(db, sql) - return json.decode(fk.SelectFromDb(db, sql)) - end, + --- Execute a `SELECT` SQL statement. + ---@param db fk.SQLite3 + ---@param sql string + ---@return table @ { [columnName] --> result : string[] } + exec_select = function(db, sql) + return json.decode(fk.SelectFromDb(db, sql)) + end, } FileIO = { - pwd = fk.QmlBackend_pwd, - ls = function(filename) - if filename == nil then - return fk.QmlBackend_ls(".") - else - return fk.QmlBackend_ls(filename) - end - end, - cd = fk.QmlBackend_cd, - exists = fk.QmlBackend_exists, - isDir = fk.QmlBackend_isDir + pwd = fk.QmlBackend_pwd, + ls = function(filename) + if filename == nil then + return fk.QmlBackend_ls(".") + else + return fk.QmlBackend_ls(filename) + end + end, + cd = fk.QmlBackend_cd, + exists = fk.QmlBackend_exists, + isDir = fk.QmlBackend_isDir } os.getms = fk.GetMicroSecond @@ -113,23 +113,23 @@ os.getms = fk.GetMicroSecond ---@class Stack : Object Stack = class("Stack") function Stack:initialize() - self.t = {} - self.p = 0 + self.t = {} + self.p = 0 end function Stack:push(e) - self.p = self.p + 1 - self.t[self.p] = e + self.p = self.p + 1 + self.t[self.p] = e end function Stack:isEmpty() - return self.p == 0 + return self.p == 0 end function Stack:pop() - if self.p == 0 then return nil end - self.p = self.p - 1 - return self.t[self.p + 1] + if self.p == 0 then return nil end + self.p = self.p - 1 + return self.t[self.p + 1] end @@ -139,15 +139,156 @@ end ---@param table string ---@param enum string[] function CreateEnum(table, enum) - local enum_format = "%s.%s = %d" - for i, v in ipairs(enum) do - print(string.format(enum_format, table, v, i)) - end + local enum_format = "%s.%s = %d" + for i, v in ipairs(enum) do + print(string.format(enum_format, table, v, i)) + end end function switch(param, case_table) - local case = case_table[param] - if case then return case() end - local def = case_table["default"] - return def and def() or nil + local case = case_table[param] + if case then return case() end + local def = case_table["default"] + return def and def() or nil 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 } diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index bce0141d..d76855a7 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -1,18 +1,16 @@ -- 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" local Trick = require "core.card_type.trick" TrickCard, DelayedTrickCard = table.unpack(Trick) local Equip = require "core.card_type.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 ---@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 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 ---@return BasicCard function fk.CreateBasicCard(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "number") end - if spec.number then assert(type(spec.number) == "number") end + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "number") end + if spec.number then assert(type(spec.number) == "number") end - local card = BasicCard:new(spec.name, spec.suit, spec.number) - return card + local card = BasicCard:new(spec.name, spec.suit, spec.number) + card.skill = spec.skill or defaultCardSkill + return card end ---@param spec CardSpec ---@return TrickCard function fk.CreateTrickCard(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "number") end - if spec.number then assert(type(spec.number) == "number") end + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "number") end + if spec.number then assert(type(spec.number) == "number") end - local card = TrickCard:new(spec.name, spec.suit, spec.number) - return card + local card = TrickCard:new(spec.name, spec.suit, spec.number) + card.skill = spec.skill or defaultCardSkill + return card end ---@param spec CardSpec ---@return DelayedTrickCard function fk.CreateDelayedTrickCard(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "number") end - if spec.number then assert(type(spec.number) == "number") end + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "number") end + if spec.number then assert(type(spec.number) == "number") end - local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number) - return card + local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number) + card.skill = spec.skill or defaultCardSkill + return card end ---@param spec CardSpec ---@return Weapon function fk.CreateWeapon(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "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 + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "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 - local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range) - return card + local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range) + card.skill = spec.skill or defaultCardSkill + return card end ---@param spec CardSpec ---@return Armor function fk.CreateArmor(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "number") end - if spec.number then assert(type(spec.number) == "number") end + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "number") end + if spec.number then assert(type(spec.number) == "number") end - local card = Armor:new(spec.name, spec.suit, spec.number) - return card + local card = Armor:new(spec.name, spec.suit, spec.number) + card.skill = spec.skill or defaultCardSkill + return card end ---@param spec CardSpec ---@return DefensiveRide function fk.CreateDefensiveRide(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "number") end - if spec.number then assert(type(spec.number) == "number") end + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "number") end + if spec.number then assert(type(spec.number) == "number") end - local card = DefensiveRide:new(spec.name, spec.suit, spec.number) - return card + local card = DefensiveRide:new(spec.name, spec.suit, spec.number) + card.skill = spec.skill or defaultCardSkill + return card end ---@param spec CardSpec ---@return OffensiveRide function fk.CreateOffensiveRide(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "number") end - if spec.number then assert(type(spec.number) == "number") end + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "number") end + if spec.number then assert(type(spec.number) == "number") end - local card = OffensiveRide:new(spec.name, spec.suit, spec.number) - return card + local card = OffensiveRide:new(spec.name, spec.suit, spec.number) + card.skill = spec.skill or defaultCardSkill + return card end ---@param spec CardSpec ---@return Treasure function fk.CreateTreasure(spec) - assert(type(spec.name) == "string" or type(spec.class_name) == "string") - if not spec.name then spec.name = spec.class_name - elseif not spec.class_name then spec.class_name = spec.name end - if spec.suit then assert(type(spec.suit) == "number") end - if spec.number then assert(type(spec.number) == "number") end + assert(type(spec.name) == "string" or type(spec.class_name) == "string") + if not spec.name then spec.name = spec.class_name + elseif not spec.class_name then spec.class_name = spec.name end + if spec.suit then assert(type(spec.suit) == "number") end + if spec.number then assert(type(spec.number) == "number") end - local card = Treasure:new(spec.name, spec.suit, spec.number) - return card -end - ----@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 + local card = Treasure:new(spec.name, spec.suit, spec.number) + card.skill = spec.skill or defaultCardSkill + return card end diff --git a/lua/freekill.lua b/lua/freekill.lua index 02704748..6fe2d094 100644 --- a/lua/freekill.lua +++ b/lua/freekill.lua @@ -10,7 +10,8 @@ class = require "middleclass" json = require "json" 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" math.randomseed(os.time()) diff --git a/lua/server/event.lua b/lua/server/event.lua index 3ed1abb5..7e7afc88 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -50,4 +50,24 @@ fk.EnterDying = 38 fk.Dying = 39 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 diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index a5c86d1d..41adc7d6 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -8,274 +8,274 @@ local GameLogic = class("GameLogic") function GameLogic:initialize(room) - self.room = room - self.skill_table = {} -- TriggerEvent --> TriggerSkill[] - self.refresh_skill_table = {} - self.skills = {} -- skillName[] - self.event_stack = Stack:new() + self.room = room + self.skill_table = {} -- TriggerEvent --> TriggerSkill[] + self.refresh_skill_table = {} + self.skills = {} -- skillName[] + self.event_stack = Stack:new() - self.role_table = { - { "lord" }, - { "lord", "rebel" }, - { "lord", "rebel", "renegade" }, - { "lord", "loyalist", "rebel", "renegade" }, - { "lord", "loyalist", "rebel", "rebel", "renegade" }, - { "lord", "loyalist", "rebel", "rebel", "rebel", "renegade" }, - { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade" }, - { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade" }, - } + self.role_table = { + { "lord" }, + { "lord", "rebel" }, + { "lord", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade" }, + } end function GameLogic:run() - -- default logic - table.shuffle(self.room.players) - self:assignRoles() - self.room:adjustSeats() + -- default logic + table.shuffle(self.room.players) + self:assignRoles() + self.room:adjustSeats() - self:chooseGenerals() - self:prepareForStart() - self:action() + self:chooseGenerals() + self:prepareForStart() + self:action() end function GameLogic:assignRoles() - local room = self.room - local n = #room.players - local roles = self.role_table[n] - table.shuffle(roles) + local room = self.room + local n = #room.players + local roles = self.role_table[n] + table.shuffle(roles) - for i = 1, n do - local p = room.players[i] - p.role = roles[i] - if p.role == "lord" then - room:broadcastProperty(p, "role") - else - room:notifyProperty(p, p, "role") - end + for i = 1, n do + local p = room.players[i] + p.role = roles[i] + if p.role == "lord" then + room:broadcastProperty(p, "role") + else + room:notifyProperty(p, p, "role") end + end end function GameLogic:chooseGenerals() - local room = self.room - local function setPlayerGeneral(player, general) - if Fk.generals[general] == nil then return end - player.general = general - self.room:notifyProperty(player, player, "general") - end - local lord = room:getLord() - local lord_general = nil - if lord ~= nil then - room.current = lord - local generals = Fk:getGeneralsRandomly(3) - for i = 1, #generals do - generals[i] = generals[i].name - end - lord_general = room:askForGeneral(lord, generals) - setPlayerGeneral(lord, lord_general) - room:broadcastProperty(lord, "general") + local room = self.room + local function setPlayerGeneral(player, general) + if Fk.generals[general] == nil then return end + player.general = general + self.room:notifyProperty(player, player, "general") + end + local lord = room:getLord() + local lord_general = nil + if lord ~= nil then + room.current = lord + local generals = Fk:getGeneralsRandomly(3) + for i = 1, #generals do + generals[i] = generals[i].name end + lord_general = room:askForGeneral(lord, generals) + setPlayerGeneral(lord, lord_general) + room:broadcastProperty(lord, "general") + end - local nonlord = room:getOtherPlayers(lord) - local generals = Fk:getGeneralsRandomly(#nonlord * 3, Fk.generals, {lord_general}) - table.shuffle(generals) - for _, p in ipairs(nonlord) do - local arg = { - (table.remove(generals, 1)).name, - (table.remove(generals, 1)).name, - (table.remove(generals, 1)).name, - } - p.request_data = json.encode(arg) - p.default_reply = arg[1] - end + local nonlord = room:getOtherPlayers(lord) + local generals = Fk:getGeneralsRandomly(#nonlord * 3, Fk.generals, {lord_general}) + table.shuffle(generals) + for _, p in ipairs(nonlord) do + local arg = { + (table.remove(generals, 1)).name, + (table.remove(generals, 1)).name, + (table.remove(generals, 1)).name, + } + p.request_data = json.encode(arg) + p.default_reply = arg[1] + end - room:doBroadcastRequest("AskForGeneral", nonlord) - for _, p in ipairs(nonlord) do - if p.general == "" and p.reply_ready then - local general = json.decode(p.client_reply)[1] - setPlayerGeneral(p, general) - else - setPlayerGeneral(p, p.default_reply) - end - p.default_reply = "" + room:doBroadcastRequest("AskForGeneral", nonlord) + for _, p in ipairs(nonlord) do + if p.general == "" and p.reply_ready then + local general = json.decode(p.client_reply)[1] + setPlayerGeneral(p, general) + else + setPlayerGeneral(p, p.default_reply) end + p.default_reply = "" + end end function GameLogic:prepareForStart() - local room = self.room - local players = room.players - room.alive_players = {table.unpack(players)} - for i = 1, #players - 1 do - players[i].next = players[i + 1] + local room = self.room + local players = room.players + room.alive_players = {table.unpack(players)} + for i = 1, #players - 1 do + 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 - players[#players].next = players[1] + room:broadcastProperty(p, "maxHp") + room:broadcastProperty(p, "hp") - 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 + -- TODO: add skills to player + end - if p.role ~= "lord" then - room:broadcastProperty(p, "general") - elseif #players >= 5 then - p.maxHp = p.maxHp + 1 - p.hp = p.hp + 1 - end - room:broadcastProperty(p, "maxHp") - room:broadcastProperty(p, "hp") + -- TODO: prepare drawPile + -- 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 - -- TODO: add skills to player - end - - -- TODO: prepare drawPile - -- 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 + self:addTriggerSkill(GameRule) + for _, trig in ipairs(Fk.global_trigger) do + self:addTriggerSkill(trig) + end end function GameLogic:action() - self:trigger(fk.GameStart) - local room = self.room + self:trigger(fk.GameStart) + local room = self.room - for _, p in ipairs(room.players) do - self:trigger(fk.DrawInitialCards, p, { num = 4 }) - end + for _, p in ipairs(room.players) do + self:trigger(fk.DrawInitialCards, p, { num = 4 }) + end - while true do - self:trigger(fk.TurnStart, room.current) - if room.game_finished then break end - room.current = room.current:getNextAlive() - end + while true do + self:trigger(fk.TurnStart, room.current) + if room.game_finished then break end + room.current = room.current:getNextAlive() + end end ---@param skill TriggerSkill function GameLogic:addTriggerSkill(skill) - if skill == nil or table.contains(self.skills, skill.name) then - return - end + if skill == nil or table.contains(self.skills, skill.name) then + return + end - table.insert(self.skills, skill.name) + table.insert(self.skills, skill.name) - for _, event in ipairs(skill.refresh_events) do - if self.refresh_skill_table[event] == nil then - self.refresh_skill_table[event] = {} - end - table.insert(self.refresh_skill_table[event], skill) + for _, event in ipairs(skill.refresh_events) do + if self.refresh_skill_table[event] == nil then + self.refresh_skill_table[event] = {} end + table.insert(self.refresh_skill_table[event], skill) + end - for _, event in ipairs(skill.events) do - if self.skill_table[event] == nil then - self.skill_table[event] = {} - end - table.insert(self.skill_table[event], skill) + for _, event in ipairs(skill.events) do + if self.skill_table[event] == nil then + self.skill_table[event] = {} end + table.insert(self.skill_table[event], skill) + end - if skill.visible then - if (Fk.related_skills[skill.name] == nil) then return end - for _, s in ipairs(Fk.related_skills[skill.name]) do - if (s.class == TriggerSkill) then - self:addTriggerSkill(s) - end - end + if skill.visible then + if (Fk.related_skills[skill.name] == nil) then return end + for _, s in ipairs(Fk.related_skills[skill.name]) do + if (s.class == TriggerSkill) then + self:addTriggerSkill(s) + end end + end end ---@param event Event ---@param target ServerPlayer ---@param data any function GameLogic:trigger(event, target, data) - local room = self.room - local broken = false - local skills = self.skill_table[event] or {} - local skills_to_refresh = self.refresh_skill_table[event] or {} - local player = target + local room = self.room + local broken = false + local skills = self.skill_table[event] or {} + local skills_to_refresh = self.refresh_skill_table[event] or {} + local player = target - self.event_stack:push({event, target, data}) + self.event_stack:push({event, target, data}) - if target == nil then - for _, skill in ipairs(skills_to_refresh) do - if skill:canRefresh(event, target, player, data) then - skill:refresh(event, target, player, data) - 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 + if target == nil then + for _, skill in ipairs(skills_to_refresh) do + if skill:canRefresh(event, target, player, data) then + skill:refresh(event, target, player, data) + end 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 - 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 - + 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 - - player = player.next - end until player == target + end + end self.event_stack:pop() 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 + 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 return GameLogic diff --git a/lua/server/lobby.lua b/lua/server/lobby.lua index 2b5064f8..103243db 100644 --- a/lua/server/lobby.lua +++ b/lua/server/lobby.lua @@ -6,63 +6,63 @@ fk.lobby_callback = {} local db = fk.ServerInstance:getDatabase() function Lobby:initialize(_lobby) - self.lobby = _lobby - self.lobby.callback = function(_self, command, jsonData) - local cb = fk.lobby_callback[command] - if (type(cb) == "function") then - cb(jsonData) - else - print("Lobby error: Unknown command " .. command); - end + self.lobby = _lobby + self.lobby.callback = function(_self, command, jsonData) + local cb = fk.lobby_callback[command] + if (type(cb) == "function") then + cb(jsonData) + else + print("Lobby error: Unknown command " .. command); end + end end fk.lobby_callback["UpdateAvatar"] = function(jsonData) - -- jsonData: [ int uid, string newavatar ] - local data = json.decode(jsonData) - local id, avatar = data[1], data[2] - local sql = "UPDATE userinfo SET avatar='%s' WHERE id=%d;" - Sql.exec(db, string.format(sql, avatar, id)) - local player = fk.ServerInstance:findPlayer(id) - player:setAvatar(avatar) - player:doNotify("UpdateAvatar", avatar) + -- jsonData: [ int uid, string newavatar ] + local data = json.decode(jsonData) + local id, avatar = data[1], data[2] + local sql = "UPDATE userinfo SET avatar='%s' WHERE id=%d;" + Sql.exec(db, string.format(sql, avatar, id)) + local player = fk.ServerInstance:findPlayer(id) + player:setAvatar(avatar) + player:doNotify("UpdateAvatar", avatar) end fk.lobby_callback["UpdatePassword"] = function(jsonData) - -- jsonData: [ int uid, string oldpassword, int newpassword ] - local data = json.decode(jsonData) - local id, old, new = data[1], data[2], data[3] - local sql_find = "SELECT password FROM userinfo WHERE id=%d;" - local sql_update = "UPDATE userinfo SET password='%s' WHERE id=%d;" + -- jsonData: [ int uid, string oldpassword, int newpassword ] + local data = json.decode(jsonData) + local id, old, new = data[1], data[2], data[3] + local sql_find = "SELECT password FROM userinfo WHERE id=%d;" + local sql_update = "UPDATE userinfo SET password='%s' WHERE id=%d;" - local passed = false - local result = Sql.exec_select(db, string.format(sql_find, id)) - passed = (result["password"][1] == sha256(old)) - if passed then - Sql.exec(db, string.format(sql_update, sha256(new), id)) - end + local passed = false + local result = Sql.exec_select(db, string.format(sql_find, id)) + passed = (result["password"][1] == sha256(old)) + if passed then + Sql.exec(db, string.format(sql_update, sha256(new), id)) + end - local player = fk.ServerInstance:findPlayer(tonumber(id)) - player:doNotify("UpdatePassword", passed and "1" or "0") + local player = fk.ServerInstance:findPlayer(tonumber(id)) + player:doNotify("UpdatePassword", passed and "1" or "0") end fk.lobby_callback["CreateRoom"] = function(jsonData) - -- jsonData: [ int uid, string name, int capacity ] - local data = json.decode(jsonData) - local owner = fk.ServerInstance:findPlayer(tonumber(data[1])) - local roomName = data[2] - local capacity = data[3] - fk.ServerInstance:createRoom(owner, roomName, capacity) + -- jsonData: [ int uid, string name, int capacity ] + local data = json.decode(jsonData) + local owner = fk.ServerInstance:findPlayer(tonumber(data[1])) + local roomName = data[2] + local capacity = data[3] + fk.ServerInstance:createRoom(owner, roomName, capacity) end fk.lobby_callback["EnterRoom"] = function(jsonData) - -- jsonData: [ int uid, int roomId ] - local data = json.decode(jsonData) - local player = fk.ServerInstance:findPlayer(tonumber(data[1])) - local room = fk.ServerInstance:findRoom(tonumber(data[2])) - room:addPlayer(player) + -- jsonData: [ int uid, int roomId ] + local data = json.decode(jsonData) + local player = fk.ServerInstance:findPlayer(tonumber(data[1])) + local room = fk.ServerInstance:findRoom(tonumber(data[2])) + room:addPlayer(player) end function CreateRoom(_room) - LobbyInstance = Lobby:new(_room) + LobbyInstance = Lobby:new(_room) end diff --git a/lua/server/room.lua b/lua/server/room.lua index 82a5db69..aab919bd 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -21,75 +21,75 @@ fk.room_callback = {} ---@param _room fk.Room function Room:initialize(_room) - self.room = _room - self.room.callback = function(_self, command, jsonData) - local cb = fk.room_callback[command] - if (type(cb) == "function") then - cb(jsonData) - else - print("Lobby error: Unknown command " .. command); - end + self.room = _room + self.room.callback = function(_self, command, jsonData) + local cb = fk.room_callback[command] + if (type(cb) == "function") then + cb(jsonData) + else + print("Lobby error: Unknown command " .. command); end + end - self.room.startGame = function(_self) - self:run() - end + self.room.startGame = function(_self) + self:run() + end - self.players = {} - self.alive_players = {} - self.current = nil - self.game_finished = false - self.timeout = _room:getTimeout() - self.tag = {} - self.draw_pile = {} - self.discard_pile = {} - self.processing_area = {} - self.void = {} - self.card_place = {} + self.players = {} + self.alive_players = {} + self.current = nil + self.game_finished = false + self.timeout = _room:getTimeout() + self.tag = {} + self.draw_pile = {} + self.discard_pile = {} + self.processing_area = {} + self.void = {} + self.card_place = {} end -- When this function returns, the Room(C++) thread stopped. function Room:run() - for _, p in fk.qlist(self.room:getPlayers()) do - local player = ServerPlayer:new(p) - player.state = p:getStateString() - player.room = self - table.insert(self.players, player) - end + for _, p in fk.qlist(self.room:getPlayers()) do + local player = ServerPlayer:new(p) + player.state = p:getStateString() + player.room = self + table.insert(self.players, player) + end - self.logic = GameLogic:new(self) - self.logic:run() + self.logic = GameLogic:new(self) + self.logic:run() end ---@param player ServerPlayer ---@param property string function Room:broadcastProperty(player, property) - for _, p in ipairs(self.players) do - self:notifyProperty(p, player, property) - end + for _, p in ipairs(self.players) do + self:notifyProperty(p, player, property) + end end ---@param p ServerPlayer ---@param player ServerPlayer ---@param property string function Room:notifyProperty(p, player, property) - p:doNotify("PropertyUpdate", json.encode{ - player:getId(), - property, - player[property], - }) + p:doNotify("PropertyUpdate", json.encode{ + player:getId(), + property, + player[property], + }) end ---@param command string ---@param jsonData string ---@param players ServerPlayer[] @ default all players function Room:doBroadcastNotify(command, jsonData, players) - players = players or self.players - local tolist = fk.SPlayerList() - for _, p in ipairs(players) do - tolist:append(p.serverplayer) - end - self.room:doBroadcastNotify(tolist, command, jsonData) + players = players or self.players + local tolist = fk.SPlayerList() + for _, p in ipairs(players) do + tolist:append(p.serverplayer) + end + self.room:doBroadcastNotify(tolist, command, jsonData) end ---@param player ServerPlayer @@ -98,261 +98,261 @@ end ---@param wait boolean @ default true ---@return string | nil function Room:doRequest(player, command, jsonData, wait) - if wait == nil then wait = true end - player:doRequest(command, jsonData, self.timeout) + if wait == nil then wait = true end + player:doRequest(command, jsonData, self.timeout) - if wait then - return player:waitForReply(self.timeout) - end + if wait then + return player:waitForReply(self.timeout) + end end ---@param command string ---@param players ServerPlayer[] function Room:doBroadcastRequest(command, players) - players = players or self.players - self:notifyMoveFocus(players, command) - for _, p in ipairs(players) do - self:doRequest(p, command, p.request_data, false) - end + players = players or self.players + self:notifyMoveFocus(players, command) + for _, p in ipairs(players) do + self:doRequest(p, command, p.request_data, false) + end - local remainTime = self.timeout - local currentTime = os.time() - local elapsed = 0 - for _, p in ipairs(players) do - elapsed = os.time() - currentTime - remainTime = remainTime - elapsed - p:waitForReply(remainTime) - end + local remainTime = self.timeout + local currentTime = os.time() + local elapsed = 0 + for _, p in ipairs(players) do + elapsed = os.time() - currentTime + remainTime = remainTime - elapsed + p:waitForReply(remainTime) + end end ---@param players ServerPlayer | ServerPlayer[] ---@param command string function Room:notifyMoveFocus(players, command) - if (players.class) then - players = {players} - end + if (players.class) then + players = {players} + end - local ids = {} - for _, p in ipairs(players) do - table.insert(ids, p:getId()) - end + local ids = {} + for _, p in ipairs(players) do + table.insert(ids, p:getId()) + end - self:doBroadcastNotify("MoveFocus", json.encode{ - ids, - command - }) + self:doBroadcastNotify("MoveFocus", json.encode{ + ids, + command + }) end function Room:adjustSeats() - local players = {} - local p = 0 + local players = {} + local p = 0 - for i = 1, #self.players do - if self.players[i].role == "lord" then - p = i - break - end - end - for j = p, #self.players do - table.insert(players, self.players[j]) - end - for j = 1, p - 1 do - table.insert(players, self.players[j]) + for i = 1, #self.players do + if self.players[i].role == "lord" then + p = i + break end + end + for j = p, #self.players do + table.insert(players, self.players[j]) + end + for j = 1, p - 1 do + table.insert(players, self.players[j]) + end - self.players = players + self.players = players - local player_circle = {} - for i = 1, #self.players do - self.players[i].seat = i - table.insert(player_circle, self.players[i]:getId()) - end + local player_circle = {} + for i = 1, #self.players do + self.players[i].seat = i + table.insert(player_circle, self.players[i]:getId()) + end - self:doBroadcastNotify("ArrangeSeats", json.encode(player_circle)) + self:doBroadcastNotify("ArrangeSeats", json.encode(player_circle)) end function Room:shuffleDrawPile() - if #self.draw_pile + #self.discard_pile == 0 then - return - end + if #self.draw_pile + #self.discard_pile == 0 then + return + end - table.insertTable(self.draw_pile, self.discard_pile) - for _, id in ipairs(self.discard_pile) do - self:setCardArea(id, Card.DrawPile) - end - self.discard_pile = {} - table.shuffle(self.draw_pile) + table.insertTable(self.draw_pile, self.discard_pile) + for _, id in ipairs(self.discard_pile) do + self:setCardArea(id, Card.DrawPile) + end + self.discard_pile = {} + table.shuffle(self.draw_pile) end ---@param num integer ---@param from string ---@return integer[] function Room:getNCards(num, from) - from = from or "top" - assert(from == "top" or from == "bottom") + from = from or "top" + assert(from == "top" or from == "bottom") - local cardIds = {} - while num > 0 do - if #self.draw_pile < 1 then - self:shuffleDrawPile() - end - - local index = from == "top" and 1 or #self.draw_pile - table.insert(cardIds, self.draw_pile[index]) - table.remove(self.draw_pile, index) - - num = num - 1 + local cardIds = {} + while num > 0 do + if #self.draw_pile < 1 then + self:shuffleDrawPile() end - return cardIds + local index = from == "top" and 1 or #self.draw_pile + table.insert(cardIds, self.draw_pile[index]) + table.remove(self.draw_pile, index) + + num = num - 1 + end + + return cardIds end ---@param cardId integer ---@param cardArea CardArea function Room:setCardArea(cardId, cardArea) - self.card_place[cardId] = cardArea + self.card_place[cardId] = cardArea end ---@param cardId integer ---@return CardArea function Room:getCardArea(cardId) - return self.card_place[cardId] or Card.Unknown + return self.card_place[cardId] or Card.Unknown end ---@param players ServerPlayer[] ---@param card_moves CardsMoveStruct[] ---@param forceVisible boolean function Room:notifyMoveCards(players, card_moves, forceVisible) - if players == nil or players == {} then players = self.players end - for _, p in ipairs(players) do - local arg = table.clone(card_moves) - for _, move in ipairs(arg) do - -- local to = self:getPlayerById(move.to) + if players == nil or players == {} then players = self.players end + for _, p in ipairs(players) do + local arg = table.clone(card_moves) + for _, move in ipairs(arg) do + -- local to = self:getPlayerById(move.to) - -- forceVisible make the move visible - -- FIXME: move.moveInfo is an array, fix this - move.moveVisible = (forceVisible) - -- if move is relevant to player, it should be open - or ((move.from == p:getId()) or (move.to == p:getId() and move.toArea ~= Card.PlayerSpecial)) - -- cards move from/to equip/judge/discard/processing should be open - or move.moveInfo.fromArea == Card.PlayerEquip - or move.toArea == Card.PlayerEquip - or move.moveInfo.fromArea == Card.PlayerJudge - or move.toArea == Card.PlayerJudge - or move.moveInfo.fromArea == Card.DiscardPile - or move.toArea == Card.DiscardPile - or move.moveInfo.fromArea == Card.Processing - or move.toArea == Card.Processing - -- TODO: PlayerSpecial - - if not move.moveVisible then - for _, info in ipairs(move.moveInfo) do - info.cardId = -1 - end - end + -- forceVisible make the move visible + -- FIXME: move.moveInfo is an array, fix this + move.moveVisible = (forceVisible) + -- if move is relevant to player, it should be open + or ((move.from == p:getId()) or (move.to == p:getId() and move.toArea ~= Card.PlayerSpecial)) + -- cards move from/to equip/judge/discard/processing should be open + or move.moveInfo.fromArea == Card.PlayerEquip + or move.toArea == Card.PlayerEquip + or move.moveInfo.fromArea == Card.PlayerJudge + or move.toArea == Card.PlayerJudge + or move.moveInfo.fromArea == Card.DiscardPile + or move.toArea == Card.DiscardPile + or move.moveInfo.fromArea == Card.Processing + or move.toArea == Card.Processing + -- TODO: PlayerSpecial + + if not move.moveVisible then + for _, info in ipairs(move.moveInfo) do + info.cardId = -1 end - p:doNotify("MoveCards", json.encode(arg)) + end end + p:doNotify("MoveCards", json.encode(arg)) + end end ---@vararg CardsMoveInfo ---@return boolean function Room:moveCards(...) - ---@type CardsMoveStruct[] - local cardsMoveStructs = {} - local infoCheck = function(info) - assert(table.contains({ Card.PlayerHand, Card.PlayerEquip, Card.PlayerJudge, Card.PlayerSpecial, Card.Processing, Card.DrawPile, Card.DiscardPile, Card.Void }, info.toArea)) - assert(info.toArea ~= Card.PlayerSpecial or type(info.specialName) == "string") - assert(type(info.moveReason) == "number") + ---@type CardsMoveStruct[] + local cardsMoveStructs = {} + local infoCheck = function(info) + assert(table.contains({ Card.PlayerHand, Card.PlayerEquip, Card.PlayerJudge, Card.PlayerSpecial, Card.Processing, Card.DrawPile, Card.DiscardPile, Card.Void }, info.toArea)) + assert(info.toArea ~= Card.PlayerSpecial or type(info.specialName) == "string") + assert(type(info.moveReason) == "number") + end + + for _, cardsMoveInfo in ipairs({...}) do + if #cardsMoveInfo.ids > 0 then + infoCheck(cardsMoveInfo) + + ---@type MoveInfo[] + local infos = {} + for _, id in ipairs(cardsMoveInfo.ids) do + table.insert(infos, { cardId = id, fromArea = self:getCardArea(id) }) + end + + ---@type CardsMoveStruct + local cardsMoveStruct = { + moveInfo = infos, + from = cardsMoveInfo.from, + to = cardsMoveInfo.to, + toArea = cardsMoveInfo.toArea, + moveReason = cardsMoveInfo.moveReason, + proposer = cardsMoveInfo.proposer, + skillName = cardsMoveInfo.skillName, + moveVisible = cardsMoveInfo.moveVisible, + specialName = cardsMoveInfo.specialName, + specialVisible = cardsMoveInfo.specialVisible, + } + + table.insert(cardsMoveStructs, cardsMoveStruct) end + end - for _, cardsMoveInfo in ipairs({...}) do - if #cardsMoveInfo.ids > 0 then - infoCheck(cardsMoveInfo) + if #cardsMoveStructs < 1 then + return false + end - ---@type MoveInfo[] - local infos = {} - for _, id in ipairs(cardsMoveInfo.ids) do - table.insert(infos, { cardId = id, fromArea = self:getCardArea(id) }) - end - - ---@type CardsMoveStruct - local cardsMoveStruct = { - moveInfo = infos, - from = cardsMoveInfo.from, - to = cardsMoveInfo.to, - toArea = cardsMoveInfo.toArea, - moveReason = cardsMoveInfo.moveReason, - proposer = cardsMoveInfo.proposer, - skillName = cardsMoveInfo.skillName, - moveVisible = cardsMoveInfo.moveVisible, - specialName = cardsMoveInfo.specialName, - specialVisible = cardsMoveInfo.specialVisible, - } - - table.insert(cardsMoveStructs, cardsMoveStruct) + if self.logic:trigger(fk.BeforeCardsMove, nil, cardsMoveStructs) then + return false + end + + self:notifyMoveCards(self.players, cardsMoveStructs) + + for _, data in ipairs(cardsMoveStructs) do + if #data.moveInfo > 0 then + infoCheck(data) + + ---@param info MoveInfo + for _, info in ipairs(data.moveInfo) do + local realFromArea = self:getCardArea(info.cardId) + local playerAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special } + + if table.contains(playerAreas, realFromArea) and data.from then + self:getPlayerById(data.from):removeCards(realFromArea, { info.cardId }, data.specialName) + elseif realFromArea ~= Card.Unknown then + local fromAreaIds = {} + if realFromArea == Card.Processing then + fromAreaIds = self.processing_area + elseif realFromArea == Card.DrawPile then + fromAreaIds = self.draw_pile + elseif realFromArea == Card.DiscardPile then + fromAreaIds = self.discard_pile + elseif realFromArea == Card.Void then + fromAreaIds = self.void + end + + table.removeOne(fromAreaIds, info.cardId) end - end - if #cardsMoveStructs < 1 then - return false - end + if table.contains(playerAreas, data.toArea) and data.to then + self:getPlayerById(data.to):addCards(data.toArea, { info.cardId }, data.specialName) + else + local toAreaIds = {} + if data.toArea == Card.Processing then + toAreaIds = self.processing_area + elseif data.toArea == Card.DrawPile then + toAreaIds = self.draw_pile + elseif data.toArea == Card.DiscardPile then + toAreaIds = self.discard_pile + elseif data.toArea == Card.Void then + toAreaIds = self.void + end - if self.logic:trigger(fk.BeforeCardsMove, nil, cardsMoveStructs) then - return false - end - - self:notifyMoveCards(self.players, cardsMoveStructs) - - for _, data in ipairs(cardsMoveStructs) do - if #data.moveInfo > 0 then - infoCheck(data) - - ---@param info MoveInfo - for _, info in ipairs(data.moveInfo) do - local realFromArea = self:getCardArea(info.cardId) - local playerAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special } - - if table.contains(playerAreas, realFromArea) and data.from then - self:getPlayerById(data.from):removeCards(realFromArea, { info.cardId }, data.specialName) - elseif realFromArea ~= Card.Unknown then - local fromAreaIds = {} - if realFromArea == Card.Processing then - fromAreaIds = self.processing_area - elseif realFromArea == Card.DrawPile then - fromAreaIds = self.draw_pile - elseif realFromArea == Card.DiscardPile then - fromAreaIds = self.discard_pile - elseif realFromArea == Card.Void then - fromAreaIds = self.void - end - - table.removeOne(fromAreaIds, info.cardId) - end - - if table.contains(playerAreas, data.toArea) and data.to then - self:getPlayerById(data.to):addCards(data.toArea, { info.cardId }, data.specialName) - else - local toAreaIds = {} - if data.toArea == Card.Processing then - toAreaIds = self.processing_area - elseif data.toArea == Card.DrawPile then - toAreaIds = self.draw_pile - elseif data.toArea == Card.DiscardPile then - toAreaIds = self.discard_pile - elseif data.toArea == Card.Void then - toAreaIds = self.void - end - - table.insert(toAreaIds, toAreaIds == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId) - end - self:setCardArea(info.cardId, data.toArea) - end + table.insert(toAreaIds, toAreaIds == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId) end + self:setCardArea(info.cardId, data.toArea) + end end + end - self.logic:trigger(fk.AfterCardsMove, nil, cardsMoveStructs) - return true + self.logic:trigger(fk.AfterCardsMove, nil, cardsMoveStructs) + return true end ---@param player ServerPlayer @@ -361,17 +361,17 @@ end ---@param fromPlace "top"|"bottom" ---@return integer[] function Room:drawCards(player, num, skillName, fromPlace) - local topCards = self:getNCards(num, fromPlace) - self:moveCards({ - ids = topCards, - to = player:getId(), - toArea = Card.PlayerHand, - moveReason = fk.ReasonDraw, - proposer = player:getId(), - skillName = skillName, - }) + local topCards = self:getNCards(num, fromPlace) + self:moveCards({ + ids = topCards, + to = player:getId(), + toArea = Card.PlayerHand, + moveReason = fk.ReasonDraw, + proposer = player:getId(), + skillName = skillName, + }) - return { table.unpack(topCards) } + return { table.unpack(topCards) } end ---@param player ServerPlayer @@ -380,145 +380,161 @@ end ---@param includeEquip boolean ---@param skillName string function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName) - if minNum < 1 then - return nil - end + if minNum < 1 then + return nil + end - local hands = player:getCardIds(Player.Hand) - local toDiscard = {} - for i = 1, minNum do - local randomId = hands[math.random(1, #hands)] - table.insert(toDiscard, randomId) - table.removeOne(hands, randomId) - end + local hands = player:getCardIds(Player.Hand) + local toDiscard = {} + for i = 1, minNum do + local randomId = hands[math.random(1, #hands)] + table.insert(toDiscard, randomId) + table.removeOne(hands, randomId) + end - self:moveCards({ - ids = toDiscard, - from = player:getId(), - toArea = Card.DiscardPile, - moveReason = fk.ReasonDiscard, - proposer = player:getId(), - skillName = skillName - }) + self:moveCards({ + ids = toDiscard, + from = player:getId(), + toArea = Card.DiscardPile, + moveReason = fk.ReasonDiscard, + proposer = player:getId(), + skillName = skillName + }) end ---@param id integer ---@return ServerPlayer function Room:getPlayerById(id) - assert(type(id) == "number") + assert(type(id) == "number") - for _, p in ipairs(self.players) do - if p:getId() == id then - return p - end + for _, p in ipairs(self.players) do + if p:getId() == id then + return p end + end - error("cannot find player by " .. id) + error("cannot find player by " .. id) +end + +---@param playerIds integer[] +function Room:sortPlayersByAction(playerIds) + +end + +function Room:deadPlayerFilter(playerIds) + local newPlayerIds = {} + for _, playerId in ipairs(playerIds) do + if self:getPlayerById(playerId):isAlive() then + table.insert(newPlayerIds, playerId) + end + end + + return newPlayerIds end ---@param sortBySeat boolean ---@return ServerPlayer[] function Room:getAlivePlayers(sortBySeat) - sortBySeat = sortBySeat or true + sortBySeat = sortBySeat or true - local alivePlayers = {} - for _, player in ipairs(self.players) do - if player:isAlive() then - table.insert(alivePlayers, player) - end + local alivePlayers = {} + for _, player in ipairs(self.players) do + if player:isAlive() then + table.insert(alivePlayers, player) end + end - return alivePlayers + return alivePlayers end ---@param player ServerPlayer ---@param sortBySeat boolean ---@return ServerPlayer[] function Room:getOtherPlayers(player, sortBySeat) - local alivePlayers = self:getAlivePlayers(sortBySeat) - for _, p in ipairs(alivePlayers) do - if p:getId() == player:getId() then - table.removeOne(alivePlayers, player) - break - end + local alivePlayers = self:getAlivePlayers(sortBySeat) + for _, p in ipairs(alivePlayers) do + if p:getId() == player:getId() then + table.removeOne(alivePlayers, player) + break end + end - return alivePlayers + return alivePlayers end ---@return ServerPlayer | null function Room:getLord() - local lord = self.players[1] - if lord.role == "lord" then return lord end - for _, p in ipairs(self.players) do - if p.role == "lord" then return p end - end + local lord = self.players[1] + if lord.role == "lord" then return lord end + for _, p in ipairs(self.players) do + if p.role == "lord" then return p end + end - return nil + return nil end ---@param expect ServerPlayer ---@return ServerPlayer[] function Room:getOtherPlayers(expect) - local ret = {table.unpack(self.players)} - table.removeOne(ret, expect) - return ret + local ret = {table.unpack(self.players)} + table.removeOne(ret, expect) + return ret end ---@param player ServerPlayer ---@param generals string[] ---@return string function Room:askForGeneral(player, generals) - local command = "AskForGeneral" - self:notifyMoveFocus(player, command) + local command = "AskForGeneral" + self:notifyMoveFocus(player, command) - if #generals == 1 then return generals[1] end - local defaultChoice = generals[1] + if #generals == 1 then return generals[1] end + local defaultChoice = generals[1] - if (player.state == "online") then - local result = self:doRequest(player, command, json.encode(generals)) - if result == "" then - return defaultChoice - else - -- TODO: result is a JSON array - -- update here when choose multiple generals - return json.decode(result)[1] - end + if (player.state == "online") then + local result = self:doRequest(player, command, json.encode(generals)) + if result == "" then + return defaultChoice + else + -- TODO: result is a JSON array + -- update here when choose multiple generals + return json.decode(result)[1] end + end - return defaultChoice + return defaultChoice end function Room:gameOver() - self.game_finished = true - -- dosomething - self.room:gameOver() + self.game_finished = true + -- dosomething + self.room:gameOver() end ---@param player ServerPlayer ---@param choices string[] ---@param skill_name string function Room:askForChoice(player, choices, skill_name, data) - if #choices == 1 then return choices[1] end - local command = "AskForChoice" - self:notifyMoveFocus(player, skill_name) - local result = self:doRequest(player, command, json.encode{ - choices, skill_name - }) - if result == "" then result = choices[1] end - return result + if #choices == 1 then return choices[1] end + local command = "AskForChoice" + self:notifyMoveFocus(player, skill_name) + local result = self:doRequest(player, command, json.encode{ + choices, skill_name + }) + if result == "" then result = choices[1] end + return result end ---@param player ServerPlayer ---@param skill_name string ---@return boolean function Room:askForSkillInvoke(player, skill_name, data) - local command = "AskForSkillInvoke" - self:notifyMoveFocus(player, skill_name) - local invoked = false - local result = self:doRequest(player, command, skill_name) - if result ~= "" then invoked = true end - return invoked + local command = "AskForSkillInvoke" + self:notifyMoveFocus(player, skill_name) + local invoked = false + local result = self:doRequest(player, command, skill_name) + if result ~= "" then invoked = true end + return invoked end ---@param player ServerPlayer @@ -528,39 +544,39 @@ end ---@param damageStruct DamageStruct|null ---@return boolean function Room:changeHp(player, num, reason, skillName, damageStruct) - if num == 0 then - return false - end - assert(reason == nil or table.contains({ "loseHp", "damage", "recover" }, reason)) + if num == 0 then + return false + end + assert(reason == nil or table.contains({ "loseHp", "damage", "recover" }, reason)) - ---@type HpChangedData - local data = { - num = num, - reason = reason, - skillName = skillName, + ---@type HpChangedData + local data = { + num = num, + reason = reason, + skillName = skillName, + } + + if self.logic:trigger(fk.BeforeHpChanged, player, data) then + return false + end + + assert(not (data.reason == "recover" and data.num < 0)) + player.hp = math.min(player.hp + data.num, player.maxHp) + + self.logic:trigger(fk.HpChanged, player, data) + + if player.hp < 1 then + ---@type DyingStruct + local dyingStruct = { + who = player:getId(), + damage = damageStruct, } + self:enterDying(dyingStruct) + elseif player.dying then + player.dying = false + end - if self.logic:trigger(fk.BeforeHpChanged, player, data) then - return false - end - - assert(not (data.reason == "recover" and data.num < 0)) - player.hp = math.min(player.hp + data.num, player.maxHp) - - self.logic:trigger(fk.HpChanged, player, data) - - if player.hp < 1 then - ---@type DyingStruct - local dyingStruct = { - who = player:getId(), - damage = damageStruct, - } - self:enterDying(dyingStruct) - elseif player.dying then - player.dying = false - end - - return true + return true end ---@param player ServerPlayer @@ -568,216 +584,437 @@ end ---@param skillName string ---@return boolean function Room:loseHp(player, num, skillName) - if num == nil then - num = 1 - elseif num < 1 then - return false - end + if num == nil then + num = 1 + elseif num < 1 then + return false + end - ---@type HpLostData - local data = { - num = num, - skillName = skillName, - } - if self.logic:trigger(fk.PreHpLost, player, data) or data.num < 1 then - return false - end + ---@type HpLostData + local data = { + num = num, + skillName = skillName, + } + if self.logic:trigger(fk.PreHpLost, player, data) or data.num < 1 then + return false + end - if not self:changeHp(player, -num, "loseHp", skillName) then - return false - end + if not self:changeHp(player, -num, "loseHp", skillName) then + return false + end - self.logic:trigger(fk.HpLost, player, data) - return true + self.logic:trigger(fk.HpLost, player, data) + return true end ---@param player ServerPlayer ---@param num integer ---@return boolean function Room:changeMaxHp(player, num) - if num == 0 then - return false - end + if num == 0 then + return false + end - player.maxHp = math.max(player.maxHp + num, 0) - local diff = player.hp - player.maxHp - if diff > 0 then - if not self:changeHp(player, -diff) then - player.hp = player.hp - diff - end + player.maxHp = math.max(player.maxHp + num, 0) + local diff = player.hp - player.maxHp + if diff > 0 then + if not self:changeHp(player, -diff) then + player.hp = player.hp - diff end + end - if player.maxHp == 0 then - self:killPlayer({ who = player:getId() }) - end + if player.maxHp == 0 then + self:killPlayer({ who = player:getId() }) + end - self.logic:trigger(fk.MaxHpChanged, player, { num = num }) - return true + self.logic:trigger(fk.MaxHpChanged, player, { num = num }) + return true end ---@param damageStruct DamageStruct ---@return boolean function Room:damage(damageStruct) - if damageStruct.damage < 1 then - return false + if damageStruct.damage < 1 then + return false + end + + assert(type(damageStruct.to) == "number") + + local stages = { + [fk.PreDamage] = damageStruct.from, + [fk.DamageCaused] = damageStruct.from, + [fk.DamageInflicted] = damageStruct.to, + } + + for event, playerId in ipairs(stages) do + local player = playerId and self:getPlayerById(playerId) or nil + if self.logic:trigger(event, player, damageStruct) or damageStruct.damage < 1 then + return false end assert(type(damageStruct.to) == "number") + end - local stages = { - [fk.PreDamage] = damageStruct.from, - [fk.DamageCaused] = damageStruct.from, - [fk.DamageInflicted] = damageStruct.to, - } + assert(self:getPlayerById(damageStruct.to)) + local victim = self:getPlayerById(damageStruct.to) + if not victim:isAlive() then + return false + end - for event, playerId in ipairs(stages) do - local player = playerId and self:getPlayerById(playerId) or nil - if self.logic:trigger(event, player, damageStruct) or damageStruct.damage < 1 then - return false - end + if not self:changeHp(victim, -damageStruct.damage, "damage", damageStruct.skillName, damageStruct) then + return false + end - assert(type(damageStruct.to) == "number") - end + stages = { + [fk.Damage] = damageStruct.from, + [fk.Damaged] = damageStruct.to, + [fk.DamageFinished] = damageStruct.from, + } - assert(self:getPlayerById(damageStruct.to)) - local victim = self:getPlayerById(damageStruct.to) - if not victim:isAlive() then - return false - end + for event, playerId in ipairs(stages) do + local player = playerId and self:getPlayerById(playerId) or nil + self.logic:trigger(event, player, damageStruct) + end - if not self:changeHp(victim, -damageStruct.damage, "damage", damageStruct.skillName, damageStruct) then - return false - end - - stages = { - [fk.Damage] = damageStruct.from, - [fk.Damaged] = damageStruct.to, - [fk.DamageFinished] = damageStruct.from, - } - - for event, playerId in ipairs(stages) do - local player = playerId and self:getPlayerById(playerId) or nil - self.logic:trigger(event, player, damageStruct) - end - - return true + return true end ---@param recoverStruct RecoverStruct ---@return boolean function Room:recover(recoverStruct) - if recoverStruct.num < 1 then - return false - end + if recoverStruct.num < 1 then + return false + end - local who = self:getPlayerById(recoverStruct.who) - if self.logic:trigger(fk.PreHpRecover, who, recoverStruct) or recoverStruct.num < 1 then - return false - end + local who = self:getPlayerById(recoverStruct.who) + if self.logic:trigger(fk.PreHpRecover, who, recoverStruct) or recoverStruct.num < 1 then + return false + end - if not self:changeHp(who, recoverStruct.num, "recover", recoverStruct.skillName) then - return false - end + if not self:changeHp(who, recoverStruct.num, "recover", recoverStruct.skillName) then + return false + end - self.logic:trigger(fk.HpRecover, who, recoverStruct) - return true + self.logic:trigger(fk.HpRecover, who, recoverStruct) + return true end ---@param dyingStruct DyingStruct function Room:enterDying(dyingStruct) - local dyingPlayer = self:getPlayerById(dyingStruct.who) - dyingPlayer.dying = true - self.logic:trigger(fk.EnterDying, dyingPlayer, dyingStruct) + local dyingPlayer = self:getPlayerById(dyingStruct.who) + dyingPlayer.dying = true + self.logic:trigger(fk.EnterDying, dyingPlayer, dyingStruct) + + if dyingPlayer.hp < 1 then + local alivePlayers = self:getAlivePlayers() + for _, player in ipairs(alivePlayers) do + self.logic:trigger(fk.Dying, player, dyingStruct) + + if player.hp > 0 then + break + end + end if dyingPlayer.hp < 1 then - local alivePlayers = self:getAlivePlayers() - for _, player in ipairs(alivePlayers) do - self.logic:trigger(fk.Dying, player, dyingStruct) - - if player.hp > 0 then - break - end - end - - if dyingPlayer.hp < 1 then - ---@type DeathStruct - local deathData = { - who = dyingPlayer:getId(), - damage = dyingStruct.damage, - } - self:killPlayer(deathData) - end + ---@type DeathStruct + local deathData = { + who = dyingPlayer:getId(), + damage = dyingStruct.damage, + } + self:killPlayer(deathData) end - - self.logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct) + end + + self.logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct) end ---@param deathStruct DeathStruct function Room:killPlayer(deathStruct) - print(self:getPlayerById(deathStruct.who).general .. " is dead") - self:gameOver() + print(self:getPlayerById(deathStruct.who).general .. " is dead") + self:gameOver() +end + +---@param room Room +---@param cardUseEvent CardUseStruct +---@param aimEventCollaborators table +---@return boolean +local onAim = function(room, cardUseEvent, aimEventCollaborators) + local eventStages = { fk.TargetSpecifying, fk.TargetConfirming, fk.TargetSpecified, fk.TargetConfirmed } + for _, stage in ipairs(eventStages) do + if not cardUseEvent.tos then + return false + end + + room:sortPlayersByAction(cardUseEvent.tos) + local aimGroup = AimGroup:initAimGroup(TargetGroup:getRealTargets(cardUseEvent.tos)) + + local collaboratorsIndex = {} + local firstTarget = true + repeat + local toId = AimGroup:getUndoneOrDoneTargets(aimGroup)[1] + ---@type AimStruct + local aimStruct + local initialEvent = false + collaboratorsIndex[toId] = collaboratorsIndex[toId] or 0 + + if not aimEventCollaborators[toId] or collaboratorsIndex[toId] >= #aimEventCollaborators[toId] then + aimStruct = { + from = cardUseEvent.from, + cardId = cardUseEvent.cardId, + to = toId, + targetGroup = cardUseEvent.tos, + nullifiedTargets = cardUseEvent.nullifiedTargets or {}, + tos = aimGroup, + firstTarget = firstTarget, + additionalDamage = cardUseEvent.addtionalDamage + } + + collaboratorsIndex[toId] = 1 + initialEvent = true + else + aimStruct = aimEventCollaborators[toId][collaboratorsIndex[toId]] + aimStruct.from = cardUseEvent.from + aimStruct.cardId = cardUseEvent.cardId + aimStruct.tos = aimGroup + aimStruct.targetGroup = cardUseEvent.tos + aimStruct.nullifiedTargets = cardUseEvent.nullifiedTargets or {} + aimStruct.firstTarget = firstTarget + end + + firstTarget = false + + if room.logic:trigger(stage, (stage == fk.TargetSpecifying or stage == fk.TargetSpecified) and room:getPlayerById(aimStruct.from) or room:getPlayerById(aimStruct.to), aimStruct) then + return false + end + AimGroup:removeDeadTargets(room, aimStruct) + + local aimEventTargetGroup = aimStruct.targetGroup + if aimEventTargetGroup then + room:sortPlayersByAction(aimEventTargetGroup) + end + + cardUseEvent.from = aimStruct.from + cardUseEvent.tos = aimEventTargetGroup + cardUseEvent.nullifiedTargets = aimStruct.nullifiedTargets + + if #AimGroup:getAllTargets(aimStruct.tos) == 0 then + return false + end + + local cancelledTargets = AimGroup:getCancelledTargets(aimStruct.tos) + if #cancelledTargets > 0 then + for _, target in ipairs(cancelledTargets) do + aimEventCollaborators[target] = {} + collaboratorsIndex[target] = 0 + end + end + aimStruct.tos[AimGroup.Cancelled] = {} + + aimEventCollaborators[toId] = aimEventCollaborators[toId] or {} + if not room:getPlayerById(toId):isAlive() then + if initialEvent then + table.insert(aimEventCollaborators[toId], aimStruct) + else + aimEventCollaborators[toId][collaboratorsIndex[toId]] = aimStruct + end + end + + AimGroup:setTargetDone(aimStruct.tos, toId) + aimGroup = aimStruct.tos + until #AimGroup:getUndoneOrDoneTargets(aimGroup) == 0 + end + + return true +end + +---@param cardUseEvent CardUseStruct +---@return boolean +function Room:useCard(cardUseEvent) + self:moveCards({ + ids = { cardUseEvent.cardId }, + from = cardUseEvent.customFrom or cardUseEvent.from, + toArea = Card.Processing, + moveReason = fk.ReasonUse, + }) + + if Fk:getCardById(cardUseEvent.cardId).skill then + Fk:getCardById(cardUseEvent.cardId).skill:onUse(self, cardUseEvent) + end + if self.logic:trigger(fk.PreCardUse, self:getPlayerById(cardUseEvent.from), cardUseEvent) then + return false + end + + if not cardUseEvent.extraUse then + self:getPlayerById(cardUseEvent.from):addCardUseHistory(Fk:getCardById(cardUseEvent.cardId).trueName, 1) + end + + if cardUseEvent.responseToEvent then + cardUseEvent.responseToEvent.cardIdsResponded = cardUseEvent.responseToEvent.cardIdsResponded or {} + table.insert(cardUseEvent.responseToEvent.cardIdsResponded, cardUseEvent.cardId) + end + + for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.BeforeCardUseEffect, fk.CardUsing }) do + -- TODO: need to complete the cards for response + + self.logic:trigger(event, self:getPlayerById(cardUseEvent.from), cardUseEvent) + if event == fk.CardUsing then + ---@type table + local aimEventCollaborators = {} + if cardUseEvent.tos and not onAim(self, cardUseEvent, aimEventCollaborators) then + break + end + + if Fk:getCardById(cardUseEvent.cardId).type == Card.TypeEquip then + if self:getCardArea(cardUseEvent.cardId) ~= Card.Processing then + break + end + + if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then + self.moveCards({ + ids = { cardUseEvent.cardId }, + toArea = Card.DiscardPile, + moveReason = fk.ReasonPutIntoDiscardPile, + }) + else + local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1] + local existingEquipId = self:getPlayerById(target):getEquipment(Fk:getCardById(cardUseEvent.cardId).sub_type) + if existingEquipId then + self:moveCards( + { + ids = { existingEquipId }, + from = target, + toArea = Card.DiscardPile, + moveReason = fk.ReasonPutIntoDiscardPile, + }, + { + ids = { cardUseEvent.cardId }, + to = target, + toArea = Card.PlayerEquip, + moveReason = fk.ReasonUse, + } + ) + else + self:moveCards({ + ids = { cardUseEvent.cardId }, + to = target, + toArea = Card.PlayerEquip, + moveReason = fk.ReasonUse, + }) + end + end + + break + elseif Fk:getCardById(cardUseEvent.cardId).sub_type == Card.SubtypeDelayedTrick then + if self:getCardArea(cardUseEvent.cardId) ~= Card.Processing then + break + end + + local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1] + if not self:getPlayerById(target).dead then + local findSameCard = false + for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Equip)) do + if Fk:getCardById(cardId).trueName == Fk:getCardById(cardUseEvent.cardId) then + findSameCard = true + end + end + + if not findSameCard then + self:moveCards({ + ids = { cardUseEvent.cardId }, + to = target, + toArea = Card.PlayerJudge, + moveReason = fk.ReasonUse, + }) + + break + end + end + + self:moveCards({ + ids = { cardUseEvent.cardId }, + toArea = Card.DiscardPile, + moveReason = fk.ReasonPutIntoDiscardPile, + }) + + break + end + + if Fk:getCardById(cardUseEvent.cardId).skill then + Fk:getCardById(cardUseEvent.cardId).skill:onEffect(self, cardUseEvent) + end + end + end + + self.logic:trigger(fk.CardUseFinished, self:getPlayerById(cardUseEvent.from), cardUseEvent) + if self:getCardArea(cardUseEvent.cardId) == Card.Processing then + self:moveCards({ + ids = { cardUseEvent.cardId }, + toArea = Card.DiscardPile, + moveReason = fk.ReasonPutIntoDiscardPile, + }) + end end fk.room_callback["QuitRoom"] = function(jsonData) - -- jsonData: [ int uid ] - local data = json.decode(jsonData) - local player = fk.ServerInstance:findPlayer(tonumber(data[1])) - local room = player:getRoom() - if not room:isLobby() then - room:removePlayer(player) - end + -- jsonData: [ int uid ] + local data = json.decode(jsonData) + local player = fk.ServerInstance:findPlayer(tonumber(data[1])) + local room = player:getRoom() + if not room:isLobby() then + room:removePlayer(player) + end end fk.room_callback["AddRobot"] = function(jsonData) - -- jsonData: [ int uid ] - local data = json.decode(jsonData) - local player = fk.ServerInstance:findPlayer(tonumber(data[1])) - local room = player:getRoom() - - if not room:isLobby() then - room:addRobot(player) - end + -- jsonData: [ int uid ] + local data = json.decode(jsonData) + local player = fk.ServerInstance:findPlayer(tonumber(data[1])) + local room = player:getRoom() + + if not room:isLobby() then + room:addRobot(player) + end end fk.room_callback["PlayerRunned"] = function(jsonData) - -- jsonData: [ int runner_id, int robot_id ] - -- note: this function is not called by Router. - -- note: when this function is called, the room must be started - local data = json.decode(jsonData) - local runner = data[1] - local robot = data[2] - for _, p in ipairs(RoomInstance.players) do - if p:getId() == runner then - p.serverplayer = RoomInstance.room:findPlayer(robot) - p.id = p.serverplayer:getId() - end + -- jsonData: [ int runner_id, int robot_id ] + -- note: this function is not called by Router. + -- note: when this function is called, the room must be started + local data = json.decode(jsonData) + local runner = data[1] + local robot = data[2] + for _, p in ipairs(RoomInstance.players) do + if p:getId() == runner then + p.serverplayer = RoomInstance.room:findPlayer(robot) + p.id = p.serverplayer:getId() end + end end fk.room_callback["PlayerStateChanged"] = function(jsonData) - -- jsonData: [ int uid, string stateString ] - -- note: this function is not called by Router. - -- note: when this function is called, the room must be started - local data = json.decode(jsonData) - local id = data[1] - local stateString = data[2] - RoomInstance:getPlayerById(id).state = stateString + -- jsonData: [ int uid, string stateString ] + -- note: this function is not called by Router. + -- note: when this function is called, the room must be started + local data = json.decode(jsonData) + local id = data[1] + local stateString = data[2] + RoomInstance:getPlayerById(id).state = stateString end fk.room_callback["RoomDeleted"] = function(jsonData) - debug.sethook(function () - error("Room is deleted when running") - end, "l") + debug.sethook(function () + error("Room is deleted when running") + end, "l") end fk.room_callback["DoLuaScript"] = function(jsonData) - -- jsonData: [ int uid, string luaScript ] - -- warning: only use this in debugging mode. - if not DebugMode then return end - local data = json.decode(jsonData) - assert(load(data[2]))() + -- jsonData: [ int uid, string luaScript ] + -- warning: only use this in debugging mode. + if not DebugMode then return end + local data = json.decode(jsonData) + assert(load(data[2]))() end function CreateRoom(_room) - RoomInstance = Room:new(_room) + RoomInstance = Room:new(_room) end diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index cea10be3..debf21bd 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -12,30 +12,30 @@ local ServerPlayer = Player:subclass("ServerPlayer") function ServerPlayer:initialize(_self) - Player.initialize(self) - self.serverplayer = _self - self.id = _self:getId() - self.room = nil + Player.initialize(self) + self.serverplayer = _self + self.id = _self:getId() + self.room = nil - self.next = nil + self.next = nil - -- Below are for doBroadcastRequest - self.request_data = "" - self.client_reply = "" - self.default_reply = "" - self.reply_ready = false - self.phases = {} + -- Below are for doBroadcastRequest + self.request_data = "" + self.client_reply = "" + self.default_reply = "" + self.reply_ready = false + self.phases = {} end ---@return integer function ServerPlayer:getId() - return self.id + return self.id end ---@param command string ---@param jsonData string function ServerPlayer:doNotify(command, jsonData) - self.serverplayer:doNotify(command, jsonData) + self.serverplayer:doNotify(command, jsonData) end --- Send a request to client, and allow client to reply within *timeout* seconds. @@ -45,10 +45,10 @@ end ---@param jsonData string ---@param timeout integer function ServerPlayer:doRequest(command, jsonData, timeout) - timeout = timeout or self.room.timeout - self.client_reply = "" - self.reply_ready = false - self.serverplayer:doRequest(command, jsonData, timeout) + timeout = timeout or self.room.timeout + self.client_reply = "" + self.reply_ready = false + self.serverplayer:doRequest(command, jsonData, timeout) end --- Wait for at most *timeout* seconds for reply from client. @@ -57,153 +57,153 @@ end ---@param timeout integer @ seconds to wait ---@return string @ JSON data function ServerPlayer:waitForReply(timeout) - local result = "" - if timeout == nil then - result = self.serverplayer:waitForReply() - else - result = self.serverplayer:waitForReply(timeout) - end - self.request_data = "" - self.client_reply = result - if result ~= "" then self.reply_ready = true end - return result + local result = "" + if timeout == nil then + result = self.serverplayer:waitForReply() + else + result = self.serverplayer:waitForReply(timeout) + end + self.request_data = "" + self.client_reply = result + if result ~= "" then self.reply_ready = true end + return result end ---@param skill Skill function ServerPlayer:hasSkill(skill) - return table.contains(self.player_skills, skill) + return table.contains(self.player_skills, skill) end function ServerPlayer:isAlive() - return self.dead == false + return self.dead == false end function ServerPlayer:getNextAlive() - if #self.room.alive_players == 0 then - return self - end + if #self.room.alive_players == 0 then + return self + end - local ret = self.next - while ret.dead do - ret = ret.next - end - return ret + local ret = self.next + while ret.dead do + ret = ret.next + end + return ret end function ServerPlayer:turnOver() - self.faceup = not self.faceup - self.room:broadcastProperty(self, "faceup") + self.faceup = not self.faceup + self.room:broadcastProperty(self, "faceup") - -- TODO: log - self.room.logic:trigger(fk.TurnedOver, self) + -- TODO: log + self.room.logic:trigger(fk.TurnedOver, self) end ---@param from_phase Phase ---@param to_phase Phase function ServerPlayer:changePhase(from_phase, to_phase) - local room = self.room - local logic = room.logic - self.phase = Player.PhaseNone + local room = self.room + local logic = room.logic + self.phase = Player.PhaseNone - local phase_change = { - from = from_phase, - to = to_phase - } + local phase_change = { + from = from_phase, + to = to_phase + } - local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change) - if skip and to_phase ~= Player.NotActive then - self.phase = from_phase - return true - end + local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change) + if skip and to_phase ~= Player.NotActive then + self.phase = from_phase + return true + end - self.phase = to_phase - room:notifyProperty(self, self, "phase") + self.phase = to_phase + room:notifyProperty(self, self, "phase") - if #self.phases > 0 then - table.remove(self.phases, 1) - end - - if not logic:trigger(fk.EventPhaseStart, self) then - if self.phase ~= Player.NotActive then - logic:trigger(fk.EventPhaseProceeding, self) - end - end + if #self.phases > 0 then + table.remove(self.phases, 1) + end + if not logic:trigger(fk.EventPhaseStart, self) then if self.phase ~= Player.NotActive then - logic:trigger(fk.EventPhaseEnd, self) + logic:trigger(fk.EventPhaseProceeding, self) end + end - return false + if self.phase ~= Player.NotActive then + logic:trigger(fk.EventPhaseEnd, self) + end + + return false end ---@param phase_table Phase[] function ServerPlayer:play(phase_table) - phase_table = phase_table or {} - if #phase_table > 0 then - if not table.contains(phase_table, Player.NotActive) then - table.insert(phase_table, Player.NotActive) - end - else - phase_table = { - Player.RoundStart, Player.Start, - Player.Judge, Player.Draw, Player.Play, Player.Discard, - Player.Finish, Player.NotActive, - } + phase_table = phase_table or {} + if #phase_table > 0 then + if not table.contains(phase_table, Player.NotActive) then + table.insert(phase_table, Player.NotActive) + end + else + phase_table = { + Player.RoundStart, Player.Start, + Player.Judge, Player.Draw, Player.Play, Player.Discard, + 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 - self.phases = phase_table - self.phase_state = {} + self.phase_index = i + local phase_change = { + from = self.phase, + to = phases[i] + } - local phases = self.phases - local phase_state = self.phase_state - local room = self.room + local logic = self.room.logic + self.phase = Player.PhaseNone - for i = 1, #phases do - phase_state[i] = { - phase = phases[i], - skipped = false - } + 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 - for i = 1, #phases do - if self.dead then - self:changePhase(self.phase, Player.NotActive) - break + 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 - self.phase_index = i - local phase_change = { - from = self.phase, - 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 + if self.phase ~= Player.NotActive then + logic:trigger(fk.EventPhaseEnd, self) + else break end end + end end return ServerPlayer diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua index ddb13229..4fab41b7 100644 --- a/lua/server/system_enum.lua +++ b/lua/server/system_enum.lua @@ -10,6 +10,9 @@ ---@alias DyingStruct { 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 @@ -21,6 +24,8 @@ fk.ReasonPut = 5 fk.ReasonPutIntoDiscardPile = 6 fk.ReasonPrey = 7 fk.ReasonExchange = 8 +fk.ReasonUse = 9 +fk.ReasonResonpse = 10 ---@alias DamageType integer diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index a82eb846..c08d29fe 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -1,118 +1,133 @@ GameRule = fk.CreateTriggerSkill{ - name = "game_rule", - events = { - fk.GameStart, fk.DrawInitialCards, fk.TurnStart, - fk.EventPhaseProceeding, fk.EventPhaseEnd, fk.EventPhaseChanging, - }, - priority = 0, + name = "game_rule", + events = { + fk.GameStart, fk.DrawInitialCards, fk.TurnStart, + fk.EventPhaseProceeding, fk.EventPhaseEnd, fk.EventPhaseChanging, + }, + priority = 0, - can_trigger = function(self, event, target, player, data) - return (target == player) or (target == nil) - end, + can_trigger = function(self, event, target, player, data) + return (target == player) or (target == nil) + end, - on_trigger = function(self, event, target, player, data) - if RoomInstance.tag["SkipGameRule"] then - RoomInstance.tag["SkipGameRule"] = false - return false + on_trigger = function(self, event, target, player, data) + if RoomInstance.tag["SkipGameRule"] then + RoomInstance.tag["SkipGameRule"] = 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 - 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 - - 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 + 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() + 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, } diff --git a/packages/standard/init.lua b/packages/standard/init.lua index 3198ce68..41b97242 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -3,161 +3,161 @@ extension.metadata = require "packages.standard.metadata" dofile "packages/standard/game_rule.lua" Fk:loadTranslationTable{ - ["standard"] = "标准包", - ["wei"] = "魏", - ["shu"] = "蜀", - ["wu"] = "吴", - ["qun"] = "群", + ["standard"] = "标准包", + ["wei"] = "魏", + ["shu"] = "蜀", + ["wu"] = "吴", + ["qun"] = "群", } local caocao = General:new(extension, "caocao", "wei", 4) extension:addGeneral(caocao) Fk:loadTranslationTable{ - ["caocao"] = "曹操", + ["caocao"] = "曹操", } local simayi = General:new(extension, "simayi", "wei", 3) extension:addGeneral(simayi) Fk:loadTranslationTable{ - ["simayi"] = "司马懿", + ["simayi"] = "司马懿", } local xiahoudun = General:new(extension, "xiahoudun", "wei", 4) extension:addGeneral(xiahoudun) Fk:loadTranslationTable{ - ["xiahoudun"] = "夏侯惇", + ["xiahoudun"] = "夏侯惇", } local zhangliao = General:new(extension, "zhangliao", "wei", 4) extension:addGeneral(zhangliao) Fk:loadTranslationTable{ - ["zhangliao"] = "张辽", + ["zhangliao"] = "张辽", } local xuchu = General:new(extension, "xuchu", "wei", 4) extension:addGeneral(xuchu) Fk:loadTranslationTable{ - ["xuchu"] = "许褚", + ["xuchu"] = "许褚", } local guojia = General:new(extension, "guojia", "wei", 4) extension:addGeneral(guojia) Fk:loadTranslationTable{ - ["guojia"] = "郭嘉", + ["guojia"] = "郭嘉", } local zhenji = General:new(extension, "zhenji", "wei", 3) extension:addGeneral(zhenji) Fk:loadTranslationTable{ - ["zhenji"] = "甄姬", + ["zhenji"] = "甄姬", } local liubei = General:new(extension, "liubei", "shu", 4) extension:addGeneral(liubei) Fk:loadTranslationTable{ - ["liubei"] = "刘备", + ["liubei"] = "刘备", } local guanyu = General:new(extension, "guanyu", "shu", 4) extension:addGeneral(guanyu) Fk:loadTranslationTable{ - ["guanyu"] = "关羽", + ["guanyu"] = "关羽", } local zhangfei = General:new(extension, "zhangfei", "shu", 4) extension:addGeneral(zhangfei) Fk:loadTranslationTable{ - ["zhangfei"] = "张飞", + ["zhangfei"] = "张飞", } local zhugeliang = General:new(extension, "zhugeliang", "shu", 3) extension:addGeneral(zhugeliang) Fk:loadTranslationTable{ - ["zhugeliang"] = "诸葛亮", + ["zhugeliang"] = "诸葛亮", } local zhaoyun = General:new(extension, "zhaoyun", "shu", 4) extension:addGeneral(zhaoyun) Fk:loadTranslationTable{ - ["zhaoyun"] = "赵云", + ["zhaoyun"] = "赵云", } local machao = General:new(extension, "machao", "shu", 4) extension:addGeneral(machao) Fk:loadTranslationTable{ - ["machao"] = "马超", + ["machao"] = "马超", } local huangyueying = General:new(extension, "huangyueying", "shu", 3) extension:addGeneral(huangyueying) Fk:loadTranslationTable{ - ["huangyueying"] = "黄月英", + ["huangyueying"] = "黄月英", } local sunquan = General:new(extension, "sunquan", "wu", 4) extension:addGeneral(sunquan) Fk:loadTranslationTable{ - ["sunquan"] = "孙权", + ["sunquan"] = "孙权", } local ganning = General:new(extension, "ganning", "wu", 4) extension:addGeneral(ganning) Fk:loadTranslationTable{ - ["ganning"] = "甘宁", + ["ganning"] = "甘宁", } local lvmeng = General:new(extension, "lvmeng", "wu", 4) extension:addGeneral(lvmeng) Fk:loadTranslationTable{ - ["lvmeng"] = "吕蒙", + ["lvmeng"] = "吕蒙", } local huanggai = General:new(extension, "huanggai", "wu", 4) extension:addGeneral(huanggai) Fk:loadTranslationTable{ - ["huanggai"] = "黄盖", + ["huanggai"] = "黄盖", } local zhouyu = General:new(extension, "zhouyu", "wu", 3) extension:addGeneral(zhouyu) Fk:loadTranslationTable{ - ["zhouyu"] = "周瑜", + ["zhouyu"] = "周瑜", } local daqiao = General:new(extension, "daqiao", "wu", 3) extension:addGeneral(daqiao) Fk:loadTranslationTable{ - ["daqiao"] = "大乔", + ["daqiao"] = "大乔", } local luxun = General:new(extension, "luxun", "wu", 3) extension:addGeneral(luxun) Fk:loadTranslationTable{ - ["luxun"] = "陆逊", + ["luxun"] = "陆逊", } local sunshangxiang = General:new(extension, "sunshangxiang", "wu", 3) extension:addGeneral(sunshangxiang) Fk:loadTranslationTable{ - ["sunshangxiang"] = "孙尚香", + ["sunshangxiang"] = "孙尚香", } local huatuo = General:new(extension, "huatuo", "qun", 3) extension:addGeneral(huatuo) Fk:loadTranslationTable{ - ["huatuo"] = "华佗", + ["huatuo"] = "华佗", } local lvbu = General:new(extension, "lvbu", "qun", 4) extension:addGeneral(lvbu) Fk:loadTranslationTable{ - ["lvbu"] = "吕布", + ["lvbu"] = "吕布", } local diaochan = General:new(extension, "diaochan", "qun", 3) extension:addGeneral(diaochan) Fk:loadTranslationTable{ - ["diaochan"] = "貂蝉", + ["diaochan"] = "貂蝉", } return extension diff --git a/packages/standard/metadata.lua b/packages/standard/metadata.lua index 51cf16a8..e4c0793f 100644 --- a/packages/standard/metadata.lua +++ b/packages/standard/metadata.lua @@ -1,14 +1,14 @@ return { - name = "standard", - author = "official", - description = "", - collaborators = { - program = {}, - designer = {}, - cv = {}, - illustrator = {}, - }, + name = "standard", + author = "official", + description = "", + collaborators = { + program = {}, + designer = {}, + cv = {}, + illustrator = {}, + }, - dependencies = {}, - extra_files = {}, + dependencies = {}, + extra_files = {}, } diff --git a/packages/standard_cards/init.lua b/packages/standard_cards/init.lua index 145e4e2f..7243f6e3 100644 --- a/packages/standard_cards/init.lua +++ b/packages/standard_cards/init.lua @@ -2,510 +2,523 @@ local extension = Package:new("standard_cards", Package.CardPack) extension.metadata = require "packages.standard_cards.metadata" Fk:loadTranslationTable{ - ["standard_cards"] = "标+EX" + ["standard_cards"] = "标+EX" } local slash = fk.CreateBasicCard{ - name = "slash", - number = 7, - suit = Card.Spade, + name = "slash", + number = 7, + suit = Card.Spade, } Fk:loadTranslationTable{ - ["slash"] = "杀", + ["slash"] = "杀", } extension:addCards({ - slash, - slash:clone(Card.Spade, 8), - slash:clone(Card.Spade, 8), - slash:clone(Card.Spade, 9), - slash:clone(Card.Spade, 9), - slash:clone(Card.Spade, 10), - slash:clone(Card.Spade, 10), + slash, + slash:clone(Card.Spade, 8), + slash:clone(Card.Spade, 8), + slash:clone(Card.Spade, 9), + slash:clone(Card.Spade, 9), + slash:clone(Card.Spade, 10), + slash:clone(Card.Spade, 10), - slash:clone(Card.Club, 2), - slash:clone(Card.Club, 3), - slash:clone(Card.Club, 4), - slash:clone(Card.Club, 5), - slash:clone(Card.Club, 6), - slash:clone(Card.Club, 7), - slash:clone(Card.Club, 8), - slash:clone(Card.Club, 8), - slash:clone(Card.Club, 9), - slash:clone(Card.Club, 9), - slash:clone(Card.Club, 10), - slash:clone(Card.Club, 10), - slash:clone(Card.Club, 11), - slash:clone(Card.Club, 11), + slash:clone(Card.Club, 2), + slash:clone(Card.Club, 3), + slash:clone(Card.Club, 4), + slash:clone(Card.Club, 5), + slash:clone(Card.Club, 6), + slash:clone(Card.Club, 7), + slash:clone(Card.Club, 8), + slash:clone(Card.Club, 8), + slash:clone(Card.Club, 9), + slash:clone(Card.Club, 9), + slash:clone(Card.Club, 10), + slash:clone(Card.Club, 10), + slash:clone(Card.Club, 11), + slash:clone(Card.Club, 11), - slash:clone(Card.Heart, 10), - slash:clone(Card.Heart, 10), - slash:clone(Card.Heart, 11), + slash:clone(Card.Heart, 10), + slash:clone(Card.Heart, 10), + slash:clone(Card.Heart, 11), - slash:clone(Card.Diamond, 6), - slash:clone(Card.Diamond, 7), - slash:clone(Card.Diamond, 8), - slash:clone(Card.Diamond, 9), - slash:clone(Card.Diamond, 10), - slash:clone(Card.Diamond, 13), + slash:clone(Card.Diamond, 6), + slash:clone(Card.Diamond, 7), + slash:clone(Card.Diamond, 8), + slash:clone(Card.Diamond, 9), + slash:clone(Card.Diamond, 10), + slash:clone(Card.Diamond, 13), }) local jink = fk.CreateBasicCard{ - name = "jink", - suit = Card.Heart, - number = 2, + name = "jink", + suit = Card.Heart, + number = 2, } Fk:loadTranslationTable{ - ["jink"] = "闪", + ["jink"] = "闪", } extension:addCards({ - jink, - jink:clone(Card.Heart, 2), - jink:clone(Card.Heart, 13), + jink, + jink:clone(Card.Heart, 2), + jink:clone(Card.Heart, 13), - jink:clone(Card.Diamond, 2), - jink:clone(Card.Diamond, 2), - jink:clone(Card.Diamond, 3), - jink:clone(Card.Diamond, 4), - jink:clone(Card.Diamond, 5), - jink:clone(Card.Diamond, 6), - jink:clone(Card.Diamond, 7), - jink:clone(Card.Diamond, 8), - jink:clone(Card.Diamond, 9), - jink:clone(Card.Diamond, 10), - jink:clone(Card.Diamond, 11), - jink:clone(Card.Diamond, 11), + jink:clone(Card.Diamond, 2), + jink:clone(Card.Diamond, 2), + jink:clone(Card.Diamond, 3), + jink:clone(Card.Diamond, 4), + jink:clone(Card.Diamond, 5), + jink:clone(Card.Diamond, 6), + jink:clone(Card.Diamond, 7), + jink:clone(Card.Diamond, 8), + jink:clone(Card.Diamond, 9), + jink:clone(Card.Diamond, 10), + jink:clone(Card.Diamond, 11), + jink:clone(Card.Diamond, 11), }) local peach = fk.CreateBasicCard{ - name = "peach", - suit = Card.Heart, - number = 3, + name = "peach", + suit = Card.Heart, + number = 3, } Fk:loadTranslationTable{ - ["peach"] = "桃", + ["peach"] = "桃", } extension:addCards({ - peach, - peach:clone(Card.Heart, 4), - peach:clone(Card.Heart, 6), - peach:clone(Card.Heart, 7), - peach:clone(Card.Heart, 8), - peach:clone(Card.Heart, 9), - peach:clone(Card.Heart, 12), - peach:clone(Card.Heart, 12), + peach, + peach:clone(Card.Heart, 4), + peach:clone(Card.Heart, 6), + peach:clone(Card.Heart, 7), + peach:clone(Card.Heart, 8), + peach:clone(Card.Heart, 9), + peach:clone(Card.Heart, 12), + peach:clone(Card.Heart, 12), }) local dismantlement = fk.CreateTrickCard{ - name = "dismantlement", - suit = Card.Spade, - number = 3, + name = "dismantlement", + suit = Card.Spade, + number = 3, } Fk:loadTranslationTable{ - ["dismantlement"] = "过河拆桥", + ["dismantlement"] = "过河拆桥", } extension:addCards({ - dismantlement, - dismantlement:clone(Card.Spade, 4), - dismantlement:clone(Card.Spade, 12), + dismantlement, + dismantlement:clone(Card.Spade, 4), + dismantlement:clone(Card.Spade, 12), - dismantlement:clone(Card.Club, 3), - dismantlement:clone(Card.Club, 4), + dismantlement:clone(Card.Club, 3), + dismantlement:clone(Card.Club, 4), - dismantlement:clone(Card.Heart, 12), + dismantlement:clone(Card.Heart, 12), }) local snatch = fk.CreateTrickCard{ - name = "snatch", - suit = Card.Spade, - number = 3, + name = "snatch", + suit = Card.Spade, + number = 3, } Fk:loadTranslationTable{ - ["snatch"] = "顺手牵羊", + ["snatch"] = "顺手牵羊", } extension:addCards({ - snatch, - snatch:clone(Card.Spade, 4), - snatch:clone(Card.Spade, 11), + snatch, + snatch:clone(Card.Spade, 4), + snatch:clone(Card.Spade, 11), - snatch:clone(Card.Diamond, 3), - snatch:clone(Card.Diamond, 4), + snatch:clone(Card.Diamond, 3), + snatch:clone(Card.Diamond, 4), }) local duel = fk.CreateTrickCard{ - name = "duel", - suit = Card.Spade, - number = 1, + name = "duel", + suit = Card.Spade, + number = 1, } Fk:loadTranslationTable{ - ["duel"] = "决斗", + ["duel"] = "决斗", } 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{ - name = "collateral", - suit = Card.Club, - number = 12, + name = "collateral", + suit = Card.Club, + number = 12, } Fk:loadTranslationTable{ - ["collateral"] = "借刀杀人", + ["collateral"] = "借刀杀人", } extension:addCards({ - collateral, - collateral:clone(Card.Club, 13), + collateral, + 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{ - name = "ex_nihilo", - suit = Card.Heart, - number = 7, + name = "ex_nihilo", + suit = Card.Heart, + number = 7, + skill = exNihiloSkill, } Fk:loadTranslationTable{ - ["ex_nihilo"] = "无中生有", + ["ex_nihilo"] = "无中生有", } extension:addCards({ - exNihilo, - exNihilo:clone(Card.Heart, 8), - exNihilo:clone(Card.Heart, 9), - exNihilo:clone(Card.Heart, 11), + exNihilo, + exNihilo:clone(Card.Heart, 8), + exNihilo:clone(Card.Heart, 9), + exNihilo:clone(Card.Heart, 11), }) local nullification = fk.CreateTrickCard{ - name = "nullification", - suit = Card.Spade, - number = 11, + name = "nullification", + suit = Card.Spade, + number = 11, } Fk:loadTranslationTable{ - ["nullification"] = "无懈可击", + ["nullification"] = "无懈可击", } extension:addCards({ - nullification, + nullification, - nullification:clone(Card.Club, 12), - nullification:clone(Card.Club, 13), + nullification:clone(Card.Club, 12), + nullification:clone(Card.Club, 13), - nullification:clone(Card.Diamond, 12), + nullification:clone(Card.Diamond, 12), }) local savageAssault = fk.CreateTrickCard{ - name = "savage_assault", - suit = Card.Spade, - number = 7, + name = "savage_assault", + suit = Card.Spade, + number = 7, } Fk:loadTranslationTable{ - ["savage_assault"] = "南蛮入侵", + ["savage_assault"] = "南蛮入侵", } extension:addCards({ - savageAssault, - savageAssault:clone(Card.Spade, 13), - savageAssault:clone(Card.Club, 7), + savageAssault, + savageAssault:clone(Card.Spade, 13), + savageAssault:clone(Card.Club, 7), }) local archeryAttack = fk.CreateTrickCard{ - name = "archery_attack", - suit = Card.Heart, - number = 1, + name = "archery_attack", + suit = Card.Heart, + number = 1, } Fk:loadTranslationTable{ - ["archery_attack"] = "万箭齐发", + ["archery_attack"] = "万箭齐发", } extension:addCards({ - archeryAttack, + archeryAttack, }) local godSalvation = fk.CreateTrickCard{ - name = "god_salvation", - suit = Card.Heart, - number = 1, + name = "god_salvation", + suit = Card.Heart, + number = 1, } Fk:loadTranslationTable{ - ["god_salvation"] = "桃园结义", + ["god_salvation"] = "桃园结义", } extension:addCards({ - godSalvation, + godSalvation, }) local amazingGrace = fk.CreateTrickCard{ - name = "amazing_grace", - suit = Card.Heart, - number = 3, + name = "amazing_grace", + suit = Card.Heart, + number = 3, } Fk:loadTranslationTable{ - ["amazing_grace"] = "五谷丰登", + ["amazing_grace"] = "五谷丰登", } extension:addCards({ - amazingGrace, - amazingGrace:clone(Card.Heart, 4), + amazingGrace, + amazingGrace:clone(Card.Heart, 4), }) local lightning = fk.CreateDelayedTrickCard{ - name = "lightning", - suit = Card.Spade, - number = 1, + name = "lightning", + suit = Card.Spade, + number = 1, } Fk:loadTranslationTable{ - ["lightning"] = "闪电", + ["lightning"] = "闪电", } extension:addCards({ - lightning, - lightning:clone(Card.Heart, 12), + lightning, + lightning:clone(Card.Heart, 12), }) local indulgence = fk.CreateDelayedTrickCard{ - name = "indulgence", - suit = Card.Spade, - number = 6, + name = "indulgence", + suit = Card.Spade, + number = 6, } Fk:loadTranslationTable{ - ["indulgence"] = "乐不思蜀", + ["indulgence"] = "乐不思蜀", } extension:addCards({ - indulgence, - indulgence:clone(Card.Club, 6), - indulgence:clone(Card.Heart, 6), + indulgence, + indulgence:clone(Card.Club, 6), + indulgence:clone(Card.Heart, 6), }) local crossbow = fk.CreateWeapon{ - name = "crossbow", - suit = Card.Club, - number = 1, + name = "crossbow", + suit = Card.Club, + number = 1, } Fk:loadTranslationTable{ - ["crossbow"] = "诸葛连弩", + ["crossbow"] = "诸葛连弩", } extension:addCards({ - crossbow, - crossbow:clone(Card.Diamond, 1), + crossbow, + crossbow:clone(Card.Diamond, 1), }) local qingGang = fk.CreateWeapon{ - name = "qinggang_sword", - suit = Card.Spade, - number = 6, + name = "qinggang_sword", + suit = Card.Spade, + number = 6, } Fk:loadTranslationTable{ - ["qinggang_sword"] = "青釭剑", + ["qinggang_sword"] = "青釭剑", } extension:addCards({ - qingGang, + qingGang, }) local iceSword = fk.CreateWeapon{ - name = "ice_sword", - suit = Card.Spade, - number = 2, + name = "ice_sword", + suit = Card.Spade, + number = 2, } Fk:loadTranslationTable{ - ["ice_sword"] = "寒冰剑", + ["ice_sword"] = "寒冰剑", } extension:addCards({ - iceSword, + iceSword, }) local doubleSwords = fk.CreateWeapon{ - name = "double_swords", - suit = Card.Spade, - number = 2, + name = "double_swords", + suit = Card.Spade, + number = 2, } Fk:loadTranslationTable{ - ["double_swords"] = "雌雄双股剑", + ["double_swords"] = "雌雄双股剑", } extension:addCards({ - doubleSwords, + doubleSwords, }) local blade = fk.CreateWeapon{ - name = "blade", - suit = Card.Spade, - number = 5, + name = "blade", + suit = Card.Spade, + number = 5, } Fk:loadTranslationTable{ - ["blade"] = "青龙偃月刀", + ["blade"] = "青龙偃月刀", } extension:addCards({ - blade, + blade, }) local spear = fk.CreateWeapon{ - name = "spear", - suit = Card.Spade, - number = 12, + name = "spear", + suit = Card.Spade, + number = 12, } Fk:loadTranslationTable{ - ["spear"] = "丈八蛇矛", + ["spear"] = "丈八蛇矛", } extension:addCards({ - spear, + spear, }) local axe = fk.CreateWeapon{ - name = "axe", - suit = Card.Diamond, - number = 5, + name = "axe", + suit = Card.Diamond, + number = 5, } Fk:loadTranslationTable{ - ["axe"] = "贯石斧", + ["axe"] = "贯石斧", } extension:addCards({ - axe, + axe, }) local halberd = fk.CreateWeapon{ - name = "halberd", - suit = Card.Diamond, - number = 12, + name = "halberd", + suit = Card.Diamond, + number = 12, } Fk:loadTranslationTable{ - ["halberd"] = "方天画戟", + ["halberd"] = "方天画戟", } extension:addCards({ - halberd, + halberd, }) local kylinBow = fk.CreateWeapon{ - name = "kylin_bow", - suit = Card.Heart, - number = 5, + name = "kylin_bow", + suit = Card.Heart, + number = 5, } Fk:loadTranslationTable{ - ["kylin_bow"] = "麒麟弓", + ["kylin_bow"] = "麒麟弓", } extension:addCards({ - kylinBow, + kylinBow, }) local eightDiagram = fk.CreateArmor{ - name = "eight_diagram", - suit = Card.Spade, - number = 2, + name = "eight_diagram", + suit = Card.Spade, + number = 2, } Fk:loadTranslationTable{ - ["eight_diagram"] = "八卦阵", + ["eight_diagram"] = "八卦阵", } extension:addCards({ - eightDiagram, - eightDiagram:clone(Card.Club, 2), + eightDiagram, + eightDiagram:clone(Card.Club, 2), }) local niohShield = fk.CreateArmor{ - name = "nioh_shield", - suit = Card.Club, - number = 2, + name = "nioh_shield", + suit = Card.Club, + number = 2, } Fk:loadTranslationTable{ - ["nioh_shield"] = "仁王盾", + ["nioh_shield"] = "仁王盾", } extension:addCards({ - niohShield, + niohShield, }) local diLu = fk.CreateDefensiveRide{ - name = "dilu", - suit = Card.Club, - number = 5, + name = "dilu", + suit = Card.Club, + number = 5, } Fk:loadTranslationTable{ - ["dilu"] = "的卢", + ["dilu"] = "的卢", } extension:addCards({ - diLu, + diLu, }) local jueYing = fk.CreateDefensiveRide{ - name = "jueying", - suit = Card.Spade, - number = 5, + name = "jueying", + suit = Card.Spade, + number = 5, } Fk:loadTranslationTable{ - ["jueying"] = "绝影", + ["jueying"] = "绝影", } extension:addCards({ - jueYing, + jueYing, }) local zhuaHuangFeiDian = fk.CreateDefensiveRide{ - name = "zhuahuangfeidian", - suit = Card.Heart, - number = 13, + name = "zhuahuangfeidian", + suit = Card.Heart, + number = 13, } Fk:loadTranslationTable{ - ["zhuahuangfeidian"] = "爪黄飞电", + ["zhuahuangfeidian"] = "爪黄飞电", } extension:addCards({ - zhuaHuangFeiDian, + zhuaHuangFeiDian, }) local chiTu = fk.CreateOffensiveRide{ - name = "chitu", - suit = Card.Heart, - number = 5, + name = "chitu", + suit = Card.Heart, + number = 5, } Fk:loadTranslationTable{ - ["chitu"] = "赤兔", + ["chitu"] = "赤兔", } extension:addCards({ - chiTu, + chiTu, }) local daYuan = fk.CreateOffensiveRide{ - name = "dayuan", - suit = Card.Spade, - number = 13, + name = "dayuan", + suit = Card.Spade, + number = 13, } Fk:loadTranslationTable{ - ["dayuan"] = "大宛", + ["dayuan"] = "大宛", } extension:addCards({ - daYuan, + daYuan, }) local ziXing = fk.CreateOffensiveRide{ - name = "zixing", - suit = Card.Heart, - number = 5, + name = "zixing", + suit = Card.Heart, + number = 5, } Fk:loadTranslationTable{ - ["zixing"] = "紫骍", + ["zixing"] = "紫骍", } extension:addCards({ - ziXing, + ziXing, }) return extension diff --git a/packages/standard_cards/metadata.lua b/packages/standard_cards/metadata.lua index bb56d252..b127e681 100644 --- a/packages/standard_cards/metadata.lua +++ b/packages/standard_cards/metadata.lua @@ -1,14 +1,14 @@ return { - name = "standard_cards", - author = "official", - description = "", - collaborators = { - program = {}, - designer = {}, - cv = {}, - illustrator = {}, - }, + name = "standard_cards", + author = "official", + description = "", + collaborators = { + program = {}, + designer = {}, + cv = {}, + illustrator = {}, + }, - dependencies = {}, - extra_files = {}, + dependencies = {}, + extra_files = {}, } diff --git a/qml/Config.qml b/qml/Config.qml index 17d08bad..db9a5b72 100644 --- a/qml/Config.qml +++ b/qml/Config.qml @@ -1,13 +1,13 @@ import QtQuick 2.15 QtObject { - // Client configuration + // Client configuration - // Player property of client - property string screenName: "" - property string password: "" + // Player property of client + property string screenName: "" + property string password: "" - // Client data - property int roomCapacity: 0 - property int roomTimeout: 0 + // Client data + property int roomCapacity: 0 + property int roomTimeout: 0 } diff --git a/qml/GlobalPopups/CreateRoom.qml b/qml/GlobalPopups/CreateRoom.qml index 8a6383bd..0fa8d71e 100644 --- a/qml/GlobalPopups/CreateRoom.qml +++ b/qml/GlobalPopups/CreateRoom.qml @@ -3,62 +3,62 @@ import QtQuick.Controls 2.0 import QtQuick.Layouts 1.15 Item { - id: root + id: root - width: childrenRect.width - height: childrenRect.height + width: childrenRect.width + height: childrenRect.height - signal finished() + signal finished() - ColumnLayout { - spacing: 20 + ColumnLayout { + spacing: 20 - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: "Room Name" - } - TextField { - id: roomName - font.pixelSize: 18 - 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: "Room Name" + } + TextField { + id: roomName + font.pixelSize: 18 + 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(); + } + } + } + } } diff --git a/qml/GlobalPopups/EditProfile.qml b/qml/GlobalPopups/EditProfile.qml index c1ceb0d6..acb5b666 100644 --- a/qml/GlobalPopups/EditProfile.qml +++ b/qml/GlobalPopups/EditProfile.qml @@ -3,98 +3,98 @@ import QtQuick.Controls 2.0 import QtQuick.Layouts 1.15 Item { - id: root + id: root - width: childrenRect.width - height: childrenRect.height + width: childrenRect.width + height: childrenRect.height - signal finished() + signal finished() - ColumnLayout { - spacing: 20 + ColumnLayout { + spacing: 20 - RowLayout { - anchors.rightMargin: 8 - spacing: 16 - Text { - text: "Username" - } - Text { - text: Self.screenName - 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: "Username" + } + Text { + text: Self.screenName + 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(); + } + } + } + } } diff --git a/qml/GlobalPopups/Test.qml b/qml/GlobalPopups/Test.qml index 001f9e78..a893f772 100644 --- a/qml/GlobalPopups/Test.qml +++ b/qml/GlobalPopups/Test.qml @@ -1,5 +1,5 @@ import QtQuick 2.0 Text { - text: "dsdsd" + text: "dsdsd" } \ No newline at end of file diff --git a/qml/Logic.js b/qml/Logic.js index 050dd905..f1b16800 100644 --- a/qml/Logic.js +++ b/qml/Logic.js @@ -1,53 +1,53 @@ var callbacks = {}; callbacks["NetworkDelayTest"] = function(jsonData) { - ClientInstance.notifyServer("Setup", JSON.stringify([ - config.screenName, - config.password - ])); + ClientInstance.notifyServer("Setup", JSON.stringify([ + config.screenName, + config.password + ])); } callbacks["ErrorMsg"] = function(jsonData) { - console.log("ERROR: " + jsonData); - toast.show(jsonData, 5000); - mainWindow.busy = false; + console.log("ERROR: " + jsonData); + toast.show(jsonData, 5000); + mainWindow.busy = false; } callbacks["BackToStart"] = function(jsonData) { - while (mainStack.depth > 1) { - mainStack.pop(); - } + while (mainStack.depth > 1) { + mainStack.pop(); + } } callbacks["EnterLobby"] = function(jsonData) { - // depth == 1 means the lobby page is not present in mainStack - if (mainStack.depth === 1) { - mainStack.push(lobby); - } else { - mainStack.pop(); - } - mainWindow.busy = false; + // depth == 1 means the lobby page is not present in mainStack + if (mainStack.depth === 1) { + mainStack.push(lobby); + } else { + mainStack.pop(); + } + mainWindow.busy = false; } callbacks["EnterRoom"] = function(jsonData) { - // jsonData: int capacity, int timeout - let data = JSON.parse(jsonData); - config.roomCapacity = data[0]; - config.roomTimeout = data[1]; - mainStack.push(room); - mainWindow.busy = false; + // jsonData: int capacity, int timeout + let data = JSON.parse(jsonData); + config.roomCapacity = data[0]; + config.roomTimeout = data[1]; + mainStack.push(room); + mainWindow.busy = false; } callbacks["UpdateRoomList"] = function(jsonData) { - let current = mainStack.currentItem; // should be lobby - current.roomModel.clear(); - JSON.parse(jsonData).forEach(function(room) { - current.roomModel.append({ - roomId: room[0], - roomName: room[1], - gameMode: room[2], - playerNum: room[3], - capacity: room[4], - }); + let current = mainStack.currentItem; // should be lobby + current.roomModel.clear(); + JSON.parse(jsonData).forEach(function(room) { + current.roomModel.append({ + roomId: room[0], + roomName: room[1], + gameMode: room[2], + playerNum: room[3], + capacity: room[4], }); + }); } diff --git a/qml/Pages/CardsOverview.qml b/qml/Pages/CardsOverview.qml index db582201..46e82c63 100644 --- a/qml/Pages/CardsOverview.qml +++ b/qml/Pages/CardsOverview.qml @@ -4,49 +4,49 @@ import QtQuick.Controls 2.0 import "RoomElement" Item { - id: root + id: root - property bool loaded: false + property bool loaded: false - ListView { - width: Math.floor(root.width / 98) * 98 - height: parent.height - anchors.centerIn: parent - ScrollBar.vertical: ScrollBar {} - model: ListModel { - id: packages - } + ListView { + width: Math.floor(root.width / 98) * 98 + height: parent.height + anchors.centerIn: parent + ScrollBar.vertical: ScrollBar {} + model: ListModel { + id: packages + } - delegate: ColumnLayout { - Text { text: Backend.translate(name) } - GridLayout { - columns: root.width / 98 - Repeater { - model: JSON.parse(Backend.getCards(name)) - CardItem { - autoBack: false - Component.onCompleted: { - let data = JSON.parse(Backend.getCardData(modelData)); - setData(data); - } - } - } + delegate: ColumnLayout { + Text { text: Backend.translate(name) } + GridLayout { + columns: root.width / 98 + Repeater { + model: JSON.parse(Backend.callLuaFunction("GetCards", [name])) + CardItem { + autoBack: false + Component.onCompleted: { + let data = JSON.parse(Backend.callLuaFunction("GetCardData", [modelData])); + setData(data); } + } } + } } + } - Button { - text: "Quit" - anchors.right: parent.right - onClicked: { - mainStack.pop(); - } + Button { + text: "Quit" + anchors.right: parent.right + onClicked: { + mainStack.pop(); } + } - function loadPackages() { - if (loaded) return; - let packs = JSON.parse(Backend.getAllCardPack()); - packs.forEach((name) => packages.append({ name: name })); - loaded = true; - } + function loadPackages() { + if (loaded) return; + let packs = JSON.parse(Backend.callLuaFunction("GetAllCardPack", [])); + packs.forEach((name) => packages.append({ name: name })); + loaded = true; + } } diff --git a/qml/Pages/GeneralsOverview.qml b/qml/Pages/GeneralsOverview.qml index d2fd23c5..622008cf 100644 --- a/qml/Pages/GeneralsOverview.qml +++ b/qml/Pages/GeneralsOverview.qml @@ -4,50 +4,50 @@ import QtQuick.Controls 2.0 import "RoomElement" Item { - id: root + id: root - property bool loaded: false + property bool loaded: false - ListView { - width: Math.floor(root.width / 98) * 98 - height: parent.height - anchors.centerIn: parent - ScrollBar.vertical: ScrollBar {} - model: ListModel { - id: packages - } + ListView { + width: Math.floor(root.width / 98) * 98 + height: parent.height + anchors.centerIn: parent + ScrollBar.vertical: ScrollBar {} + model: ListModel { + id: packages + } - delegate: ColumnLayout { - Text { text: Backend.translate(name) } - GridLayout { - columns: root.width / 98 - Repeater { - model: JSON.parse(Backend.getGenerals(name)) - GeneralCardItem { - autoBack: false - Component.onCompleted: { - let data = JSON.parse(Backend.getGeneralData(modelData)); - name = modelData; - kingdom = data.kingdom; - } - } - } + delegate: ColumnLayout { + Text { text: Backend.translate(name) } + GridLayout { + columns: root.width / 98 + Repeater { + model: JSON.parse(Backend.callLuaFunction("GetGenerals", [name])) + GeneralCardItem { + autoBack: false + Component.onCompleted: { + let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [modelData])); + name = modelData; + kingdom = data.kingdom; } + } } + } } + } - Button { - text: "Quit" - anchors.right: parent.right - onClicked: { - mainStack.pop(); - } + Button { + text: "Quit" + anchors.right: parent.right + onClicked: { + mainStack.pop(); } + } - function loadPackages() { - if (loaded) return; - let packs = JSON.parse(Backend.getAllGeneralPack()); - packs.forEach((name) => packages.append({ name: name })); - loaded = true; - } + function loadPackages() { + if (loaded) return; + let packs = JSON.parse(Backend.callLuaFunction("GetAllGeneralPack", [])); + packs.forEach((name) => packages.append({ name: name })); + loaded = true; + } } diff --git a/qml/Pages/Init.qml b/qml/Pages/Init.qml index 32622c56..0d513301 100644 --- a/qml/Pages/Init.qml +++ b/qml/Pages/Init.qml @@ -2,50 +2,50 @@ import QtQuick 2.15 import QtQuick.Controls 2.0 Item { - id: root + id: root - Frame { - id: join_server - anchors.centerIn: parent - Column { - spacing: 8 - TextField { - id: server_addr - text: "127.0.0.1" - } - TextField { - id: screenNameEdit - text: "player" - } - /*TextField { - id: avatarEdit - text: "liubei" - }*/ - TextField { - id: passwordEdit - text: "" - echoMode: TextInput.Password - passwordCharacter: "*" - } - Button { - text: "Join Server" - onClicked: { - config.screenName = screenNameEdit.text; - config.password = passwordEdit.text; - mainWindow.busy = true; - 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"); - } - } + Frame { + id: join_server + anchors.centerIn: parent + Column { + spacing: 8 + TextField { + id: server_addr + text: "127.0.0.1" + } + TextField { + id: screenNameEdit + text: "player" + } + /*TextField { + id: avatarEdit + text: "liubei" + }*/ + TextField { + id: passwordEdit + text: "" + echoMode: TextInput.Password + passwordCharacter: "*" + } + Button { + text: "Join Server" + onClicked: { + config.screenName = screenNameEdit.text; + config.password = passwordEdit.text; + mainWindow.busy = true; + 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"); + } + } } + } } diff --git a/qml/Pages/Lobby.qml b/qml/Pages/Lobby.qml index 06786652..280611f5 100644 --- a/qml/Pages/Lobby.qml +++ b/qml/Pages/Lobby.qml @@ -5,154 +5,154 @@ import QtQuick.Layouts 1.15 import "Logic.js" as Logic Item { - id: root - property alias roomModel: roomModel - Component { - 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 - } + id: root + property alias roomModel: roomModel + Component { + id: roomDelegate 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 - } - } + width: roomList.width * 0.9 + spacing: 16 + Text { + text: roomId + } - ColumnLayout { - Button { - text: "Edit Profile" - onClicked: { - globalPopup.source = "EditProfile.qml"; - globalPopup.open(); - } - } - 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(); - } - } + 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 { + 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 { - id: lobby_dialog - z: 1000 - 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(); + ColumnLayout { + Button { + text: "Edit Profile" + onClicked: { + globalPopup.source = "EditProfile.qml"; + globalPopup.open(); } + } + 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() - { - item.x = Math.round((root.width - item.width) / 2); - item.y = Math.round(root.height * 0.67 - item.height / 2); - } + Loader { + id: lobby_dialog + z: 1000 + 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: { - toast.show("Welcome to FreeKill lobby!"); + function moveToCenter() + { + 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!"); + } } diff --git a/qml/Pages/Logic.js b/qml/Pages/Logic.js index 0c0831c7..e1578741 100644 --- a/qml/Pages/Logic.js +++ b/qml/Pages/Logic.js @@ -1,13 +1,13 @@ callbacks["UpdateAvatar"] = function(jsonData) { - mainWindow.busy = false; - Self.avatar = jsonData; - toast.show("Update avatar done."); + mainWindow.busy = false; + Self.avatar = jsonData; + toast.show("Update avatar done."); } callbacks["UpdatePassword"] = function(jsonData) { - mainWindow.busy = false; - if (jsonData === "1") - toast.show("Update password done."); - else - toast.show("Old password wrong!", 5000); + mainWindow.busy = false; + if (jsonData === "1") + toast.show("Update password done."); + else + toast.show("Old password wrong!", 5000); } diff --git a/qml/Pages/MetroButton.qml b/qml/Pages/MetroButton.qml index 78d6cc97..3027cf0a 100644 --- a/qml/Pages/MetroButton.qml +++ b/qml/Pages/MetroButton.qml @@ -1,69 +1,69 @@ import QtQuick 2.15 Item { - property bool enabled: true - property alias text: title.text - property alias textColor: title.color - property alias textFont: title.font - property alias backgroundColor: bg.color - property alias border: bg.border - property alias iconSource: icon.source - property int padding: 5 + property bool enabled: true + property alias text: title.text + property alias textColor: title.color + property alias textFont: title.font + property alias backgroundColor: bg.color + property alias border: bg.border + property alias iconSource: icon.source + property int padding: 5 - signal clicked + signal clicked - id: button - width: icon.width + title.implicitWidth + padding * 2 - height: Math.max(icon.height, title.implicitHeight) + padding * 2 + id: button + width: icon.width + title.implicitWidth + padding * 2 + height: Math.max(icon.height, title.implicitHeight) + padding * 2 - Rectangle { - id: bg - anchors.fill: parent - color: "black" - border.width: 2 - border.color: "white" - opacity: 0.8 + Rectangle { + id: bg + anchors.fill: parent + color: "black" + border.width: 2 + border.color: "white" + 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: [ - 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 - } - - Text { - id: title - font.pixelSize: 18 - // font.family: "WenQuanYi Micro Hei" - anchors.verticalCenter: parent.verticalCenter - text: "" - color: "white" - } + Text { + id: title + font.pixelSize: 18 + // font.family: "WenQuanYi Micro Hei" + anchors.verticalCenter: parent.verticalCenter + text: "" + color: "white" } + } } diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml index 0a5c1d16..5c1ff15d 100644 --- a/qml/Pages/Room.qml +++ b/qml/Pages/Room.qml @@ -5,325 +5,347 @@ import "RoomElement" import "RoomLogic.js" as Logic Item { - id: roomScene + id: roomScene - property int playerNum: 0 - property var dashboardModel + property int playerNum: 0 + property var dashboardModel - property bool isOwner: false - property bool isStarted: false + property bool isOwner: false + property bool isStarted: false - property alias popupBox: popupBox - property alias promptText: prompt.text + property alias popupBox: popupBox + 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 { - 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], - }])}} + 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 { - text: "quit" - anchors.top: parent.top - anchors.right: parent.right - onClicked: { - ClientInstance.clearPlayers(); - ClientInstance.notifyServer("QuitRoom", "[]"); - } + id: endPhaseButton + text: "End" + anchors.bottom: parent.bottom + anchors.bottomMargin: 40 + anchors.right: parent.right + anchors.rightMargin: 30 + visible: false; + onClicked: Logic.doCancelButton(); } - Button { - text: "add robot" - visible: dashboardModel.isOwner && !isStarted - anchors.centerIn: parent - onClicked: { - ClientInstance.notifyServer("AddRobot", "[]"); - } + } + + 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(); } - 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; + function moveToCenter() + { + item.x = Math.round((roomArea.width - item.width) / 2); + item.y = Math.round(roomArea.height * 0.67 - item.height / 2); + } + } - if (popupBox.item != null) { - popupBox.item.finished(); - } - } - } - }, + Component.onCompleted: { + toast.show("Sucesessfully entered room."); - Transition { - from: "*"; to: "playing" - ScriptAction { - script: { - 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 + 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 } - Item { - id: roomArea - width: roomScene.width - height: roomScene.height - dashboard.height + playerNum = config.roomCapacity; - Repeater { - id: photos - model: photoModel - Photo { - 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 - } - } - - 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 - } + 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 + }); } - Dashboard { - 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(); - } + Logic.arrangePhotos(); + } } diff --git a/qml/Pages/RoomElement/CardArea.qml b/qml/Pages/RoomElement/CardArea.qml index c2ce9ae7..d3b92e98 100644 --- a/qml/Pages/RoomElement/CardArea.qml +++ b/qml/Pages/RoomElement/CardArea.qml @@ -3,75 +3,75 @@ import QtQuick 2.15 // CardArea stores CardItem. Item { - property var cards: [] - property int length: 0 + property var cards: [] + property int length: 0 - id: root + id: root - function add(inputs) - { - if (inputs instanceof Array) { - cards.push(...inputs); - } else { - cards.push(inputs); + function add(inputs) + { + if (inputs instanceof Array) { + cards.push(...inputs); + } else { + 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) - { - 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; - return result; + 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; + } } - 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; - } - - 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); - } + 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); + } + } } diff --git a/qml/Pages/RoomElement/CardItem.qml b/qml/Pages/RoomElement/CardItem.qml index d9d15b1b..df784dcc 100644 --- a/qml/Pages/RoomElement/CardItem.qml +++ b/qml/Pages/RoomElement/CardItem.qml @@ -13,242 +13,242 @@ import "../skin-bank.js" as SkinBank */ Item { - id: root - width: 93 - height: 130 + id: root + width: 93 + height: 130 - // properties for the view - property string suit: "club" - property int number: 7 - property string name: "slash" - property string subtype: "" - property string color: "" // only use when suit is empty - property string footnote: "" // footnote, e.g. "A use card to B" - property bool footnoteVisible: true - property bool known: true // if false it only show a card back - property bool enabled: true // if false the card will be grey - property alias card: cardItem - property alias glow: glowItem + // properties for the view + property string suit: "club" + property int number: 7 + property string name: "slash" + property string subtype: "" + property string color: "" // only use when suit is empty + property string footnote: "" // footnote, e.g. "A use card to B" + property bool footnoteVisible: true + property bool known: true // if false it only show a card back + property bool enabled: true // if false the card will be grey + property alias card: cardItem + property alias glow: glowItem - function getColor() { - if (suit != "") - return (suit == "heart" || suit == "diamond") ? "red" : "black"; - else return color; + function getColor() { + if (suit != "") + return (suit == "heart" || suit == "diamond") ? "red" : "black"; + 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 - 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 + onEntered: { + parent.entered(); + if (draggable) { + glow.visible = true; + root.z++; + } } - Image { - id: cardItem - source: known ? (name != "" ? SkinBank.CARD_DIR + name : "") - : (SkinBank.CARD_DIR + "card-back") - anchors.fill: parent - fillMode: Image.PreserveAspectCrop + onExited: { + parent.exited(); + if (draggable) { + glow.visible = false; + root.z--; + } } - Image { - id: suitItem - visible: known - source: suit != "" ? SkinBank.CARD_SUIT_DIR + suit : "" - x: 3 - y: 19 - width: 21 - height: 17 + onClicked: { + selected = selectable ? !selected : false; + parent.clicked(); + } + } + + ParallelAnimation { + id: goBackAnimation + + PropertyAnimation { + target: root + property: "x" + to: origX + easing.type: Easing.OutQuad + duration: goBackDuration } - Image { - id: numberItem - visible: known - source: (suit != "" && number > 0) ? SkinBank.CARD_DIR - + "number/" + root.getColor() + "/" + number : "" - x: 0 - y: 0 - width: 27 - height: 28 + PropertyAnimation { + target: root + property: "y" + to: origY + easing.type: Easing.OutQuad + duration: goBackDuration } - Image { - id: colorItem - visible: known && suit == "" - source: (visible && color != "") ? SkinBank.CARD_SUIT_DIR + "/" + color : "" - x: 1 + 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 + } } - 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 + onStopped: { + if (!moveAborted) + root.moveFinished(); } + } - Rectangle { - visible: !root.selectable - anchors.fill: parent - color: Qt.rgba(0, 0, 0, 0.5) - opacity: 0.7 + 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; } + } - 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(); - } - - 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(); - }); - } + function destroyOnStop() + { + root.moveFinished.connect(function(){ + root.destroy(); + }); + } } diff --git a/qml/Pages/RoomElement/ChoiceBox.qml b/qml/Pages/RoomElement/ChoiceBox.qml index ead7e80d..4242ba83 100644 --- a/qml/Pages/RoomElement/ChoiceBox.qml +++ b/qml/Pages/RoomElement/ChoiceBox.qml @@ -2,33 +2,33 @@ import QtQuick 2.15 import ".." GraphicsBox { - property var options: [] - property string skill_name: "" - property int result + property var options: [] + property string skill_name: "" + property int result - id: root - title.text: skill_name + ": Please choose" - width: Math.max(140, body.width + 20) - height: body.height + title.height + 20 + id: root + title.text: skill_name + ": Please choose" + width: Math.max(140, body.width + 20) + height: body.height + title.height + 20 - Column { - id: body - x: 10 - y: title.height + 5 - spacing: 10 + Column { + id: body + x: 10 + y: title.height + 5 + spacing: 10 - Repeater { - model: options + Repeater { + model: options - MetroButton { - text: modelData - anchors.horizontalCenter: parent.horizontalCenter + MetroButton { + text: modelData + anchors.horizontalCenter: parent.horizontalCenter - onClicked: { - result = index; - root.close(); - } - } + onClicked: { + result = index; + root.close(); } + } } + } } diff --git a/qml/Pages/RoomElement/ChooseGeneralBox.qml b/qml/Pages/RoomElement/ChooseGeneralBox.qml index dd906ad9..853df57b 100644 --- a/qml/Pages/RoomElement/ChooseGeneralBox.qml +++ b/qml/Pages/RoomElement/ChooseGeneralBox.qml @@ -3,175 +3,175 @@ import ".." import "../skin-bank.js" as SkinBank GraphicsBox { - property alias generalList: generalList - // property var generalList: [] - property int choiceNum: 1 - property var choices: [] - property var selectedItem: [] - property bool loaded: false + property alias generalList: generalList + // property var generalList: [] + property int choiceNum: 1 + property var choices: [] + property var selectedItem: [] + property bool loaded: false - ListModel { - id: generalList + ListModel { + 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 - 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) - } - } - } - - 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(); - } - } + Item { + id: splitLine + width: parent.width - 80 + height: 6 + anchors.horizontalCenter: parent.horizontalCenter + clip: true } - Repeater { - id: generalCardList - model: generalList + Item { + width: parent.width + height: 165 - GeneralCardItem { - name: model.name - selectable: true - draggable: true + Row { + id: resultArea + anchors.centerIn: parent + spacing: 10 - 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.push(this); - updatePosition(); - } + Repeater { + id: resultList + model: choiceNum - onReleased: { - if (!isClicked) - arrangeCards(); - } + Rectangle { + color: "#1D1E19" + radius: 3 + width: 93 + height: 130 + } } + } } - function arrangeCards() - { - let item, i; + Item { + id: buttonArea + width: parent.width + height: 40 - selectedItem = []; - for (i = 0; i < generalList.count; i++) { - item = generalCardList.itemAt(i); - if (item.y > splitLine.y) - selectedItem.push(item); + MetroButton { + id: fightButton + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + 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); + } } - - selectedItem.sort((a, b) => a.x - b.x); - - if (selectedItem.length > choiceNum) - selectedItem.splice(choiceNum, selectedItem.length - choiceNum); - + if (toSelect && selectedItem.length < choiceNum) + selectedItem.push(this); 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() - { - 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); - } - } + selectedItem.sort((a, b) => a.x - b.x); - fightButton.enabled = (choices.length == choiceNum); + if (selectedItem.length > choiceNum) + selectedItem.splice(choiceNum, selectedItem.length - choiceNum); - for (i = 0; i < generalCardList.count; i++) { - item = generalCardList.itemAt(i); - if (selectedItem.indexOf(item) != -1) - continue; + updatePosition(); + } - 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); - } - } + function updatePosition() + { + 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); + + 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); + } + } + } } diff --git a/qml/Pages/RoomElement/Dashboard.qml b/qml/Pages/RoomElement/Dashboard.qml index cac9f336..e70bcf7c 100644 --- a/qml/Pages/RoomElement/Dashboard.qml +++ b/qml/Pages/RoomElement/Dashboard.qml @@ -3,29 +3,166 @@ import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 RowLayout { - id: root + id: root - property alias self: selfPhoto - property alias handcardArea: handcardAreaItem - property alias equipArea: selfPhoto.equipArea - property alias delayedTrickArea: selfPhoto.delayedTrickArea - property alias specialArea: selfPhoto.specialArea + property alias self: selfPhoto + property alias handcardArea: handcardAreaItem + property alias equipArea: selfPhoto.equipArea + property alias delayedTrickArea: selfPhoto.delayedTrickArea + property alias specialArea: selfPhoto.specialArea - Item { - width: 40 + property bool selected: selfPhoto.selected + + 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 { - id: handcardAreaItem - Layout.fillWidth: true - Layout.preferredHeight: 130 - Layout.alignment: Qt.AlignVCenter - } - - Photo { - id: selfPhoto - handcards: handcardAreaItem.length - } - - Item { width: 5 } + pendings = []; + handcardAreaItem.adjustCards(); + cardSelected(-1); + } } diff --git a/qml/Pages/RoomElement/GeneralCardItem.qml b/qml/Pages/RoomElement/GeneralCardItem.qml index b5aeb10f..a4a703a0 100644 --- a/qml/Pages/RoomElement/GeneralCardItem.qml +++ b/qml/Pages/RoomElement/GeneralCardItem.qml @@ -13,12 +13,12 @@ import "../skin-bank.js" as SkinBank */ CardItem { - property string kingdom: "qun" - name: "caocao" - // description: Sanguosha.getGeneralDescription(name) - suit: "" - number: 0 - footnote: "" - card.source: SkinBank.GENERAL_DIR + name - glow.color: "white" //Engine.kingdomColor[kingdom] + property string kingdom: "qun" + name: "caocao" + // description: Sanguosha.getGeneralDescription(name) + suit: "" + number: 0 + footnote: "" + card.source: SkinBank.GENERAL_DIR + name + glow.color: "white" //Engine.kingdomColor[kingdom] } diff --git a/qml/Pages/RoomElement/GlowText.qml b/qml/Pages/RoomElement/GlowText.qml index 243f8b3f..69b6b658 100644 --- a/qml/Pages/RoomElement/GlowText.qml +++ b/qml/Pages/RoomElement/GlowText.qml @@ -2,29 +2,29 @@ import QtQuick 2.15 import QtGraphicalEffects 1.0 Item { - property alias text: textItem.text - property alias color: textItem.color - property alias font: textItem.font - property alias fontSizeMode: textItem.fontSizeMode - property alias horizontalAlignment: textItem.horizontalAlignment - property alias verticalAlignment: textItem.verticalAlignment - property alias style: textItem.style - property alias styleColor: textItem.styleColor - property alias wrapMode: textItem.wrapMode - property alias lineHeight: textItem.lineHeight - property alias glow: glowItem + property alias text: textItem.text + property alias color: textItem.color + property alias font: textItem.font + property alias fontSizeMode: textItem.fontSizeMode + property alias horizontalAlignment: textItem.horizontalAlignment + property alias verticalAlignment: textItem.verticalAlignment + property alias style: textItem.style + property alias styleColor: textItem.styleColor + property alias wrapMode: textItem.wrapMode + property alias lineHeight: textItem.lineHeight + property alias glow: glowItem - width: textItem.implicitWidth - height: textItem.implicitHeight + width: textItem.implicitWidth + height: textItem.implicitHeight - Text { - id: textItem - anchors.fill: parent - } + Text { + id: textItem + anchors.fill: parent + } - Glow { - id: glowItem - source: textItem - anchors.fill: textItem - } + Glow { + id: glowItem + source: textItem + anchors.fill: textItem + } } diff --git a/qml/Pages/RoomElement/GraphicsBox.qml b/qml/Pages/RoomElement/GraphicsBox.qml index afb479b3..21b10102 100644 --- a/qml/Pages/RoomElement/GraphicsBox.qml +++ b/qml/Pages/RoomElement/GraphicsBox.qml @@ -2,52 +2,52 @@ import QtQuick 2.15 import QtGraphicalEffects 1.0 Item { - property alias title: titleItem - signal accepted() //Read result - signal finished() //Close the box + property alias title: titleItem + signal accepted() //Read result + signal finished() //Close the box - id: root + id: root - Rectangle { - id: background - anchors.fill: parent - color: "#B0000000" - radius: 5 - border.color: "#A6967A" - border.width: 1 - } + Rectangle { + id: background + anchors.fill: parent + color: "#B0000000" + radius: 5 + border.color: "#A6967A" + border.width: 1 + } - DropShadow { - source: background - anchors.fill: background - color: "#B0000000" - radius: 5 - samples: 12 - spread: 0.2 - horizontalOffset: 5 - verticalOffset: 4 - transparentBorder: true - } + DropShadow { + source: background + anchors.fill: background + color: "#B0000000" + radius: 5 + samples: 12 + spread: 0.2 + horizontalOffset: 5 + verticalOffset: 4 + transparentBorder: true + } - Text { - id: titleItem - color: "#E4D5A0" - font.pixelSize: 18 - horizontalAlignment: Text.AlignHCenter - anchors.top: parent.top - anchors.topMargin: 4 - anchors.horizontalCenter: parent.horizontalCenter - } + Text { + id: titleItem + color: "#E4D5A0" + font.pixelSize: 18 + horizontalAlignment: Text.AlignHCenter + anchors.top: parent.top + anchors.topMargin: 4 + anchors.horizontalCenter: parent.horizontalCenter + } - MouseArea { - anchors.fill: parent - drag.target: parent - drag.axis: Drag.XAndYAxis - } + MouseArea { + anchors.fill: parent + drag.target: parent + drag.axis: Drag.XAndYAxis + } - function close() - { - accepted(); - finished(); - } + function close() + { + accepted(); + finished(); + } } diff --git a/qml/Pages/RoomElement/HandcardArea.qml b/qml/Pages/RoomElement/HandcardArea.qml index 839bd81e..2c6815ee 100644 --- a/qml/Pages/RoomElement/HandcardArea.qml +++ b/qml/Pages/RoomElement/HandcardArea.qml @@ -2,130 +2,130 @@ import QtQuick 2.15 import "../../util.js" as Utility Item { - property alias cards: cardArea.cards - property alias length: cardArea.length - property var selectedCards: [] + property alias cards: cardArea.cards + property alias length: cardArea.length + property var selectedCards: [] - signal cardSelected(int cardId, bool selected) + signal cardSelected(int cardId, bool selected) - id: area + id: area - CardArea { - anchors.fill: parent - id: cardArea - onLengthChanged: area.updateCardPosition(true); + CardArea { + anchors.fill: parent + id: cardArea + 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) - { - cardArea.add(inputs); - if (inputs instanceof Array) { - for (let i = 0; i < inputs.length; i++) - filterInputCard(inputs[i]); - } else { - filterInputCard(inputs); - } + if (animated) { + for (i = 0; i < cards.length; i++) + cards[i].goBack(true) } + } - function filterInputCard(card) - { - card.autoBack = true; - card.draggable = true; - card.selectable = false; - card.clicked.connect(adjustCards); + 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 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 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 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 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; + } } - - 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; - } - - 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); + if (card === undefined) { + selectedCards = []; + } else { + selectedCards = [card]; } + updateCardPosition(true); + } } diff --git a/qml/Pages/RoomElement/IndicatorLine.qml b/qml/Pages/RoomElement/IndicatorLine.qml index 9c4ad5dc..d0d92f9c 100644 --- a/qml/Pages/RoomElement/IndicatorLine.qml +++ b/qml/Pages/RoomElement/IndicatorLine.qml @@ -1,101 +1,101 @@ import QtQuick 2.15 Item { - property point start: Qt.point(0, 0) - property var end: [] - property alias running: pointToAnimation.running - property color color: "#96943D" - property real ratio: 0 - property int lineWidth: 6 + property point start: Qt.point(0, 0) + property var end: [] + property alias running: pointToAnimation.running + property color color: "#96943D" + property real ratio: 0 + property int lineWidth: 6 - signal finished() + signal finished() - id: root - anchors.fill: parent + id: root + anchors.fill: parent - Repeater { - model: end + Repeater { + model: end - Rectangle { - width: 6 - height: Math.sqrt(Math.pow(modelData.x - start.x, 2) + Math.pow(modelData.y - start.y, 2)) * ratio - x: start.x - y: start.y - antialiasing: true + Rectangle { + width: 6 + height: Math.sqrt(Math.pow(modelData.x - start.x, 2) + Math.pow(modelData.y - start.y, 2)) * ratio + x: start.x + y: start.y + antialiasing: true - gradient: Gradient { - GradientStop { - position: 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; - } - } - } + gradient: Gradient { + GradientStop { + position: 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; + } + } + } + } + } + + SequentialAnimation { + id: pointToAnimation + + PropertyAnimation { + target: root + property: "ratio" + to: 1 + easing.type: Easing.OutCubic + duration: 200 } - SequentialAnimation { - id: pointToAnimation - - 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(); - } + PauseAnimation { + duration: 200 } + + PropertyAnimation { + target: root + property: "opacity" + to: 0 + easing.type: Easing.InQuart + duration: 300 + } + + onStopped: { + root.visible = false; + root.finished(); + } + } } diff --git a/qml/Pages/RoomElement/InvisibleCardArea.qml b/qml/Pages/RoomElement/InvisibleCardArea.qml index 67561bb2..10e7cc1d 100644 --- a/qml/Pages/RoomElement/InvisibleCardArea.qml +++ b/qml/Pages/RoomElement/InvisibleCardArea.qml @@ -1,111 +1,111 @@ import QtQuick 2.15 Item { - property var cards: [] - property int length: 0 - property var pendingInput: [] - property bool checkExisting: false + property var cards: [] + property int length: 0 + property var pendingInput: [] + 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 (inputs instanceof Array) { - for (let i = 0; i < inputs.length; i++) { - card = inputs[i]; - pendingInput.push(card); - cards.push(card.toData()); - } + if (cards[i].cid === cid) + return true; + } + return false; + } - if (checkExisting) - length = cards.length; - else - length += inputs.length; - } else { - pendingInput.push(inputs); - cards.push(inputs.toData()); + function remove(outputs) + { + let component = Qt.createComponent("CardItem.qml"); + if (component.status !== Component.Ready) + return []; - if (checkExisting) - length = cards.length; - else - length++; + 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.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) - { - 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 = []; - } + pendingInput = []; + } } diff --git a/qml/Pages/RoomElement/Photo.qml b/qml/Pages/RoomElement/Photo.qml index 7214dd3d..5b8647dc 100644 --- a/qml/Pages/RoomElement/Photo.qml +++ b/qml/Pages/RoomElement/Photo.qml @@ -5,309 +5,377 @@ import "PhotoElement" import "../skin-bank.js" as SkinBank Item { - id: root - width: 175 - height: 233 - scale: 0.8 - property string general: "" - property string screenName: "" - property string role: "unknown" - property string kingdom: "qun" - property string netstate: "online" - property alias handcards: handcardAreaItem.length - property int maxHp: 0 - property int hp: 0 - property int seatNumber: 1 - property bool isDead: false - property bool dying: false - property bool faceup: true - property bool chained: false - property bool drank: false - property bool isOwner: false - property string status: "normal" + id: root + width: 175 + height: 233 + scale: 0.8 + property int playerid + property string general: "" + property string screenName: "" + property string role: "unknown" + property string kingdom: "qun" + property string netstate: "online" + property alias handcards: handcardAreaItem.length + property int maxHp: 0 + property int hp: 0 + property int seatNumber: 1 + property bool isDead: false + property bool dying: false + property bool faceup: true + property bool chained: false + property bool drank: false + property bool isOwner: false + property string status: "normal" - property alias handcardArea: handcardAreaItem - property alias equipArea: equipAreaItem - property alias delayedTrickArea: delayedTrickAreaItem - property alias specialArea: handcardAreaItem + property alias handcardArea: handcardAreaItem + property alias equipArea: equipAreaItem + property alias delayedTrickArea: delayedTrickAreaItem + property alias specialArea: handcardAreaItem - property alias progressBar: progressBar - property alias progressTip: progressTip.text + property alias progressBar: progressBar + property alias progressTip: progressTip.text - property bool selectable: false - property bool selected: false + property bool selectable: false + property bool selected: false - Behavior on x { - NumberAnimation { duration: 600; easing.type: Easing.InOutQuad } + Behavior on x { + 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 { - NumberAnimation { duration: 600; easing.type: Easing.InOutQuad } - } + PixmapAnimation { + id: animPlaying + source: "playing" + anchors.centerIn: parent + loop: true + scale: 1.1 + visible: root.state === "playing" + } - PixmapAnimation { - id: animFrame - source: "selected" - anchors.centerIn: parent - loop: true - scale: 1.1 - } + PixmapAnimation { + id: animSelected + source: "selected" + anchors.centerIn: parent + loop: true + scale: 1.1 + visible: root.state === "candidate" && selected + } - Image { - id: back - source: SkinBank.PHOTO_BACK_DIR + root.kingdom - } + Image { + id: back + 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 { - 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: "" + 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 } + } - HpBar { - id: hp - x: 8 - value: root.hp - maxValue: root.maxHp - anchors.bottom: parent.bottom - anchors.bottomMargin: 36 + MouseArea { + anchors.fill: parent + onClicked: { + if (parent.state != "candidate" || !parent.selectable) + return; + parent.selected = !parent.selected; } + } - Image { - id: generalImage - width: 138 - height: 222 - smooth: true - visible: false - fillMode: Image.PreserveAspectCrop - source: (general != "") ? SkinBank.GENERAL_DIR + general : "" + RoleComboBox { + id: role + value: root.role + anchors.top: parent.top + anchors.topMargin: -4 + anchors.right: parent.right + anchors.rightMargin: -4 + } + + 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 } - - Rectangle { - id: photoMask - x: 31 - y: 5 - width: 138 - height: 222 - radius: 8 - visible: false + PropertyAnimation { + target: root + property: "x" + to: root.x + easing.type: Easing.OutQuad + duration: 100 } + } + + function tremble() { + trembleAnimation.start() + } - OpacityMask { - anchors.fill: photoMask - source: generalImage - maskSource: photoMask + 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 = ""; + } } + } - Colorize { - anchors.fill: photoMask - source: generalImage - saturation: 0 - visible: root.isDead + 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: "" } + } - 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 - } + PixmapAnimation { + id: animSelectable + source: "selectable" + anchors.centerIn: parent + loop: true + visible: root.state === "candidate" && selectable + } - Image { - visible: equipAreaItem.length > 0 - source: SkinBank.PHOTO_DIR + "equipbg" - x: 31 - y: 121 - } + InvisibleCardArea { + id: handcardAreaItem + anchors.centerIn: parent + } - Image { - source: root.status != "normal" ? SkinBank.STATUS_DIR + root.status : "" - x: -6 - } + DelayedTrickArea { + id: delayedTrickAreaItem + rows: 1 + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + } - Image { - id: turnedOver - visible: !root.faceup - source: SkinBank.PHOTO_DIR + "faceturned" - x: 29; y: 5 - } + InvisibleCardArea { + id: defaultArea + anchors.centerIn: parent + } - 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: 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; - } + onGeneralChanged: { + if (!roomScene.isStarted) return; + generalName.text = Backend.translate(general); + let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [general])); + kingdom = data.kingdom; + } } diff --git a/qml/Pages/RoomElement/PhotoElement/DelayedTrickArea.qml b/qml/Pages/RoomElement/PhotoElement/DelayedTrickArea.qml index 87070c93..c9eff3d7 100644 --- a/qml/Pages/RoomElement/PhotoElement/DelayedTrickArea.qml +++ b/qml/Pages/RoomElement/PhotoElement/DelayedTrickArea.qml @@ -4,62 +4,62 @@ import ".." import "../../skin-bank.js" as SkinBank Item { - property alias rows: grid.rows - property alias columns: grid.columns + property alias rows: grid.rows + property alias columns: grid.columns - InvisibleCardArea { - id: area - checkExisting: true + InvisibleCardArea { + id: area + 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 { - id: cards + function add(inputs) + { + area.add(inputs); + if (inputs instanceof Array) { + cards.append(...inputs); + } else { + cards.append(inputs); } + } - Grid { - id: grid - anchors.fill: parent - rows: 100 - columns: 100 - - Repeater { - model: cards - - Image { - source: SkinBank.DELAYED_TRICK_DIR + name - } + function remove(outputs) + { + let result = area.remove(outputs); + 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; } + } } - function add(inputs) - { - area.add(inputs); - if (inputs instanceof Array) { - cards.append(...inputs); - } else { - cards.append(inputs); - } - } + return result; + } - function remove(outputs) - { - let result = area.remove(outputs); - 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); - } + function updateCardPosition(animated) + { + area.updateCardPosition(animated); + } } diff --git a/qml/Pages/RoomElement/PhotoElement/EquipArea.qml b/qml/Pages/RoomElement/PhotoElement/EquipArea.qml index c3be0ba6..77a9cfbe 100644 --- a/qml/Pages/RoomElement/PhotoElement/EquipArea.qml +++ b/qml/Pages/RoomElement/PhotoElement/EquipArea.qml @@ -11,110 +11,110 @@ import "../../skin-bank.js" as SkinBank */ Column { - height: 88 - width: 138 - property int itemHeight: Math.floor(height / 4) - property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem] - property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"] - property int length: area.length + height: 88 + width: 138 + property int itemHeight: Math.floor(height / 4) + property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem] + property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"] + property int length: area.length - InvisibleCardArea { - id: area - checkExisting: true - } + InvisibleCardArea { + id: area + checkExisting: true + } - EquipItem { - id: treasureItem + EquipItem { + 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 height: itemHeight + icon: "horse" opacity: 0 + } } - EquipItem { - id: weaponItem + Item { + width: Math.floor(parent.width / 2) + height: itemHeight + + EquipItem { + id: offensiveHorseItem width: parent.width height: itemHeight + icon: "horse" opacity: 0 + } } + } - EquipItem { - id: armorItem - width: parent.width - height: itemHeight - opacity: 0 + function add(inputs) + { + area.add(inputs); + + 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 { - width: parent.width - height: itemHeight - - Item { - width: Math.ceil(parent.width / 2) - height: itemHeight - - EquipItem { - id: defensiveHorseItem - 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 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(); } + } } - function add(inputs) - { - area.add(inputs); + return result; + } - 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(); - } - } - - 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); - } + function updateCardPosition(animated) + { + area.updateCardPosition(animated); + } } diff --git a/qml/Pages/RoomElement/PhotoElement/EquipItem.qml b/qml/Pages/RoomElement/PhotoElement/EquipItem.qml index e4bbb0cc..1ce32762 100644 --- a/qml/Pages/RoomElement/PhotoElement/EquipItem.qml +++ b/qml/Pages/RoomElement/PhotoElement/EquipItem.qml @@ -4,139 +4,139 @@ import "../../../util.js" as Utility import "../../skin-bank.js" as SkinBank Item { - property int cid: 0 - property string name: "" - property string suit: "" - property int number: 0 + property int cid: 0 + property string name: "" + property string suit: "" + property int number: 0 - property string icon: "" - property alias text: textItem.text + property string icon: "" + property alias text: textItem.text - id: root + id: root - Image { - id: iconItem - anchors.verticalCenter: parent.verticalCenter - x: 3 + Image { + id: iconItem + anchors.verticalCenter: parent.verticalCenter + 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 { - id: suitItem - anchors.right: parent.right - source: suit ? SkinBank.CARD_SUIT_DIR + suit : "" - width: implicitWidth / implicitHeight * height - height: 16 + 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 } - 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 + NumberAnimation { + target: root + property: "opacity" + duration: 200 + easing.type: Easing.InOutQuad + from: 1 + to: 0 } + } - 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 + 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 = Backend.translate(name); + icon = name; } + } - ParallelAnimation { - id: showAnime + function show() + { + showAnime.start(); + } - NumberAnimation { - target: root - property: "x" - 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(); - } + function hide() + { + hideAnime.start(); + } } diff --git a/qml/Pages/RoomElement/PhotoElement/HpBar.qml b/qml/Pages/RoomElement/PhotoElement/HpBar.qml index 470a0e49..6e0a0d1c 100644 --- a/qml/Pages/RoomElement/PhotoElement/HpBar.qml +++ b/qml/Pages/RoomElement/PhotoElement/HpBar.qml @@ -2,71 +2,71 @@ import QtQuick 2.15 import ".." Column { - id: root - property int maxValue: 4 - property int value: 4 - property var colors: ["#F4180E", "#F4180E", "#E3B006", "#25EC27"] + id: root + property int maxValue: 4 + property int value: 4 + property var colors: ["#F4180E", "#F4180E", "#E3B006", "#25EC27"] - Repeater { - id: repeater - model: maxValue <= 4 ? maxValue : 0 - Magatama { - state: (maxValue - 1 - index) >= value ? 0 : (value >= 3 || value >= maxValue ? 3 : (value <= 0 ? 0 : value)) - } + Repeater { + id: repeater + model: maxValue <= 4 ? maxValue : 0 + Magatama { + 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 { - visible: maxValue > 4 - spacing: -4 + 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 - Magatama { - state: (value >= 3 || value >= maxValue) ? 3 : (value <= 0 ? 0 : value) - } - - 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 - } + 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 + } + } } diff --git a/qml/Pages/RoomElement/PhotoElement/Magatama.qml b/qml/Pages/RoomElement/PhotoElement/Magatama.qml index f8e4628c..a2ab7041 100644 --- a/qml/Pages/RoomElement/PhotoElement/Magatama.qml +++ b/qml/Pages/RoomElement/PhotoElement/Magatama.qml @@ -2,57 +2,57 @@ import QtQuick 2.15 import "../../skin-bank.js" as SkinBank Image { - source: SkinBank.MAGATAMA_DIR + "0" - state: "3" + source: SkinBank.MAGATAMA_DIR + "0" + state: "3" - states: [ - State { - name: "3" - PropertyChanges { - target: main - source: SkinBank.MAGATAMA_DIR + "3" - opacity: 1 - scale: 1 - } - }, - State { - name: "2" - PropertyChanges { - target: main - source: SkinBank.MAGATAMA_DIR + "2" - opacity: 1 - scale: 1 - } - }, - State { - name: "1" - PropertyChanges { - target: main - source: SkinBank.MAGATAMA_DIR + "1" - opacity: 1 - scale: 1 - } - }, - State { - name: "0" - PropertyChanges { - target: main - source: SkinBank.MAGATAMA_DIR + "0" - opacity: 0 - scale: 4 - } - } - ] - - transitions: Transition { - PropertyAnimation { - properties: "opacity,scale" - } + states: [ + State { + name: "3" + PropertyChanges { + target: main + source: SkinBank.MAGATAMA_DIR + "3" + opacity: 1 + scale: 1 + } + }, + State { + name: "2" + PropertyChanges { + target: main + source: SkinBank.MAGATAMA_DIR + "2" + opacity: 1 + scale: 1 + } + }, + State { + name: "1" + PropertyChanges { + target: main + source: SkinBank.MAGATAMA_DIR + "1" + opacity: 1 + scale: 1 + } + }, + State { + name: "0" + PropertyChanges { + target: main + source: SkinBank.MAGATAMA_DIR + "0" + opacity: 0 + scale: 4 + } } + ] - Image { - id: main - anchors.centerIn: parent + transitions: Transition { + PropertyAnimation { + properties: "opacity,scale" } + } + + Image { + id: main + anchors.centerIn: parent + } } diff --git a/qml/Pages/RoomElement/PhotoElement/RoleComboBox.qml b/qml/Pages/RoomElement/PhotoElement/RoleComboBox.qml index b1d6e2e0..aae166f9 100644 --- a/qml/Pages/RoomElement/PhotoElement/RoleComboBox.qml +++ b/qml/Pages/RoomElement/PhotoElement/RoleComboBox.qml @@ -3,45 +3,45 @@ import QtQuick 2.15 import "../../skin-bank.js" as SkinBank 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 var options: ["unknown", "loyalist", "rebel", "renegade"] - id: root - source: visible ? SkinBank.ROLE_DIR + value : "" - visible: value != "hidden" + id: assumptionBox + source: SkinBank.ROLE_DIR + value + visible: root.value == "unknown" - Image { - property string value: "unknown" + MouseArea { + anchors.fill: parent + onClicked: optionPopupBox.visible = true; + } + } - id: assumptionBox - source: SkinBank.ROLE_DIR + value - visible: root.value == "unknown" + Column { + id: optionPopupBox + visible: false + spacing: 2 + + Repeater { + model: options + + Image { + source: SkinBank.ROLE_DIR + modelData MouseArea { - anchors.fill: parent - onClicked: optionPopupBox.visible = true; - } - } - - 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; - } - } - } + anchors.fill: parent + onClicked: { + optionPopupBox.visible = false; + assumptionBox.value = modelData; + } } + } } + } } diff --git a/qml/Pages/RoomElement/PixmapAnimation.qml b/qml/Pages/RoomElement/PixmapAnimation.qml index f2ebc003..715eb4b3 100644 --- a/qml/Pages/RoomElement/PixmapAnimation.qml +++ b/qml/Pages/RoomElement/PixmapAnimation.qml @@ -3,87 +3,87 @@ import Qt.labs.folderlistmodel 2.15 import "../skin-bank.js" as SkinBank Item { - property string source: "" - property int currentFrame: 0 - property alias interval: timer.interval - property int loadedFrameCount: 0 - property bool autoStart: false - property bool loop: false + property string source: "" + property int currentFrame: 0 + property alias interval: timer.interval + property int loadedFrameCount: 0 + property bool autoStart: false + property bool loop: false - signal loaded() - signal started() - signal finished() + signal loaded() + signal started() + signal finished() - id: root - width: childrenRect.width - height: childrenRect.height + id: root + width: childrenRect.width + height: childrenRect.height - FolderListModel { - id: fileModel - folder: SkinBank.PIXANIM_DIR + source - nameFilters: ["*.png"] - showDirs: false - } + FolderListModel { + id: fileModel + folder: SkinBank.PIXANIM_DIR + source + nameFilters: ["*.png"] + showDirs: false + } - Repeater { - id: frames - model: fileModel + Repeater { + id: frames + model: fileModel - Image { - source: SkinBank.PIXANIM_DIR + root.source + "/" + index - visible: false - onStatusChanged: { - if (status == Image.Ready) { - loadedFrameCount++; - if (loadedFrameCount == fileModel.count) - root.loaded(); - } - } + Image { + source: SkinBank.PIXANIM_DIR + root.source + "/" + index + visible: false + onStatusChanged: { + if (status == Image.Ready) { + loadedFrameCount++; + if (loadedFrameCount == fileModel.count) + root.loaded(); } + } } + } - onLoaded: { - if (autoStart) - timer.start(); - } + onLoaded: { + if (autoStart) + timer.start(); + } - Timer { - id: timer - interval: 50 - repeat: true - onTriggered: { - if (currentFrame >= fileModel.count) { - frames.itemAt(fileModel.count - 1).visible = false; - if (loop) { - 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(); + Timer { + id: timer + interval: 50 + repeat: true + onTriggered: { + if (currentFrame >= fileModel.count) { + frames.itemAt(fileModel.count - 1).visible = false; + if (loop) { + currentFrame = 0; } else { - root.loaded.connect(function(){ - timer.start(); - }); + timer.stop(); + root.finished(); + return; } - } + } - function stop() - { - timer.stop(); + if (currentFrame > 0) + frames.itemAt(currentFrame - 1).visible = false; + 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(); + } } diff --git a/qml/Pages/RoomElement/SkillArea.qml b/qml/Pages/RoomElement/SkillArea.qml index ee41fbc8..4554a08c 100644 --- a/qml/Pages/RoomElement/SkillArea.qml +++ b/qml/Pages/RoomElement/SkillArea.qml @@ -1,5 +1,5 @@ import QtQuick 2.15 Flickable { - id: root + id: root } diff --git a/qml/Pages/RoomElement/TablePile.qml b/qml/Pages/RoomElement/TablePile.qml index f49f6f6d..58cfd9e0 100644 --- a/qml/Pages/RoomElement/TablePile.qml +++ b/qml/Pages/RoomElement/TablePile.qml @@ -1,135 +1,135 @@ import QtQuick 2.15 Item { - property var discardedCards: [] - property alias cards: area.cards - property bool toVanish: false + property var discardedCards: [] + property alias cards: area.cards + property bool toVanish: false - id: root + id: root - CardArea { - id: area - } + CardArea { + id: area + } - InvisibleCardArea { - id: invisibleArea - } + InvisibleCardArea { + id: invisibleArea + } - Timer { - id: vanishTimer - interval: 1500 - repeat: true - running: true - triggeredOnStart: true - onTriggered: { - let i, card; - if (toVanish) { - for (i = 0; i < discardedCards.length; i++) { - card = discardedCards[i]; - card.origOpacity = 0; - card.goBack(true); - 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 - } + Timer { + id: vanishTimer + interval: 1500 + repeat: true + running: true + triggeredOnStart: true + onTriggered: { + let i, card; + if (toVanish) { + for (i = 0; i < discardedCards.length; i++) { + card = discardedCards[i]; + card.origOpacity = 0; + card.goBack(true); + card.destroyOnStop() } - } - 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; - } - } - } + cards.splice(0, discardedCards.length); 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 (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; - } - - 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) - } + 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) + } + } } diff --git a/qml/Pages/RoomLogic.js b/qml/Pages/RoomLogic.js index de90f53e..bc264dc0 100644 --- a/qml/Pages/RoomLogic.js +++ b/qml/Pages/RoomLogic.js @@ -1,377 +1,465 @@ var Card = { - Unknown : 0, - PlayerHand : 1, - PlayerEquip : 2, - PlayerJudge : 3, - PlayerSpecial : 4, - Processing : 5, - DrawPile : 6, - DiscardPile : 7, - Void : 8 + Unknown : 0, + PlayerHand : 1, + PlayerEquip : 2, + PlayerJudge : 3, + PlayerSpecial : 4, + Processing : 5, + DrawPile : 6, + DiscardPile : 7, + Void : 8 } function arrangePhotos() { - /* Layout of photos: - * +---------------+ - * | 6 5 4 3 2 | - * | 7 1 | - * | dashboard | - * +---------------+ - */ + /* Layout of photos: + * +---------------+ + * | 6 5 4 3 2 | + * | 7 1 | + * | dashboard | + * +---------------+ + */ - const photoWidth = 175; - const roomAreaPadding = 10; - let verticalPadding = Math.max(10, roomArea.width * 0.01); - let horizontalSpacing = Math.max(30, roomArea.height * 0.1); - let verticalSpacing = (roomArea.width - photoWidth * 7 - verticalPadding * 2) / 6; + const photoWidth = 175; + const roomAreaPadding = 10; + let verticalPadding = Math.max(10, roomArea.width * 0.01); + let horizontalSpacing = Math.max(30, roomArea.height * 0.1); + let verticalSpacing = (roomArea.width - photoWidth * 7 - verticalPadding * 2) / 6; - // Position 1-7 - const regions = [ - { x: verticalPadding + (photoWidth + verticalSpacing) * 6, y: roomAreaPadding + horizontalSpacing * 2 }, - { x: verticalPadding + (photoWidth + verticalSpacing) * 5, y: roomAreaPadding + horizontalSpacing }, - { x: verticalPadding + (photoWidth + verticalSpacing) * 4, y: roomAreaPadding }, - { x: verticalPadding + (photoWidth + verticalSpacing) * 3, y: roomAreaPadding }, - { x: verticalPadding + (photoWidth + verticalSpacing) * 2, y: roomAreaPadding }, - { x: verticalPadding + photoWidth + verticalSpacing, y: roomAreaPadding + horizontalSpacing }, - { x: verticalPadding, y: roomAreaPadding + horizontalSpacing * 2 }, - ]; + // Position 1-7 + const regions = [ + { x: verticalPadding + (photoWidth + verticalSpacing) * 6, y: roomAreaPadding + horizontalSpacing * 2 }, + { x: verticalPadding + (photoWidth + verticalSpacing) * 5, y: roomAreaPadding + horizontalSpacing }, + { x: verticalPadding + (photoWidth + verticalSpacing) * 4, y: roomAreaPadding }, + { x: verticalPadding + (photoWidth + verticalSpacing) * 3, y: roomAreaPadding }, + { x: verticalPadding + (photoWidth + verticalSpacing) * 2, y: roomAreaPadding }, + { x: verticalPadding + photoWidth + verticalSpacing, y: roomAreaPadding + horizontalSpacing }, + { x: verticalPadding, y: roomAreaPadding + horizontalSpacing * 2 }, + ]; - const regularSeatIndex = [ - [4], - [3, 5], - [1, 4, 7], - [1, 3, 5, 7], - [1, 3, 4, 5, 7], - [1, 2, 3, 5, 6, 7], - [1, 2, 3, 4, 5, 6, 7], - ]; - let seatIndex = regularSeatIndex[playerNum - 2]; + const regularSeatIndex = [ + [4], + [3, 5], + [1, 4, 7], + [1, 3, 5, 7], + [1, 3, 4, 5, 7], + [1, 2, 3, 5, 6, 7], + [1, 2, 3, 4, 5, 6, 7], + ]; + let seatIndex = regularSeatIndex[playerNum - 2]; - let item, region, i; + let item, region, i; - for (i = 0; i < playerNum - 1; i++) { - item = photos.itemAt(i); - if (!item) - continue; + for (i = 0; i < playerNum - 1; i++) { + item = photos.itemAt(i); + if (!item) + continue; - region = regions[seatIndex[photoModel.get(i).index] - 1]; - item.x = region.x; - item.y = region.y; - } + region = regions[seatIndex[photoModel.get(i).index] - 1]; + item.x = region.x; + item.y = region.y; + } } function doOkButton() { - replyToServer("1"); + if (roomScene.state == "playing") { + replyToServer(JSON.stringify( + { + card: dashboard.getSelectedCard(), + targets: selected_targets + } + )); + return; + } + replyToServer("1"); } function doCancelButton() { - replyToServer(""); + replyToServer(""); } function replyToServer(jsonData) { - roomScene.state = "notactive"; - ClientInstance.replyToServer("", jsonData); + roomScene.state = "notactive"; + ClientInstance.replyToServer("", jsonData); } function getPhotoModel(id) { - for (let i = 0; i < photoModel.count; i++) { - let item = photoModel.get(i); - if (item.id === id) { - return item; - } + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (item.id === id) { + return item; } - return undefined; + } + return undefined; } function getPhoto(id) { - for (let i = 0; i < photoModel.count; i++) { - let item = photoModel.get(i); - if (item.id === id) { - return photos.itemAt(i); - } + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (item.id === id) { + return photos.itemAt(i); } - return undefined; + } + return undefined; } function getPhotoOrDashboard(id) { - let photo = getPhoto(id); - if (!photo) { - if (id === Self.id) - return dashboard; - } - return photo; + let photo = getPhoto(id); + if (!photo) { + if (id === Self.id) + return dashboard; + } + return photo; } function getAreaItem(area, id) { - if (area === Card.DrawPile) { - return drawPile; - } else if (area === Card.DiscardPile || area === Card.Processing) { - return tablePile; - } else if (area === Card.AG) { - 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; + if (area === Card.DrawPile) { + return drawPile; + } else if (area === Card.DiscardPile || area === Card.Processing) { + return tablePile; + } else if (area === Card.AG) { + 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; + + return null; } function moveCards(moves) { - for (let i = 0; i < moves.length; i++) { - let move = moves[i]; - let from = getAreaItem(move.fromArea, move.from); - let to = getAreaItem(move.toArea, move.to); - if (!from || !to || from === to) - continue; - let items = from.remove(move.ids); - if (items.length > 0) - to.add(items); - to.updateCardPosition(true); - } + for (let i = 0; i < moves.length; i++) { + let move = moves[i]; + let from = getAreaItem(move.fromArea, move.from); + let to = getAreaItem(move.toArea, move.to); + if (!from || !to || from === to) + continue; + let items = from.remove(move.ids); + if (items.length > 0) + to.add(items); + to.updateCardPosition(true); + } } function setEmotion(id, emotion) { - let component = Qt.createComponent("RoomElement/PixmapAnimation.qml"); - if (component.status !== Component.Ready) - return; + let component = Qt.createComponent("RoomElement/PixmapAnimation.qml"); + if (component.status !== Component.Ready) + return; - let photo = getPhoto(id); - if (!photo) { - if (id === dashboardModel.id) { - photo = dashboard.self; - } else { - return null; - } + let photo = getPhoto(id); + if (!photo) { + if (id === dashboardModel.id) { + photo = dashboard.self; + } else { + return null; } + } - let animation = component.createObject(photo, {source: emotion, anchors: {centerIn: photo}}); - animation.finished.connect(() => animation.destroy()); - animation.start(); + let animation = component.createObject(photo, {source: emotion, anchors: {centerIn: photo}}); + animation.finished.connect(() => animation.destroy()); + animation.start(); } function changeHp(id, delta, losthp) { - let photo = getPhoto(id); - if (!photo) { - if (id === dashboardModel.id) { - photo = dashboard.self; - } else { - return null; - } + let photo = getPhoto(id); + if (!photo) { + if (id === dashboardModel.id) { + photo = dashboard.self; + } else { + return null; } - if (delta < 0) { - if (!losthp) { - setEmotion(id, "damage") - photo.tremble() - } + } + if (delta < 0) { + if (!losthp) { + setEmotion(id, "damage") + photo.tremble() } + } } function doIndicate(from, tos) { - let component = Qt.createComponent("RoomElement/IndicatorLine.qml"); - if (component.status !== Component.Ready) - return; + let component = Qt.createComponent("RoomElement/IndicatorLine.qml"); + if (component.status !== Component.Ready) + return; - let fromItem = getPhotoOrDashboard(from); - let fromPos = mapFromItem(fromItem, fromItem.width / 2, fromItem.height / 2); + let fromItem = getPhotoOrDashboard(from); + let fromPos = mapFromItem(fromItem, fromItem.width / 2, fromItem.height / 2); - let end = []; - for (let i = 0; i < tos.length; i++) { - if (from === tos[i]) - continue; - let toItem = getPhotoOrDashboard(tos[i]); - let toPos = mapFromItem(toItem, toItem.width / 2, toItem.height / 2); - end.push(toPos); - } + let end = []; + for (let i = 0; i < tos.length; i++) { + if (from === tos[i]) + continue; + let toItem = getPhotoOrDashboard(tos[i]); + let toPos = mapFromItem(toItem, toItem.width / 2, toItem.height / 2); + end.push(toPos); + } - let color = "#96943D"; - let line = component.createObject(roomScene, {start: fromPos, end: end, color: color}); - line.finished.connect(() => line.destroy()); - line.running = true; + let color = "#96943D"; + let line = component.createObject(roomScene, {start: fromPos, end: end, color: color}); + line.finished.connect(() => line.destroy()); + line.running = true; } callbacks["AddPlayer"] = function(jsonData) { - // jsonData: int id, string screenName, string avatar - for (let i = 0; i < photoModel.count; i++) { - let item = photoModel.get(i); - if (item.id === -1) { - let data = JSON.parse(jsonData); - let uid = data[0]; - let name = data[1]; - let avatar = data[2]; - item.id = uid; - item.screenName = name; - item.general = avatar; - return; - } + // jsonData: int id, string screenName, string avatar + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (item.id === -1) { + let data = JSON.parse(jsonData); + let uid = data[0]; + let name = data[1]; + let avatar = data[2]; + item.id = uid; + item.screenName = name; + item.general = avatar; + 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) { - // jsonData: int uid - let uid = JSON.parse(jsonData)[0]; - let model = getPhotoModel(uid); - if (typeof(model) !== "undefined") { - model.id = -1; - model.screenName = ""; - model.general = ""; - } + // jsonData: int uid + let uid = JSON.parse(jsonData)[0]; + let model = getPhotoModel(uid); + if (typeof(model) !== "undefined") { + model.id = -1; + model.screenName = ""; + model.general = ""; + } } callbacks["RoomOwner"] = function(jsonData) { - // jsonData: int uid of the owner - let uid = JSON.parse(jsonData)[0]; + // jsonData: int uid of the owner + let uid = JSON.parse(jsonData)[0]; - if (dashboardModel.id === uid) { - dashboardModel.isOwner = true; - roomScene.dashboardModelChanged(); - return; - } + if (dashboardModel.id === uid) { + dashboardModel.isOwner = true; + roomScene.dashboardModelChanged(); + return; + } - let model = getPhotoModel(uid); - if (typeof(model) !== "undefined") { - model.isOwner = true; - } + let model = getPhotoModel(uid); + if (typeof(model) !== "undefined") { + model.isOwner = true; + } } callbacks["PropertyUpdate"] = function(jsonData) { - // jsonData: int id, string property_name, value - let data = JSON.parse(jsonData); - let uid = data[0]; - let property_name = data[1]; - let value = data[2]; + // jsonData: int id, string property_name, value + let data = JSON.parse(jsonData); + let uid = data[0]; + let property_name = data[1]; + let value = data[2]; - if (Self.id === uid) { - dashboardModel[property_name] = value; - roomScene.dashboardModelChanged(); - return; - } + if (Self.id === uid) { + dashboardModel[property_name] = value; + roomScene.dashboardModelChanged(); + return; + } - let model = getPhotoModel(uid); - if (typeof(model) !== "undefined") { - model[property_name] = value; - } + let model = getPhotoModel(uid); + if (typeof(model) !== "undefined") { + model[property_name] = value; + } } callbacks["ArrangeSeats"] = function(jsonData) { - // jsonData: seat order - let order = JSON.parse(jsonData); - roomScene.isStarted = true; + // jsonData: seat order + let order = JSON.parse(jsonData); + roomScene.isStarted = true; - for (let i = 0; i < photoModel.count; i++) { - let item = photoModel.get(i); - item.seatNumber = order.indexOf(item.id) + 1; - } + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + item.seatNumber = order.indexOf(item.id) + 1; + } - dashboardModel.seatNumber = order.indexOf(Self.id) + 1; - roomScene.dashboardModelChanged(); - - // make Self to the first of list, then reorder photomodel - let selfIndex = order.indexOf(Self.id); - let after = order.splice(selfIndex); - after.push(...order); - let photoOrder = after.slice(1); + dashboardModel.seatNumber = order.indexOf(Self.id) + 1; + roomScene.dashboardModelChanged(); + + // make Self to the first of list, then reorder photomodel + let selfIndex = order.indexOf(Self.id); + let after = order.splice(selfIndex); + after.push(...order); + let photoOrder = after.slice(1); - for (let i = 0; i < photoModel.count; i++) { - let item = photoModel.get(i); - item.index = photoOrder.indexOf(item.id); - } - - arrangePhotos(); + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + item.index = photoOrder.indexOf(item.id); + } + + arrangePhotos(); } function cancelAllFocus() { - let item; - for (let i = 0; i < playerNum - 1; i++) { - item = photos.itemAt(i); - item.progressBar.visible = false; - item.progressTip = ""; - } + let item; + for (let i = 0; i < playerNum - 1; i++) { + item = photos.itemAt(i); + item.progressBar.visible = false; + item.progressTip = ""; + } } callbacks["MoveFocus"] = function(jsonData) { - // jsonData: int[] focuses, string command - cancelAllFocus(); - let data = JSON.parse(jsonData); - let focuses = data[0]; - let command = data[1]; - - let item, model; - for (let i = 0; i < playerNum - 1; i++) { - model = photoModel.get(i); - if (focuses.indexOf(model.id) != -1) { - item = photos.itemAt(i); - item.progressBar.visible = true; - item.progressTip = command + " thinking..."; - } + // jsonData: int[] focuses, string command + cancelAllFocus(); + let data = JSON.parse(jsonData); + let focuses = data[0]; + let command = data[1]; + + let item, model; + for (let i = 0; i < playerNum - 1; i++) { + model = photoModel.get(i); + if (focuses.indexOf(model.id) != -1) { + item = photos.itemAt(i); + item.progressBar.visible = true; + item.progressTip = command + " thinking..."; } + } } callbacks["PlayerRunned"] = function(jsonData) { - // jsonData: int runner, int robot - let data = JSON.parse(jsonData); - let runner = data[0]; - let robot = data[1]; + // jsonData: int runner, int robot + let data = JSON.parse(jsonData); + let runner = data[0]; + let robot = data[1]; - let model = getPhotoModel(runner); - if (typeof(model) !== "undefined") { - model.id = robot; - } + let model = getPhotoModel(runner); + if (typeof(model) !== "undefined") { + model.id = robot; + } } callbacks["AskForGeneral"] = function(jsonData) { - // jsonData: string[] Generals - // TODO: choose multiple generals - let data = JSON.parse(jsonData); - roomScene.promptText = "Please choose 1 general"; - roomScene.state = "replying"; - roomScene.popupBox.source = "RoomElement/ChooseGeneralBox.qml"; - let box = roomScene.popupBox.item; - box.choiceNum = 1; - box.accepted.connect(() => { - replyToServer(JSON.stringify([box.choices[0]])); - }); - for (let i = 0; i < data.length; i++) - box.generalList.append({ "name": data[i] }); - box.updatePosition(); + // jsonData: string[] Generals + // TODO: choose multiple generals + let data = JSON.parse(jsonData); + roomScene.promptText = "Please choose 1 general"; + roomScene.state = "replying"; + roomScene.popupBox.source = "RoomElement/ChooseGeneralBox.qml"; + let box = roomScene.popupBox.item; + box.choiceNum = 1; + box.accepted.connect(() => { + replyToServer(JSON.stringify([box.choices[0]])); + }); + for (let i = 0; i < data.length; i++) + box.generalList.append({ "name": data[i] }); + box.updatePosition(); } callbacks["AskForSkillInvoke"] = function(jsonData) { - // jsonData: string name - roomScene.promptText = "Do you want to invoke '" + jsonData + "' ?"; - roomScene.state = "responding"; + // jsonData: string name + roomScene.promptText = "Do you want to invoke '" + jsonData + "' ?"; + roomScene.state = "responding"; } callbacks["AskForChoice"] = function(jsonData) { - // jsonData: [ string[] choices, string skill ] - // TODO: multiple choices, e.g. benxi_ol - let data = JSON.parse(jsonData); - let choices = data[0]; - let skill_name = data[1]; - roomScene.promptText = skill_name + ": Please make choice"; - roomScene.state = "replying"; - roomScene.popupBox.source = "RoomElement/ChoiceBox.qml"; - let box = roomScene.popupBox.item; - box.options = choices; - box.skill_name = skill_name; - box.accepted.connect(() => { - replyToServer(choices[box.result]); - }); + // jsonData: [ string[] choices, string skill ] + // TODO: multiple choices, e.g. benxi_ol + let data = JSON.parse(jsonData); + let choices = data[0]; + let skill_name = data[1]; + roomScene.promptText = skill_name + ": Please make choice"; + roomScene.state = "replying"; + roomScene.popupBox.source = "RoomElement/ChoiceBox.qml"; + let box = roomScene.popupBox.item; + box.options = choices; + box.skill_name = skill_name; + box.accepted.connect(() => { + replyToServer(choices[box.result]); + }); } callbacks["MoveCards"] = function(jsonData) { - // jsonData: merged moves - let moves = JSON.parse(jsonData); - moveCards(moves); + // jsonData: merged moves + let moves = JSON.parse(jsonData); + 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"; + } } diff --git a/qml/Toast.qml b/qml/Toast.qml index 2cf878a9..62d67e56 100644 --- a/qml/Toast.qml +++ b/qml/Toast.qml @@ -1,56 +1,56 @@ import QtQuick 2.15 Rectangle { - function show(text, duration) { - message.text = text; - time = Math.max(duration, 2 * fadeTime); - animation.start(); + function show(text, duration) { + message.text = text; + time = Math.max(duration, 2 * fadeTime); + 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 - - 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 + PauseAnimation { + duration: time - 2 * fadeTime } - SequentialAnimation on opacity { - id: animation - running: false - - - NumberAnimation { - to: .9 - duration: fadeTime - } - - PauseAnimation { - duration: time - 2 * fadeTime - } - - NumberAnimation { - to: 0 - duration: fadeTime - } - - onRunningChanged: { - if (!running) { - toast.model.remove(index); - } - } + NumberAnimation { + to: 0 + duration: fadeTime } + + onRunningChanged: { + if (!running) { + toast.model.remove(index); + } + } + } } diff --git a/qml/ToastManager.qml b/qml/ToastManager.qml index efb374bd..71cdb21d 100644 --- a/qml/ToastManager.qml +++ b/qml/ToastManager.qml @@ -3,35 +3,35 @@ import QtQuick 2.15 // copy from https://gist.github.com/jonmcclung/bae669101d17b103e94790341301c129 // and modified some code ListView { - function show(text, duration) { - if (duration === undefined) { - duration = 3000; - } - model.insert(0, {text: text, duration: duration}); + function show(text, duration) { + if (duration === undefined) { + duration = 3000; } + model.insert(0, {text: text, duration: duration}); + } - id: root + id: root - z: Infinity - spacing: 5 - anchors.fill: parent - anchors.bottomMargin: 10 - verticalLayoutDirection: ListView.BottomToTop + z: Infinity + spacing: 5 + anchors.fill: parent + anchors.bottomMargin: 10 + verticalLayoutDirection: ListView.BottomToTop - interactive: false + interactive: false - displaced: Transition { - NumberAnimation { - properties: "y" - easing.type: Easing.InOutQuad - } + displaced: Transition { + NumberAnimation { + properties: "y" + easing.type: Easing.InOutQuad } - - delegate: Toast { - Component.onCompleted: { - show(text, duration); - } + } + + delegate: Toast { + Component.onCompleted: { + show(text, duration); } + } - model: ListModel {id: model} + model: ListModel {id: model} } diff --git a/qml/main.qml b/qml/main.qml index ffb8b920..6667664a 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -5,116 +5,116 @@ import "Logic.js" as Logic import "Pages" Window { - id: mainWindow - visible: true - width: 720 - height: 480 - property var callbacks: Logic.callbacks + id: mainWindow + visible: true + width: 720 + height: 480 + property var callbacks: Logic.callbacks - StackView { - id: mainStack - visible: !mainWindow.busy - initialItem: init - anchors.fill: parent + StackView { + id: mainStack + visible: !mainWindow.busy + initialItem: init + 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 {} } - 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 + enter: Transition { + NumberAnimation { properties: "opacity"; from: 0; to: 1 } + NumberAnimation { properties: "scale"; from: 0.4; to: 1 } } - Config { - id: config + onAboutToHide: { + globalPopupDim.stateVisible = false } - // 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 } - } + exit: Transition { + NumberAnimation { properties: "opacity"; from: 1; to: 0 } + NumberAnimation { properties: "scale"; from: 1; to: 0.4 } } - 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 - } - - 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 = ""; - }); - } - } + Loader { + visible: !mainWindow.busy + source: globalPopup.source === "" ? "" : "GlobalPopups/" + globalPopup.source + onSourceChanged: { + if (item === null) + return; + item.finished.connect(() => { + globalPopup.close(); + globalPopup.source = ""; + }); + } } + } - ToastManager { - id: toast - } + ToastManager { + id: toast + } - Connections { - target: Backend - function onNotifyUI(command, jsonData) { - let cb = callbacks[command] - if (typeof(cb) === "function") { - cb(jsonData); - } else { - callbacks["ErrorMsg"]("Unknown command " + command + "!"); - } - } + Connections { + target: Backend + function onNotifyUI(command, jsonData) { + let cb = callbacks[command] + if (typeof(cb) === "function") { + cb(jsonData); + } else { + callbacks["ErrorMsg"]("Unknown command " + command + "!"); + } } + } } diff --git a/qml/util.js b/qml/util.js index 796be1c0..5f18888c 100644 --- a/qml/util.js +++ b/qml/util.js @@ -1,21 +1,21 @@ .pragma library function convertNumber(number) { - if (number === 1) - return "A"; - if (number >= 2 && number <= 10) - return number; - if (number >= 11 && number <= 13) { - const strs = ["J", "Q", "K"]; - return strs[number - 11]; - } - return ""; + if (number === 1) + return "A"; + if (number >= 2 && number <= 10) + return number; + if (number >= 11 && number <= 13) { + const strs = ["J", "Q", "K"]; + return strs[number - 11]; + } + return ""; } Array.prototype.contains = function(element) { - return this.indexOf(element) != -1; + return this.indexOf(element) != -1; } Array.prototype.prepend = function() { - this.splice(0, 0, ...arguments); + this.splice(0, 0, ...arguments); } diff --git a/server/init.sql b/server/init.sql index f3e09c9c..69afb583 100644 --- a/server/init.sql +++ b/server/init.sql @@ -1,12 +1,12 @@ CREATE TABLE userinfo ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(255), - password CHAR(64), - avatar VARCHAR(64), - lastLoginIp VARCHAR(64), - banned BOOLEAN + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(255), + password CHAR(64), + avatar VARCHAR(64), + lastLoginIp VARCHAR(64), + banned BOOLEAN ); CREATE TABLE banip ( - ip VARCHAR(64) + ip VARCHAR(64) ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ed26a31b..52ec07c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,39 +1,39 @@ set(freekill_SRCS - "main.cpp" - "core/player.cpp" - "core/util.cpp" - "network/server_socket.cpp" - "network/client_socket.cpp" - "network/router.cpp" - "server/server.cpp" - "server/serverplayer.cpp" - "server/room.cpp" - "client/client.cpp" - "client/clientplayer.cpp" - "ui/qmlbackend.cpp" - "swig/freekill-wrap.cxx" + "main.cpp" + "core/player.cpp" + "core/util.cpp" + "network/server_socket.cpp" + "network/client_socket.cpp" + "network/router.cpp" + "server/server.cpp" + "server/serverplayer.cpp" + "server/room.cpp" + "client/client.cpp" + "client/clientplayer.cpp" + "ui/qmlbackend.cpp" + "swig/freekill-wrap.cxx" ) set(freekill_HEADERS - "core/util.h" - "core/player.h" - "network/server_socket.h" - "network/client_socket.h" - "network/router.h" - "server/server.h" - "server/serverplayer.h" - "server/room.h" - "client/client.h" - "client/clientplayer.h" - "ui/qmlbackend.h" + "core/util.h" + "core/player.h" + "network/server_socket.h" + "network/client_socket.h" + "network/router.h" + "server/server.h" + "server/serverplayer.h" + "server/room.h" + "client/client.h" + "client/clientplayer.h" + "ui/qmlbackend.h" ) if (WIN32) - set(LUA_LIB ${PROJECT_SOURCE_DIR}/lib/win/lua54.dll) - set(SQLITE3_LIB ${PROJECT_SOURCE_DIR}/lib/win/sqlite3.dll) + set(LUA_LIB ${PROJECT_SOURCE_DIR}/lib/win/lua54.dll) + set(SQLITE3_LIB ${PROJECT_SOURCE_DIR}/lib/win/sqlite3.dll) else () - set(LUA_LIB lua5.4) - set(SQLITE3_LIB sqlite3) + set(LUA_LIB lua5.4) + set(SQLITE3_LIB sqlite3) endif () 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) file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i") add_custom_command( - OUTPUT ${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx - DEPENDS ${SWIG_FILES} - COMMENT "Generating freekill-wrap.cxx" - COMMAND swig -c++ -lua -Wall -o - ${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx - ${PROJECT_SOURCE_DIR}/src/swig/freekill.i + OUTPUT ${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx + DEPENDS ${SWIG_FILES} + COMMENT "Generating freekill-wrap.cxx" + COMMAND swig -c++ -lua -Wall -o + ${PROJECT_SOURCE_DIR}/src/swig/freekill-wrap.cxx + ${PROJECT_SOURCE_DIR}/src/swig/freekill.i ) diff --git a/src/client/client.cpp b/src/client/client.cpp index bd9a702f..9194324b 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -7,67 +7,67 @@ Client *ClientInstance; ClientPlayer *Self; Client::Client(QObject* parent) - : QObject(parent), callback(0) + : QObject(parent), callback(0) { - ClientInstance = this; - Self = new ClientPlayer(0, this); - QQmlApplicationEngine *engine = Backend->getEngine(); - engine->rootContext()->setContextProperty("ClientInstance", ClientInstance); - engine->rootContext()->setContextProperty("Self", Self); + ClientInstance = this; + Self = new ClientPlayer(0, this); + QQmlApplicationEngine *engine = Backend->getEngine(); + engine->rootContext()->setContextProperty("ClientInstance", ClientInstance); + engine->rootContext()->setContextProperty("Self", Self); - ClientSocket *socket = new ClientSocket; - connect(socket, &ClientSocket::error_message, this, &Client::error_message); - router = new Router(this, socket, Router::TYPE_CLIENT); + ClientSocket *socket = new ClientSocket; + connect(socket, &ClientSocket::error_message, this, &Client::error_message); + router = new Router(this, socket, Router::TYPE_CLIENT); - L = CreateLuaState(); - DoLuaScript(L, "lua/freekill.lua"); - DoLuaScript(L, "lua/client/client.lua"); + L = CreateLuaState(); + DoLuaScript(L, "lua/freekill.lua"); + DoLuaScript(L, "lua/client/client.lua"); } Client::~Client() { - ClientInstance = nullptr; - lua_close(L); - router->getSocket()->disconnectFromHost(); - router->getSocket()->deleteLater(); + ClientInstance = nullptr; + lua_close(L); + router->getSocket()->disconnectFromHost(); + router->getSocket()->deleteLater(); } 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) { - int type = Router::TYPE_REPLY | Router::SRC_CLIENT | Router::DEST_SERVER; - router->reply(type, command, jsonData); + int type = Router::TYPE_REPLY | Router::SRC_CLIENT | Router::DEST_SERVER; + router->reply(type, command, jsonData); } void Client::notifyServer(const QString& command, const QString& jsonData) { - int type = Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER; - router->notify(type, command, jsonData); + int type = Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER; + router->notify(type, command, jsonData); } ClientPlayer *Client::addPlayer(int id, const QString &name, const QString &avatar) { - ClientPlayer *player = new ClientPlayer(id); - player->setScreenName(name); - player->setAvatar(avatar); + ClientPlayer *player = new ClientPlayer(id); + player->setScreenName(name); + player->setAvatar(avatar); - players[id] = player; - return player; + players[id] = player; + return player; } void Client::removePlayer(int id) { - ClientPlayer *p = players[id]; - p->deleteLater(); - players[id] = nullptr; + ClientPlayer *p = players[id]; + p->deleteLater(); + players[id] = nullptr; } void Client::clearPlayers() { - players.clear(); + players.clear(); } lua_State *Client::getLuaState() { - return L; + return L; } diff --git a/src/client/client.h b/src/client/client.h index 307851dc..557a2398 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -6,33 +6,33 @@ #include "qmlbackend.h" class Client : public QObject { - Q_OBJECT + Q_OBJECT public: - Client(QObject *parent = nullptr); - ~Client(); + Client(QObject *parent = nullptr); + ~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 notifyServer(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 callLua(const QString &command, const QString &jsonData); - LuaFunction callback; + Q_INVOKABLE void callLua(const QString &command, const QString &jsonData); + LuaFunction callback; - ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); - void removePlayer(int id); - Q_INVOKABLE void clearPlayers(); + ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); + void removePlayer(int id); + Q_INVOKABLE void clearPlayers(); - lua_State *getLuaState(); + lua_State *getLuaState(); signals: - void error_message(const QString &msg); + void error_message(const QString &msg); private: - Router *router; - QMap players; + Router *router; + QMap players; - lua_State *L; + lua_State *L; }; extern Client *ClientInstance; diff --git a/src/client/clientplayer.cpp b/src/client/clientplayer.cpp index 07334f7f..f0142a33 100644 --- a/src/client/clientplayer.cpp +++ b/src/client/clientplayer.cpp @@ -1,9 +1,9 @@ #include "clientplayer.h" ClientPlayer::ClientPlayer(int id, QObject* parent) - : Player(parent) + : Player(parent) { - setId(id); + setId(id); } ClientPlayer::~ClientPlayer() diff --git a/src/client/clientplayer.h b/src/client/clientplayer.h index c0f8a663..a55a4ed7 100644 --- a/src/client/clientplayer.h +++ b/src/client/clientplayer.h @@ -4,23 +4,23 @@ #include "player.h" class ClientPlayer : public Player { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(int id READ getId CONSTANT) - Q_PROPERTY(QString screenName - READ getScreenName - WRITE setScreenName - NOTIFY screenNameChanged - ) - Q_PROPERTY(QString avatar - READ getAvatar - WRITE setAvatar - NOTIFY avatarChanged - ) + Q_PROPERTY(int id READ getId CONSTANT) + Q_PROPERTY(QString screenName + READ getScreenName + WRITE setScreenName + NOTIFY screenNameChanged + ) + Q_PROPERTY(QString avatar + READ getAvatar + WRITE setAvatar + NOTIFY avatarChanged + ) public: - ClientPlayer(int id, QObject *parent = nullptr); - ~ClientPlayer(); + ClientPlayer(int id, QObject *parent = nullptr); + ~ClientPlayer(); private: }; diff --git a/src/core/player.cpp b/src/core/player.cpp index e77c5aa5..f4d46200 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -1,10 +1,10 @@ #include "player.h" Player::Player(QObject* parent) - : QObject(parent) - , id(0) - , state(Player::Invalid) - , ready(false) + : QObject(parent) + , id(0) + , state(Player::Invalid) + , ready(false) { } @@ -14,85 +14,85 @@ Player::~Player() int Player::getId() const { - return id; + return id; } void Player::setId(int id) { - this->id = id; + this->id = id; } QString Player::getScreenName() const { - return screenName; + return screenName; } void Player::setScreenName(const QString& name) { - this->screenName = name; - emit screenNameChanged(); + this->screenName = name; + emit screenNameChanged(); } QString Player::getAvatar() const { - return avatar; + return avatar; } void Player::setAvatar(const QString& avatar) { - this->avatar = avatar; - emit avatarChanged(); + this->avatar = avatar; + emit avatarChanged(); } Player::State Player::getState() const { - return state; + return state; } QString Player::getStateString() const { - switch (state) { - case Online: - return QStringLiteral("online"); - case Trust: - return QStringLiteral("trust"); - case Robot: - return QStringLiteral("robot"); - case Offline: - return QStringLiteral("offline"); - default: - return QStringLiteral("invalid"); - } + switch (state) { + case Online: + return QStringLiteral("online"); + case Trust: + return QStringLiteral("trust"); + case Robot: + return QStringLiteral("robot"); + case Offline: + return QStringLiteral("offline"); + default: + return QStringLiteral("invalid"); + } } void Player::setState(Player::State state) { - this->state = state; - emit stateChanged(); + this->state = state; + emit stateChanged(); } void Player::setStateString(const QString &state) { - if (state == QStringLiteral("online")) - setState(Online); - else if (state == QStringLiteral("trust")) - setState(Trust); - else if (state == QStringLiteral("robot")) - setState(Robot); - else if (state == QStringLiteral("offline")) - setState(Offline); - else - setState(Invalid); + if (state == QStringLiteral("online")) + setState(Online); + else if (state == QStringLiteral("trust")) + setState(Trust); + else if (state == QStringLiteral("robot")) + setState(Robot); + else if (state == QStringLiteral("offline")) + setState(Offline); + else + setState(Invalid); } bool Player::isReady() const { - return ready; + return ready; } void Player::setReady(bool ready) { - this->ready = ready; - emit readyChanged(); + this->ready = ready; + emit readyChanged(); } diff --git a/src/core/player.h b/src/core/player.h index b0cb0657..fa25a949 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -4,49 +4,49 @@ // Common part of ServerPlayer and ClientPlayer // dont initialize it directly class Player : public QObject { - Q_OBJECT + Q_OBJECT public: - enum State{ - Invalid, - Online, - Trust, // Trust or run - Robot, // only for real robot - Offline - }; + enum State{ + Invalid, + Online, + Trust, // Trust or run + Robot, // only for real robot + Offline + }; - explicit Player(QObject *parent = nullptr); - ~Player(); + explicit Player(QObject *parent = nullptr); + ~Player(); - int getId() const; - void setId(int id); + int getId() const; + void setId(int id); - QString getScreenName() const; - void setScreenName(const QString &name); + QString getScreenName() const; + void setScreenName(const QString &name); - QString getAvatar() const; - void setAvatar(const QString &avatar); + QString getAvatar() const; + void setAvatar(const QString &avatar); - State getState() const; - QString getStateString() const; - void setState(State state); - void setStateString(const QString &state); + State getState() const; + QString getStateString() const; + void setState(State state); + void setStateString(const QString &state); - bool isReady() const; - void setReady(bool ready); + bool isReady() const; + void setReady(bool ready); signals: - void screenNameChanged(); - void avatarChanged(); - void stateChanged(); - void readyChanged(); + void screenNameChanged(); + void avatarChanged(); + void stateChanged(); + void readyChanged(); private: - int id; - QString screenName; // screenName should not be same. - QString avatar; - State state; - bool ready; + int id; + QString screenName; // screenName should not be same. + QString avatar; + State state; + bool ready; }; #endif // _PLAYER_H diff --git a/src/core/util.cpp b/src/core/util.cpp index 9a63e2fe..9df01962 100644 --- a/src/core/util.cpp +++ b/src/core/util.cpp @@ -1,123 +1,123 @@ #include "util.h" extern "C" { - int luaopen_fk(lua_State *); + int luaopen_fk(lua_State *); } lua_State *CreateLuaState() { - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - luaopen_fk(L); + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + luaopen_fk(L); - return L; + return L; } bool DoLuaScript(lua_State *L, const char *script) { - lua_getglobal(L, "debug"); - lua_getfield(L, -1, "traceback"); - lua_replace(L, -2); + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, -2); - luaL_loadfile(L, script); - int error = lua_pcall(L, 0, LUA_MULTRET, -2); + luaL_loadfile(L, script); + int error = lua_pcall(L, 0, LUA_MULTRET, -2); - if (error) { - const char *error_msg = lua_tostring(L, -1); - qDebug() << error_msg; - lua_pop(L, 2); - return false; - } - lua_pop(L, 1); - return true; + if (error) { + const char *error_msg = lua_tostring(L, -1); + qDebug() << error_msg; + lua_pop(L, 2); + return false; + } + lua_pop(L, 1); + return true; } // For Lua debugging void Dumpstack(lua_State *L) { - int top = lua_gettop(L); - for (int i = 1; i <= top; i++) { - printf("%d\t%s\t", i, luaL_typename(L, i)); - switch (lua_type(L, i)) { - case LUA_TNUMBER: - printf("%g\n",lua_tonumber(L, i)); - break; - case LUA_TSTRING: - printf("%s\n",lua_tostring(L, i)); - break; - case LUA_TBOOLEAN: - printf("%s\n", (lua_toboolean(L, i) ? "true" : "false")); - break; - case LUA_TNIL: - printf("%s\n", "nil"); - break; - default: - printf("%p\n",lua_topointer(L, i)); - break; - } + int top = lua_gettop(L); + for (int i = 1; i <= top; i++) { + printf("%d\t%s\t", i, luaL_typename(L, i)); + switch (lua_type(L, i)) { + case LUA_TNUMBER: + printf("%g\n",lua_tonumber(L, i)); + break; + case LUA_TSTRING: + printf("%s\n",lua_tostring(L, i)); + break; + case LUA_TBOOLEAN: + printf("%s\n", (lua_toboolean(L, i) ? "true" : "false")); + break; + case LUA_TNIL: + printf("%s\n", "nil"); + break; + default: + printf("%p\n",lua_topointer(L, i)); + break; } + } } sqlite3 *OpenDatabase(const QString &filename) { - sqlite3 *ret; - int rc; - if (!QFile::exists(filename)) { - QFile file("./server/init.sql"); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "cannot open init.sql. Quit now."; - 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); - } + sqlite3 *ret; + int rc; + if (!QFile::exists(filename)) { + QFile file("./server/init.sql"); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "cannot open init.sql. Quit now."; + 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 static int callback(void *jsonDoc, int argc, char **argv, char **cols) { - QJsonObject obj; - for (int i = 0; i < argc; i++) { - QJsonArray arr = obj[QString(cols[i])].toArray(); - arr << QString(argv[i] ? argv[i] : "#null"); - obj[QString(cols[i])] = arr; - } - ((QJsonObject *)jsonDoc)->swap(obj); - return 0; + QJsonObject obj; + for (int i = 0; i < argc; i++) { + QJsonArray arr = obj[QString(cols[i])].toArray(); + arr << QString(argv[i] ? argv[i] : "#null"); + obj[QString(cols[i])] = arr; + } + ((QJsonObject *)jsonDoc)->swap(obj); + return 0; } QJsonObject SelectFromDatabase(sqlite3 *db, const QString &sql) { - QJsonObject obj; - sqlite3_exec(db, sql.toUtf8().data(), callback, (void *)&obj, nullptr); - return obj; + QJsonObject obj; + sqlite3_exec(db, sql.toUtf8().data(), callback, (void *)&obj, nullptr); + return obj; } QString SelectFromDb(sqlite3 *db, const QString &sql) { - QJsonObject obj = SelectFromDatabase(db, sql); - return QJsonDocument(obj).toJson(); + QJsonObject obj = SelectFromDatabase(db, sql); + return QJsonDocument(obj).toJson(); } 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) { - sqlite3_close(db); + sqlite3_close(db); } diff --git a/src/main.cpp b/src/main.cpp index db24b097..89062f1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,59 +3,61 @@ int main(int argc, char *argv[]) { - QCoreApplication *app; - QCoreApplication::setApplicationName("FreeKill"); - QCoreApplication::setApplicationVersion("Alpha 0.0.1"); + QCoreApplication *app; + QCoreApplication::setApplicationName("FreeKill"); + QCoreApplication::setApplicationVersion("Alpha 0.0.1"); - QCommandLineParser parser; - parser.setApplicationDescription("FreeKill server"); - parser.addHelpOption(); - parser.addVersionOption(); - parser.addOption({{"s", "server"}, "start server at ", "port"}); - QStringList cliOptions; - for (int i = 0; i < argc; i++) - cliOptions << argv[i]; + QCommandLineParser parser; + parser.setApplicationDescription("FreeKill server"); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addOption({{"s", "server"}, "start server at ", "port"}); + QStringList cliOptions; + for (int i = 0; i < argc; i++) + cliOptions << argv[i]; - parser.parse(cliOptions); + parser.parse(cliOptions); - bool startServer = parser.isSet("server"); - ushort serverPort = 9527; + bool startServer = parser.isSet("server"); + ushort serverPort = 9527; - if (startServer) { - app = new QCoreApplication(argc, argv); - bool ok = false; - if (parser.value("server").toInt(&ok) && ok) - serverPort = parser.value("server").toInt(); - Server *server = new Server; - if (!server->listen(QHostAddress::Any, serverPort)) { - fprintf(stderr, "cannot listen on port %d!\n", serverPort); - app->exit(1); - } - return app->exec(); + if (startServer) { + app = new QCoreApplication(argc, argv); + bool ok = false; + if (parser.value("server").toInt(&ok) && ok) + serverPort = parser.value("server").toInt(); + Server *server = new Server; + if (!server->listen(QHostAddress::Any, serverPort)) { + fprintf(stderr, "cannot listen on port %d!\n", serverPort); + app->exit(1); } + return app->exec(); + } - app = new QGuiApplication(argc, argv); + app = new QGuiApplication(argc, argv); - QQmlApplicationEngine *engine = new QQmlApplicationEngine; - - QmlBackend backend; - backend.setEngine(engine); - - engine->rootContext()->setContextProperty("Backend", &backend); - engine->rootContext()->setContextProperty("AppPath", QUrl::fromLocalFile(QDir::currentPath())); + QQmlApplicationEngine *engine = new QQmlApplicationEngine; + + QmlBackend backend; + backend.setEngine(engine); + + engine->rootContext()->setContextProperty("Backend", &backend); + engine->rootContext()->setContextProperty("AppPath", QUrl::fromLocalFile(QDir::currentPath())); #ifdef QT_DEBUG - bool debugging = true; + bool debugging = true; #else - bool debugging = false; + bool debugging = false; #endif - engine->rootContext()->setContextProperty("Debugging", debugging); - engine->load("qml/main.qml"); + engine->rootContext()->setContextProperty("Debugging", debugging); + engine->load("qml/main.qml"); + if (engine->rootObjects().isEmpty()) + return -1; - int ret = app->exec(); + int ret = app->exec(); - // delete the engine first - // to avoid "TypeError: Cannot read property 'xxx' of null" - delete engine; + // delete the engine first + // to avoid "TypeError: Cannot read property 'xxx' of null" + delete engine; - return ret; + return ret; } diff --git a/src/network/client_socket.cpp b/src/network/client_socket.cpp index 64cfb53a..e0ea33b6 100644 --- a/src/network/client_socket.cpp +++ b/src/network/client_socket.cpp @@ -2,94 +2,94 @@ ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) { - init(); + init(); } ClientSocket::ClientSocket(QTcpSocket* socket) { - socket->setParent(this); - this->socket = socket; - timerSignup.setSingleShot(true); - connect(&timerSignup, &QTimer::timeout, this, &ClientSocket::disconnectFromHost); - init(); + socket->setParent(this); + this->socket = socket; + timerSignup.setSingleShot(true); + connect(&timerSignup, &QTimer::timeout, this, &ClientSocket::disconnectFromHost); + init(); } void ClientSocket::init() { - connect(socket, &QTcpSocket::connected, - this, &ClientSocket::connected); - connect(socket, &QTcpSocket::disconnected, - this, &ClientSocket::disconnected); - connect(socket, &QTcpSocket::readyRead, - this, &ClientSocket::getMessage); - connect(socket, &QTcpSocket::errorOccurred, - this, &ClientSocket::raiseError); + connect(socket, &QTcpSocket::connected, + this, &ClientSocket::connected); + connect(socket, &QTcpSocket::disconnected, + this, &ClientSocket::disconnected); + connect(socket, &QTcpSocket::readyRead, + this, &ClientSocket::getMessage); + connect(socket, &QTcpSocket::errorOccurred, + this, &ClientSocket::raiseError); } void ClientSocket::connectToHost(const QHostAddress &address, ushort port) { - socket->connectToHost(address, port); + socket->connectToHost(address, port); } void ClientSocket::getMessage() { - while (socket->canReadLine()) { - char msg[16000]; // buffer - socket->readLine(msg, sizeof(msg)); - emit message_got(msg); - } + while (socket->canReadLine()) { + char msg[16000]; // buffer + socket->readLine(msg, sizeof(msg)); + emit message_got(msg); + } } void ClientSocket::disconnectFromHost() { - socket->disconnectFromHost(); + socket->disconnectFromHost(); } void ClientSocket::send(const QByteArray &msg) { - socket->write(msg); - if (!msg.endsWith("\n")) - socket->write("\n"); - socket->flush(); + socket->write(msg); + if (!msg.endsWith("\n")) + socket->write("\n"); + socket->flush(); } bool ClientSocket::isConnected() const { - return socket->state() == QTcpSocket::ConnectedState; + return socket->state() == QTcpSocket::ConnectedState; } QString ClientSocket::peerName() const { - QString peer_name = socket->peerName(); - if (peer_name.isEmpty()) - peer_name = QString("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort()); + QString peer_name = socket->peerName(); + if (peer_name.isEmpty()) + peer_name = QString("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort()); - return peer_name; + return peer_name; } QString ClientSocket::peerAddress() const { - return socket->peerAddress().toString(); + return socket->peerAddress().toString(); } void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error) { - // translate error message - QString reason; - switch (socket_error) { - case QAbstractSocket::ConnectionRefusedError: - reason = tr("Connection was refused or timeout"); break; - case QAbstractSocket::RemoteHostClosedError: - reason = tr("Remote host close this connection"); break; - case QAbstractSocket::HostNotFoundError: - reason = tr("Host not found"); break; - case QAbstractSocket::SocketAccessError: - reason = tr("Socket access error"); break; - case QAbstractSocket::NetworkError: - return; // this error is ignored ... - default: reason = tr("Unknow error"); break; - } + // translate error message + QString reason; + switch (socket_error) { + case QAbstractSocket::ConnectionRefusedError: + reason = tr("Connection was refused or timeout"); break; + case QAbstractSocket::RemoteHostClosedError: + reason = tr("Remote host close this connection"); break; + case QAbstractSocket::HostNotFoundError: + reason = tr("Host not found"); break; + case QAbstractSocket::SocketAccessError: + reason = tr("Socket access error"); break; + case QAbstractSocket::NetworkError: + return; // this error is ignored ... + default: reason = tr("Unknow error"); break; + } - emit error_message(tr("Connection failed, error code = %1\n reason: %2") - .arg(socket_error).arg(reason)); + emit error_message(tr("Connection failed, error code = %1\n reason: %2") + .arg(socket_error).arg(reason)); } diff --git a/src/network/client_socket.h b/src/network/client_socket.h index 3db14196..279bd85d 100644 --- a/src/network/client_socket.h +++ b/src/network/client_socket.h @@ -2,34 +2,34 @@ #define _CLIENT_SOCKET_H class ClientSocket : public QObject { - Q_OBJECT + Q_OBJECT public: - ClientSocket(); - // For server use - ClientSocket(QTcpSocket *socket); + ClientSocket(); + // For server use + ClientSocket(QTcpSocket *socket); - void connectToHost(const QHostAddress &address = QHostAddress::LocalHost, ushort port = 9527u); - void disconnectFromHost(); - void send(const QByteArray& msg); - bool isConnected() const; - QString peerName() const; - QString peerAddress() const; - QTimer timerSignup; + void connectToHost(const QHostAddress &address = QHostAddress::LocalHost, ushort port = 9527u); + void disconnectFromHost(); + void send(const QByteArray& msg); + bool isConnected() const; + QString peerName() const; + QString peerAddress() const; + QTimer timerSignup; signals: - void message_got(const QByteArray& msg); - void error_message(const QString &msg); - void disconnected(); - void connected(); + void message_got(const QByteArray& msg); + void error_message(const QString &msg); + void disconnected(); + void connected(); private slots: - void getMessage(); - void raiseError(QAbstractSocket::SocketError error); + void getMessage(); + void raiseError(QAbstractSocket::SocketError error); private: - QTcpSocket *socket; - void init(); + QTcpSocket *socket; + void init(); }; #endif // _CLIENT_SOCKET_H diff --git a/src/network/router.cpp b/src/network/router.cpp index ad3aeb3b..c7eb0696 100644 --- a/src/network/router.cpp +++ b/src/network/router.cpp @@ -5,200 +5,200 @@ #include "serverplayer.h" Router::Router(QObject *parent, ClientSocket *socket, RouterType type) - : QObject(parent) + : QObject(parent) { - this->type = type; - this->socket = nullptr; - setSocket(socket); - expectedReplyId = -1; - replyTimeout = 0; - extraReplyReadySemaphore = nullptr; + this->type = type; + this->socket = nullptr; + setSocket(socket); + expectedReplyId = -1; + replyTimeout = 0; + extraReplyReadySemaphore = nullptr; } Router::~Router() { - abortRequest(); + abortRequest(); } ClientSocket* Router::getSocket() const { - return socket; + return socket; } void Router::setSocket(ClientSocket *socket) { - if (this->socket != nullptr) { - this->socket->disconnect(this); - disconnect(this->socket); - this->socket->deleteLater(); - } + if (this->socket != nullptr) { + this->socket->disconnect(this); + disconnect(this->socket); + this->socket->deleteLater(); + } - this->socket = nullptr; - if (socket != nullptr) { - connect(this, &Router::messageReady, socket, &ClientSocket::send); - connect(socket, &ClientSocket::message_got, this, &Router::handlePacket); - connect(socket, &ClientSocket::disconnected, this, &Router::abortRequest); - socket->setParent(this); - this->socket = socket; - } + this->socket = nullptr; + if (socket != nullptr) { + connect(this, &Router::messageReady, socket, &ClientSocket::send); + connect(socket, &ClientSocket::message_got, this, &Router::handlePacket); + connect(socket, &ClientSocket::disconnected, this, &Router::abortRequest); + socket->setParent(this); + this->socket = socket; + } } void Router::setReplyReadySemaphore(QSemaphore *semaphore) { - extraReplyReadySemaphore = semaphore; + extraReplyReadySemaphore = semaphore; } 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 - if (replyReadySemaphore.available() > 0) - replyReadySemaphore.acquire(replyReadySemaphore.available()); + // In case a request is called without a following waitForReply call + if (replyReadySemaphore.available() > 0) + replyReadySemaphore.acquire(replyReadySemaphore.available()); - static int requestId = 0; - requestId++; + static int requestId = 0; + requestId++; - replyMutex.lock(); - expectedReplyId = requestId; - replyTimeout = timeout; - requestStartTime = QDateTime::currentDateTime(); - m_reply = QString(); - replyMutex.unlock(); + replyMutex.lock(); + expectedReplyId = requestId; + replyTimeout = timeout; + requestStartTime = QDateTime::currentDateTime(); + m_reply = QString(); + replyMutex.unlock(); - QJsonArray body; - body << requestId; - body << type; - body << command; - body << jsonData; - body << timeout; + QJsonArray body; + body << requestId; + body << type; + body << command; + body << jsonData; + 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) { - QJsonArray body; - body << this->requestId; - body << type; - body << command; - body << jsonData; + QJsonArray body; + body << this->requestId; + body << type; + body << command; + 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) { - QJsonArray body; - body << -2; // requestId = -2 mean this is for notification - body << type; - body << command; - body << jsonData; + QJsonArray body; + body << -2; // requestId = -2 mean this is for notification + body << type; + body << command; + body << jsonData; - emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact)); + emit messageReady(QJsonDocument(body).toJson(QJsonDocument::Compact)); } int Router::getTimeout() const { - return requestTimeout; + return requestTimeout; } // cancel last request from the sender void Router::cancelRequest() { - replyMutex.lock(); - expectedReplyId = -1; - replyTimeout = 0; - extraReplyReadySemaphore = nullptr; - replyMutex.unlock(); + replyMutex.lock(); + expectedReplyId = -1; + replyTimeout = 0; + extraReplyReadySemaphore = nullptr; + replyMutex.unlock(); - if (replyReadySemaphore.available() > 0) - replyReadySemaphore.acquire(replyReadySemaphore.available()); + if (replyReadySemaphore.available() > 0) + replyReadySemaphore.acquire(replyReadySemaphore.available()); } QString Router::waitForReply() { - replyReadySemaphore.acquire(); - return m_reply; + replyReadySemaphore.acquire(); + return m_reply; } QString Router::waitForReply(int timeout) { - replyReadySemaphore.tryAcquire(1, timeout * 1000); - return m_reply; + replyReadySemaphore.tryAcquire(1, timeout * 1000); + return m_reply; } void Router::abortRequest() { - replyMutex.lock(); - if (expectedReplyId != -1) { - replyReadySemaphore.release(); - if (extraReplyReadySemaphore) - extraReplyReadySemaphore->release(); - expectedReplyId = -1; - extraReplyReadySemaphore = nullptr; - } - replyMutex.unlock(); + replyMutex.lock(); + if (expectedReplyId != -1) { + replyReadySemaphore.release(); + if (extraReplyReadySemaphore) + extraReplyReadySemaphore->release(); + expectedReplyId = -1; + extraReplyReadySemaphore = nullptr; + } + replyMutex.unlock(); } void Router::handlePacket(const QByteArray& rawPacket) { - QJsonDocument packet = QJsonDocument::fromJson(rawPacket); - if (packet.isNull() || !packet.isArray()) - return; + QJsonDocument packet = QJsonDocument::fromJson(rawPacket); + if (packet.isNull() || !packet.isArray()) + return; - int requestId = packet[0].toInt(); - int type = packet[1].toInt(); - QString command = packet[2].toString(); - QString jsonData = packet[3].toString(); + int requestId = packet[0].toInt(); + int type = packet[1].toInt(); + QString command = packet[2].toString(); + QString jsonData = packet[3].toString(); - if (type & TYPE_NOTIFICATION) { - if (type & DEST_CLIENT) { - ClientInstance->callLua(command, jsonData); - } else { - ServerPlayer *player = qobject_cast(parent()); - // Add the uid of sender to jsonData - QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array(); - arr.prepend(player->getId()); + if (type & TYPE_NOTIFICATION) { + if (type & DEST_CLIENT) { + ClientInstance->callLua(command, jsonData); + } else { + ServerPlayer *player = qobject_cast(parent()); + // Add the uid of sender to jsonData + QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array(); + arr.prepend(player->getId()); - Room *room = player->getRoom(); - room->lockLua(__FUNCTION__); - room->callLua(command, QJsonDocument(arr).toJson()); - room->unlockLua(__FUNCTION__); - } + Room *room = player->getRoom(); + room->lockLua(__FUNCTION__); + room->callLua(command, QJsonDocument(arr).toJson()); + room->unlockLua(__FUNCTION__); } - else if (type & TYPE_REQUEST) { - this->requestId = requestId; - this->requestTimeout = packet[4].toInt(); + } + else if (type & TYPE_REQUEST) { + this->requestId = requestId; + this->requestTimeout = packet[4].toInt(); - if (type & DEST_CLIENT) { - qobject_cast(parent())->callLua(command, jsonData); - } else { - // requesting server is not allowed - Q_ASSERT(false); - } + if (type & DEST_CLIENT) { + qobject_cast(parent())->callLua(command, jsonData); + } else { + // requesting server is not allowed + Q_ASSERT(false); } - else if (type & TYPE_REPLY) { - QMutexLocker locker(&replyMutex); + } + else if (type & TYPE_REPLY) { + QMutexLocker locker(&replyMutex); - if (requestId != this->expectedReplyId) - return; + if (requestId != this->expectedReplyId) + return; - this->expectedReplyId = -1; + this->expectedReplyId = -1; - if (replyTimeout >= 0 && replyTimeout < - requestStartTime.secsTo(QDateTime::currentDateTime())) - return; + if (replyTimeout >= 0 && replyTimeout < + requestStartTime.secsTo(QDateTime::currentDateTime())) + return; - m_reply = jsonData; - // TODO: callback? + m_reply = jsonData; + // TODO: callback? - replyReadySemaphore.release(); - if (extraReplyReadySemaphore) { - extraReplyReadySemaphore->release(); - extraReplyReadySemaphore = nullptr; - } - locker.unlock(); - emit replyReady(); + replyReadySemaphore.release(); + if (extraReplyReadySemaphore) { + extraReplyReadySemaphore->release(); + extraReplyReadySemaphore = nullptr; } + locker.unlock(); + emit replyReady(); + } } diff --git a/src/network/router.h b/src/network/router.h index f76db1c1..406d81cc 100644 --- a/src/network/router.h +++ b/src/network/router.h @@ -4,75 +4,75 @@ class ClientSocket; class Router : public QObject { - Q_OBJECT + Q_OBJECT public: - enum PacketType { - TYPE_REQUEST = 0x100, - TYPE_REPLY = 0x200, - TYPE_NOTIFICATION = 0x400, - SRC_CLIENT = 0x010, - SRC_SERVER = 0x020, - SRC_LOBBY = 0x040, - DEST_CLIENT = 0x001, - DEST_SERVER = 0x002, - DEST_LOBBY = 0x004 - }; + enum PacketType { + TYPE_REQUEST = 0x100, + TYPE_REPLY = 0x200, + TYPE_NOTIFICATION = 0x400, + SRC_CLIENT = 0x010, + SRC_SERVER = 0x020, + SRC_LOBBY = 0x040, + DEST_CLIENT = 0x001, + DEST_SERVER = 0x002, + DEST_LOBBY = 0x004 + }; - enum RouterType { - TYPE_SERVER, - TYPE_CLIENT - }; - Router(QObject *parent, ClientSocket *socket, RouterType type); - ~Router(); + enum RouterType { + TYPE_SERVER, + TYPE_CLIENT + }; + Router(QObject *parent, ClientSocket *socket, RouterType type); + ~Router(); - ClientSocket *getSocket() const; - void setSocket(ClientSocket *socket); + ClientSocket *getSocket() const; + void setSocket(ClientSocket *socket); - void setReplyReadySemaphore(QSemaphore *semaphore); + void setReplyReadySemaphore(QSemaphore *semaphore); - void request(int type, const QString &command, - const QString &jsonData, int timeout); - void reply(int type, const QString &command, const QString &jsonData); - void notify(int type, const QString &command, const QString &jsonData); + void request(int type, const QString &command, + const QString &jsonData, int timeout); + void reply(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 abortRequest(); + void cancelRequest(); + void abortRequest(); - QString waitForReply(); - QString waitForReply(int timeout); + QString waitForReply(); + QString waitForReply(int timeout); signals: - void messageReady(const QByteArray &message); - void unknownPacket(const QByteArray &packet); - void replyReady(); + void messageReady(const QByteArray &message); + void unknownPacket(const QByteArray &packet); + void replyReady(); protected: - void handlePacket(const QByteArray &rawPacket); + void handlePacket(const QByteArray &rawPacket); private: - ClientSocket *socket; - RouterType type; + ClientSocket *socket; + RouterType type; - // For sender - int requestId; - int requestTimeout; + // For sender + int requestId; + int requestTimeout; - // For receiver - QDateTime requestStartTime; - QMutex replyMutex; - int expectedReplyId; - int replyTimeout; - QString m_reply; // should be json string - QSemaphore replyReadySemaphore; - QSemaphore *extraReplyReadySemaphore; + // For receiver + QDateTime requestStartTime; + QMutex replyMutex; + int expectedReplyId; + int replyTimeout; + QString m_reply; // should be json string + QSemaphore replyReadySemaphore; + QSemaphore *extraReplyReadySemaphore; - // Two Lua global table for callbacks and interactions - // stored in the lua_State of the sender - // LuaTable interactions; - // LuaTable callbacks; + // Two Lua global table for callbacks and interactions + // stored in the lua_State of the sender + // LuaTable interactions; + // LuaTable callbacks; }; #endif // _ROUTER_H diff --git a/src/network/server_socket.cpp b/src/network/server_socket.cpp index cf401a02..cd346a3b 100644 --- a/src/network/server_socket.cpp +++ b/src/network/server_socket.cpp @@ -3,23 +3,23 @@ ServerSocket::ServerSocket() { - server = new QTcpServer(this); - connect(server, &QTcpServer::newConnection, - this, &ServerSocket::processNewConnection); + server = new QTcpServer(this); + connect(server, &QTcpServer::newConnection, + this, &ServerSocket::processNewConnection); } bool ServerSocket::listen(const QHostAddress &address, ushort port) { - return server->listen(address, port); + return server->listen(address, port); } void ServerSocket::processNewConnection() { - QTcpSocket *socket = server->nextPendingConnection(); - ClientSocket *connection = new ClientSocket(socket); - connect(connection, &ClientSocket::disconnected, this, [connection](){ - connection->deleteLater(); - }); - emit new_connection(connection); + QTcpSocket *socket = server->nextPendingConnection(); + ClientSocket *connection = new ClientSocket(socket); + connect(connection, &ClientSocket::disconnected, this, [connection](){ + connection->deleteLater(); + }); + emit new_connection(connection); } diff --git a/src/network/server_socket.h b/src/network/server_socket.h index 03c52312..a3a8e8c9 100644 --- a/src/network/server_socket.h +++ b/src/network/server_socket.h @@ -4,21 +4,21 @@ class ClientSocket; class ServerSocket : public QObject { - Q_OBJECT + Q_OBJECT public: - ServerSocket(); + ServerSocket(); - bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u); + bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u); signals: - void new_connection(ClientSocket *socket); + void new_connection(ClientSocket *socket); private slots: - void processNewConnection(); + void processNewConnection(); private: - QTcpServer *server; + QTcpServer *server; }; #endif // _SERVER_SOCKET_H diff --git a/src/resources.qrc b/src/resources.qrc deleted file mode 100644 index 7c75584c..00000000 --- a/src/resources.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - qml/main.qml - - diff --git a/src/server/room.cpp b/src/server/room.cpp index 21b01cb4..64f89e0a 100644 --- a/src/server/room.cpp +++ b/src/server/room.cpp @@ -5,306 +5,306 @@ Room::Room(Server* server) { - id = server->nextRoomId; - server->nextRoomId++; - this->server = server; - setParent(server); - owner = nullptr; - gameStarted = false; - robot_id = -1; - timeout = 15; - if (!isLobby()) { - connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer); - connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer); - } + id = server->nextRoomId; + server->nextRoomId++; + this->server = server; + setParent(server); + owner = nullptr; + gameStarted = false; + robot_id = -1; + timeout = 15; + if (!isLobby()) { + connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer); + connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer); + } - L = CreateLuaState(); - DoLuaScript(L, "lua/freekill.lua"); - if (isLobby()) { - DoLuaScript(L, "lua/server/lobby.lua"); - } else { - DoLuaScript(L, "lua/server/room.lua"); - } - initLua(); + L = CreateLuaState(); + DoLuaScript(L, "lua/freekill.lua"); + if (isLobby()) { + DoLuaScript(L, "lua/server/lobby.lua"); + } else { + DoLuaScript(L, "lua/server/room.lua"); + } + initLua(); } Room::~Room() { - // TODO - if (isRunning()) { - callLua("RoomDeleted", ""); - unlockLua(__FUNCTION__); - wait(); - } - lua_close(L); + // TODO + if (isRunning()) { + callLua("RoomDeleted", ""); + unlockLua(__FUNCTION__); + wait(); + } + lua_close(L); } Server *Room::getServer() const { - return server; + return server; } int Room::getId() const { - return id; + return id; } bool Room::isLobby() const { - return id == 0; + return id == 0; } QString Room::getName() const { - return name; + return name; } void Room::setName(const QString &name) { - this->name = name; + this->name = name; } int Room::getCapacity() const { - return capacity; + return capacity; } void Room::setCapacity(int capacity) { - this->capacity = capacity; + this->capacity = capacity; } bool Room::isFull() const { - return players.count() == capacity; + return players.count() == capacity; } bool Room::isAbandoned() const { - if (players.isEmpty()) - return true; - - foreach (ServerPlayer *p, players) { - if (p->getState() == Player::Online) - return false; - } + if (players.isEmpty()) return true; + + foreach (ServerPlayer *p, players) { + if (p->getState() == Player::Online) + return false; + } + return true; } ServerPlayer *Room::getOwner() const { - return owner; + return owner; } void Room::setOwner(ServerPlayer *owner) { - this->owner = owner; - QJsonArray jsonData; - jsonData << owner->getId(); - doBroadcastNotify(players, "RoomOwner", QJsonDocument(jsonData).toJson()); + this->owner = owner; + QJsonArray jsonData; + jsonData << owner->getId(); + doBroadcastNotify(players, "RoomOwner", QJsonDocument(jsonData).toJson()); } void Room::addPlayer(ServerPlayer *player) { - if (!player) return; + if (!player) return; - if (isFull() || gameStarted) { - player->doNotify("ErrorMsg", "Room is full or already started!"); - if (runned_players.contains(player->getId())) { - player->doNotify("ErrorMsg", "Running away is shameful."); - } - return; + if (isFull() || gameStarted) { + player->doNotify("ErrorMsg", "Room is full or already started!"); + if (runned_players.contains(player->getId())) { + player->doNotify("ErrorMsg", "Running away is shameful."); + } + 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; - - // 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()); + if (this->owner != nullptr) { + jsonData = QJsonArray(); + jsonData << this->owner->getId(); + player->doNotify("RoomOwner", 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()); - } - - if (this->owner != nullptr) { - jsonData = QJsonArray(); - jsonData << this->owner->getId(); - player->doNotify("RoomOwner", QJsonDocument(jsonData).toJson()); - } - - if (isFull() && !gameStarted) - start(); - } - emit playerAdded(player); + if (isFull() && !gameStarted) + start(); + } + emit playerAdded(player); } void Room::addRobot(ServerPlayer *player) { - if (player != owner || isFull()) return; + if (player != owner || isFull()) return; - ServerPlayer *robot = new ServerPlayer(this); - robot->setState(Player::Robot); - robot->setId(robot_id); - robot->setAvatar("guanyu"); - robot->setScreenName(QString("COMP-%1").arg(robot_id)); - robot_id--; + ServerPlayer *robot = new ServerPlayer(this); + robot->setState(Player::Robot); + robot->setId(robot_id); + robot->setAvatar("guanyu"); + robot->setScreenName(QString("COMP-%1").arg(robot_id)); + robot_id--; - addPlayer(robot); + addPlayer(robot); } void Room::removePlayer(ServerPlayer *player) { - players.removeOne(player); - emit playerRemoved(player); + players.removeOne(player); + emit playerRemoved(player); - if (isLobby()) return; + if (isLobby()) return; - if (gameStarted) { - // TODO: if the player is died.. + if (gameStarted) { + // TODO: if the player is died.. - // create robot first - ServerPlayer *robot = new ServerPlayer(this); - robot->setState(Player::Robot); - robot->setId(robot_id); - robot->setAvatar(player->getAvatar()); - robot->setScreenName(QString("COMP-%1").arg(robot_id)); - robot_id--; + // create robot first + ServerPlayer *robot = new ServerPlayer(this); + robot->setState(Player::Robot); + robot->setId(robot_id); + robot->setAvatar(player->getAvatar()); + robot->setScreenName(QString("COMP-%1").arg(robot_id)); + robot_id--; - players.append(robot); + players.append(robot); - // tell lua & clients - QJsonArray jsonData; - jsonData << player->getId(); - jsonData << robot->getId(); - callLua("PlayerRunned", QJsonDocument(jsonData).toJson()); - doBroadcastNotify(getPlayers(), "PlayerRunned", QJsonDocument(jsonData).toJson()); - runned_players << player->getId(); + // tell lua & clients + QJsonArray jsonData; + jsonData << player->getId(); + jsonData << robot->getId(); + callLua("PlayerRunned", QJsonDocument(jsonData).toJson()); + doBroadcastNotify(getPlayers(), "PlayerRunned", QJsonDocument(jsonData).toJson()); + runned_players << player->getId(); - // FIXME: abortRequest here will result crash - // but if dont abort and room is abandoned, the main thread will wait until replyed - // player->abortRequest(); - } else { - QJsonArray jsonData; - jsonData << player->getId(); - doBroadcastNotify(getPlayers(), "RemovePlayer", QJsonDocument(jsonData).toJson()); - } + // FIXME: abortRequest here will result crash + // but if dont abort and room is abandoned, the main thread will wait until replyed + // player->abortRequest(); + } else { + QJsonArray jsonData; + jsonData << player->getId(); + doBroadcastNotify(getPlayers(), "RemovePlayer", QJsonDocument(jsonData).toJson()); + } - if (isAbandoned()) { - // FIXME: do not delete room here - // create a new thread and delete the room - emit abandoned(); - } else if (player == owner) { - setOwner(players.first()); - } + if (isAbandoned()) { + // FIXME: do not delete room here + // create a new thread and delete the room + emit abandoned(); + } else if (player == owner) { + setOwner(players.first()); + } } QList Room::getPlayers() const { - return players; + return players; } QList Room::getOtherPlayers(ServerPlayer* expect) const { - QList others = getPlayers(); - others.removeOne(expect); - return others; + QList others = getPlayers(); + others.removeOne(expect); + return others; } ServerPlayer *Room::findPlayer(int id) const { - foreach (ServerPlayer *p, players) { - if (p->getId() == id) - return p; - } - return nullptr; + foreach (ServerPlayer *p, players) { + if (p->getId() == id) + return p; + } + return nullptr; } int Room::getTimeout() const { - return timeout; + return timeout; } void Room::setTimeout(int timeout) { - this->timeout = timeout; + this->timeout = timeout; } bool Room::isStarted() const { - return gameStarted; + return gameStarted; } void Room::doRequest(const QList targets, int timeout) { - // TODO + // TODO } void Room::doNotify(const QList targets, int timeout) { - // TODO + // TODO } void Room::doBroadcastNotify(const QList targets, - const QString& command, const QString& jsonData) + const QString& command, const QString& jsonData) { - foreach (ServerPlayer *p, targets) { - p->doNotify(command, jsonData); - } + foreach (ServerPlayer *p, targets) { + p->doNotify(command, jsonData); + } } void Room::gameOver() { - gameStarted = false; - runned_players.clear(); - // clean not online players - foreach (ServerPlayer *p, players) { - if (p->getState() != Player::Online) { - p->deleteLater(); - } + gameStarted = false; + runned_players.clear(); + // clean not online players + foreach (ServerPlayer *p, players) { + if (p->getState() != Player::Online) { + p->deleteLater(); } + } } void Room::lockLua(const QString &caller) { - if (!gameStarted) return; - lua_mutex.lock(); + if (!gameStarted) return; + lua_mutex.lock(); #ifdef QT_DEBUG - //qDebug() << caller << "=> room->L is locked."; + //qDebug() << caller << "=> room->L is locked."; #endif } void Room::unlockLua(const QString &caller) { - if (!gameStarted) return; - lua_mutex.unlock(); + if (!gameStarted) return; + lua_mutex.unlock(); #ifdef QT_DEBUG - //qDebug() << caller << "=> room->L is unlocked."; + //qDebug() << caller << "=> room->L is unlocked."; #endif } void Room::run() { - gameStarted = true; - lockLua(__FUNCTION__); - roomStart(); - unlockLua(__FUNCTION__); + gameStarted = true; + lockLua(__FUNCTION__); + roomStart(); + unlockLua(__FUNCTION__); } diff --git a/src/server/room.h b/src/server/room.h index 5ac23691..9d05d3fa 100644 --- a/src/server/room.h +++ b/src/server/room.h @@ -5,86 +5,86 @@ class Server; class ServerPlayer; class Room : public QThread { - Q_OBJECT + Q_OBJECT public: - explicit Room(Server *m_server); - ~Room(); + explicit Room(Server *m_server); + ~Room(); - // Property reader & setter - // ==================================={ - Server *getServer() const; - int getId() const; - bool isLobby() const; - QString getName() const; - void setName(const QString &name); - int getCapacity() const; - void setCapacity(int capacity); - bool isFull() const; - bool isAbandoned() const; + // Property reader & setter + // ==================================={ + Server *getServer() const; + int getId() const; + bool isLobby() const; + QString getName() const; + void setName(const QString &name); + int getCapacity() const; + void setCapacity(int capacity); + bool isFull() const; + bool isAbandoned() const; - ServerPlayer *getOwner() const; - void setOwner(ServerPlayer *owner); + ServerPlayer *getOwner() const; + void setOwner(ServerPlayer *owner); - void addPlayer(ServerPlayer *player); - void addRobot(ServerPlayer *player); - void removePlayer(ServerPlayer *player); - QList getPlayers() const; - QList getOtherPlayers(ServerPlayer *expect) const; - ServerPlayer *findPlayer(int id) const; + void addPlayer(ServerPlayer *player); + void addRobot(ServerPlayer *player); + void removePlayer(ServerPlayer *player); + QList getPlayers() const; + QList getOtherPlayers(ServerPlayer *expect) const; + ServerPlayer *findPlayer(int id) const; - int getTimeout() const; - void setTimeout(int timeout); + int getTimeout() const; + void setTimeout(int timeout); - bool isStarted() const; - // ====================================} + bool isStarted() const; + // ====================================} - void doRequest(const QList targets, int timeout); - void doNotify(const QList targets, int timeout); + void doRequest(const QList targets, int timeout); + void doNotify(const QList targets, int timeout); - void doBroadcastNotify( - const QList targets, - const QString &command, - const QString &jsonData - ); + void doBroadcastNotify( + const QList targets, + const QString &command, + const QString &jsonData + ); - void gameOver(); + void gameOver(); - void initLua(); - void callLua(const QString &command, const QString &jsonData); - LuaFunction callback; + void initLua(); + void callLua(const QString &command, const QString &jsonData); + LuaFunction callback; - void roomStart(); - LuaFunction startGame; + void roomStart(); + LuaFunction startGame; - void lockLua(const QString &caller); - void unlockLua(const QString &caller); + void lockLua(const QString &caller); + void unlockLua(const QString &caller); signals: - void abandoned(); + void abandoned(); - void playerAdded(ServerPlayer *player); - void playerRemoved(ServerPlayer *player); + void playerAdded(ServerPlayer *player); + void playerRemoved(ServerPlayer *player); protected: - virtual void run(); + virtual void run(); private: - Server *server; - int id; // Lobby's id is 0 - QString name; // “阴间大乱斗” - int capacity; // by default is 5, max is 8 - bool m_abandoned; // If room is empty, delete it + Server *server; + int id; // Lobby's id is 0 + QString name; // “阴间大乱斗” + int capacity; // by default is 5, max is 8 + bool m_abandoned; // If room is empty, delete it - ServerPlayer *owner; // who created this room? - QList players; - QList runned_players; - int robot_id; - bool gameStarted; + ServerPlayer *owner; // who created this room? + QList players; + QList runned_players; + int robot_id; + bool gameStarted; - int timeout; + int timeout; - lua_State *L; - QMutex lua_mutex; + lua_State *L; + QMutex lua_mutex; }; #endif // _ROOM_H diff --git a/src/server/server.cpp b/src/server/server.cpp index eea3c2a4..8b451f64 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -9,253 +9,253 @@ Server *ServerInstance; Server::Server(QObject* parent) - : QObject(parent) + : QObject(parent) { - ServerInstance = this; - db = OpenDatabase(); - server = new ServerSocket(); - server->setParent(this); - connect(server, &ServerSocket::new_connection, - this, &Server::processNewConnection); + ServerInstance = this; + db = OpenDatabase(); + server = new ServerSocket(); + server->setParent(this); + connect(server, &ServerSocket::new_connection, + this, &Server::processNewConnection); - // create lobby - nextRoomId = 0; - createRoom(nullptr, "Lobby", INT32_MAX); - connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList); - connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList); + // create lobby + nextRoomId = 0; + createRoom(nullptr, "Lobby", INT32_MAX); + connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList); + connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList); } Server::~Server() { - ServerInstance = nullptr; - m_lobby->deleteLater(); - sqlite3_close(db); + ServerInstance = nullptr; + m_lobby->deleteLater(); + sqlite3_close(db); } 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) { - Room *room = new Room(this); - connect(room, &Room::abandoned, this, &Server::onRoomAbandoned); - if (room->isLobby()) - m_lobby = room; - else - rooms.insert(room->getId(), room); + Room *room = new Room(this); + connect(room, &Room::abandoned, this, &Server::onRoomAbandoned); + if (room->isLobby()) + m_lobby = room; + else + rooms.insert(room->getId(), room); - room->setName(name); - room->setCapacity(capacity); - room->addPlayer(owner); - if (!room->isLobby()) room->setOwner(owner); + room->setName(name); + room->setCapacity(capacity); + room->addPlayer(owner); + if (!room->isLobby()) room->setOwner(owner); } Room *Server::findRoom(int id) const { - return rooms.value(id); + return rooms.value(id); } Room *Server::lobby() const { - return m_lobby; + return m_lobby; } ServerPlayer *Server::findPlayer(int id) const { - return players.value(id); + return players.value(id); } void Server::removePlayer(int id) { - players.remove(id); + players.remove(id); } void Server::updateRoomList() { - QJsonArray arr; - foreach (Room *room, rooms) { - QJsonArray obj; - obj << room->getId(); // roomId - obj << room->getName(); // roomName - obj << "Role"; // gameMode - obj << room->getPlayers().count(); // playerNum - obj << room->getCapacity(); // capacity - arr << obj; - } - lobby()->doBroadcastNotify( - lobby()->getPlayers(), - "UpdateRoomList", - QJsonDocument(arr).toJson() - ); + QJsonArray arr; + foreach (Room *room, rooms) { + QJsonArray obj; + obj << room->getId(); // roomId + obj << room->getName(); // roomName + obj << "Role"; // gameMode + obj << room->getPlayers().count(); // playerNum + obj << room->getCapacity(); // capacity + arr << obj; + } + lobby()->doBroadcastNotify( + lobby()->getPlayers(), + "UpdateRoomList", + QJsonDocument(arr).toJson() + ); } sqlite3 *Server::getDatabase() { - return db; + return db; } void Server::processNewConnection(ClientSocket* client) { - qDebug() << client->peerAddress() << "connected"; - // version check, file check, ban IP, reconnect, etc + qDebug() << client->peerAddress() << "connected"; + // version check, file check, ban IP, reconnect, etc - connect(client, &ClientSocket::disconnected, this, [client](){ - qDebug() << client->peerAddress() << "disconnected"; - }); + connect(client, &ClientSocket::disconnected, this, [client](){ + qDebug() << client->peerAddress() << "disconnected"; + }); - // network delay test - QJsonArray body; - body << -2; - body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); - body << "NetworkDelayTest"; - body << "[]"; - client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); - // Note: the client should send a setup string next - connect(client, &ClientSocket::message_got, this, &Server::processRequest); - client->timerSignup.start(30000); + // network delay test + QJsonArray body; + body << -2; + body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); + body << "NetworkDelayTest"; + body << "[]"; + client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); + // Note: the client should send a setup string next + connect(client, &ClientSocket::message_got, this, &Server::processRequest); + client->timerSignup.start(30000); } void Server::processRequest(const QByteArray& msg) { - ClientSocket *client = qobject_cast(sender()); - client->disconnect(this, SLOT(processRequest(const QByteArray &))); - client->timerSignup.stop(); + ClientSocket *client = qobject_cast(sender()); + client->disconnect(this, SLOT(processRequest(const QByteArray &))); + client->timerSignup.stop(); - bool valid = true; - QJsonDocument doc = QJsonDocument::fromJson(msg); - if (doc.isNull() || !doc.isArray()) { - valid = false; - } else { - if (doc.array().size() != 4 - || doc[0] != -2 - || doc[1] != (Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER) - || doc[2] != "Setup" - ) - valid = false; - else - valid = (QJsonDocument::fromJson(doc[3].toString().toUtf8()).array().size() == 2); - } + bool valid = true; + QJsonDocument doc = QJsonDocument::fromJson(msg); + if (doc.isNull() || !doc.isArray()) { + valid = false; + } else { + if (doc.array().size() != 4 + || doc[0] != -2 + || doc[1] != (Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER) + || doc[2] != "Setup" + ) + valid = false; + else + valid = (QJsonDocument::fromJson(doc[3].toString().toUtf8()).array().size() == 2); + } - if (!valid) { - qDebug() << "Invalid setup string:" << msg; - QJsonArray body; - body << -2; - body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); - body << "ErrorMsg"; - body << "INVALID SETUP STRING"; - client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); - client->disconnectFromHost(); - return; - } + if (!valid) { + qDebug() << "Invalid setup string:" << msg; + QJsonArray body; + body << -2; + body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); + body << "ErrorMsg"; + body << "INVALID SETUP STRING"; + client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); + client->disconnectFromHost(); + return; + } - QJsonArray arr = QJsonDocument::fromJson(doc[3].toString().toUtf8()).array(); - handleNameAndPassword(client, arr[0].toString(), arr[1].toString()); + QJsonArray arr = QJsonDocument::fromJson(doc[3].toString().toUtf8()).array(); + handleNameAndPassword(client, arr[0].toString(), arr[1].toString()); } void Server::handleNameAndPassword(ClientSocket *client, const QString& name, const QString& password) { - // First check the name and password - // Matches a string that does not contain special characters - QRegExp nameExp("[^\\0000-\\0057\\0072-\\0100\\0133-\\0140\\0173-\\0177]+"); - QByteArray passwordHash = QCryptographicHash::hash(password.toLatin1(), QCryptographicHash::Sha256).toHex(); - bool passed = false; - QString error_msg; - QJsonObject result; + // First check the name and password + // Matches a string that does not contain special characters + QRegExp nameExp("[^\\0000-\\0057\\0072-\\0100\\0133-\\0140\\0173-\\0177]+"); + QByteArray passwordHash = QCryptographicHash::hash(password.toLatin1(), QCryptographicHash::Sha256).toHex(); + bool passed = false; + QString error_msg; + QJsonObject result; - if (nameExp.exactMatch(name)) { - // Then we check the database, - QString sql_find = QString("SELECT * FROM userinfo \ - WHERE name='%1';").arg(name); - result = SelectFromDatabase(db, sql_find); - QJsonArray arr = result["password"].toArray(); - if (arr.isEmpty()) { - // not present in database, register - QString sql_reg = QString("INSERT INTO userinfo (name,password,\ - avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4',%5);") - .arg(name) - .arg(QString(passwordHash)) - .arg("liubei") - .arg(client->peerAddress()) - .arg("FALSE"); - ExecSQL(db, sql_reg); - result = SelectFromDatabase(db, sql_find); // refresh result - 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"; - } - } + if (nameExp.exactMatch(name)) { + // Then we check the database, + QString sql_find = QString("SELECT * FROM userinfo \ + WHERE name='%1';").arg(name); + result = SelectFromDatabase(db, sql_find); + QJsonArray arr = result["password"].toArray(); + if (arr.isEmpty()) { + // not present in database, register + QString sql_reg = QString("INSERT INTO userinfo (name,password,\ + avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4',%5);") + .arg(name) + .arg(QString(passwordHash)) + .arg("liubei") + .arg(client->peerAddress()) + .arg("FALSE"); + ExecSQL(db, sql_reg); + result = SelectFromDatabase(db, sql_find); // refresh result + passed = true; } 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) { - // create new ServerPlayer and setup - ServerPlayer *player = new ServerPlayer(lobby()); - player->setSocket(client); - client->disconnect(this); - connect(player, &ServerPlayer::disconnected, this, &Server::onUserDisconnected); - connect(player, &Player::stateChanged, this, &Server::onUserStateChanged); - player->setScreenName(name); - player->setAvatar(result["avatar"].toArray()[0].toString()); - player->setId(result["id"].toArray()[0].toString().toInt()); - players.insert(player->getId(), player); + if (passed) { + // create new ServerPlayer and setup + ServerPlayer *player = new ServerPlayer(lobby()); + player->setSocket(client); + client->disconnect(this); + connect(player, &ServerPlayer::disconnected, this, &Server::onUserDisconnected); + connect(player, &Player::stateChanged, this, &Server::onUserStateChanged); + player->setScreenName(name); + player->setAvatar(result["avatar"].toArray()[0].toString()); + player->setId(result["id"].toArray()[0].toString().toInt()); + players.insert(player->getId(), player); - // tell the lobby player's basic property - QJsonArray arr; - arr << player->getId(); - arr << player->getScreenName(); - arr << player->getAvatar(); - player->doNotify("Setup", QJsonDocument(arr).toJson()); + // tell the lobby player's basic property + QJsonArray arr; + arr << player->getId(); + arr << player->getScreenName(); + arr << player->getAvatar(); + player->doNotify("Setup", QJsonDocument(arr).toJson()); - lobby()->addPlayer(player); - } else { - qDebug() << client->peerAddress() << "lost connection:" << error_msg; - QJsonArray body; - body << -2; - body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); - body << "ErrorMsg"; - body << error_msg; - client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); - client->disconnectFromHost(); - return; - } + lobby()->addPlayer(player); + } else { + qDebug() << client->peerAddress() << "lost connection:" << error_msg; + QJsonArray body; + body << -2; + body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); + body << "ErrorMsg"; + body << error_msg; + client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); + client->disconnectFromHost(); + return; + } } void Server::onRoomAbandoned() { - Room *room = qobject_cast(sender()); - room->gameOver(); - rooms.remove(room->getId()); - updateRoomList(); - room->deleteLater(); + Room *room = qobject_cast(sender()); + room->gameOver(); + rooms.remove(room->getId()); + updateRoomList(); + room->deleteLater(); } void Server::onUserDisconnected() { - ServerPlayer *player = qobject_cast(sender()); - qDebug() << "Player" << player->getId() << "disconnected"; - Room *room = player->getRoom(); - if (room->isStarted()) { - player->setState(Player::Offline); - // TODO: add a robot - } else { - player->deleteLater(); - } + ServerPlayer *player = qobject_cast(sender()); + qDebug() << "Player" << player->getId() << "disconnected"; + Room *room = player->getRoom(); + if (room->isStarted()) { + player->setState(Player::Offline); + // TODO: add a robot + } else { + player->deleteLater(); + } } void Server::onUserStateChanged() { - ServerPlayer *player = qobject_cast(sender()); - QJsonArray arr; - arr << player->getId(); - arr << player->getStateString(); - player->getRoom()->callLua("PlayerStateChanged", QJsonDocument(arr).toJson()); + ServerPlayer *player = qobject_cast(sender()); + QJsonArray arr; + arr << player->getId(); + arr << player->getStateString(); + player->getRoom()->callLua("PlayerStateChanged", QJsonDocument(arr).toJson()); } diff --git a/src/server/server.h b/src/server/server.h index 8d963246..58101306 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -8,49 +8,49 @@ class ServerPlayer; #include "room.h" class Server : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit Server(QObject *parent = nullptr); - ~Server(); + explicit Server(QObject *parent = nullptr); + ~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); - Room *findRoom(int id) const; - Room *lobby() const; + void createRoom(ServerPlayer *owner, const QString &name, int capacity); + Room *findRoom(int id) const; + Room *lobby() const; - ServerPlayer *findPlayer(int id) const; - void removePlayer(int id); + ServerPlayer *findPlayer(int id) const; + void removePlayer(int id); - void updateRoomList(); + void updateRoomList(); - sqlite3 *getDatabase(); + sqlite3 *getDatabase(); signals: - void roomCreated(Room *room); - void playerAdded(ServerPlayer *player); - void playerRemoved(ServerPlayer *player); + void roomCreated(Room *room); + void playerAdded(ServerPlayer *player); + void playerRemoved(ServerPlayer *player); public slots: - void processNewConnection(ClientSocket *client); - void processRequest(const QByteArray &msg); + void processNewConnection(ClientSocket *client); + void processRequest(const QByteArray &msg); - void onRoomAbandoned(); - void onUserDisconnected(); - void onUserStateChanged(); + void onRoomAbandoned(); + void onUserDisconnected(); + void onUserStateChanged(); private: - ServerSocket *server; - Room *m_lobby; - QMap rooms; - int nextRoomId; - friend Room::Room(Server *server); - QHash players; + ServerSocket *server; + Room *m_lobby; + QMap rooms; + int nextRoomId; + friend Room::Room(Server *server); + QHash 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; diff --git a/src/server/serverplayer.cpp b/src/server/serverplayer.cpp index a863484d..a456dbea 100644 --- a/src/server/serverplayer.cpp +++ b/src/server/serverplayer.cpp @@ -6,111 +6,111 @@ ServerPlayer::ServerPlayer(Room *room) { - socket = nullptr; - router = new Router(this, socket, Router::TYPE_SERVER); - setState(Player::Online); - this->room = room; - server = room->getServer(); + socket = nullptr; + router = new Router(this, socket, Router::TYPE_SERVER); + setState(Player::Online); + this->room = room; + server = room->getServer(); } 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); - if (room != nullptr) { - // now we are in lobby, so quit lobby - room->removePlayer(this); - } - server->removePlayer(getId()); - router->deleteLater(); + } + server->removePlayer(getId()); + router->deleteLater(); } void ServerPlayer::setSocket(ClientSocket *socket) { - if (this->socket != nullptr) { - this->socket->disconnect(this); - disconnect(this->socket); - this->socket->deleteLater(); - } + if (this->socket != nullptr) { + this->socket->disconnect(this); + disconnect(this->socket); + this->socket->deleteLater(); + } - this->socket = nullptr; - if (socket != nullptr) { - connect(socket, &ClientSocket::disconnected, this, &ServerPlayer::disconnected); - this->socket = socket; - } + this->socket = nullptr; + if (socket != nullptr) { + connect(socket, &ClientSocket::disconnected, this, &ServerPlayer::disconnected); + this->socket = socket; + } - router->setSocket(socket); + router->setSocket(socket); } Server *ServerPlayer::getServer() const { - return server; + return server; } Room *ServerPlayer::getRoom() const { - return room; + return room; } void ServerPlayer::setRoom(Room* room) { - this->room = room; + this->room = room; } void ServerPlayer::speak(const QString& message) { - ; + ; } void ServerPlayer::doRequest(const QString& command, const QString& jsonData, int timeout) { - if (getState() != Player::Online) return; - int type = Router::TYPE_REQUEST | Router::SRC_SERVER | Router::DEST_CLIENT; - router->request(type, command, jsonData, timeout); + if (getState() != Player::Online) return; + int type = Router::TYPE_REQUEST | Router::SRC_SERVER | Router::DEST_CLIENT; + router->request(type, command, jsonData, timeout); } void ServerPlayer::abortRequest() { - router->abortRequest(); + router->abortRequest(); } QString ServerPlayer::waitForReply() { - room->unlockLua(__FUNCTION__); - QString ret; - if (getState() != Player::Online) { - QThread::sleep(1); - ret = ""; - } else { - ret = router->waitForReply(); - } - room->lockLua(__FUNCTION__); - return ret; + room->unlockLua(__FUNCTION__); + QString ret; + if (getState() != Player::Online) { + QThread::sleep(1); + ret = ""; + } else { + ret = router->waitForReply(); + } + room->lockLua(__FUNCTION__); + return ret; } QString ServerPlayer::waitForReply(int timeout) { - room->unlockLua(__FUNCTION__); - QString ret; - if (getState() != Player::Online) { - QThread::sleep(1); - ret = ""; - } else { - ret = router->waitForReply(timeout); - } - room->lockLua(__FUNCTION__); - return ret; + room->unlockLua(__FUNCTION__); + QString ret; + if (getState() != Player::Online) { + QThread::sleep(1); + ret = ""; + } else { + ret = router->waitForReply(timeout); + } + room->lockLua(__FUNCTION__); + return ret; } void ServerPlayer::doNotify(const QString& command, const QString& jsonData) { - if (getState() != Player::Online) return; - int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT; - router->notify(type, command, jsonData); + if (getState() != Player::Online) return; + int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT; + router->notify(type, command, jsonData); } void ServerPlayer::prepareForRequest(const QString& command, const QString& data) { - requestCommand = command; - requestData = data; + requestCommand = command; + requestData = data; } diff --git a/src/server/serverplayer.h b/src/server/serverplayer.h index d2640535..b385b6bd 100644 --- a/src/server/serverplayer.h +++ b/src/server/serverplayer.h @@ -9,40 +9,40 @@ class Server; class Room; class ServerPlayer : public Player { - Q_OBJECT + Q_OBJECT public: - explicit ServerPlayer(Room *room); - ~ServerPlayer(); + explicit ServerPlayer(Room *room); + ~ServerPlayer(); - void setSocket(ClientSocket *socket); + void setSocket(ClientSocket *socket); - Server *getServer() const; - Room *getRoom() const; - void setRoom(Room *room); + Server *getServer() const; + Room *getRoom() const; + void setRoom(Room *room); - void speak(const QString &message); + void speak(const QString &message); - void doRequest(const QString &command, - const QString &jsonData, int timeout = -1); - void abortRequest(); - QString waitForReply(int timeout); - QString waitForReply(); - void doNotify(const QString &command, const QString &jsonData); + void doRequest(const QString &command, + const QString &jsonData, int timeout = -1); + void abortRequest(); + QString waitForReply(int timeout); + QString waitForReply(); + void doNotify(const QString &command, const QString &jsonData); - void prepareForRequest(const QString &command, - const QString &data); + void prepareForRequest(const QString &command, + const QString &data); signals: - void disconnected(); - + void disconnected(); + private: - ClientSocket *socket; // socket for communicating with client - Router *router; - Server *server; - Room *room; // Room that player is in, maybe lobby + ClientSocket *socket; // socket for communicating with client + Router *router; + Server *server; + Room *room; // Room that player is in, maybe lobby - QString requestCommand; - QString requestData; + QString requestCommand; + QString requestData; }; #endif // _SERVERPLAYER_H diff --git a/src/swig/client.i b/src/swig/client.i index 3667aad6..fb8bd81b 100644 --- a/src/swig/client.i +++ b/src/swig/client.i @@ -2,13 +2,13 @@ %nodefaultdtor QmlBackend; class QmlBackend : public QObject { 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 QStringList ls(const QString &dir); - static QString pwd(); - static bool exists(const QString &file); - static bool isDir(const QString &file); + static void cd(const QString &path); + static QStringList ls(const QString &dir); + static QString pwd(); + static bool exists(const QString &file); + static bool isDir(const QString &file); }; extern QmlBackend *Backend; @@ -17,13 +17,13 @@ extern QmlBackend *Backend; %nodefaultdtor Client; class Client : public QObject { public: - void replyToServer(const QString &command, const QString &json_data); - void notifyServer(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); - LuaFunction callback; + LuaFunction callback; - ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); - void removePlayer(int id); + ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); + void removePlayer(int id); }; extern Client *ClientInstance; @@ -31,24 +31,24 @@ extern Client *ClientInstance; %{ void Client::callLua(const QString& command, const QString& json_data) { - Q_ASSERT(callback); + Q_ASSERT(callback); - lua_getglobal(L, "debug"); - lua_getfield(L, -1, "traceback"); - lua_replace(L, -2); + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, -2); - lua_rawgeti(L, LUA_REGISTRYINDEX, callback); - SWIG_NewPointerObj(L, this, SWIGTYPE_p_Client, 0); - lua_pushstring(L, command.toUtf8()); - lua_pushstring(L, json_data.toUtf8()); + lua_rawgeti(L, LUA_REGISTRYINDEX, callback); + SWIG_NewPointerObj(L, this, SWIGTYPE_p_Client, 0); + lua_pushstring(L, command.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) { - const char *error_msg = lua_tostring(L, -1); - qDebug() << error_msg; - lua_pop(L, 2); - } - lua_pop(L, 1); + if (error) { + const char *error_msg = lua_tostring(L, -1); + qDebug() << error_msg; + lua_pop(L, 2); + } + lua_pop(L, 1); } %} diff --git a/src/swig/naturalvar.i b/src/swig/naturalvar.i index 59e49070..03b736d4 100644 --- a/src/swig/naturalvar.i +++ b/src/swig/naturalvar.i @@ -7,10 +7,10 @@ %typemap(in) LuaFunction %{ if (lua_isfunction(L, $input)) { - lua_pushvalue(L, $input); - $1 = luaL_ref(L, LUA_REGISTRYINDEX); + lua_pushvalue(L, $input); + $1 = luaL_ref(L, LUA_REGISTRYINDEX); } else { - $1 = 0; + $1 = 0; } %} @@ -35,8 +35,8 @@ SWIG_arg ++; %typemap(in, checkfn = "lua_isstring") QString const & %{ - $1_str = QString::fromUtf8(lua_tostring(L, $input)); - $1 = &$1_str; + $1_str = QString::fromUtf8(lua_tostring(L, $input)); + $1 = &$1_str; %} %typemap(out) QString const & @@ -48,10 +48,10 @@ SWIG_arg ++; %typemap(in, checkfn = "lua_istable") QStringList %{ for (size_t i = 0; i < lua_rawlen(L, $input); ++i) { - lua_rawgeti(L, $input, i + 1); - const char *elem = luaL_checkstring(L, -1); - $1 << QString::fromUtf8(QByteArray(elem)); - lua_pop(L, 1); + lua_rawgeti(L, $input, i + 1); + const char *elem = luaL_checkstring(L, -1); + $1 << QString::fromUtf8(QByteArray(elem)); + 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); for (int i = 0; i < $1.length(); i++) { - QString str = $1.at(i); - lua_pushstring(L, str.toUtf8().constData()); - lua_rawseti(L, -2, i + 1); + QString str = $1.at(i); + lua_pushstring(L, str.toUtf8().constData()); + lua_rawseti(L, -2, i + 1); } SWIG_arg++; @@ -70,7 +70,7 @@ SWIG_arg++; %typemap(typecheck) QStringList %{ - $1 = lua_istable(L, $input) ? 1 : 0; + $1 = lua_istable(L, $input) ? 1 : 0; %} diff --git a/src/swig/player.i b/src/swig/player.i index f9b6841e..cd0ad9e2 100644 --- a/src/swig/player.i +++ b/src/swig/player.i @@ -2,29 +2,29 @@ %nodefaultdtor Player; class Player : public QObject { public: - enum State{ - Invalid, - Online, - Trust, - Offline - }; + enum State{ + Invalid, + Online, + Trust, + Offline + }; - int getId() const; - void setId(int id); + int getId() const; + void setId(int id); - QString getScreenName() const; - void setScreenName(const QString &name); + QString getScreenName() const; + void setScreenName(const QString &name); - QString getAvatar() const; - void setAvatar(const QString &avatar); + QString getAvatar() const; + void setAvatar(const QString &avatar); - State getState() const; - QString getStateString() const; - void setState(State state); - void setStateString(const QString &state); + State getState() const; + QString getStateString() const; + void setState(State state); + void setStateString(const QString &state); - bool isReady() const; - void setReady(bool ready); + bool isReady() const; + void setReady(bool ready); }; %nodefaultctor ClientPlayer; @@ -39,17 +39,17 @@ extern ClientPlayer *Self; %nodefaultdtor ServerPlayer; class ServerPlayer : public Player { public: - Server *getServer() const; - Room *getRoom() const; - void setRoom(Room *room); + Server *getServer() const; + Room *getRoom() const; + void setRoom(Room *room); - void speak(const QString &message); + void speak(const QString &message); - void doRequest(const QString &command, - const QString &json_data, int timeout); - QString waitForReply(); - QString waitForReply(int timeout); - void doNotify(const QString &command, const QString &json_data); + void doRequest(const QString &command, + const QString &json_data, int timeout); + QString waitForReply(); + QString waitForReply(int timeout); + 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); }; diff --git a/src/swig/qt.i b/src/swig/qt.i index d8968898..195c79aa 100644 --- a/src/swig/qt.i +++ b/src/swig/qt.i @@ -5,29 +5,29 @@ class QThread {}; template class QList { public: - QList(); - ~QList(); - int length() const; - void append(const T &elem); - void prepend(const T &elem); - bool isEmpty() const; - bool contains(const T &value) const; - T first() const; - T last() const; - void removeAt(int i); - int removeAll(const T &value); - bool removeOne(const T &value); - QList mid(int pos, int length = -1) const; - int indexOf(const T &value, int from = 0); - void replace(int i, const T &value); - void swapItemsAt(int i, int j); + QList(); + ~QList(); + int length() const; + void append(const T &elem); + void prepend(const T &elem); + bool isEmpty() const; + bool contains(const T &value) const; + T first() const; + T last() const; + void removeAt(int i); + int removeAll(const T &value); + bool removeOne(const T &value); + QList mid(int pos, int length = -1) const; + int indexOf(const T &value, int from = 0); + void replace(int i, const T &value); + void swapItemsAt(int i, int j); }; %extend QList { - T at(int i) const - { - return $self->value(i); - } + T at(int i) const + { + return $self->value(i); + } } %template(SPlayerList) QList; @@ -39,10 +39,10 @@ public: %{ #include static int GetMicroSecond(lua_State *L) { - struct timeval tv; - gettimeofday(&tv, nullptr); - long microsecond = tv.tv_sec * 1000000 + tv.tv_usec; - lua_pushnumber(L, microsecond); - return 1; -} + struct timeval tv; + gettimeofday(&tv, nullptr); + long microsecond = tv.tv_sec * 1000000 + tv.tv_usec; + lua_pushnumber(L, microsecond); + return 1; +} %} diff --git a/src/swig/server.i b/src/swig/server.i index c2494d6e..a3c5f4cc 100644 --- a/src/swig/server.i +++ b/src/swig/server.i @@ -2,12 +2,12 @@ %nodefaultdtor Server; class Server : public QObject { public: - Room *lobby() const; - void createRoom(ServerPlayer *owner, const QString &name, int capacity); - Room *findRoom(int id) const; - ServerPlayer *findPlayer(int id) const; + Room *lobby() const; + void createRoom(ServerPlayer *owner, const QString &name, int capacity); + Room *findRoom(int id) const; + ServerPlayer *findPlayer(int id) const; - sqlite3 *getDatabase(); + sqlite3 *getDatabase(); }; extern Server *ServerInstance; @@ -16,104 +16,104 @@ extern Server *ServerInstance; %nodefaultdtor Room; class Room : public QThread { public: - // Property reader & setter - // ==================================={ - Server *getServer() const; - int getId() const; - bool isLobby() const; - QString getName() const; - void setName(const QString &name); - int getCapacity() const; - void setCapacity(int capacity); - bool isFull() const; - bool isAbandoned() const; + // Property reader & setter + // ==================================={ + Server *getServer() const; + int getId() const; + bool isLobby() const; + QString getName() const; + void setName(const QString &name); + int getCapacity() const; + void setCapacity(int capacity); + bool isFull() const; + bool isAbandoned() const; - ServerPlayer *getOwner() const; - void setOwner(ServerPlayer *owner); + ServerPlayer *getOwner() const; + void setOwner(ServerPlayer *owner); - void addPlayer(ServerPlayer *player); - void addRobot(ServerPlayer *player); - void removePlayer(ServerPlayer *player); - QList getPlayers() const; - ServerPlayer *findPlayer(int id) const; + void addPlayer(ServerPlayer *player); + void addRobot(ServerPlayer *player); + void removePlayer(ServerPlayer *player); + QList getPlayers() const; + ServerPlayer *findPlayer(int id) const; - int getTimeout() const; + int getTimeout() const; - bool isStarted() const; - // ====================================} + bool isStarted() const; + // ====================================} - void doRequest(const QList targets, int timeout); - void doNotify(const QList targets, int timeout); - void doBroadcastNotify( - const QList targets, - const QString &command, - const QString &jsonData - ); - - void gameOver(); + void doRequest(const QList targets, int timeout); + void doNotify(const QList targets, int timeout); + void doBroadcastNotify( + const QList targets, + const QString &command, + const QString &jsonData + ); + + void gameOver(); - LuaFunction callback; - LuaFunction startGame; + LuaFunction callback; + LuaFunction startGame; }; %{ void Room::initLua() { - lua_getglobal(L, "debug"); - lua_getfield(L, -1, "traceback"); - lua_replace(L, -2); - lua_getglobal(L, "CreateRoom"); - SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); - int error = lua_pcall(L, 1, 0, -2); - lua_pop(L, 1); - if (error) { - const char *error_msg = lua_tostring(L, -1); - qDebug() << error_msg; - } + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, -2); + lua_getglobal(L, "CreateRoom"); + SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); + int error = lua_pcall(L, 1, 0, -2); + lua_pop(L, 1); + if (error) { + const char *error_msg = lua_tostring(L, -1); + qDebug() << error_msg; + } } void Room::callLua(const QString& command, const QString& json_data) { - Q_ASSERT(callback); + Q_ASSERT(callback); - lua_getglobal(L, "debug"); - lua_getfield(L, -1, "traceback"); - lua_replace(L, -2); + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, -2); - lua_rawgeti(L, LUA_REGISTRYINDEX, callback); - SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); - lua_pushstring(L, command.toUtf8()); - lua_pushstring(L, json_data.toUtf8()); + lua_rawgeti(L, LUA_REGISTRYINDEX, callback); + SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); + lua_pushstring(L, command.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) { - const char *error_msg = lua_tostring(L, -1); - qDebug() << error_msg; - lua_pop(L, 2); - } - lua_pop(L, 1); + if (error) { + const char *error_msg = lua_tostring(L, -1); + qDebug() << error_msg; + lua_pop(L, 2); + } + lua_pop(L, 1); } void Room::roomStart() { - Q_ASSERT(startGame); + Q_ASSERT(startGame); - lua_getglobal(L, "debug"); - lua_getfield(L, -1, "traceback"); - lua_replace(L, -2); + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, -2); - lua_rawgeti(L, LUA_REGISTRYINDEX, startGame); - SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0); + lua_rawgeti(L, LUA_REGISTRYINDEX, startGame); + 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) { - const char *error_msg = lua_tostring(L, -1); - qDebug() << error_msg; - lua_pop(L, 2); - } - lua_pop(L, 1); + if (error) { + const char *error_msg = lua_tostring(L, -1); + qDebug() << error_msg; + lua_pop(L, 2); + } + lua_pop(L, 1); } - + %} diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index d2a157ef..5d702e8a 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -5,150 +5,151 @@ QmlBackend *Backend; QmlBackend::QmlBackend(QObject* parent) - : QObject(parent) + : QObject(parent) { - Backend = this; - engine = nullptr; + Backend = this; + engine = nullptr; } QQmlApplicationEngine *QmlBackend::getEngine() const { - return engine; + return engine; } void QmlBackend::setEngine(QQmlApplicationEngine *engine) { - this->engine = engine; + this->engine = engine; } void QmlBackend::startServer(ushort port) { - if (!ServerInstance) { - Server *server = new Server(this); + if (!ServerInstance) { + Server *server = new Server(this); - if (!server->listen(QHostAddress::Any, port)) { - server->deleteLater(); - emit notifyUI("ErrorMsg", tr("Cannot start server!")); - } + if (!server->listen(QHostAddress::Any, port)) { + server->deleteLater(); + emit notifyUI("ErrorMsg", tr("Cannot start server!")); } + } } void QmlBackend::joinServer(QString address) { - if (ClientInstance != nullptr) return; - Client *client = new Client(this); - connect(client, &Client::error_message, [this, client](const QString &msg){ - client->deleteLater(); - emit notifyUI("ErrorMsg", msg); - emit notifyUI("BackToStart", "[]"); - }); - QString addr = "127.0.0.1"; - ushort port = 9527u; + if (ClientInstance != nullptr) return; + Client *client = new Client(this); + connect(client, &Client::error_message, [this, client](const QString &msg){ + client->deleteLater(); + emit notifyUI("ErrorMsg", msg); + emit notifyUI("BackToStart", "[]"); + }); + QString addr = "127.0.0.1"; + ushort port = 9527u; - if (address.contains(QChar(':'))) { - QStringList texts = address.split(QChar(':')); - addr = texts.value(0); - port = texts.value(1).toUShort(); - } else { - addr = address; - } + if (address.contains(QChar(':'))) { + QStringList texts = address.split(QChar(':')); + addr = texts.value(0); + port = texts.value(1).toUShort(); + } else { + addr = address; + } - client->connectToHost(QHostAddress(addr), port); + client->connectToHost(QHostAddress(addr), port); } void QmlBackend::quitLobby() { - delete ClientInstance; + delete ClientInstance; } void QmlBackend::emitNotifyUI(const QString &command, const QString &jsonData) { - emit notifyUI(command, jsonData); + emit notifyUI(command, jsonData); } void QmlBackend::cd(const QString &path) { - QDir::setCurrent(path); + QDir::setCurrent(path); } QStringList QmlBackend::ls(const QString &dir) { - return QDir(dir).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + return QDir(dir).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); } QString QmlBackend::pwd() { - return QDir::currentPath(); + return QDir::currentPath(); } bool QmlBackend::exists(const QString &file) { - return QFile::exists(file); + return QFile::exists(file); } bool QmlBackend::isDir(const QString &file) { - return QFileInfo(file).isDir(); + return QFileInfo(file).isDir(); } -#define CALLFUNC int err = lua_pcall(L, 1, 1, 0); \ - const char *result = lua_tostring(L, -1); \ - if (err) { \ - qDebug() << result; \ - lua_pop(L, 1); \ - return ""; \ - } \ - lua_pop(L, 1); \ - return QString(result); \ - QString QmlBackend::translate(const QString &src) { - lua_State *L = ClientInstance->getLuaState(); - lua_getglobal(L, "Translate"); - lua_pushstring(L, src.toUtf8().data()); + lua_State *L = ClientInstance->getLuaState(); + lua_getglobal(L, "Translate"); + lua_pushstring(L, src.toUtf8().data()); - CALLFUNC + int err = lua_pcall(L, 1, 1, 0); + const char *result = lua_tostring(L, -1); + if (err) { + qDebug() << result; + lua_pop(L, 1); + return ""; + } + lua_pop(L, 1); + return QString(result); } -QString QmlBackend::getGeneralData(const QString &general_name) { - lua_State *L = ClientInstance->getLuaState(); - lua_getglobal(L, "GetGeneralData"); - lua_pushstring(L, general_name.toUtf8().data()); - - CALLFUNC +void QmlBackend::pushLuaValue(lua_State *L, QVariant v) { + QVariantList list; + switch(v.type()) { + case QVariant::Bool: + lua_pushboolean(L, v.toBool()); + break; + case QVariant::Int: + case QVariant::UInt: + lua_pushinteger(L, v.toInt()); + break; + case QVariant::Double: + lua_pushnumber(L, v.toDouble()); + break; + case QVariant::String: + lua_pushstring(L, v.toString().toUtf8().data()); + break; + case QVariant::List: + lua_newtable(L); + list = v.toList(); + for (int i = 1; i <= list.length(); i++) { + lua_pushinteger(L, i); + pushLuaValue(L, list[i - 1]); + lua_settable(L, -3); + } + break; + default: + qDebug() << "cannot handle QVariant type" << v.type(); + lua_pushnil(L); + break; + } } -QString QmlBackend::getCardData(int id) { - lua_State *L = ClientInstance->getLuaState(); - lua_getglobal(L, "GetCardData"); - lua_pushinteger(L, id); +QString QmlBackend::callLuaFunction(const QString &func_name, + QVariantList params) +{ + lua_State *L = ClientInstance->getLuaState(); + lua_getglobal(L, func_name.toLatin1().data()); - CALLFUNC + foreach (QVariant v, params) { + pushLuaValue(L, v); + } + + int err = lua_pcall(L, params.length(), 1, 0); + const char *result = lua_tostring(L, -1); + if (err) { + qDebug() << result; + lua_pop(L, 1); + return ""; + } + lua_pop(L, 1); + return QString(result); } - -QString QmlBackend::getAllGeneralPack() { - lua_State *L = ClientInstance->getLuaState(); - lua_getglobal(L, "GetAllGeneralPack"); - lua_pushinteger(L, 0); - - CALLFUNC -} - -QString QmlBackend::getGenerals(const QString &pack_name) { - lua_State *L = ClientInstance->getLuaState(); - lua_getglobal(L, "GetGenerals"); - lua_pushstring(L, pack_name.toUtf8().data()); - - CALLFUNC -} - -QString QmlBackend::getAllCardPack() { - lua_State *L = ClientInstance->getLuaState(); - lua_getglobal(L, "GetAllCardPack"); - lua_pushinteger(L, 0); - - CALLFUNC -} - -QString QmlBackend::getCards(const QString &pack_name) { - lua_State *L = ClientInstance->getLuaState(); - lua_getglobal(L, "GetCards"); - lua_pushstring(L, pack_name.toUtf8().data()); - - CALLFUNC -} - -#undef CALLFUNC diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index abf700c7..fec1b4f5 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -2,43 +2,41 @@ #define _QMLBACKEND_H class QmlBackend : public QObject { - Q_OBJECT + Q_OBJECT public: - QmlBackend(QObject *parent = nullptr); + QmlBackend(QObject *parent = nullptr); - QQmlApplicationEngine *getEngine() const; - void setEngine(QQmlApplicationEngine *engine); + QQmlApplicationEngine *getEngine() const; + void setEngine(QQmlApplicationEngine *engine); - Q_INVOKABLE void startServer(ushort port); - Q_INVOKABLE void joinServer(QString address); + Q_INVOKABLE void startServer(ushort port); + Q_INVOKABLE void joinServer(QString address); - // Lobby - Q_INVOKABLE void quitLobby(); + // Lobby + Q_INVOKABLE void quitLobby(); - // lua --> qml - void emitNotifyUI(const QString &command, const QString &jsonData); + // lua --> qml + void emitNotifyUI(const QString &command, const QString &jsonData); - // File used by both Lua and Qml - static Q_INVOKABLE void cd(const QString &path); - static Q_INVOKABLE QStringList ls(const QString &dir = ""); - static Q_INVOKABLE QString pwd(); - static Q_INVOKABLE bool exists(const QString &file); - static Q_INVOKABLE bool isDir(const QString &file); + // File used by both Lua and Qml + static Q_INVOKABLE void cd(const QString &path); + static Q_INVOKABLE QStringList ls(const QString &dir = ""); + static Q_INVOKABLE QString pwd(); + static Q_INVOKABLE bool exists(const QString &file); + static Q_INVOKABLE bool isDir(const QString &file); - // read data from lua, call lua functions - Q_INVOKABLE QString translate(const QString &src); - Q_INVOKABLE QString getGeneralData(const QString &general_name); - Q_INVOKABLE QString getCardData(int id); - Q_INVOKABLE QString getAllGeneralPack(); - Q_INVOKABLE QString getGenerals(const QString &pack_name); - Q_INVOKABLE QString getAllCardPack(); - Q_INVOKABLE QString getCards(const QString &pack_name); + // read data from lua, call lua functions + Q_INVOKABLE QString translate(const QString &src); + Q_INVOKABLE QString callLuaFunction(const QString &func_name, + QVariantList params); signals: - void notifyUI(const QString &command, const QString &jsonData); + void notifyUI(const QString &command, const QString &jsonData); private: - QQmlApplicationEngine *engine; + QQmlApplicationEngine *engine; + + void pushLuaValue(lua_State *L, QVariant v); }; extern QmlBackend *Backend;