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

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,7 +5,6 @@ Client = class('Client')
-- load client classes -- load client classes
ClientPlayer = require "client.clientplayer" ClientPlayer = require "client.clientplayer"
dofile "lua/client/client_util.lua"
fk.client_callback = {} fk.client_callback = {}
@ -142,3 +141,4 @@ end
-- Create ClientInstance (used by Lua) -- Create ClientInstance (used by Lua)
ClientInstance = Client:new() ClientInstance = Client:new()
dofile "lua/client/client_util.lua"

View File

@ -1,3 +1,5 @@
-- All functions in this file are used by Qml
function Translate(src) function Translate(src)
return Fk.translations[src] return Fk.translations[src]
end end
@ -12,19 +14,31 @@ function GetGeneralData(name)
} }
end end
local cardSubtypeStrings = {
[Card.SubtypeNone] = "none",
[Card.SubtypeDelayedTrick] = "delayed_trick",
[Card.SubtypeWeapon] = "weapon",
[Card.SubtypeArmor] = "armor",
[Card.SubtypeDefensiveRide] = "defensive_horse",
[Card.SubtypeOffensiveRide] = "offensive_horse",
[Card.SubtypeTreasure] = "treasure",
}
function GetCardData(id) function GetCardData(id)
local card = Fk.cards[id] local card = Fk.cards[id]
if card == nil then return json.encode{ if card == nil then return json.encode{
cid = id, cid = id,
known = false known = false
} end } end
return json.encode{ local ret = {
cid = id, cid = id,
name = card.name, name = card.name,
number = card.number, number = card.number,
suit = card:getSuitString(), suit = card:getSuitString(),
color = card.color, color = card.color,
subtype = cardSubtypeStrings[card.sub_type]
} }
return json.encode(ret)
end end
function GetAllGeneralPack() function GetAllGeneralPack()
@ -62,3 +76,70 @@ function GetCards(pack_name)
end end
return json.encode(ret) return json.encode(ret)
end 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

@ -3,6 +3,7 @@
---@field name string ---@field name string
---@field suit Suit ---@field suit Suit
---@field number integer ---@field number integer
---@field trueName string
---@field color Color ---@field color Color
---@field id integer ---@field id integer
---@field type CardType ---@field type CardType
@ -26,10 +27,9 @@ Card.NoColor = 3
---@alias CardType integer ---@alias CardType integer
Card.TypeSkill = 1 Card.TypeBasic = 1
Card.TypeBasic = 2 Card.TypeTrick = 2
Card.TypeTrick = 3 Card.TypeEquip = 3
Card.TypeEquip = 4
---@alias CardSubtype integer ---@alias CardSubtype integer
@ -57,6 +57,7 @@ function Card:initialize(name, suit, number, color)
self.name = name self.name = name
self.suit = suit or Card.NoSuit self.suit = suit or Card.NoSuit
self.number = number or 0 self.number = number or 0
self.trueName = name
if suit == Card.Spade or suit == Card.Club then if suit == Card.Spade or suit == Card.Club then
self.color = Card.Black self.color = Card.Black
@ -72,6 +73,7 @@ function Card:initialize(name, suit, number, color)
self.id = 0 self.id = 0
self.type = 0 self.type = 0
self.sub_type = Card.SubTypeNone self.sub_type = Card.SubTypeNone
self.skill = nil
end end
function Card:getSuitString() function Card:getSuitString()

View File

@ -11,6 +11,7 @@ end
---@return BasicCard ---@return BasicCard
function BasicCard:clone(suit, number) function BasicCard:clone(suit, number)
local newCard = BasicCard:new(self.name, suit, number) local newCard = BasicCard:new(self.name, suit, number)
newCard.skill = self.skill
return newCard return newCard
end end

View File

@ -1,9 +1,11 @@
---@class EquipCard : Card ---@class EquipCard : Card
---@field equipSkill Skill
local EquipCard = Card:subclass("EquipCard") local EquipCard = Card:subclass("EquipCard")
function EquipCard:initialize(name, suit, number) function EquipCard:initialize(name, suit, number)
Card.initialize(self, name, suit, number) Card.initialize(self, name, suit, number)
self.type = Card.TypeEquip self.type = Card.TypeEquip
self.equipSkill = nil
end end
---@class Weapon : EquipCard ---@class Weapon : EquipCard
@ -20,6 +22,7 @@ end
---@return Weapon ---@return Weapon
function Weapon:clone(suit, number) function Weapon:clone(suit, number)
local newCard = Weapon:new(self.name, suit, number, self.attack_range) local newCard = Weapon:new(self.name, suit, number, self.attack_range)
newCard.skill = self.skill
return newCard return newCard
end end
@ -36,6 +39,7 @@ end
---@return Armor ---@return Armor
function Armor:clone(suit, number) function Armor:clone(suit, number)
local newCard = Armor:new(self.name, suit, number) local newCard = Armor:new(self.name, suit, number)
newCard.skill = self.skill
return newCard return newCard
end end
@ -52,6 +56,7 @@ end
---@return DefensiveRide ---@return DefensiveRide
function DefensiveRide:clone(suit, number) function DefensiveRide:clone(suit, number)
local newCard = DefensiveRide:new(self.name, suit, number) local newCard = DefensiveRide:new(self.name, suit, number)
newCard.skill = self.skill
return newCard return newCard
end end
@ -68,6 +73,7 @@ end
---@return OffensiveRide ---@return OffensiveRide
function OffensiveRide:clone(suit, number) function OffensiveRide:clone(suit, number)
local newCard = OffensiveRide:new(self.name, suit, number) local newCard = OffensiveRide:new(self.name, suit, number)
newCard.skill = self.skill
return newCard return newCard
end end
@ -84,6 +90,7 @@ end
---@return Treasure ---@return Treasure
function Treasure:clone(suit, number) function Treasure:clone(suit, number)
local newCard = Treasure:new(self.name, suit, number) local newCard = Treasure:new(self.name, suit, number)
newCard.skill = self.skill
return newCard return newCard
end end

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

