Use card (#19)

* the process of using card (uncompleted)

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

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

* basic ui logic

* ActiveSkill

* modidy ActiveSkill defaults

* todo: defaultEquipSkill

* client

* send use card to server

* playing phase, equip

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 624 B

View File

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

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

Binary file not shown.

View File

@ -5,140 +5,140 @@ Client = class('Client')
-- load client classes
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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

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

View File

@ -2,32 +2,36 @@
local TrickCard = Card:subclass("TrickCard")
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 }

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -19,6 +19,7 @@
---@field mark table<string, integer>
---@field player_cards table<integer, integer[]>
---@field special_cards table<string, integer[]>
---@field cardUsedHistory table<string, integer>
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

View File

@ -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

View File

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

View File

@ -6,12 +6,12 @@
local TriggerSkill = Skill:subclass("TriggerSkill")
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

View File

@ -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 }

View File

@ -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

View File

@ -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())

View File

@ -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

View File

@ -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<number, TriggerSkill[]>
local priority_table = {} ---@type number[]
for _, skill in ipairs(skills) do
if skill:triggerable(event, target, player, data) then
local priority = skill.priority_table[event]
if triggerable_skills[priority] == nil then
triggerable_skills[priority] = {}
end
table.insert(triggerable_skills[priority], skill)
if not table.contains(priority_table, priority) then
table.insert(priority_table, priority)
end
end
end
for _, priority in ipairs(priority_table) do
local triggerables = triggerable_skills[priority]
local skill_names = {} ---@type string[]
for _, skill in ipairs(triggerables) do
table.insert(skill_names, skill.name)
end
while #skill_names > 0 do
local skill_name = room:askForChoice(player, skill_names, "trigger")
local skill = triggerables[table.indexOf(skill_names, skill_name)]
broken = skill:trigger(event, target, player, data)
if broken then break end
table.removeOne(skill_names, skill_name)
table.removeOne(triggerables, skill)
end
end
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<number, TriggerSkill[]>
local priority_table = {} ---@type number[]
for _, skill in ipairs(skills) do
if skill:triggerable(event, target, player, data) then
local priority = skill.priority_table[event]
if triggerable_skills[priority] == nil then
triggerable_skills[priority] = {}
end
table.insert(triggerable_skills[priority], skill)
if not table.contains(priority_table, priority) then
table.insert(priority_table, priority)
end
end
end
for _, priority in ipairs(priority_table) do
local triggerables = triggerable_skills[priority]
local skill_names = {} ---@type string[]
for _, skill in ipairs(triggerables) do
table.insert(skill_names, skill.name)
end
while #skill_names > 0 do
local skill_name = room:askForChoice(player, skill_names, "trigger")
local skill = triggerables[table.indexOf(skill_names, skill_name)]
broken = skill:trigger(event, target, player, data)
if broken then break end
table.removeOne(skill_names, skill_name)
table.removeOne(triggerables, skill)
end
end
if broken then break end
player = player.next
end until player == target
self.event_stack:pop()
return broken
end
return GameLogic

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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,
}

View File

@ -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

View File

@ -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 = {},
}

View File

@ -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

View File

@ -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 = {},
}

View File

@ -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
}

View File

@ -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();
}
}
}
}
}

View File

@ -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();
}
}
}
}
}

View File

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

View File

@ -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],
});
});
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}
}
}
}

View File

@ -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!");
}
}

View File

@ -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);
}

View File

@ -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"
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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();
});
}
}

View File

@ -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();
}
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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]
}

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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 = [];
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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;
}
}
}
}
}
}

View File

@ -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();
}
}

View File

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

View File

@ -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)
}
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}
}
}

View File

@ -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}
}

View File

@ -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 + "!");
}
}
}
}

View File

@ -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);
}

View File

@ -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)
);

View File

@ -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
)

View File

@ -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;
}

View File

@ -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<int, ClientPlayer *> players;
Router *router;
QMap<int, ClientPlayer *> players;
lua_State *L;
lua_State *L;
};
extern Client *ClientInstance;

View File

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

View File

@ -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:
};

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}

View File

@ -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>", "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>", "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;
}

View File

@ -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));
}

View File

@ -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

View File

