diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 9419edf8..082239cc 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -160,7 +160,7 @@ end function GetSkillData(skill_name) local skill = Fk.skills[skill_name] local freq = "notactive" - if skill:isInstanceOf(ActiveSkill) then + if skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill) then freq = "active" end return json.encode{ @@ -173,8 +173,12 @@ end function ActiveCanUse(skill_name) local skill = Fk.skills[skill_name] local ret = false - if skill and skill:isInstanceOf(ActiveSkill) then - ret = skill:canUse(Self) + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:canUse(Self) + elseif skill:isInstanceOf(ViewAsSkill) then + ret = skill:enabledAtPlay(Self) + end end return json.encode(ret) end @@ -182,8 +186,12 @@ end function ActiveCardFilter(skill_name, to_select, selected, selected_targets) local skill = Fk.skills[skill_name] local ret = false - if skill and skill:isInstanceOf(ActiveSkill) then - ret = skill:cardFilter(to_select, selected, selected_targets) + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:cardFilter(to_select, selected, selected_targets) + elseif skill:isInstanceOf(ViewAsSkill) then + ret = skill:cardFilter(to_select, selected) + end end return json.encode(ret) end @@ -191,8 +199,15 @@ end function ActiveTargetFilter(skill_name, to_select, selected, selected_cards) local skill = Fk.skills[skill_name] local ret = false - if skill and skill:isInstanceOf(ActiveSkill) then - ret = skill:targetFilter(to_select, selected, selected_cards) + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:targetFilter(to_select, selected, selected_cards) + elseif skill:isInstanceOf(ViewAsSkill) then + local card = skill:viewAs(selected_cards) + if card then + ret = card.skill:targetFilter(to_select, selected, selected_cards) + end + end end return json.encode(ret) end @@ -200,15 +215,46 @@ end function ActiveFeasible(skill_name, selected, selected_cards) local skill = Fk.skills[skill_name] local ret = false - if skill and skill:isInstanceOf(ActiveSkill) then - ret = skill:feasible(selected, selected_cards) + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:feasible(selected, selected_cards) + elseif skill:isInstanceOf(ViewAsSkill) then + local card = skill:viewAs(selected_cards) + if card then + ret = card.skill:feasible(selected, selected_cards) + end + end end return json.encode(ret) end --- ViewAsSkill (Todo) function CanViewAs(skill_name, card_ids) - return "true" + local skill = Fk.skills[skill_name] + local ret = false + if skill then + if skill:isInstanceOf(ViewAsSkill) then + ret = skill:viewAs(card_ids) ~= nil + elseif skill:isInstanceOf(ActiveSkill) then + ret = true + end + end + return json.encode(ret) +end + +function CardFitPattern(card_name, pattern) + local exp = Exppattern:Parse(pattern) + local ret = exp:matchExp(card_name) + return json.encode(ret) +end + +function SkillFitPattern(skill_name, pattern) + local skill = Fk.skills[skill_name] + local ret = false + if skill and skill.pattern then + local exp = Exppattern:Parse(pattern) + ret = exp:matchExp(skill.pattern) + end + return json.encode(ret) end Fk:loadTranslationTable{ @@ -355,10 +401,22 @@ Fk:loadTranslationTable{ -- useCard ["#UseCard"] = "%from 使用了牌 %card", ["#UseCardToTargets"] = "%from 使用了牌 %card,目标是 %to", - ["#CardUseCollaborator"] = "%from 在此次 %card 中的子目标是 %to", + ["#CardUseCollaborator"] = "%from 在此次 %arg 中的子目标是 %to", ["#UseCardToCard"] = "%from 使用了牌 %card,目标是 %arg", ["#ResponsePlayCard"] = "%from 打出了牌 %card", + ["#UseVCard"] = "%from 将 %card 当 %arg 使用", + ["#UseVCardToTargets"] = "%from 将 %card 当 %arg 使用,目标是 %to", + ["#UseVCardToCard"] = "%from 将 %card 当 %arg2 使用,目标是 %arg", + ["#ResponsePlayVCard"] = "%from 将 %card 当 %arg 打出", + ["#UseV0Card"] = "%from 使用了 %arg", + ["#UseV0CardToTargets"] = "%from 使用了 %arg,目标是 %to", + ["#UseV0CardToCard"] = "%from 使用了 %arg2,目标是 %arg", + ["#ResponsePlayV0Card"] = "%from 打出了 %arg", + + -- skill + ["#InvokeSkill"] = "%from 发动了 “%arg”", + -- judge ["#StartJudgeReason"] = "%from 开始了 %arg 的判定", ["#InitialJudge"] = "%from 的判定牌为 %card", diff --git a/lua/core/card.lua b/lua/core/card.lua index 56052e10..f29e094e 100644 --- a/lua/core/card.lua +++ b/lua/core/card.lua @@ -9,6 +9,7 @@ ---@field type CardType ---@field sub_type CardSubtype ---@field area CardArea +---@field subcards integer[] local Card = class("Card") ---@alias Suit integer @@ -74,6 +75,74 @@ function Card:initialize(name, suit, number, color) self.type = 0 self.sub_type = Card.SubTypeNone self.skill = nil + self.subcards = {} +end + +---@param suit Suit +---@param number integer +---@return Card +function Card:clone(suit, number) + local newCard = self.class:new(self.name, suit, number) + newCard.skill = self.skill + return newCard +end + +function Card:isVirtual() + return self.id <= 0 +end + +function Card:getEffectiveId() + if self:isVirtual() then + return #self.subcards > 0 and self.subcards[1] or nil + end + return self.id +end + +local function updateColorAndNumber(card) + local color = Card.NoColor + local number = 0 + local different_color = false + for _, id in ipairs(card.subcards) do + local c = Fk:getCardById(id) + number = math.min(number + c.number, 13) + if color ~= c.color then + if not different_color then + if color ~= Card.NoColor then + different_color = true + end + color = c.color + else + color = Card.NoColor + end + end + end + + card.color = color + card.number = number +end + +---@param card integer|Card +function Card:addSubcard(card) + if type(card) == "number" then + table.insert(self.subcards, card) + else + assert(card:isInstanceOf(Card)) + assert(not card:isVirtual(), "Can not add virtual card as subcard") + table.insert(self.subcards, card.id) + end + + updateColorAndNumber(self) +end + +function Card:addSubcards(cards) + for _, c in ipairs(cards) do + self:addSubcard(c) + end +end + +function Card:clearSubcards() + self.subcards = {} + updateColorAndNumber(self) end function Card:getSuitString() @@ -91,6 +160,16 @@ function Card:getSuitString() end end +function Card:getColorString() + local color = self.color + if color == Card.Black then + return "black" + elseif color == Card.Red then + return "red" + end + return "nocolor" +end + local function getNumberStr(num) if num == 1 then return "A" @@ -107,9 +186,13 @@ end -- for sendLog function Card:toLogString() local ret = string.format('%s', Fk:translate(self.name) .. "[") - ret = ret .. Fk:translate("log_" .. self:getSuitString()) - if self.number > 0 then - ret = ret .. string.format('%s', self.color == Card.Red and "#CC3131" or "black", getNumberStr(self.number)) + if self:isVirtual() then + ret = ret .. Fk:translate(self:getColorString()) + else + ret = ret .. Fk:translate("log_" .. self:getSuitString()) + if self.number > 0 then + ret = ret .. string.format('%s', self.color == Card.Red and "#CC3131" or "black", getNumberStr(self.number)) + end end ret = ret .. ']' return ret diff --git a/lua/core/card_type/basic.lua b/lua/core/card_type/basic.lua index 330811cc..e969713f 100644 --- a/lua/core/card_type/basic.lua +++ b/lua/core/card_type/basic.lua @@ -6,13 +6,4 @@ function BasicCard:initialize(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) - newCard.skill = self.skill - return newCard -end - return BasicCard diff --git a/lua/core/card_type/equip.lua b/lua/core/card_type/equip.lua index 2b072065..37db029e 100644 --- a/lua/core/card_type/equip.lua +++ b/lua/core/card_type/equip.lua @@ -17,15 +17,6 @@ function Weapon:initialize(name, suit, number, attackRange) 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) - newCard.skill = self.skill - return newCard -end - ---@class Armor : EquipCard local Armor = EquipCard:subclass("armor") @@ -34,15 +25,6 @@ function Armor:initialize(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) - newCard.skill = self.skill - return newCard -end - ---@class DefensiveRide : EquipCard local DefensiveRide = EquipCard:subclass("DefensiveRide") @@ -51,15 +33,6 @@ function DefensiveRide:initialize(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) - newCard.skill = self.skill - return newCard -end - ---@class OffensiveRide : EquipCard local OffensiveRide = EquipCard:subclass("OffensiveRide") @@ -68,15 +41,6 @@ function OffensiveRide:initialize(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) - newCard.skill = self.skill - return newCard -end - ---@class Treasure : EquipCard local Treasure = EquipCard:subclass("Treasure") @@ -85,13 +49,4 @@ function Treasure:initialize(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) - newCard.skill = self.skill - return newCard -end - return { EquipCard, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure } diff --git a/lua/core/card_type/trick.lua b/lua/core/card_type/trick.lua index 2afc0f72..0c366e68 100644 --- a/lua/core/card_type/trick.lua +++ b/lua/core/card_type/trick.lua @@ -6,17 +6,6 @@ function TrickCard:initialize(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) - - newCard.skill = self.skill - - return newCard -end - ---@class DelayedTrickCard : TrickCard local DelayedTrickCard = TrickCard:subclass("DelayedTrickCard") @@ -25,13 +14,4 @@ function DelayedTrickCard:initialize(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) - newCard.skill = self.skill - return newCard -end - return { TrickCard, DelayedTrickCard } diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 6164939a..c2e00002 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -137,12 +137,14 @@ function Engine:addGenerals(generals) end local cardId = 1 +local _card_name_table = {} ---@param card Card function Engine:addCard(card) assert(card.class:isSubclassOf(Card)) card.id = cardId cardId = cardId + 1 table.insert(self.cards, card) + _card_name_table[card.name] = card end ---@param cards Card[] @@ -152,6 +154,16 @@ function Engine:addCards(cards) end end +---@param name string +---@param suit Suit +---@param number integer +---@return Card +function Engine:cloneCard(name, suit, number) + local cd = _card_name_table[name] + assert(cd, "Attempt to clone a card that not added to engine") + return cd:clone(suit, number) +end + ---@param num integer ---@param generalPool General[] ---@param except string[] diff --git a/lua/core/exppattern.lua b/lua/core/exppattern.lua new file mode 100644 index 00000000..533e9738 --- /dev/null +++ b/lua/core/exppattern.lua @@ -0,0 +1,246 @@ +--[[ + + Exppattern is a string that describes cards of a same 'type', e.g. name, + suit, etc. + + The string will be parsed and construct a new Exppattern instance. + Then we can use this instance to check the card. + + Syntax for the string form: + 1. the whole string can be splited by ';'. Every slice stands for a Matcher + 2. For the matcher string, it can be splited by '|'. + 3. And the arrays in class Match is concated by ',' in string. + + Example: + slash,jink|2~4|spade;.|.|.|.|.|trick + +]]-- + +---@class Matcher +---@field name string[] +---@field number integer[] +---@field suit integer[] +---@field place string[] +---@field generalName string[] +---@field cardType integer[] +---@field id integer[] + +local numbertable = { + ["A"] = 1, + ["J"] = 11, + ["Q"] = 12, + ["K"] = 13, +} + +local suittable = { + [Card.Spade] = "spade", + [Card.Club] = "club", + [Card.Heart] = "heart", + [Card.Diamond] = "diamond", +} + +local typetable = { + [Card.TypeBasic] = "basic", + [Card.TypeTrick] = "trick", + [Card.TypeEquip] = "equip", +} + +---@param matcher Matcher +---@param card Card +local function matchCard(matcher, card) + if type(card) == "number" then + card = Fk:getCardById(card) + end + + if matcher.name and not table.contains(matcher.name, card.name) then + return false + end + + if matcher.number and not table.contains(matcher.number, card.number) then + return false + end + + if matcher.suit and not table.contains(matcher.suit, card:getSuitString()) then + return false + end + + -- TODO: place + -- TODO: generalName + + if matcher.cardType and not table.contains(matcher.cardType, typetable[card.type]) then + return false + end + + if matcher.id and not table.contains(matcher.id, card.id) then + return false + end + + return true +end + +local function hasIntersection(a, b) + if a == nil or b == nil then + return true + end + + local tmp = {} + for _, e in ipairs(a) do + tmp[e] = true + end + for _, e in ipairs(b) do + if tmp[e] then + return true + end + end + return false +end + +---@param a Matcher +---@param b Matcher +local function matchMatcher(a, b) + local keys = { + "name", + "number", + "suit", + "place", + "generalName", + "cardType", + "id", + } + + for _, k in ipairs(keys) do + if not hasIntersection(a[k], b[k]) then + return false + end + end + + return true +end + +local function parseMatcher(str) + local t = str:split("|") + if #t < 7 then + for i = 1, 7 - #t do + table.insert(t, ".") + end + end + + for i, item in ipairs(t) do + t[i] = item:split(",") + end + + local ret = {} ---@type Matcher + ret.name = not table.contains(t[1], ".") and t[1] or nil + + if not table.contains(t[2], ".") then + ret.number = {} + for _, num in ipairs(t[2]) do + local n = tonumber(num) + if not n then + n = numbertable[num] + end + if n then + table.insertIfNeed(ret.number, n) + else + if string.find(n, "~") then + local start, _end = table.unpack(n:split("~")) + for i = start, _end do + table.insertIfNeed(ret.number, n) + end + end + end + end + end + + if not table.contains(t[3], ".") then + ret.suit = {} + for _, num in ipairs(t[3]) do + local n = suittable[num] + if n then + table.insertIfNeed(ret.suit, n) + end + end + end + + ret.place = not table.contains(t[4], ".") and t[4] or nil + ret.generalName = not table.contains(t[5], ".") and t[5] or nil + + if not table.contains(t[6], ".") then + ret.cardType = {} + for _, num in ipairs(t[6]) do + local n = typetable[num] + if n then + table.insertIfNeed(ret.cardType, n) + end + end + end + + if not table.contains(t[7], ".") then + ret.id = {} + for _, num in ipairs(t[6]) do + local n = tonumber(num) + if n and n > 0 then + table.insertIfNeed(ret.id, n) + end + end + end + + return ret +end + +---@class Exppattern: Object +---@field matchers Matcher[] +local Exppattern = class("Exppattern") + +function Exppattern:initialize(spec) + if not spec then + self.matchers = {} + elseif spec[1] ~= nil then + self.matchers = spec + else + self.matchers = {} + self.matchers[1] = spec + end +end + +---@param str string +function Exppattern.static:Parse(str) + local ret = Exppattern:new() + local t = str:split(";") + for i, s in ipairs(t) do + ret.matchers[i] = parseMatcher(s) + end + return ret +end + +---@param card Card +function Exppattern:match(card) + for _, matcher in ipairs(self.matchers) do + local result = matchCard(matcher, card) + if result then + return true + end + end + return false +end + +function Exppattern:matchExp(exp) + if type(exp) == "string" then + exp = Exppattern:Parse(exp) + end + + local a = self.matchers + local b = exp.matchers + + for _, m in ipairs(a) do + for _, n in ipairs(b) do + if matchMatcher(m, n) then + return true + end + end + end + + return false +end + +return Exppattern diff --git a/lua/core/skill_type/view_as.lua b/lua/core/skill_type/view_as.lua index e69de29b..bbaa3e7c 100644 --- a/lua/core/skill_type/view_as.lua +++ b/lua/core/skill_type/view_as.lua @@ -0,0 +1,35 @@ +---@class ViewAsSkill +---@field pattern string @ cards that can be viewAs'ed by this skill +local ViewAsSkill = Skill:subclass("ViewAsSkill") + +function ViewAsSkill:initialize(name) + Skill.initialize(self, name, Skill.NotFrequent) + self.pattern = "" +end + +---@param to_select integer @ id of a card not selected +---@param selected integer[] @ ids of selected cards +---@return boolean +function ViewAsSkill:cardFilter(to_select, selected) + return false +end + +---@param cards integer[] @ ids of cards +---@return card +function ViewAsSkill:viewAs(cards) + return nil +end + +-- For extra judgement, like mark or HP + +---@param player Player +function ViewAsSkill:enabledAtPlay(player) + return true +end + +---@param player Player +function ViewAsSkill:enabledAtResponse(player) + return true +end + +return ViewAsSkill diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index 9a736110..233aadd2 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -4,6 +4,7 @@ dofile "lua/server/event.lua" dofile "lua/server/system_enum.lua" TriggerSkill = require "core.skill_type.trigger" ActiveSkill = require "core.skill_type.active_skill" +ViewAsSkill = require "core.skill_type.view_as" DistanceSkill = require "core.skill_type.distance" StatusSkills = { DistanceSkill, @@ -111,6 +112,37 @@ function fk.CreateActiveSkill(spec) return skill end +---@class ViewAsSkillSpec: SkillSpec +---@field card_filter fun(self: ViewAsSkill, to_select: integer, selected: integer[]): boolean +---@field view_as fun(self: ViewAsSkill, cards: integer[]) +---@field pattern string +---@field enabled_at_play fun(self: ViewAsSkill, player: Player): boolean +---@field enabled_at_response fun(self: ViewAsSkill, player: Player): boolean + +---@param spec ViewAsSkillSpec +---@return ViewAsSkill +function fk.CreateViewAsSkill(spec) + assert(type(spec.name) == "string") + assert(type(spec.view_as) == "function") + + local skill = ViewAsSkill:new(spec.name) + skill.viewAs = spec.view_as + if spec.card_filter then + skill.cardFilter = spec.card_filter + end + if type(spec.pattern) == "string" then + skill.pattern = spec.pattern + end + if type(spec.enabled_at_play) == "function" then + skill.enabledAtPlay = spec.enabled_at_play + end + if type(spec.enabled_at_response) == "function" then + skill.enabledAtResponse = spec.enabled_at_response + end + + return skill +end + ---@class DistanceSpec: SkillSpec ---@field correct_func fun(self: DistanceSkill, from: Player, to: Player) ---@field global boolean diff --git a/lua/freekill.lua b/lua/freekill.lua index 9122bc7d..2754f077 100644 --- a/lua/freekill.lua +++ b/lua/freekill.lua @@ -20,6 +20,7 @@ Engine = require "core.engine" Package = require "core.package" General = require "core.general" Card = require "core.card" +Exppattern = require "core.exppattern" Skill = require "core.skill" Player = require "core.player" diff --git a/lua/server/room.lua b/lua/server/room.lua index bb649f9c..6fc89dcc 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -55,9 +55,15 @@ function Room:initialize(_room) end local co = coroutine.create(co_func) while not self.game_finished do - coroutine.resume(co) + local ret, err_msg = coroutine.resume(co) + + -- handle error + if ret == false then + fk.qCritical(err_msg) + print(debug.traceback(co)) + break + end end - fk.qInfo("Game Finished.") end self.players = {} @@ -426,6 +432,19 @@ function Room:sendLogEvent(type, data, players) self:doBroadcastNotify("LogEvent", json.encode(data), players) end +---@param player ServerPlayer +---@param skill_name string +---@param skill_type nil +function Room:notifySkillInvoked(player, skill_name, skill_type) + self:sendLog{ + type = "#InvokeSkill", + from = player.id, + arg = skill_name, + } + + -- TODO: notifySkill animation +end + ------------------------------------------------------------------------ -- interactive functions ------------------------------------------------------------------------ @@ -604,64 +623,101 @@ function Room:askForSkillInvoke(player, skill_name, data) end ---@param player ServerPlayer +---@param data string ---@return CardUseStruct -function Room:askForUseCard(player, card_name, prompt, cancelable, extra_data) +function Room:handleUseCardReply(player, data) + data = json.decode(data) + local card = data.card + local targets = data.targets + if type(card) == "string" then + local card_data = json.decode(card) + local skill = Fk.skills[card_data.skill] + local selected_cards = card_data.subcards + if skill:isInstanceOf(ActiveSkill) then + self:notifySkillInvoked(player, skill.name) + skill:onEffect(self, { + from = player.id, + cards = selected_cards, + tos = targets, + }) + return nil + elseif skill:isInstanceOf(ViewAsSkill) then + local c = skill:viewAs(selected_cards) + if c then + self:notifySkillInvoked(player, skill.name) + local use = {} ---@type CardUseStruct + use.from = player.id + use.tos = {} + for _, target in ipairs(targets) do + table.insert(use.tos, { target }) + end + use.card = c + return use + end + end + else + local use = {} ---@type CardUseStruct + use.from = player.id + use.tos = {} + for _, target in ipairs(targets) do + table.insert(use.tos, { target }) + end + if #use.tos == 0 then + use.tos = nil + end + use.card = Fk:getCardById(card) + return use + end +end + +-- available extra_data: +-- * must_targets: integer[] +---@param player ServerPlayer +---@param card_name string +---@param pattern string +---@param prompt string +---@return CardUseStruct +function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extra_data) local command = "AskForUseCard" self:notifyMoveFocus(player, card_name) cancelable = cancelable or false extra_data = extra_data or {} + pattern = pattern or card_name prompt = prompt or "#AskForUseCard" - local data = {card_name, prompt, cancelable, extra_data} + local data = {card_name, pattern, prompt, cancelable, extra_data} local result = self:doRequest(player, command, json.encode(data)) if result ~= "" then - data = json.decode(result) - local card = data.card - local targets = data.targets - if type(card) == "string" then - -- TODO: ViewAsSkill - return nil - else - local use = {} ---@type CardUseStruct - use.from = player.id - use.tos = {} - for _, target in ipairs(targets) do - table.insert(use.tos, { target }) - end - if #use.tos == 0 then - use.tos = nil - end - use.cardId = card - return use - end + return self:handleUseCardReply(player, result) end return nil end -function Room:askForResponse(player, card_name, prompt, cancelable, extra_data) +---@param player ServerPlayer +---@param card_name string +---@param pattern string +---@param prompt string +---@param cancelable string +function Room:askForResponse(player, card_name, pattern, prompt, cancelable, extra_data) local command = "AskForResponseCard" self:notifyMoveFocus(player, card_name) cancelable = cancelable or false extra_data = extra_data or {} + pattern = pattern or card_name prompt = prompt or "#AskForResponseCard" - local data = {card_name, prompt, cancelable, extra_data} + local data = {card_name, pattern, prompt, cancelable, extra_data} local result = self:doRequest(player, command, json.encode(data)) if result ~= "" then - data = json.decode(result) - local card = data.card - local targets = data.targets - if type(card) == "string" then - -- TODO: ViewAsSkill - return nil - else - return card + local use = self:handleUseCardReply(player, result) + if use then + return use.card end end return nil end -function Room:askForNullification(players, card_name, prompt, cancelable, extra_data) +function Room:askForNullification(players, card_name, pattern, prompt, cancelable, extra_data) if #players == 0 then return nil end @@ -671,33 +727,16 @@ function Room:askForNullification(players, card_name, prompt, cancelable, extra_ cancelable = cancelable or false extra_data = extra_data or {} prompt = prompt or "#AskForUseCard" + pattern = pattern or card_name self:notifyMoveFocus(self.alive_players, card_name) self:doBroadcastNotify("WaitForNullification", "") - local data = {card_name, prompt, cancelable, extra_data} + local data = {card_name, pattern, prompt, cancelable, extra_data} local winner = self:doRaceRequest(command, players, json.encode(data)) if winner then local result = winner.client_reply - data = json.decode(result) - local card = data.card - local targets = data.targets - if type(card) == "string" then - -- TODO: ViewAsSkill - return nil - else - local use = {} ---@type CardUseStruct - use.from = winner.id - use.tos = {} - for _, target in ipairs(targets) do - table.insert(use.tos, { target }) - end - if #use.tos == 0 then - use.tos = nil - end - use.cardId = card - return use - end + return self:handleUseCardReply(winner, result) end return nil end @@ -706,6 +745,114 @@ end -- use card logic, and wrappers ------------------------------------------------------------------------ +---@param room Room +---@param cardUseEvent CardUseStruct +local sendCardEmotionAndLog = function(room, cardUseEvent) + local from = cardUseEvent.from + local card = cardUseEvent.card + room:setEmotion(room:getPlayerById(from), card.name) + room:doAnimate("Indicate", { + from = from, + to = cardUseEvent.tos or {}, + }) + + local useCardIds = card:isVirtual() and card.subcards or { card.id } + if cardUseEvent.tos and #cardUseEvent.tos > 0 then + local to = {} + for _, t in ipairs(cardUseEvent.tos) do + table.insert(to, t[1]) + end + + if card:isVirtual() then + if #useCardIds == 0 then + room:sendLog{ + type = "#UseV0CardToTargets", + from = from, + to = to, + arg = card:toLogString(), + } + else + room:sendLog{ + type = "#UseVCardToTargets", + from = from, + to = to, + card = useCardIds, + arg = card:toLogString(), + } + end + else + room:sendLog{ + type = "#UseCardToTargets", + from = from, + to = to, + card = useCardIds + } + end + + for _, t in ipairs(cardUseEvent.tos) do + if t[2] then + local temp = {table.unpack(t)} + table.remove(temp, 1) + room:sendLog{ + type = "#CardUseCollaborator", + from = t[1], + to = temp, + arg = card.name, + } + end + end + elseif cardUseEvent.toCard then + if card:isVirtual() then + if #useCardIds == 0 then + room:sendLog{ + type = "#UseV0CardToCard", + from = from, + arg = cardUseEvent.toCard.name, + arg2 = card:toLogString(), + } + else + room:sendLog{ + type = "#UseVCardToCard", + from = from, + card = useCardIds, + arg = cardUseEvent.toCard.name, + arg2 = card:toLogString(), + } + end + else + room:sendLog{ + type = "#UseCardToCard", + from = from, + card = useCardIds, + arg = cardUseEvent.toCard.name, + } + end + else + if card:isVirtual() then + if #useCardIds == 0 then + room:sendLog{ + type = "#UseV0Card", + from = from, + arg = card:toLogString(), + } + else + room:sendLog{ + type = "#UseVCard", + from = from, + card = useCardIds, + arg = card:toLogString(), + } + end + else + room:sendLog{ + type = "#UseCard", + from = from, + card = useCardIds, + } + end + end +end + ---@param room Room ---@param cardUseEvent CardUseStruct ---@param aimEventCollaborators table @@ -732,7 +879,7 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators) if not aimEventCollaborators[toId] or collaboratorsIndex[toId] > #aimEventCollaborators[toId] then aimStruct = { from = cardUseEvent.from, - cardId = cardUseEvent.cardId, + card = cardUseEvent.card, to = toId, targetGroup = cardUseEvent.tos, nullifiedTargets = cardUseEvent.nullifiedTargets or {}, @@ -760,7 +907,7 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators) else aimStruct = aimEventCollaborators[toId][collaboratorsIndex[toId]] aimStruct.from = cardUseEvent.from - aimStruct.cardId = cardUseEvent.cardId + aimStruct.card = cardUseEvent.card aimStruct.tos = aimGroup aimStruct.targetGroup = cardUseEvent.tos aimStruct.nullifiedTargets = cardUseEvent.nullifiedTargets or {} @@ -820,74 +967,33 @@ end function Room:useCard(cardUseEvent) local from = cardUseEvent.from self:moveCards({ - ids = { cardUseEvent.cardId }, + ids = self:getSubcardsByRule(cardUseEvent.card), from = from, toArea = Card.Processing, moveReason = fk.ReasonUse, }) - if Fk:getCardById(cardUseEvent.cardId).skill then - Fk:getCardById(cardUseEvent.cardId).skill:onUse(self, cardUseEvent) + if cardUseEvent.card.skill then + cardUseEvent.card.skill:onUse(self, cardUseEvent) end - self:setEmotion(self:getPlayerById(from), Fk:getCardById(cardUseEvent.cardId).name) - self:doAnimate("Indicate", { - from = from, - to = cardUseEvent.tos or {}, - }) - if cardUseEvent.tos and #cardUseEvent.tos > 0 then - local to = {} - for _, t in ipairs(cardUseEvent.tos) do - table.insert(to, t[1]) - end - self:sendLog{ - type = "#UseCardToTargets", - from = from, - to = to, - card = {cardUseEvent.cardId}, - } - for _, t in ipairs(cardUseEvent.tos) do - if t[2] then - local temp = {table.unpack(t)} - table.remove(temp, 1) - self:sendLog{ - type = "#CardUseCollaborator", - from = t[1], - to = temp, - card = {cardUseEvent.cardId}, - } - end - end - elseif cardUseEvent.toCardId then - self:sendLog{ - type = "#UseCardToCard", - from = from, - card = {cardUseEvent.cardId}, - arg = Fk:getCardById(cardUseEvent.toCardId).name, - } - else - self:sendLog{ - type = "#UseCard", - from = from, - card = {cardUseEvent.cardId}, - } - end + sendCardEmotionAndLog(self, cardUseEvent) 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) + self:getPlayerById(cardUseEvent.from):addCardUseHistory(cardUseEvent.card.trueName, 1) end if cardUseEvent.responseToEvent then - cardUseEvent.responseToEvent.cardIdsResponded = cardUseEvent.responseToEvent.cardIdsResponded or {} - table.insert(cardUseEvent.responseToEvent.cardIdsResponded, cardUseEvent.cardId) + cardUseEvent.responseToEvent.cardsResponded = cardUseEvent.responseToEvent.cardsResponded or {} + table.insert(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card) end for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.BeforeCardUseEffect, fk.CardUsing }) do - if not cardUseEvent.toCardId and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then + if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then break end @@ -899,20 +1005,21 @@ function Room:useCard(cardUseEvent) break end - if Fk:getCardById(cardUseEvent.cardId).type == Card.TypeEquip then - if self:getCardArea(cardUseEvent.cardId) ~= Card.Processing then + local realCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing }) + if cardUseEvent.card.type == Card.TypeEquip then + if #realCardIds == 0 then break end if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then self.moveCards({ - ids = { cardUseEvent.cardId }, + ids = realCardIds, 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) + local existingEquipId = self:getPlayerById(target):getEquipment(cardUseEvent.card.sub_type) if existingEquipId then self:moveCards( { @@ -922,7 +1029,7 @@ function Room:useCard(cardUseEvent) moveReason = fk.ReasonPutIntoDiscardPile, }, { - ids = { cardUseEvent.cardId }, + ids = realCardIds, to = target, toArea = Card.PlayerEquip, moveReason = fk.ReasonUse, @@ -930,7 +1037,7 @@ function Room:useCard(cardUseEvent) ) else self:moveCards({ - ids = { cardUseEvent.cardId }, + ids = realCardIds, to = target, toArea = Card.PlayerEquip, moveReason = fk.ReasonUse, @@ -939,8 +1046,8 @@ function Room:useCard(cardUseEvent) end break - elseif Fk:getCardById(cardUseEvent.cardId).sub_type == Card.SubtypeDelayedTrick then - if self:getCardArea(cardUseEvent.cardId) ~= Card.Processing then + elseif cardUseEvent.card.sub_type == Card.SubtypeDelayedTrick then + if #realCardIds == 0 then break end @@ -948,14 +1055,14 @@ function Room:useCard(cardUseEvent) if not self:getPlayerById(target).dead then local findSameCard = false for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do - if Fk:getCardById(cardId).trueName == Fk:getCardById(cardUseEvent.cardId) then + if Fk:getCardById(cardId).trueName == cardUseEvent.card.trueName then findSameCard = true end end if not findSameCard then self:moveCards({ - ids = { cardUseEvent.cardId }, + ids = realCardIds, to = target, toArea = Card.PlayerJudge, moveReason = fk.ReasonUse, @@ -966,7 +1073,7 @@ function Room:useCard(cardUseEvent) end self:moveCards({ - ids = { cardUseEvent.cardId }, + ids = realCardIds, toArea = Card.DiscardPile, moveReason = fk.ReasonPutIntoDiscardPile, }) @@ -974,13 +1081,13 @@ function Room:useCard(cardUseEvent) break end - if Fk:getCardById(cardUseEvent.cardId).skill then + if cardUseEvent.card.skill then ---@type CardEffectEvent local cardEffectEvent = { from = cardUseEvent.from, tos = cardUseEvent.tos, - cardId = cardUseEvent.cardId, - toCardId = cardUseEvent.toCardId, + card = cardUseEvent.card, + toCard = cardUseEvent.toCard, responseToEvent = cardUseEvent.responseToEvent, nullifiedTargets = cardUseEvent.nullifiedTargets, disresponsiveList = cardUseEvent.disresponsiveList, @@ -989,7 +1096,7 @@ function Room:useCard(cardUseEvent) cardIdsResponded = cardUseEvent.nullifiedTargets, } - if cardUseEvent.toCardId ~= nil then + if cardUseEvent.toCard ~= nil then self:doCardEffect(cardEffectEvent) else local collaboratorsIndex = {} @@ -1034,9 +1141,11 @@ function Room:useCard(cardUseEvent) end self.logic:trigger(fk.CardUseFinished, self:getPlayerById(cardUseEvent.from), cardUseEvent) - if self:getCardArea(cardUseEvent.cardId) == Card.Processing then + + local leftRealCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing }) + if #leftRealCardIds > 0 then self:moveCards({ - ids = { cardUseEvent.cardId }, + ids = leftRealCardIds, toArea = Card.DiscardPile, moveReason = fk.ReasonPutIntoDiscardPile, }) @@ -1053,7 +1162,7 @@ function Room:doCardEffect(cardEffectEvent) break end - if not cardEffectEvent.toCardId and (not (self:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to) or #self:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0) then + if not cardEffectEvent.toCard and (not (self:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to) or #self:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0) then break end @@ -1066,7 +1175,7 @@ function Room:doCardEffect(cardEffectEvent) end if event == fk.PreCardEffect then - if Fk:getCardById(cardEffectEvent.cardId).name == 'slash' and + if cardEffectEvent.card.name == 'slash' and not ( cardEffectEvent.disresponsive or cardEffectEvent.unoffsetable or @@ -1076,11 +1185,11 @@ function Room:doCardEffect(cardEffectEvent) local to = self:getPlayerById(cardEffectEvent.to) local use = self:askForUseCard(to, "jink") if use then - use.toCardId = cardEffectEvent.cardId + use.toCard = cardEffectEvent.card use.responseToEvent = cardEffectEvent self:useCard(use) end - elseif Fk:getCardById(cardEffectEvent.cardId).type == Card.TypeTrick then + elseif cardEffectEvent.card.type == Card.TypeTrick then local players = {} for _, p in ipairs(self.alive_players) do local cards = p.player_cards[Player.Hand] @@ -1094,7 +1203,7 @@ function Room:doCardEffect(cardEffectEvent) local use = self:askForNullification(players) if use then - use.toCardId = cardEffectEvent.cardId + use.toCard = cardEffectEvent.card use.responseToEvent = cardEffectEvent self:useCard(use) end @@ -1102,9 +1211,8 @@ function Room:doCardEffect(cardEffectEvent) end if event == fk.CardEffecting then - local cardEffecting = Fk:getCardById(cardEffectEvent.cardId) - if cardEffecting.skill then - cardEffecting.skill:onEffect(self, cardEffectEvent) + if cardEffectEvent.card.skill then + cardEffectEvent.card.skill:onEffect(self, cardEffectEvent) end end end @@ -1113,22 +1221,48 @@ end ---@param cardResponseEvent CardResponseEvent function Room:responseCard(cardResponseEvent) local from = cardResponseEvent.customFrom or cardResponseEvent.from + local card = cardResponseEvent.card + local cardIds = self:getSubcardsByRule(card) + + if card:isVirtual() then + if #cardIds == 0 then + self:sendLog{ + type = "#ResponsePlayV0Card", + from = from, + arg = card:toLogString(), + } + else + self:sendLog{ + type = "#ResponsePlayVCard", + from = from, + card = cardIds, + arg = card:toLogString(), + } + end + else + self:sendLog{ + type = "#ResponsePlayCard", + from = from, + card = cardIds, + } + end self:moveCards({ - ids = { cardResponseEvent.cardId }, + ids = cardIds, from = from, toArea = Card.Processing, moveReason = fk.ReasonResonpse, }) - self:setEmotion(self:getPlayerById(from), Fk:getCardById(cardResponseEvent.cardId).name) + self:setEmotion(self:getPlayerById(from), card.name) for _, event in ipairs({ fk.PreCardRespond, fk.CardResponding, fk.CardRespondFinished }) do self.logic:trigger(event, self:getPlayerById(cardResponseEvent.from), cardResponseEvent) end - if self:getCardArea(cardResponseEvent.cardId) == Card.Processing or cardResponseEvent.skipDrop then + local realCardIds = self:getSubcardsByRule(cardResponseEvent.card, { Card.Processing }) + if #realCardIds > 0 and not cardResponseEvent.skipDrop then self:moveCards({ - ids = { cardResponseEvent.cardId }, + ids = realCardIds, toArea = Card.DiscardPile, moveReason = fk.ReasonPutIntoDiscardPile, }) @@ -1780,6 +1914,25 @@ function Room:gameOver(winner) coroutine.yield() end +---@param card Card +---@param fromAreas CardArea[]|null +---@return integer[] +function Room:getSubcardsByRule(card, fromAreas) + if card:isVirtual() and #card.subcards == 0 then + return {} + end + + local cardIds = {} + fromAreas = fromAreas or {} + for _, cardId in ipairs(card:isVirtual() and card.subcards or { card.id }) do + if #fromAreas == 0 or table.contains(fromAreas, self:getCardArea(cardId)) then + table.insert(cardIds, cardId) + end + end + + return cardIds +end + function CreateRoom(_room) RoomInstance = Room:new(_room) end diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua index cd0a06c9..0a95196c 100644 --- a/lua/server/system_enum.lua +++ b/lua/server/system_enum.lua @@ -10,13 +10,13 @@ ---@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, subTargets: integer[]|null, targetGroup: TargetGroup|null, nullifiedTargets: integer[]|null, firstTarget: boolean, additionalDamage: integer|null, disresponsive: boolean|null, unoffsetableList: boolean|null } ----@alias CardEffectEvent { from: integer, to: integer, subTargets: integer[]|null, 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, disresponsive: boolean|null, unoffsetable: boolean|null } +---@alias CardUseStruct { from: integer, tos: TargetGroup, card: Card, toCard: Card|null, responseToEvent: CardUseStruct|null, nullifiedTargets: interger[]|null, extraUse: boolean|null, disresponsiveList: integer[]|null, unoffsetableList: integer[]|null, addtionalDamage: integer|null, customFrom: integer|null, cardsResponded: Card[]|null } +---@alias AimStruct { from: integer, card: Card, tos: AimGroup, to: integer, subTargets: integer[]|null, targetGroup: TargetGroup|null, nullifiedTargets: integer[]|null, firstTarget: boolean, additionalDamage: integer|null, disresponsive: boolean|null, unoffsetableList: boolean|null } +---@alias CardEffectEvent { from: integer, to: integer, subTargets: integer[]|null, tos: TargetGroup, card: Card, toCard: Card|null, responseToEvent: CardUseStruct|null, nullifiedTargets: interger[]|null, extraUse: boolean|null, disresponsiveList: integer[]|null, unoffsetableList: integer[]|null, addtionalDamage: integer|null, customFrom: integer|null, cardsResponded: Card[]|null, disresponsive: boolean|null, unoffsetable: boolean|null } ---@alias SkillEffectEvent { from: integer, tos: integer[], cards: integer[] } ---@alias JudgeStruct { who: ServerPlayer, card: Card, reason: string } ----@alias CardResponseEvent { from: integer, cardId: integer, responseToEvent: CardEffectEvent|null, skipDrop: boolean|null, customFrom: integer|null } +---@alias CardResponseEvent { from: integer, card: Card, responseToEvent: CardEffectEvent|null, skipDrop: boolean|null, customFrom: integer|null } ---@alias CardMoveReason integer diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index 49bee7fc..9d26a5af 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -125,7 +125,7 @@ GameRule = fk.CreateTriggerSkill{ ---@type CardEffectEvent local effect_data = { - cardId = cards[i], + card = card, to = player.id, tos = { {player.id} }, } @@ -144,26 +144,8 @@ GameRule = fk.CreateTriggerSkill{ local result = room:doRequest(player, "PlayCard", player.id) if result == "" then break end - local data = json.decode(result) - local card = data.card - local targets = data.targets - if type(card) == "string" then - local card_data = json.decode(card) - local skill = Fk.skills[card_data.skill] - local selected_cards = card_data.subcards - skill:onEffect(room, { - from = player.id, - cards = selected_cards, - tos = targets, - }) - else - local use = {} ---@type CardUseStruct - use.from = player.id - use.tos = {} - for _, target in ipairs(targets) do - table.insert(use.tos, { target }) - end - use.cardId = card + local use = room:handleUseCardReply(player, result) + if use then room:useCard(use) end end diff --git a/packages/standard/init.lua b/packages/standard/init.lua index 3de6a6a2..9df6a710 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -11,6 +11,12 @@ Fk:loadTranslationTable{ ["qun"] = "群", } +Fk:loadTranslationTable{ + ["black"] = "黑色", + ["red"] = '红色', + ["nocolor"] = '无色', +} + local caocao = General:new(extension, "caocao", "wei", 4) Fk:loadTranslationTable{ ["caocao"] = "曹操", @@ -74,9 +80,27 @@ Fk:loadTranslationTable{ ["liubei"] = "刘备", } +local wusheng = fk.CreateViewAsSkill{ + name = "wusheng", + pattern = "slash", + card_filter = function(self, to_select, selected) + if #selected == 1 then return false end + return Fk:getCardById(to_select).color == Card.Red + end, + view_as = function(self, cards) + if #cards ~= 1 then + return nil + end + local c = Fk:cloneCard("slash") + c:addSubcard(cards[1]) + return c + end, +} local guanyu = General:new(extension, "guanyu", "shu", 4) +guanyu:addSkill(wusheng) Fk:loadTranslationTable{ ["guanyu"] = "关羽", + ["wusheng"] = "武圣", } local zhangfei = General:new(extension, "zhangfei", "shu", 4) diff --git a/packages/standard_cards/init.lua b/packages/standard_cards/init.lua index 341ebf9d..ee45383a 100644 --- a/packages/standard_cards/init.lua +++ b/packages/standard_cards/init.lua @@ -195,6 +195,7 @@ local dismantlement = fk.CreateTrickCard{ } Fk:loadTranslationTable{ ["dismantlement"] = "过河拆桥", + ["dismantlement_skill"] = "过河拆桥", } extension:addCards({ @@ -241,6 +242,7 @@ local snatch = fk.CreateTrickCard{ } Fk:loadTranslationTable{ ["snatch"] = "顺手牵羊", + ["snatch_skill"] = "顺手牵羊", } extension:addCards({ @@ -275,11 +277,11 @@ local duelSkill = fk.CreateActiveSkill{ break end - local cardIdResponded = room:askForResponse(currentResponser, 'slash') - if cardIdResponded then + local cardResponded = room:askForResponse(currentResponser, 'slash') + if cardResponded then room:responseCard({ from = currentResponser.id, - cardId = cardIdResponded, + card = cardResponded, responseToEvent = effect, }) else @@ -336,17 +338,13 @@ local collateralSkill = fk.CreateActiveSkill{ cardUseEvent.tos = { { cardUseEvent.tos[1][1], cardUseEvent.tos[2][1] } } end, on_effect = function(self, room, effect) - local cardIdResponded = nil - if not (effect.disresponsive or table.contains(effect.disresponsiveList or {}, effect.to)) then - cardIdResponded = room:askForResponse(room:getPlayerById(effect.to), 'slash') - end + local use = room:askForUseCard( + room:getPlayerById(effect.to), + "slash", nil, nil, nil, { must_targets = effect.subTargets } + ) - if cardIdResponded then - room:useCard({ - from = effect.to, - tos = { { effect.subTargets[1] } }, - cardId = cardIdResponded, - }) + if use then + room:useCard(use) else room:obtainCard(effect.from, room:getPlayerById(effect.to):getEquipment(Card.SubtypeWeapon), true, fk.ReasonGive) end @@ -436,15 +434,15 @@ local savageAssaultSkill = fk.CreateActiveSkill{ end end, on_effect = function(self, room, effect) - local cardIdResponded = nil + local cardResponded = nil if not (effect.disresponsive or table.contains(effect.disresponsiveList or {}, effect.to)) then - cardIdResponded = room:askForResponse(room:getPlayerById(effect.to), 'slash') + cardResponded = room:askForResponse(room:getPlayerById(effect.to), 'slash') end - if cardIdResponded then + if cardResponded then room:responseCard({ from = effect.to, - cardId = cardIdResponded, + card = cardResponded, responseToEvent = effect, }) else @@ -485,15 +483,15 @@ local archeryAttackSkill = fk.CreateActiveSkill{ end end, on_effect = function(self, room, effect) - local cardIdResponded = nil + local cardResponded = nil if not (effect.disresponsive or table.contains(effect.disresponsiveList or {}, effect.to)) then - cardIdResponded = room:askForResponse(room:getPlayerById(effect.to), 'jink') + cardResponded = room:askForResponse(room:getPlayerById(effect.to), 'jink') end - if cardIdResponded then + if cardResponded then room:responseCard({ from = effect.to, - cardId = cardIdResponded, + card = cardResponded, responseToEvent = effect, }) else @@ -628,7 +626,7 @@ local lightningSkill = fk.CreateActiveSkill{ local to = room:getPlayerById(effect.to) local nextp = to:getNextAlive() room:moveCards{ - ids = { effect.cardId }, + ids = room:getSubcardsByRule(effect.card, { Card.Processing }), to = nextp.id, toArea = Card.PlayerJudge, moveReason = fk.ReasonPut @@ -686,7 +684,7 @@ local indulgenceSkill = fk.CreateActiveSkill{ end, on_nullified = function(self, room, effect) room:moveCards{ - ids = { effect.cardId }, + ids = room:getSubcardsByRule(effect.card, { Card.Processing }), toArea = Card.DiscardPile, moveReason = fk.ReasonPutIntoDiscardPile } diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml index a8bae4ce..57cd5d88 100644 --- a/qml/Pages/Room.qml +++ b/qml/Pages/Room.qml @@ -26,6 +26,7 @@ Item { property var selected_targets: [] property string responding_card property bool respond_play: false + property var extra_data: ({}) Image { source: AppPath + "/image/gamebg" diff --git a/qml/Pages/RoomElement/Dashboard.qml b/qml/Pages/RoomElement/Dashboard.qml index 6f334875..62958d99 100644 --- a/qml/Pages/RoomElement/Dashboard.qml +++ b/qml/Pages/RoomElement/Dashboard.qml @@ -13,7 +13,6 @@ RowLayout { property bool selected: selfPhoto.selected - property bool is_pending: false property string pending_skill: "" property var pending_card property var pendings: [] // int[], store cid @@ -116,7 +115,7 @@ RowLayout { if (cname) { let ids = [], cards = handcardAreaItem.cards; for (let i = 0; i < cards.length; i++) { - if (cards[i].name === cname) + if (JSON.parse(Backend.callLuaFunction("CardFitPattern", [cards[i].name, cname]))) ids.push(cards[i].cid); } handcardAreaItem.enableCards(ids); @@ -242,7 +241,10 @@ RowLayout { function enableSkills(cname) { if (cname) { - // TODO: vs skill + for (let i = 0; i < skillButtons.count; i++) { + let item = skillButtons.itemAt(i); + item.enabled = JSON.parse(Backend.callLuaFunction("SkillFitPattern", [item.orig, cname])); + } return; } for (let i = 0; i < skillButtons.count; i++) { diff --git a/qml/Pages/RoomLogic.js b/qml/Pages/RoomLogic.js index a1c8f3e4..f346f596 100644 --- a/qml/Pages/RoomLogic.js +++ b/qml/Pages/RoomLogic.js @@ -298,6 +298,14 @@ function enableTargets(card) { // card: int | { skill: string, subcards: int[] } okButton.enabled = JSON.parse(Backend.callLuaFunction( "CardFeasible", [card, selected_targets] )); + if (okButton.enabled) { + if (roomScene.extra_data instanceof Object) { + let must = roomScene.extra_data.must_targets; + if (must instanceof Array) { + okButton.enabled = (must.length === 0); + } + } + } } else { all_photos.forEach(photo => { photo.state = "normal"; @@ -337,6 +345,16 @@ function updateSelectedTargets(playerid, selected) { okButton.enabled = JSON.parse(Backend.callLuaFunction( "CardFeasible", [card, selected_targets] )); + if (okButton.enabled) { + if (roomScene.extra_data instanceof Object) { + let must = roomScene.extra_data.must_targets; + if (must instanceof Array) { + okButton.enabled = (must.filter((val) => { + return selected_targets.indexOf(val) === -1; + }).length === 0); + } + } + } } else { all_photos.forEach(photo => { photo.state = "normal"; @@ -628,14 +646,19 @@ callbacks["GameLog"] = function(jsonData) { } callbacks["AskForUseCard"] = function(jsonData) { - // jsonData: card, prompt, cancelable, {} + // jsonData: card, pattern, prompt, cancelable, {} let data = JSON.parse(jsonData); let cardname = data[0]; - let prompt = data[1]; + let pattern = data[1]; + let prompt = data[2]; + let extra_data = data[4]; + if (extra_data != null) { + roomScene.extra_data = extra_data; + } roomScene.promptText = Backend.translate(prompt) .arg(Backend.translate(cardname)); - roomScene.responding_card = cardname; + roomScene.responding_card = pattern; roomScene.respond_play = false; roomScene.state = "responding"; okButton.enabled = false; @@ -643,14 +666,15 @@ callbacks["AskForUseCard"] = function(jsonData) { } callbacks["AskForResponseCard"] = function(jsonData) { - // jsonData: card, prompt, cancelable, {} + // jsonData: card_name, pattern, prompt, cancelable, {} let data = JSON.parse(jsonData); let cardname = data[0]; - let prompt = data[1]; + let pattern = data[1]; + let prompt = data[2]; roomScene.promptText = Backend.translate(prompt) .arg(Backend.translate(cardname)); - roomScene.responding_card = cardname; + roomScene.responding_card = pattern; roomScene.respond_play = true; roomScene.state = "responding"; okButton.enabled = false;