@ -11,6 +11,9 @@ end
---@return TrickCard ---@return TrickCard
function TrickCard:clone(suit, number) function TrickCard:clone(suit, number)
local newCard = TrickCard:new(self.name, suit, number) local newCard = TrickCard:new(self.name, suit, number)
newCard.skill = self.skill
return newCard return newCard
end end
@ -27,6 +30,7 @@ end
---@return DelayedTrickCard ---@return DelayedTrickCard
function DelayedTrickCard:clone(suit, number) function DelayedTrickCard:clone(suit, number)
local newCard = DelayedTrickCard:new(self.name, suit, number) local newCard = DelayedTrickCard:new(self.name, suit, number)
newCard.skill = self.skill
return newCard return newCard
end end

View File

@ -19,6 +19,7 @@
---@field mark table<string, integer> ---@field mark table<string, integer>
---@field player_cards table<integer, integer[]> ---@field player_cards table<integer, integer[]>
---@field special_cards table<string, integer[]> ---@field special_cards table<string, integer[]>
---@field cardUsedHistory table<string, integer>
local Player = class("Player") local Player = class("Player")
---@alias Phase integer ---@alias Phase integer
@ -65,6 +66,8 @@ function Player:initialize()
[Player.Judge] = {}, [Player.Judge] = {},
} }
self.special_cards = {} self.special_cards = {}
self.cardUsedHistory = {}
end end
---@param general General ---@param general General
@ -194,6 +197,18 @@ function Player:getCardIds(playerAreas, specialName)
return cardIds return cardIds
end 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 nil
end
function Player:getMaxCards() function Player:getMaxCards()
local baseValue = math.max(self.hp, 0) local baseValue = math.max(self.hp, 0)
@ -205,7 +220,7 @@ end
function Player:getEquipBySubtype(subtype) function Player:getEquipBySubtype(subtype)
local equipId = nil local equipId = nil
for _, id in ipairs(self.player_cards[Player.Equip]) do for _, id in ipairs(self.player_cards[Player.Equip]) do
if Fk.getCardById(id).sub_type == subtype then if Fk:getCardById(id).sub_type == subtype then
equipId = id equipId = id
break break
end end
@ -215,10 +230,23 @@ function Player:getEquipBySubtype(subtype)
end end
function Player:getAttackRange() function Player:getAttackRange()
local weapon = Fk.getCardById(self:getEquipBySubtype(Card.SubtypeWeapon)) local weapon = Fk:getCardById(self:getEquipBySubtype(Card.SubtypeWeapon))
local baseAttackRange = math.max(weapon and weapon.attack_range or 1, 0) local baseAttackRange = math.max(weapon and weapon.attack_range or 1, 0)
return math.max(baseAttackRange, 0) return math.max(baseAttackRange, 0)
end 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 return Player

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

