Vs skill (#44)

* vs skill concept

* unify card:clone

* virtual card

* wusheng

* exppattern

* use virtual card(WIP)

* change cardId to card

* virtual card log

* notify skill invoked

* fix coroutine bug; allow vsskill to response

* extra_data for askForUseCard

Co-authored-by: Ho-spair <linyuy@163.com>
This commit is contained in:
notify 2023-01-16 19:13:07 +08:00 committed by GitHub
parent 88d40018db
commit b6530eae9d
18 changed files with 857 additions and 280 deletions

View File

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

View File

@ -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('<font color="#0598BC"><b>%s</b></font>', Fk:translate(self.name) .. "[")
ret = ret .. Fk:translate("log_" .. self:getSuitString())
if self.number > 0 then
ret = ret .. string.format('<font color="%s"><b>%s</b></font>', 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('<font color="%s"><b>%s</b></font>', self.color == Card.Red and "#CC3131" or "black", getNumberStr(self.number))
end
end
ret = ret .. '<font color="#0598BC"><b>]</b></font>'
return ret

View File

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

View File

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

View File

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

View File

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

246
lua/core/exppattern.lua Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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<string, AimStruct[]>
@ -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

View File

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

View File

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

View File

@ -11,6 +11,12 @@ Fk:loadTranslationTable{
["qun"] = "",
}
Fk:loadTranslationTable{
["black"] = "黑色",
["red"] = '<font color="#CC3131">红色</font>',
["nocolor"] = '<font color="grey">无色</font>',
}
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)

View File

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

View File

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

View File

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

View File

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