@ -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<ServerPlayer *>(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<ServerPlayer *>(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<Client *>(parent())->callLua(command, jsonData);
} else {
// requesting server is not allowed
Q_ASSERT(false);
}
if (type & DEST_CLIENT) {
qobject_cast<Client *>(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();
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

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

View File

@ -5,306 +5,306 @@
Room::Room(Server* server)
{
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<ServerPlayer *> Room::getPlayers() const
{
return players;
return players;
}
QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer* expect) const
{
QList<ServerPlayer *> others = getPlayers();
others.removeOne(expect);
return others;
QList<ServerPlayer *> 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<ServerPlayer *> targets, int timeout)
{
// TODO
// TODO
}
void Room::doNotify(const QList<ServerPlayer *> targets, int timeout)
{
// TODO
// TODO
}
void Room::doBroadcastNotify(const QList<ServerPlayer *> 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__);
}

View File

@ -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<ServerPlayer*> getPlayers() const;
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
ServerPlayer *findPlayer(int id) const;
void addPlayer(ServerPlayer *player);
void addRobot(ServerPlayer *player);
void removePlayer(ServerPlayer *player);
QList<ServerPlayer*> getPlayers() const;
QList<ServerPlayer *> 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<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout);
void doRequest(const QList<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout);
void doBroadcastNotify(
const QList<ServerPlayer *> targets,
const QString &command,
const QString &jsonData
);
void doBroadcastNotify(
const QList<ServerPlayer *> 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<ServerPlayer *> players;
QList<int> runned_players;
int robot_id;
bool gameStarted;
ServerPlayer *owner; // who created this room?
QList<ServerPlayer *> players;
QList<int> 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

View File

@ -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<ClientSocket *>(sender());
client->disconnect(this, SLOT(processRequest(const QByteArray &)));
client->timerSignup.stop();
ClientSocket *client = qobject_cast<ClientSocket *>(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<Room *>(sender());
room->gameOver();
rooms.remove(room->getId());
updateRoomList();
room->deleteLater();
Room *room = qobject_cast<Room *>(sender());
room->gameOver();
rooms.remove(room->getId());
updateRoomList();
room->deleteLater();
}
void Server::onUserDisconnected()
{
ServerPlayer *player = qobject_cast<ServerPlayer *>(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<ServerPlayer *>(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<ServerPlayer *>(sender());
QJsonArray arr;
arr << player->getId();
arr << player->getStateString();
player->getRoom()->callLua("PlayerStateChanged", QJsonDocument(arr).toJson());
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
QJsonArray arr;
arr << player->getId();
arr << player->getStateString();
player->getRoom()->callLua("PlayerStateChanged", QJsonDocument(arr).toJson());
}

View File

@ -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<int, Room *> rooms;
int nextRoomId;
friend Room::Room(Server *server);
QHash<int, ServerPlayer *> players;
ServerSocket *server;
Room *m_lobby;
QMap<int, Room *> rooms;
int nextRoomId;
friend Room::Room(Server *server);
QHash<int, ServerPlayer *> players;
sqlite3 *db;
sqlite3 *db;
void handleNameAndPassword(ClientSocket *client, const QString &name, const QString &password);
void handleNameAndPassword(ClientSocket *client, const QString &name, const QString &password);
};
extern Server *ServerInstance;

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
%}

View File

@ -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;
%}

View File

@ -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);
};

View File

@ -5,29 +5,29 @@ class QThread {};
template <class T>
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<T> 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<T> 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<ServerPlayer *>;
@ -39,10 +39,10 @@ public:
%{
#include <sys/time.h>
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;
}
%}

View File

@ -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<ServerPlayer *> getPlayers() const;
ServerPlayer *findPlayer(int id) const;
void addPlayer(ServerPlayer *player);
void addRobot(ServerPlayer *player);
void removePlayer(ServerPlayer *player);
QList<ServerPlayer *> getPlayers() const;
ServerPlayer *findPlayer(int id) const;
int getTimeout() const;
int getTimeout() const;
bool isStarted() const;
// ====================================}
bool isStarted() const;
// ====================================}
void doRequest(const QList<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout);
void doBroadcastNotify(
const QList<ServerPlayer *> targets,
const QString &command,
const QString &jsonData
);
void gameOver();
void doRequest(const QList<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout);
void doBroadcastNotify(
const QList<ServerPlayer *> 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);
}
%}

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