@ -151,3 +151,144 @@ function switch(param, case_table)
local def = case_table["default"] local def = case_table["default"]
return def and def() or nil return def and def() or nil
end end
---@class TargetGroup : Object
local TargetGroup = class("TargetGroup")
function TargetGroup.static:getRealTargets(targetGroup)
if not targetGroup then
return {}
end
local realTargets = {}
for _, targets in ipairs(targetGroup) do
table.insert(realTargets, targets[1])
end
return realTargets
end
function TargetGroup.static:includeRealTargets(targetGroup, playerId)
if not targetGroup then
return false
end
for _, targets in ipairs(targetGroup) do
if targets[1] == playerId then
return true
end
end
return false
end
function TargetGroup.static:removeTarget(targetGroup, playerId)
if not targetGroup then
return
end
for index, targets in ipairs(targetGroup) do
if (targets[1] == playerId) then
table.remove(targetGroup, index)
return
end
end
end
function TargetGroup.static:pushTargets(targetGroup, playerIds)
if not targetGroup then
return
end
if type(playerIds) == "table" then
table.insert(targetGroup, playerIds)
elseif type(playerIds) == "number" then
table.insert(targetGroup, { playerIds })
end
end
---@class AimGroup : Object
local AimGroup = class("AimGroup")
AimGroup.Undone = 1
AimGroup.Done = 2
AimGroup.Cancelled = 3
function AimGroup.static:initAimGroup(playerIds)
return { [AimGroup.Undone] = playerIds, [AimGroup.Done] = {}, [AimGroup.Cancelled] = {} }
end
function AimGroup.static:getAllTargets(aimGroup)
local targets = {}
table.insertTable(targets, aimGroup[AimGroup.Undone])
table.insertTable(targets, aimGroup[AimGroup.Done])
return targets
end
function AimGroup.static:getUndoneOrDoneTargets(aimGroup, done)
return done and aimGroup[AimGroup.Done] or aimGroup[AimGroup.Undone]
end
function AimGroup.static:setTargetDone(aimGroup, playerId)
local index = table.indexOf(aimGroup[AimGroup.Undone], playerId)
if index ~= -1 then
table.remove(aimGroup[AimGroup.Undone], index)
table.insert(aimGroup[AimGroup.Done], playerId)
end
end
function AimGroup.static:addTargets(room, aimEvent, playerIds)
local playerId = type(playerIds) == "table" and playerIds[1] or playerIds
table.insert(aimEvent.tos[AimGroup.Undone], playerId)
room:sortPlayersByAction(aimEvent.tos[AimGroup.Undone])
if aimEvent.targetGroup then
TargetGroup:pushTargets(aimEvent.targetGroup, playerIds)
end
end
function AimGroup.static:cancelTarget(aimEvent, playerId)
local cancelled = false
for status = AimGroup.Undone, AimGroup.Done do
local indexList = {}
for index, pId in ipairs(aimEvent.tos[status]) do
if pId == playerId then
table.insert(indexList, index)
end
end
if #indexList > 0 then
cancelled = true
for i = 1, #indexList do
table.remove(aimEvent.tos[status], indexList[i])
end
end
end
if cancelled then
table.insert(aimEvent.tos[AimGroup.Cancelled], playerId)
if aimEvent.targetGroup then
TargetGroup:removeTarget(aimEvent.targetGroup, playerId)
end
end
end
function AimGroup.static:removeDeadTargets(room, aimEvent)
for index = AimGroup.Undone, AimGroup.Done do
aimEvent.tos[index] = room:deadPlayerFilter(aimEvent.tos[index])
end
if aimEvent.targetGroup then
local targets = TargetGroup:getRealTargets(aimEvent.targetGroup)
for _, target in ipairs(targets) do
if not room:getPlayerById(target):isAlive() then
TargetGroup:removeTarget(aimEvent.targetGroup, target)
end
end
end
end
function AimGroup.static:getCancelledTargets(aimGroup)
return aimGroup[AimGroup.Cancelled]
end
return { TargetGroup, AimGroup }

View File

@ -1,18 +1,16 @@
-- load types for extension -- load types for extension
SkillCard = require "core.card_type.skill" dofile "lua/server/event.lua"
dofile "lua/server/system_enum.lua"
TriggerSkill = require "core.skill_type.trigger"
ActiveSkill = require "core.skill_type.active_skill"
BasicCard = require "core.card_type.basic" BasicCard = require "core.card_type.basic"
local Trick = require "core.card_type.trick" local Trick = require "core.card_type.trick"
TrickCard, DelayedTrickCard = table.unpack(Trick) TrickCard, DelayedTrickCard = table.unpack(Trick)
local Equip = require "core.card_type.equip" local Equip = require "core.card_type.equip"
_, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure = table.unpack(Equip) _, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure = table.unpack(Equip)
dofile "lua/server/event.lua"
dofile "lua/server/system_enum.lua"
TriggerSkill = require "core.skill_type.trigger"
---@class CardSpec: Card
---@class SkillSpec: Skill ---@class SkillSpec: Skill
---@alias TrigFunc fun(self: TriggerSkill, event: Event, target: ServerPlayer, player: ServerPlayer):boolean ---@alias TrigFunc fun(self: TriggerSkill, event: Event, target: ServerPlayer, player: ServerPlayer):boolean
@ -26,111 +24,6 @@ TriggerSkill = require "core.skill_type.trigger"
---@field on_refresh TrigFunc ---@field on_refresh TrigFunc
---@field can_refresh TrigFunc ---@field can_refresh TrigFunc
---@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
local card = BasicCard:new(spec.name, spec.suit, spec.number)
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
local card = TrickCard:new(spec.name, spec.suit, spec.number)
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
local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number)
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
local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range)
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
local card = Armor:new(spec.name, spec.suit, spec.number)
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
local card = DefensiveRide:new(spec.name, spec.suit, spec.number)
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
local card = OffensiveRide:new(spec.name, spec.suit, spec.number)
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
local card = Treasure:new(spec.name, spec.suit, spec.number)
return card
end
---@param spec TriggerSkillSpec ---@param spec TriggerSkillSpec
---@return TriggerSkill ---@return TriggerSkill
function fk.CreateTriggerSkill(spec) function fk.CreateTriggerSkill(spec)
@ -189,3 +82,150 @@ function fk.CreateTriggerSkill(spec)
end end
return skill return skill
end 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
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
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
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
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
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
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
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
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" json = require "json"
dofile "lua/lib/sha256.lua" dofile "lua/lib/sha256.lua"
dofile "lua/core/util.lua" local GroupUtils = require "core.util"
TargetGroup, AimGroup = table.unpack(GroupUtils)
dofile "lua/core/debug.lua" dofile "lua/core/debug.lua"
math.randomseed(os.time()) math.randomseed(os.time())

