禅与bugfix (#312)

- 将Utility如canUseCardTo的一些函数搬运到了本体
- 为技能添加hooked_piles属性,当失去技能时自动弃置hooked_piles内的所有私人牌堆
- 修复了添加技能没写source_skill的bug
- 修复了ActiveSkill的interaction不传入Skill本身而是metatable的bug
- 修复了主动询问canUse时没有传入extra_data的bug
- 修复了多选时按钮选项变回空白的bug
- 修复了判定阶段被中途拿走判定牌后报错的bug
This commit is contained in:
YoumuKon 2024-02-04 15:55:44 +08:00 committed by GitHub
parent ea65a3dd4b
commit d4bb4e21bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 257 additions and 35 deletions

View File

@ -39,6 +39,7 @@ GraphicsBox {
id: choicetitle id: choicetitle
width: parent.width width: parent.width
text: luatr(modelData) text: luatr(modelData)
triggered: root.result.includes(index)
enabled: options.indexOf(modelData) !== -1 enabled: options.indexOf(modelData) !== -1
&& (root.result.length < max_num || triggered) && (root.result.length < max_num || triggered)
textFont.pixelSize: 24 textFont.pixelSize: 24

View File

@ -24,6 +24,7 @@
---@field public special_skills? string[] @ 衍生技能,如重铸 ---@field public special_skills? string[] @ 衍生技能,如重铸
---@field public is_damage_card boolean @ 是否为会造成伤害的牌 ---@field public is_damage_card boolean @ 是否为会造成伤害的牌
---@field public multiple_targets boolean @ 是否为指定多个目标的牌 ---@field public multiple_targets boolean @ 是否为指定多个目标的牌
---@field public is_passive? boolean @ 是否只能在响应时使用或打出
---@field public is_derived? boolean @ 判断是否为衍生牌 ---@field public is_derived? boolean @ 判断是否为衍生牌
local Card = class("Card") local Card = class("Card")
@ -145,11 +146,12 @@ function Card:clone(suit, number)
newCard.special_skills = self.special_skills newCard.special_skills = self.special_skills
newCard.is_damage_card = self.is_damage_card newCard.is_damage_card = self.is_damage_card
newCard.multiple_targets = self.multiple_targets newCard.multiple_targets = self.multiple_targets
newCard.is_passive = self.is_passive
newCard.is_derived = self.is_derived newCard.is_derived = self.is_derived
return newCard return newCard
end end
--- 检测是否为虚拟卡牌如果其ID为0及以下,则为虚拟卡牌。 --- 检测是否为虚拟卡牌如果其ID为0,则为虚拟卡牌。
function Card:isVirtual() function Card:isVirtual()
return self.id == 0 return self.id == 0
end end

View File

@ -872,9 +872,20 @@ end
--- 确认玩家是否可以使用特定牌。 --- 确认玩家是否可以使用特定牌。
---@param card Card @ 特定牌 ---@param card Card @ 特定牌
function Player:canUse(card) ---@param extra_data? UseExtraData @ 额外数据
assert(card, "Error: No Card") function Player:canUse(card, extra_data)
return card.skill:canUse(self, card) return card.skill:canUse(self, card, extra_data)
end
--- 确认玩家是否可以对特定玩家使用特定牌。
---@param card Card @ 特定牌
---@param to Player @ 特定玩家
---@param extra_data? UseExtraData @ 额外数据
function Player:canUseTo(card, to, extra_data)
if self:prohibitUse(card) or self:isProhibited(to, card) then return false end
local distance_limited = not (extra_data and extra_data.bypass_distances)
local can_use = self:canUse(card, extra_data)
return can_use and card.skill:modTargetFilter(to.id, {}, self.id, card, distance_limited)
end end
--- 确认玩家是否被禁止对特定玩家使用特定牌。 --- 确认玩家是否被禁止对特定玩家使用特定牌。

View File

@ -4,6 +4,7 @@
---@field public main_skill UsableSkill ---@field public main_skill UsableSkill
---@field public max_use_time integer[] ---@field public max_use_time integer[]
---@field public expand_pile? string | integer[] | fun(self: UsableSkill): integer[]|string? ---@field public expand_pile? string | integer[] | fun(self: UsableSkill): integer[]|string?
---@field public derived_piles? string | string[]
local UsableSkill = Skill:subclass("UsableSkill") local UsableSkill = Skill:subclass("UsableSkill")
function UsableSkill:initialize(name, frequency) function UsableSkill:initialize(name, frequency)

View File

@ -48,6 +48,11 @@ end
local function readUsableSpecToSkill(skill, spec) local function readUsableSpecToSkill(skill, spec)
readCommonSpecToSkill(skill, spec) readCommonSpecToSkill(skill, spec)
assert(spec.main_skill == nil or spec.main_skill:isInstanceOf(UsableSkill)) assert(spec.main_skill == nil or spec.main_skill:isInstanceOf(UsableSkill))
if type(spec.derived_piles) == "string" then
skill.derived_piles = {spec.derived_piles}
else
skill.derived_piles = spec.derived_piles or {}
end
skill.main_skill = spec.main_skill skill.main_skill = spec.main_skill
skill.target_num = spec.target_num or skill.target_num skill.target_num = spec.target_num or skill.target_num
skill.min_target_num = spec.min_target_num or skill.min_target_num skill.min_target_num = spec.min_target_num or skill.min_target_num
@ -191,8 +196,8 @@ function fk.CreateActiveSkill(spec)
readUsableSpecToSkill(skill, spec) readUsableSpecToSkill(skill, spec)
if spec.can_use then if spec.can_use then
skill.canUse = function(curSkill, player, card) skill.canUse = function(curSkill, player, card, extra_data)
return spec.can_use(curSkill, player, card) and curSkill:isEffectable(player) return spec.can_use(curSkill, player, card, extra_data) and curSkill:isEffectable(player)
end end
end end
if spec.card_filter then skill.cardFilter = spec.card_filter end if spec.card_filter then skill.cardFilter = spec.card_filter end
@ -211,9 +216,9 @@ function fk.CreateActiveSkill(spec)
if spec.interaction then if spec.interaction then
skill.interaction = setmetatable({}, { skill.interaction = setmetatable({}, {
__call = function(self) __call = function()
if type(spec.interaction) == "function" then if type(spec.interaction) == "function" then
return spec.interaction(self) return spec.interaction(skill)
else else
return spec.interaction return spec.interaction
end end
@ -445,6 +450,7 @@ end
---@field public special_skills? string[] ---@field public special_skills? string[]
---@field public is_damage_card? boolean ---@field public is_damage_card? boolean
---@field public multiple_targets? boolean ---@field public multiple_targets? boolean
---@field public is_passive? boolean
local defaultCardSkill = fk.CreateActiveSkill{ local defaultCardSkill = fk.CreateActiveSkill{
name = "default_card_skill", name = "default_card_skill",
@ -487,6 +493,7 @@ local function readCardSpecToCard(card, spec)
card.special_skills = spec.special_skills card.special_skills = spec.special_skills
card.is_damage_card = spec.is_damage_card card.is_damage_card = spec.is_damage_card
card.multiple_targets = spec.multiple_targets card.multiple_targets = spec.multiple_targets
card.is_passive = spec.is_passive
end end
---@param spec CardSpec ---@param spec CardSpec

View File

@ -268,7 +268,7 @@ GameEvent.functions[GameEvent.Phase] = function(self)
local room = self.room local room = self.room
local logic = room.logic local logic = room.logic
local player = self.data[1] local player = self.data[1] ---@type Player
if not logic:trigger(fk.EventPhaseStart, player) then if not logic:trigger(fk.EventPhaseStart, player) then
if player.phase ~= Player.NotActive then if player.phase ~= Player.NotActive then
logic:trigger(fk.EventPhaseProceeding, player) logic:trigger(fk.EventPhaseProceeding, player)
@ -285,12 +285,14 @@ GameEvent.functions[GameEvent.Phase] = function(self)
end, end,
[Player.Judge] = function() [Player.Judge] = function()
local cards = player:getCardIds(Player.Judge) local cards = player:getCardIds(Player.Judge)
for i = #cards, 1, -1 do while #cards > 0 do
local card local cid = table.remove(cards)
card = player:removeVirtualEquip(cards[i]) if not cid then return end
local card = player:removeVirtualEquip(cid)
if not card then if not card then
card = Fk:getCardById(cards[i]) card = Fk:getCardById(cid)
end end
if table.contains(player:getCardIds(Player.Judge), cid) then
room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name) room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name)
---@type CardEffectEvent ---@type CardEffectEvent
@ -304,6 +306,7 @@ GameEvent.functions[GameEvent.Phase] = function(self)
card.skill:onNullified(room, effect_data) card.skill:onNullified(room, effect_data)
end end
end end
end
end, end,
[Player.Draw] = function() [Player.Draw] = function()
local data = { local data = {

View File

@ -599,6 +599,151 @@ function GameLogic:getEventsOfScope(eventType, n, func, scope)
return start_event:searchEvents(eventType, n, func) return start_event:searchEvents(eventType, n, func)
end end
-- 在指定历史范围中找符合条件的事件(逆序)
---@param eventType integer @ 要查找的事件类型
---@param func fun(e: GameEvent): boolean @ 过滤用的函数
---@param n integer @ 最多找多少个
---@param end_id integer @ 查询历史范围从最后的事件开始逆序查找直到id为end_id的事件不含
---@return GameEvent[] @ 找到的符合条件的所有事件最多n个但不保证有n个
function GameLogic:getEventsByRule(eventType, n, func, end_id)
local ret = {}
local events = self.event_recorder[eventType] or Util.DummyTable
for i = #events, 1, -1 do
local e = events[i]
if e.id <= end_id then break end
if func(e) then
table.insert(ret, e)
if #ret >= n then break end
end
end
return ret
end
--- 获取实际的伤害事件
---@param n integer @ 最多找多少个
---@param func fun(e: GameEvent): boolean @ 过滤用的函数
---@param scope? integer @ 查询历史范围,只能是当前阶段/回合/轮次
---@param end_id? integer @ 查询历史范围从最后的事件开始逆序查找直到id为end_id的事件不含
---@return GameEvent[] @ 找到的符合条件的所有事件最多n个但不保证有n个
function GameLogic:getActualDamageEvents(n, func, scope, end_id)
if not end_id then
scope = scope or Player.HistoryTurn
end
n = n or 1
func = func or Util.TrueFunc
local eventType = GameEvent.Damage
local ret = {}
local endIdRecorded
local tempEvents = {}
local addTempEvents = function(reverse)
if #tempEvents > 0 and #ret < n then
table.sort(tempEvents, function(a, b)
if reverse then
return a.data[1].dealtRecorderId > b.data[1].dealtRecorderId
else
return a.data[1].dealtRecorderId < b.data[1].dealtRecorderId
end
end)
for _, e in ipairs(tempEvents) do
table.insert(ret, e)
if #ret >= n then return true end
end
end
endIdRecorded = nil
tempEvents = {}
return false
end
if scope then
local event = self:getCurrentEvent()
local start_event ---@type GameEvent
if scope == Player.HistoryGame then
start_event = self.all_game_events[1]
elseif scope == Player.HistoryRound then
start_event = event:findParent(GameEvent.Round, true)
elseif scope == Player.HistoryTurn then
start_event = event:findParent(GameEvent.Turn, true)
elseif scope == Player.HistoryPhase then
start_event = event:findParent(GameEvent.Phase, true)
end
if not start_event then return {} end
local events = self.event_recorder[eventType] or Util.DummyTable
local from = start_event.id
local to = start_event.end_id
if math.abs(to) == 1 then to = #self.all_game_events end
for _, v in ipairs(events) do
local damageStruct = v.data[1]
if damageStruct.dealtRecorderId then
if endIdRecorded and v.id > endIdRecorded then
local result = addTempEvents()
if result then
return ret
end
end
if v.id >= from and v.id <= to then
if not endIdRecorded and v.end_id > -1 and v.end_id > v.id then
endIdRecorded = v.end_id
end
if func(v) then
if endIdRecorded then
table.insert(tempEvents, v)
else
table.insert(ret, v)
end
end
end
if #ret >= n then break end
end
end
addTempEvents()
else
local events = self.event_recorder[eventType] or Util.DummyTable
for i = #events, 1, -1 do
local e = events[i]
if e.id <= end_id then break end
local damageStruct = e.data[1]
if damageStruct.dealtRecorderId then
if e.end_id == -1 or (endIdRecorded and endIdRecorded > e.end_id) then
local result = addTempEvents(true)
if result then
return ret
end
if func(e) then
table.insert(ret, e)
end
else
endIdRecorded = e.end_id
if func(e) then
table.insert(tempEvents, e)
end
end
if #ret >= n then break end
end
end
addTempEvents(true)
end
return ret
end
function GameLogic:dumpEventStack(detailed) function GameLogic:dumpEventStack(detailed)
local top = self:getCurrentEvent() local top = self:getCurrentEvent()
local i = self.game_event_stack.p local i = self.game_event_stack.p

View File

@ -19,23 +19,38 @@ MarkEnum.MinusMaxCards = "MinusMaxCards"
---于本回合内减少标记值数量的手牌上限 ---于本回合内减少标记值数量的手牌上限
MarkEnum.MinusMaxCardsInTurn = "MinusMaxCards-turn" MarkEnum.MinusMaxCardsInTurn = "MinusMaxCards-turn"
---使用牌无次数限制,可带清除标记后缀(-tmp为请求专用 ---使用牌无次数限制
MarkEnum.BypassTimesLimit = "BypassTimesLimit" MarkEnum.BypassTimesLimit = "BypassTimesLimit"
---使用牌无距离限制,可带清除标记后缀(-tmp为请求专用 ---使用牌无距离限制
MarkEnum.BypassDistancesLimit = "BypassDistancesLimit" MarkEnum.BypassDistancesLimit = "BypassDistancesLimit"
---对其使用牌无次数限制,可带清除标记后缀 ---对其使用牌无次数限制
MarkEnum.BypassTimesLimitTo = "BypassTimesLimitTo" MarkEnum.BypassTimesLimitTo = "BypassTimesLimitTo"
---对其使用牌无距离限制,可带清除标记后缀 ---对其使用牌无距离限制
MarkEnum.BypassDistancesLimitTo = "BypassDistancesLimitTo" MarkEnum.BypassDistancesLimitTo = "BypassDistancesLimitTo"
---非锁定技失效,可带清除标记后缀 ---非锁定技失效
MarkEnum.UncompulsoryInvalidity = "UncompulsoryInvalidity" MarkEnum.UncompulsoryInvalidity = "UncompulsoryInvalidity"
---不可明置,可带清除标记后缀值为表m - 主将, d - 副将) ---不可明置值为表m - 主将, d - 副将)
MarkEnum.RevealProhibited = "RevealProhibited" MarkEnum.RevealProhibited = "RevealProhibited"
---不计入距离、座次后缀,可带清除标记后缀 ---不计入距离、座次后缀
MarkEnum.PlayerRemoved = "PlayerRemoved" MarkEnum.PlayerRemoved = "PlayerRemoved"
---各种清除标记后缀 ---各种清除标记后缀
---
---phase阶段结束后
---
---turn回合结束后
---
---round轮次结束后
MarkEnum.TempMarkSuffix = { "-phase", "-turn", "-round" } MarkEnum.TempMarkSuffix = { "-phase", "-turn", "-round" }
---卡牌标记版本的清除标记后缀 ---卡牌标记版本的清除标记后缀
MarkEnum.CardTempMarkSuffix = { "-phase", "-turn", "-round", "-inhand" } ---
---phase阶段结束后
---
---turn回合结束后
---
---round轮次结束后
---
---inhand离开手牌区后
MarkEnum.CardTempMarkSuffix = { "-phase", "-turn", "-round",
"-inhand" }

View File

@ -1726,6 +1726,26 @@ function Room:askForSkillInvoke(player, skill_name, data, prompt)
return invoked return invoked
end end
-- 获取使用牌的合法额外目标(【借刀杀人】等带副目标的卡牌除外)
---@param data CardUseStruct @ 使用事件的data
---@param bypass_distances boolean? @ 是否无距离关系的限制
---@param use_AimGroup boolean? @ 某些场合需要使用AimGroupby smart Ho-spair
---@return integer[] @ 返回满足条件的player的id列表
function Room:getUseExtraTargets(data, bypass_distances, use_AimGroup)
if not (data.card.type == Card.TypeBasic or data.card:isCommonTrick()) then return {} end
if data.card.skill:getMinTargetNum() > 1 then return {} end --stupid collateral
local tos = {}
local current_targets = use_AimGroup and AimGroup:getAllTargets(data.tos) or TargetGroup:getRealTargets(data.tos)
for _, p in ipairs(self.alive_players) do
if not table.contains(current_targets, p.id) and not self:getPlayerById(data.from):isProhibited(p, data.card) then
if data.card.skill:modTargetFilter(p.id, {}, data.from, data.card, not bypass_distances) then
table.insert(tos, p.id)
end
end
end
return tos
end
--为使用牌增减目标 --为使用牌增减目标
---@param player ServerPlayer @ 执行的玩家 ---@param player ServerPlayer @ 执行的玩家
---@param targets ServerPlayer[] @ 可选的目标范围 ---@param targets ServerPlayer[] @ 可选的目标范围
@ -2913,7 +2933,7 @@ end
--- 让一名玩家获得一张牌 --- 让一名玩家获得一张牌
---@param player integer|ServerPlayer @ 要拿牌的玩家 ---@param player integer|ServerPlayer @ 要拿牌的玩家
---@param cid integer|Card @ 要拿到的卡牌 ---@param cid integer|Card|integer[] @ 要拿到的卡牌
---@param unhide? boolean @ 是否明着拿 ---@param unhide? boolean @ 是否明着拿
---@param reason? CardMoveReason @ 卡牌移动的原因 ---@param reason? CardMoveReason @ 卡牌移动的原因
---@param proposer? integer @ 移动操作者的id ---@param proposer? integer @ 移动操作者的id
@ -3114,6 +3134,7 @@ function Room:handleAddLoseSkills(player, skill_names, source_skill, sendlog, no
if #skill_names == 0 then return end if #skill_names == 0 then return end
local losts = {} ---@type boolean[] local losts = {} ---@type boolean[]
local triggers = {} ---@type Skill[] local triggers = {} ---@type Skill[]
local lost_piles = {} ---@type integer[]
for _, skill in ipairs(skill_names) do for _, skill in ipairs(skill_names) do
if string.sub(skill, 1, 1) == "-" then if string.sub(skill, 1, 1) == "-" then
local actual_skill = string.sub(skill, 2, #skill) local actual_skill = string.sub(skill, 2, #skill)
@ -3135,12 +3156,17 @@ function Room:handleAddLoseSkills(player, skill_names, source_skill, sendlog, no
table.insert(losts, true) table.insert(losts, true)
table.insert(triggers, s) table.insert(triggers, s)
if s.derived_piles then
for _, pile_name in ipairs(s.derived_piles) do
table.insertTableIfNeed(lost_piles, player:getPile(pile_name))
end
end
end end
end end
else else
local sk = Fk.skills[skill] local sk = Fk.skills[skill]
if sk and not player:hasSkill(sk, true, true) then if sk and not player:hasSkill(sk, true, true) then
local got_skills = player:addSkill(sk) local got_skills = player:addSkill(sk, source_skill)
for _, s in ipairs(got_skills) do for _, s in ipairs(got_skills) do
-- TODO: limit skill mark -- TODO: limit skill mark
@ -3171,6 +3197,15 @@ function Room:handleAddLoseSkills(player, skill_names, source_skill, sendlog, no
self.logic:trigger(event, player, triggers[i]) self.logic:trigger(event, player, triggers[i])
end end
end end
if #lost_piles > 0 then
self:moveCards({
ids = lost_piles,
from = player.id,
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
})
end
end end
-- 判定 -- 判定
@ -3236,7 +3271,7 @@ function Room:retrial(card, player, judge, skillName, exchange)
end end
--- 弃置一名角色的牌。 --- 弃置一名角色的牌。
---@param card_ids integer[] @ 被弃掉的牌 ---@param card_ids integer[]|integer @ 被弃掉的牌
---@param skillName? string @ 技能名 ---@param skillName? string @ 技能名
---@param who ServerPlayer @ 被弃牌的人 ---@param who ServerPlayer @ 被弃牌的人
---@param thrower? ServerPlayer @ 弃别人牌的人 ---@param thrower? ServerPlayer @ 弃别人牌的人

View File

@ -134,7 +134,7 @@ fk.IceDamage = 4
---@field public additionalEffect? integer ---@field public additionalEffect? integer
---@class CardEffectEvent ---@class CardEffectEvent
---@field public from integer ---@field public from? integer
---@field public to integer ---@field public to integer
---@field public subTargets? integer[] ---@field public subTargets? integer[]
---@field public tos TargetGroup ---@field public tos TargetGroup

View File

@ -112,6 +112,7 @@ local jink = fk.CreateBasicCard{
suit = Card.Heart, suit = Card.Heart,
number = 2, number = 2,
skill = jinkSkill, skill = jinkSkill,
is_passive = true,
} }
extension:addCards({ extension:addCards({
@ -468,6 +469,7 @@ local nullification = fk.CreateTrickCard{
suit = Card.Spade, suit = Card.Spade,
number = 11, number = 11,
skill = nullificationSkill, skill = nullificationSkill,
is_passive = true,
} }
extension:addCards({ extension:addCards({