View File

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

View File

@ -416,6 +416,22 @@ function Room:getPlayerById(id)
error("cannot find player by " .. id) error("cannot find player by " .. id)
end end
---@param playerIds integer[]
function Room:sortPlayersByAction(playerIds)
end
function Room:deadPlayerFilter(playerIds)
local newPlayerIds = {}
for _, playerId in ipairs(playerIds) do
if self:getPlayerById(playerId):isAlive() then
table.insert(newPlayerIds, playerId)
end
end
return newPlayerIds
end
---@param sortBySeat boolean ---@param sortBySeat boolean
---@return ServerPlayer[] ---@return ServerPlayer[]
function Room:getAlivePlayers(sortBySeat) function Room:getAlivePlayers(sortBySeat)
@ -718,6 +734,227 @@ function Room:killPlayer(deathStruct)
self:gameOver() self:gameOver()
end end
---@param room Room
---@param cardUseEvent CardUseStruct
---@param aimEventCollaborators table<string, AimStruct[]>
---@return boolean
local onAim = function(room, cardUseEvent, aimEventCollaborators)
local eventStages = { fk.TargetSpecifying, fk.TargetConfirming, fk.TargetSpecified, fk.TargetConfirmed }
for _, stage in ipairs(eventStages) do
if not cardUseEvent.tos then
return false
end
room:sortPlayersByAction(cardUseEvent.tos)
local aimGroup = AimGroup:initAimGroup(TargetGroup:getRealTargets(cardUseEvent.tos))
local collaboratorsIndex = {}
local firstTarget = true
repeat
local toId = AimGroup:getUndoneOrDoneTargets(aimGroup)[1]
---@type AimStruct
local aimStruct
local initialEvent = false
collaboratorsIndex[toId] = collaboratorsIndex[toId] or 0
if not aimEventCollaborators[toId] or collaboratorsIndex[toId] >= #aimEventCollaborators[toId] then
aimStruct = {
from = cardUseEvent.from,
cardId = cardUseEvent.cardId,
to = toId,
targetGroup = cardUseEvent.tos,
nullifiedTargets = cardUseEvent.nullifiedTargets or {},
tos = aimGroup,
firstTarget = firstTarget,
additionalDamage = cardUseEvent.addtionalDamage
}
collaboratorsIndex[toId] = 1
initialEvent = true
else
aimStruct = aimEventCollaborators[toId][collaboratorsIndex[toId]]
aimStruct.from = cardUseEvent.from
aimStruct.cardId = cardUseEvent.cardId
aimStruct.tos = aimGroup
aimStruct.targetGroup = cardUseEvent.tos
aimStruct.nullifiedTargets = cardUseEvent.nullifiedTargets or {}
aimStruct.firstTarget = firstTarget
end
firstTarget = false
if room.logic:trigger(stage, (stage == fk.TargetSpecifying or stage == fk.TargetSpecified) and room:getPlayerById(aimStruct.from) or room:getPlayerById(aimStruct.to), aimStruct) then
return false
end
AimGroup:removeDeadTargets(room, aimStruct)
local aimEventTargetGroup = aimStruct.targetGroup
if aimEventTargetGroup then
room:sortPlayersByAction(aimEventTargetGroup)
end
cardUseEvent.from = aimStruct.from
cardUseEvent.tos = aimEventTargetGroup
cardUseEvent.nullifiedTargets = aimStruct.nullifiedTargets
if #AimGroup:getAllTargets(aimStruct.tos) == 0 then
return false
end
local cancelledTargets = AimGroup:getCancelledTargets(aimStruct.tos)
if #cancelledTargets > 0 then
for _, target in ipairs(cancelledTargets) do
aimEventCollaborators[target] = {}
collaboratorsIndex[target] = 0
end
end
aimStruct.tos[AimGroup.Cancelled] = {}
aimEventCollaborators[toId] = aimEventCollaborators[toId] or {}
if not room:getPlayerById(toId):isAlive() then
if initialEvent then
table.insert(aimEventCollaborators[toId], aimStruct)
else
aimEventCollaborators[toId][collaboratorsIndex[toId]] = aimStruct
end
end
AimGroup:setTargetDone(aimStruct.tos, toId)
aimGroup = aimStruct.tos
until #AimGroup:getUndoneOrDoneTargets(aimGroup) == 0
end
return true
end
---@param cardUseEvent CardUseStruct
---@return boolean
function Room:useCard(cardUseEvent)
self:moveCards({
ids = { cardUseEvent.cardId },
from = cardUseEvent.customFrom or cardUseEvent.from,
toArea = Card.Processing,
moveReason = fk.ReasonUse,
})
if Fk:getCardById(cardUseEvent.cardId).skill then
Fk:getCardById(cardUseEvent.cardId).skill:onUse(self, cardUseEvent)
end
if self.logic:trigger(fk.PreCardUse, self:getPlayerById(cardUseEvent.from), cardUseEvent) then
return false
end
if not cardUseEvent.extraUse then
self:getPlayerById(cardUseEvent.from):addCardUseHistory(Fk:getCardById(cardUseEvent.cardId).trueName, 1)
end
if cardUseEvent.responseToEvent then
cardUseEvent.responseToEvent.cardIdsResponded = cardUseEvent.responseToEvent.cardIdsResponded or {}
table.insert(cardUseEvent.responseToEvent.cardIdsResponded, cardUseEvent.cardId)
end
for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.BeforeCardUseEffect, fk.CardUsing }) do
-- TODO: need to complete the cards for response
self.logic:trigger(event, self:getPlayerById(cardUseEvent.from), cardUseEvent)
if event == fk.CardUsing then
---@type table<string, AimStruct>
local aimEventCollaborators = {}
if cardUseEvent.tos and not onAim(self, cardUseEvent, aimEventCollaborators) then
break
end
if Fk:getCardById(cardUseEvent.cardId).type == Card.TypeEquip then
if self:getCardArea(cardUseEvent.cardId) ~= Card.Processing then
break
end
if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then
self.moveCards({
ids = { cardUseEvent.cardId },
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
})
else
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
local existingEquipId = self:getPlayerById(target):getEquipment(Fk:getCardById(cardUseEvent.cardId).sub_type)
if existingEquipId then
self:moveCards(
{
ids = { existingEquipId },
from = target,
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
},
{
ids = { cardUseEvent.cardId },
to = target,
toArea = Card.PlayerEquip,
moveReason = fk.ReasonUse,
}
)
else
self:moveCards({
ids = { cardUseEvent.cardId },
to = target,
toArea = Card.PlayerEquip,
moveReason = fk.ReasonUse,
})
end
end
break
elseif Fk:getCardById(cardUseEvent.cardId).sub_type == Card.SubtypeDelayedTrick then
if self:getCardArea(cardUseEvent.cardId) ~= Card.Processing then
break
end
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
if not self:getPlayerById(target).dead then
local findSameCard = false
for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Equip)) do
if Fk:getCardById(cardId).trueName == Fk:getCardById(cardUseEvent.cardId) then
findSameCard = true
end
end
if not findSameCard then
self:moveCards({
ids = { cardUseEvent.cardId },
to = target,
toArea = Card.PlayerJudge,
moveReason = fk.ReasonUse,
})
break
end
end
self:moveCards({
ids = { cardUseEvent.cardId },
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
})
break
end
if Fk:getCardById(cardUseEvent.cardId).skill then
Fk:getCardById(cardUseEvent.cardId).skill:onEffect(self, cardUseEvent)
end
end
end
self.logic:trigger(fk.CardUseFinished, self:getPlayerById(cardUseEvent.from), cardUseEvent)
if self:getCardArea(cardUseEvent.cardId) == Card.Processing then
self:moveCards({
ids = { cardUseEvent.cardId },
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
})
end
end
fk.room_callback["QuitRoom"] = function(jsonData) fk.room_callback["QuitRoom"] = function(jsonData)
-- jsonData: [ int uid ] -- jsonData: [ int uid ]
local data = json.decode(jsonData) local data = json.decode(jsonData)

View File

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

View File

@ -83,7 +83,22 @@ GameRule = fk.CreateTriggerSkill{
room:drawCards(player, 2, self.name) room:drawCards(player, 2, self.name)
end, end,
[Player.Play] = function() [Player.Play] = function()
room:askForSkillInvoke(player, "rule") 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, end,
[Player.Discard] = function() [Player.Discard] = function()
local discardNum = #player:getCardIds(Player.Hand) - player:getMaxCards() local discardNum = #player:getCardIds(Player.Hand) - player:getMaxCards()

View File

@ -167,10 +167,23 @@ extension:addCards({
collateral:clone(Card.Club, 13), collateral:clone(Card.Club, 13),
}) })
local exNihiloSkill = fk.CreateActiveSkill{
name = "ex_nihilo_skill",
on_use = function(self, room, cardUseEvent)
if not cardUseEvent.tos or #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then
cardUseEvent.tos = { { cardUseEvent.from } }
end
end,
on_effect = function(self, room, cardEffectEvent)
room:drawCards(room:getPlayerById(TargetGroup:getRealTargets(cardEffectEvent.tos)[1]), 2, "ex_nihilo")
end
}
local exNihilo = fk.CreateTrickCard{ local exNihilo = fk.CreateTrickCard{
name = "ex_nihilo", name = "ex_nihilo",
suit = Card.Heart, suit = Card.Heart,
number = 7, number = 7,
skill = exNihiloSkill,
} }
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["ex_nihilo"] = "无中生有", ["ex_nihilo"] = "无中生有",

View File

@ -22,11 +22,11 @@ Item {
GridLayout { GridLayout {
columns: root.width / 98 columns: root.width / 98
Repeater { Repeater {
model: JSON.parse(Backend.getCards(name)) model: JSON.parse(Backend.callLuaFunction("GetCards", [name]))
CardItem { CardItem {
autoBack: false autoBack: false
Component.onCompleted: { Component.onCompleted: {
let data = JSON.parse(Backend.getCardData(modelData)); let data = JSON.parse(Backend.callLuaFunction("GetCardData", [modelData]));
setData(data); setData(data);
} }
} }
@ -45,7 +45,7 @@ Item {
function loadPackages() { function loadPackages() {
if (loaded) return; if (loaded) return;
let packs = JSON.parse(Backend.getAllCardPack()); let packs = JSON.parse(Backend.callLuaFunction("GetAllCardPack", []));
packs.forEach((name) => packages.append({ name: name })); packs.forEach((name) => packages.append({ name: name }));
loaded = true; loaded = true;
} }

View File

@ -22,11 +22,11 @@ Item {
GridLayout { GridLayout {
columns: root.width / 98 columns: root.width / 98
Repeater { Repeater {
model: JSON.parse(Backend.getGenerals(name)) model: JSON.parse(Backend.callLuaFunction("GetGenerals", [name]))
GeneralCardItem { GeneralCardItem {
autoBack: false autoBack: false
Component.onCompleted: { Component.onCompleted: {
let data = JSON.parse(Backend.getGeneralData(modelData)); let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [modelData]));
name = modelData; name = modelData;
kingdom = data.kingdom; kingdom = data.kingdom;
} }
@ -46,7 +46,7 @@ Item {
function loadPackages() { function loadPackages() {
if (loaded) return; if (loaded) return;
let packs = JSON.parse(Backend.getAllGeneralPack()); let packs = JSON.parse(Backend.callLuaFunction("GetAllGeneralPack", []));
packs.forEach((name) => packages.append({ name: name })); packs.forEach((name) => packages.append({ name: name }));
loaded = true; loaded = true;
} }

View File

@ -16,6 +16,8 @@ Item {
property alias popupBox: popupBox property alias popupBox: popupBox
property alias promptText: prompt.text property alias promptText: prompt.text
property var selected_targets: []
// tmp // tmp
Row { Row {
Button{text:"摸1牌" Button{text:"摸1牌"
@ -68,6 +70,11 @@ Item {
okCancel.visible = false; okCancel.visible = false;
endPhaseButton.visible = false; endPhaseButton.visible = false;
dashboard.disableAllCards();
if (dashboard.pending_skill !== "")
dashboard.stopPending();
selected_targets = [];
if (popupBox.item != null) { if (popupBox.item != null) {
popupBox.item.finished(); popupBox.item.finished();
} }
@ -79,6 +86,7 @@ Item {
from: "*"; to: "playing" from: "*"; to: "playing"
ScriptAction { ScriptAction {
script: { script: {
dashboard.enableCards();
progress.visible = true; progress.visible = true;
okCancel.visible = true; okCancel.visible = true;
endPhaseButton.visible = true; endPhaseButton.visible = true;
@ -130,6 +138,7 @@ Item {
id: photos id: photos
model: photoModel model: photoModel
Photo { Photo {
playerid: model.id
general: model.general general: model.general
screenName: model.screenName screenName: model.screenName
role: model.role role: model.role
@ -144,6 +153,10 @@ Item {
chained: model.chained chained: model.chained
drank: model.drank drank: model.drank
isOwner: model.isOwner isOwner: model.isOwner
onSelectedChanged: {
Logic.updateSelectedTargets(playerid, selected);
}
} }
} }
@ -170,6 +183,7 @@ Item {
width: roomScene.width width: roomScene.width
anchors.top: roomArea.bottom anchors.top: roomArea.bottom
self.playerid: dashboardModel.id
self.general: dashboardModel.general self.general: dashboardModel.general
self.screenName: dashboardModel.screenName self.screenName: dashboardModel.screenName
self.role: dashboardModel.role self.role: dashboardModel.role
@ -184,6 +198,14 @@ Item {
self.chained: dashboardModel.chained self.chained: dashboardModel.chained
self.drank: dashboardModel.drank self.drank: dashboardModel.drank
self.isOwner: dashboardModel.isOwner self.isOwner: dashboardModel.isOwner
onSelectedChanged: {
Logic.updateSelectedTargets(self.playerid, selected);
}
onCardSelected: {
Logic.enableTargets(card);
}
} }
Item { Item {

View File

@ -11,6 +11,16 @@ RowLayout {
property alias delayedTrickArea: selfPhoto.delayedTrickArea property alias delayedTrickArea: selfPhoto.delayedTrickArea
property alias specialArea: selfPhoto.specialArea property alias specialArea: selfPhoto.specialArea
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 { Item {
width: 40 width: 40
} }
@ -28,4 +38,131 @@ RowLayout {
} }
Item { width: 5 } 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;
}
}
pendings = [];
handcardAreaItem.adjustCards();
cardSelected(-1);
}
} }

View File

@ -57,7 +57,7 @@ Item {
let items = []; let items = [];
for (let i = 0; i < outputs.length; i++) { for (let i = 0; i < outputs.length; i++) {
if (_contains(outputs[i])) { if (_contains(outputs[i])) {
let state = JSON.parse(Backend.getCardData(outputs[i])) let state = JSON.parse(Backend.callLuaFunction("GetCardData", [outputs[i]]))
state.x = parentPos.x; state.x = parentPos.x;
state.y = parentPos.y; state.y = parentPos.y;
state.opacity = 0; state.opacity = 0;

View File

@ -9,6 +9,7 @@ Item {
width: 175 width: 175
height: 233 height: 233
scale: 0.8 scale: 0.8
property int playerid
property string general: "" property string general: ""
property string screenName: "" property string screenName: ""
property string role: "unknown" property string role: "unknown"
@ -45,12 +46,63 @@ Item {
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad } 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();
}
}
}
]
PixmapAnimation { PixmapAnimation {
id: animFrame id: animPlaying
source: "playing"
anchors.centerIn: parent
loop: true
scale: 1.1
visible: root.state === "playing"
}
PixmapAnimation {
id: animSelected
source: "selected" source: "selected"
anchors.centerIn: parent anchors.centerIn: parent
loop: true loop: true
scale: 1.1 scale: 1.1
visible: root.state === "candidate" && selected
} }
Image { Image {
@ -193,6 +245,15 @@ Item {
} }
} }
MouseArea {
anchors.fill: parent
onClicked: {
if (parent.state != "candidate" || !parent.selectable)
return;
parent.selected = !parent.selected;
}
}
RoleComboBox { RoleComboBox {
id: role id: role
value: root.role value: root.role
@ -202,6 +263,12 @@ Item {
anchors.rightMargin: -4 anchors.rightMargin: -4
} }
Image {
visible: root.state === "candidate" && !selectable && !selected
source: SkinBank.PHOTO_DIR + "disable"
x: 31; y: -21
}
GlowText { GlowText {
id: seatNum id: seatNum
visible: !progressBar.visible visible: !progressBar.visible
@ -285,6 +352,7 @@ Item {
source: "selectable" source: "selectable"
anchors.centerIn: parent anchors.centerIn: parent
loop: true loop: true
visible: root.state === "candidate" && selectable
} }
InvisibleCardArea { InvisibleCardArea {
@ -307,7 +375,7 @@ Item {
onGeneralChanged: { onGeneralChanged: {
if (!roomScene.isStarted) return; if (!roomScene.isStarted) return;
generalName.text = Backend.translate(general); generalName.text = Backend.translate(general);
let data = JSON.parse(Backend.getGeneralData(general)); let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [general]));
kingdom = data.kingdom; kingdom = data.kingdom;
} }
} }

View File

@ -125,7 +125,7 @@ Item {
text = "-1" text = "-1"
icon = "horse"; icon = "horse";
} else { } else {
text = name; text = Backend.translate(name);
icon = name; icon = name;
} }
} }

View File

@ -61,6 +61,15 @@ function arrangePhotos() {
} }
function doOkButton() { function doOkButton() {
if (roomScene.state == "playing") {
replyToServer(JSON.stringify(
{
card: dashboard.getSelectedCard(),
targets: selected_targets
}
));
return;
}
replyToServer("1"); replyToServer("1");
} }
@ -218,6 +227,76 @@ callbacks["AddPlayer"] = function(jsonData) {
} }
} }
function enableTargets(card) { // card: int | { skill: string, subcards: int[] }
let i = 0;
let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string";
let all_photos = [dashboard.self];
for (i = 0; i < playerNum - 1; i++) {
all_photos.push(photos.itemAt(i))
}
selected_targets = [];
for (i = 0; i < playerNum; i++) {
all_photos[i].selected = false;
}
if (candidate) {
let data = {
ok_enabled: false,
enabled_targets: []
}
all_photos.forEach(photo => {
photo.state = "candidate";
let id = photo.playerid;
let ret = JSON.parse(Backend.callLuaFunction(
"CanUseCardToTarget",
[card, id, selected_targets]
));
photo.selectable = ret;
})
okButton.enabled = JSON.parse(Backend.callLuaFunction(
"CardFeasible", [card, selected_targets]
));
} else {
all_photos.forEach(photo => {
photo.state = "normal";
photo.selected = false;
});
okButton.enabled = false;
}
}
function updateSelectedTargets(playerid, selected) {
let i = 0;
let card = dashboard.getSelectedCard();
let all_photos = [dashboard.self]
for (i = 0; i < playerNum - 1; i++) {
all_photos.push(photos.itemAt(i))
}
if (selected) {
selected_targets.push(playerid);
} else {
selected_targets.splice(selected_targets.indexOf(playerid), 1);
}
all_photos.forEach(photo => {
if (photo.selected) return;
let id = photo.playerid;
let ret = JSON.parse(Backend.callLuaFunction(
"CanUseCardToTarget",
[card, id, selected_targets]
));
photo.selectable = ret;
})
okButton.enabled = JSON.parse(Backend.callLuaFunction(
"CardFeasible", [card, selected_targets]
));
}
callbacks["RemovePlayer"] = function(jsonData) { callbacks["RemovePlayer"] = function(jsonData) {
// jsonData: int uid // jsonData: int uid
let uid = JSON.parse(jsonData)[0]; let uid = JSON.parse(jsonData)[0];
@ -375,3 +454,12 @@ callbacks["MoveCards"] = function(jsonData) {
let moves = JSON.parse(jsonData); let moves = JSON.parse(jsonData);
moveCards(moves); moveCards(moves);
} }
callbacks["PlayCard"] = function(jsonData) {
// jsonData: int playerId
let playerId = parseInt(jsonData);
if (playerId == Self.id) {
roomScene.promptText = "Please use a card";
roomScene.state = "playing";
}
}

View File

@ -50,6 +50,8 @@ int main(int argc, char *argv[])
#endif #endif
engine->rootContext()->setContextProperty("Debugging", debugging); engine->rootContext()->setContextProperty("Debugging", debugging);
engine->load("qml/main.qml"); engine->load("qml/main.qml");
if (engine->rootObjects().isEmpty())
return -1;
int ret = app->exec(); int ret = app->exec();

View File

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

View File

@ -85,70 +85,71 @@ bool QmlBackend::isDir(const QString &file) {
return QFileInfo(file).isDir(); return QFileInfo(file).isDir();
} }
#define CALLFUNC int err = lua_pcall(L, 1, 1, 0); \
const char *result = lua_tostring(L, -1); \
if (err) { \
qDebug() << result; \
lua_pop(L, 1); \
return ""; \
} \
lua_pop(L, 1); \
return QString(result); \
QString QmlBackend::translate(const QString &src) { QString QmlBackend::translate(const QString &src) {
lua_State *L = ClientInstance->getLuaState(); lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "Translate"); lua_getglobal(L, "Translate");
lua_pushstring(L, src.toUtf8().data()); lua_pushstring(L, src.toUtf8().data());
CALLFUNC int err = lua_pcall(L, 1, 1, 0);
const char *result = lua_tostring(L, -1);
if (err) {
qDebug() << result;
lua_pop(L, 1);
return "";
}
lua_pop(L, 1);
return QString(result);
} }
QString QmlBackend::getGeneralData(const QString &general_name) { void QmlBackend::pushLuaValue(lua_State *L, QVariant v) {
QVariantList list;
switch(v.type()) {
case QVariant::Bool:
lua_pushboolean(L, v.toBool());
break;
case QVariant::Int:
case QVariant::UInt:
lua_pushinteger(L, v.toInt());
break;
case QVariant::Double:
lua_pushnumber(L, v.toDouble());
break;
case QVariant::String:
lua_pushstring(L, v.toString().toUtf8().data());
break;
case QVariant::List:
lua_newtable(L);
list = v.toList();
for (int i = 1; i <= list.length(); i++) {
lua_pushinteger(L, i);
pushLuaValue(L, list[i - 1]);
lua_settable(L, -3);
}
break;
default:
qDebug() << "cannot handle QVariant type" << v.type();
lua_pushnil(L);
break;
}
}
QString QmlBackend::callLuaFunction(const QString &func_name,
QVariantList params)
{
lua_State *L = ClientInstance->getLuaState(); lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "GetGeneralData"); lua_getglobal(L, func_name.toLatin1().data());
lua_pushstring(L, general_name.toUtf8().data());
CALLFUNC foreach (QVariant v, params) {
pushLuaValue(L, v);
} }
QString QmlBackend::getCardData(int id) { int err = lua_pcall(L, params.length(), 1, 0);
lua_State *L = ClientInstance->getLuaState(); const char *result = lua_tostring(L, -1);
lua_getglobal(L, "GetCardData"); if (err) {
lua_pushinteger(L, id); qDebug() << result;
lua_pop(L, 1);
CALLFUNC return "";
} }
lua_pop(L, 1);
QString QmlBackend::getAllGeneralPack() { return QString(result);
lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "GetAllGeneralPack");
lua_pushinteger(L, 0);
CALLFUNC
} }
QString QmlBackend::getGenerals(const QString &pack_name) {
lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "GetGenerals");
lua_pushstring(L, pack_name.toUtf8().data());
CALLFUNC
}
QString QmlBackend::getAllCardPack() {
lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "GetAllCardPack");
lua_pushinteger(L, 0);
CALLFUNC
}
QString QmlBackend::getCards(const QString &pack_name) {
lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "GetCards");
lua_pushstring(L, pack_name.toUtf8().data());
CALLFUNC
}
#undef CALLFUNC

View File

@ -27,18 +27,16 @@ public:
// read data from lua, call lua functions // read data from lua, call lua functions
Q_INVOKABLE QString translate(const QString &src); Q_INVOKABLE QString translate(const QString &src);
Q_INVOKABLE QString getGeneralData(const QString &general_name); Q_INVOKABLE QString callLuaFunction(const QString &func_name,
Q_INVOKABLE QString getCardData(int id); QVariantList params);
Q_INVOKABLE QString getAllGeneralPack();
Q_INVOKABLE QString getGenerals(const QString &pack_name);
Q_INVOKABLE QString getAllCardPack();
Q_INVOKABLE QString getCards(const QString &pack_name);
signals: signals:
void notifyUI(const QString &command, const QString &jsonData); void notifyUI(const QString &command, const QString &jsonData);
private: private:
QQmlApplicationEngine *engine; QQmlApplicationEngine *engine;
void pushLuaValue(lua_State *L, QVariant v);
}; };
extern QmlBackend *Backend; extern QmlBackend *Backend;