禅与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:
parent
ea65a3dd4b
commit
d4bb4e21bb
|
@ -39,6 +39,7 @@ GraphicsBox {
|
|||
id: choicetitle
|
||||
width: parent.width
|
||||
text: luatr(modelData)
|
||||
triggered: root.result.includes(index)
|
||||
enabled: options.indexOf(modelData) !== -1
|
||||
&& (root.result.length < max_num || triggered)
|
||||
textFont.pixelSize: 24
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
---@field public special_skills? string[] @ 衍生技能,如重铸
|
||||
---@field public is_damage_card boolean @ 是否为会造成伤害的牌
|
||||
---@field public multiple_targets boolean @ 是否为指定多个目标的牌
|
||||
---@field public is_passive? boolean @ 是否只能在响应时使用或打出
|
||||
---@field public is_derived? boolean @ 判断是否为衍生牌
|
||||
local Card = class("Card")
|
||||
|
||||
|
@ -145,11 +146,12 @@ function Card:clone(suit, number)
|
|||
newCard.special_skills = self.special_skills
|
||||
newCard.is_damage_card = self.is_damage_card
|
||||
newCard.multiple_targets = self.multiple_targets
|
||||
newCard.is_passive = self.is_passive
|
||||
newCard.is_derived = self.is_derived
|
||||
return newCard
|
||||
end
|
||||
|
||||
--- 检测是否为虚拟卡牌,如果其ID为0及以下,则为虚拟卡牌。
|
||||
--- 检测是否为虚拟卡牌,如果其ID为0,则为虚拟卡牌。
|
||||
function Card:isVirtual()
|
||||
return self.id == 0
|
||||
end
|
||||
|
|
|
@ -872,9 +872,20 @@ end
|
|||
|
||||
--- 确认玩家是否可以使用特定牌。
|
||||
---@param card Card @ 特定牌
|
||||
function Player:canUse(card)
|
||||
assert(card, "Error: No Card")
|
||||
return card.skill:canUse(self, card)
|
||||
---@param extra_data? UseExtraData @ 额外数据
|
||||
function Player:canUse(card, extra_data)
|
||||
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
|
||||
|
||||
--- 确认玩家是否被禁止对特定玩家使用特定牌。
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
---@field public main_skill UsableSkill
|
||||
---@field public max_use_time integer[]
|
||||
---@field public expand_pile? string | integer[] | fun(self: UsableSkill): integer[]|string?
|
||||
---@field public derived_piles? string | string[]
|
||||
local UsableSkill = Skill:subclass("UsableSkill")
|
||||
|
||||
function UsableSkill:initialize(name, frequency)
|
||||
|
|
|
@ -48,6 +48,11 @@ end
|
|||
local function readUsableSpecToSkill(skill, spec)
|
||||
readCommonSpecToSkill(skill, spec)
|
||||
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.target_num = spec.target_num or skill.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)
|
||||
|
||||
if spec.can_use then
|
||||
skill.canUse = function(curSkill, player, card)
|
||||
return spec.can_use(curSkill, player, card) and curSkill:isEffectable(player)
|
||||
skill.canUse = function(curSkill, player, card, extra_data)
|
||||
return spec.can_use(curSkill, player, card, extra_data) and curSkill:isEffectable(player)
|
||||
end
|
||||
end
|
||||
if spec.card_filter then skill.cardFilter = spec.card_filter end
|
||||
|
@ -211,9 +216,9 @@ function fk.CreateActiveSkill(spec)
|
|||
|
||||
if spec.interaction then
|
||||
skill.interaction = setmetatable({}, {
|
||||
__call = function(self)
|
||||
__call = function()
|
||||
if type(spec.interaction) == "function" then
|
||||
return spec.interaction(self)
|
||||
return spec.interaction(skill)
|
||||
else
|
||||
return spec.interaction
|
||||
end
|
||||
|
@ -445,6 +450,7 @@ end
|
|||
---@field public special_skills? string[]
|
||||
---@field public is_damage_card? boolean
|
||||
---@field public multiple_targets? boolean
|
||||
---@field public is_passive? boolean
|
||||
|
||||
local defaultCardSkill = fk.CreateActiveSkill{
|
||||
name = "default_card_skill",
|
||||
|
@ -487,6 +493,7 @@ local function readCardSpecToCard(card, spec)
|
|||
card.special_skills = spec.special_skills
|
||||
card.is_damage_card = spec.is_damage_card
|
||||
card.multiple_targets = spec.multiple_targets
|
||||
card.is_passive = spec.is_passive
|
||||
end
|
||||
|
||||
---@param spec CardSpec
|
||||
|
|
|
@ -268,7 +268,7 @@ GameEvent.functions[GameEvent.Phase] = function(self)
|
|||
local room = self.room
|
||||
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 player.phase ~= Player.NotActive then
|
||||
logic:trigger(fk.EventPhaseProceeding, player)
|
||||
|
@ -285,23 +285,26 @@ GameEvent.functions[GameEvent.Phase] = function(self)
|
|||
end,
|
||||
[Player.Judge] = function()
|
||||
local cards = player:getCardIds(Player.Judge)
|
||||
for i = #cards, 1, -1 do
|
||||
local card
|
||||
card = player:removeVirtualEquip(cards[i])
|
||||
while #cards > 0 do
|
||||
local cid = table.remove(cards)
|
||||
if not cid then return end
|
||||
local card = player:removeVirtualEquip(cid)
|
||||
if not card then
|
||||
card = Fk:getCardById(cards[i])
|
||||
card = Fk:getCardById(cid)
|
||||
end
|
||||
room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name)
|
||||
if table.contains(player:getCardIds(Player.Judge), cid) then
|
||||
room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name)
|
||||
|
||||
---@type CardEffectEvent
|
||||
local effect_data = {
|
||||
card = card,
|
||||
to = player.id,
|
||||
tos = { {player.id} },
|
||||
}
|
||||
room:doCardEffect(effect_data)
|
||||
if effect_data.isCancellOut and card.skill then
|
||||
card.skill:onNullified(room, effect_data)
|
||||
---@type CardEffectEvent
|
||||
local effect_data = {
|
||||
card = card,
|
||||
to = player.id,
|
||||
tos = { {player.id} },
|
||||
}
|
||||
room:doCardEffect(effect_data)
|
||||
if effect_data.isCancellOut and card.skill then
|
||||
card.skill:onNullified(room, effect_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
|
|
@ -599,6 +599,151 @@ function GameLogic:getEventsOfScope(eventType, n, func, scope)
|
|||
return start_event:searchEvents(eventType, n, func)
|
||||
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)
|
||||
local top = self:getCurrentEvent()
|
||||
local i = self.game_event_stack.p
|
||||
|
|
|
@ -19,23 +19,38 @@ MarkEnum.MinusMaxCards = "MinusMaxCards"
|
|||
---于本回合内减少标记值数量的手牌上限
|
||||
MarkEnum.MinusMaxCardsInTurn = "MinusMaxCards-turn"
|
||||
|
||||
---使用牌无次数限制,可带清除标记后缀(-tmp为请求专用)
|
||||
---使用牌无次数限制
|
||||
MarkEnum.BypassTimesLimit = "BypassTimesLimit"
|
||||
---使用牌无距离限制,可带清除标记后缀(-tmp为请求专用)
|
||||
---使用牌无距离限制
|
||||
MarkEnum.BypassDistancesLimit = "BypassDistancesLimit"
|
||||
---对其使用牌无次数限制,可带清除标记后缀
|
||||
---对其使用牌无次数限制
|
||||
MarkEnum.BypassTimesLimitTo = "BypassTimesLimitTo"
|
||||
---对其使用牌无距离限制,可带清除标记后缀
|
||||
---对其使用牌无距离限制
|
||||
MarkEnum.BypassDistancesLimitTo = "BypassDistancesLimitTo"
|
||||
---非锁定技失效,可带清除标记后缀
|
||||
---非锁定技失效
|
||||
MarkEnum.UncompulsoryInvalidity = "UncompulsoryInvalidity"
|
||||
---不可明置,可带清除标记后缀(值为表,m - 主将, d - 副将)
|
||||
---不可明置(值为表,m - 主将, d - 副将)
|
||||
MarkEnum.RevealProhibited = "RevealProhibited"
|
||||
---不计入距离、座次后缀,可带清除标记后缀
|
||||
---不计入距离、座次后缀
|
||||
MarkEnum.PlayerRemoved = "PlayerRemoved"
|
||||
|
||||
---各种清除标记后缀
|
||||
---
|
||||
---phase:阶段结束后
|
||||
---
|
||||
---turn:回合结束后
|
||||
---
|
||||
---round:轮次结束后
|
||||
MarkEnum.TempMarkSuffix = { "-phase", "-turn", "-round" }
|
||||
|
||||
---卡牌标记版本的清除标记后缀
|
||||
MarkEnum.CardTempMarkSuffix = { "-phase", "-turn", "-round", "-inhand" }
|
||||
---
|
||||
---phase:阶段结束后
|
||||
---
|
||||
---turn:回合结束后
|
||||
---
|
||||
---round:轮次结束后
|
||||
---
|
||||
---inhand:离开手牌区后
|
||||
MarkEnum.CardTempMarkSuffix = { "-phase", "-turn", "-round",
|
||||
"-inhand" }
|
||||
|
|
|
@ -1726,6 +1726,26 @@ function Room:askForSkillInvoke(player, skill_name, data, prompt)
|
|||
return invoked
|
||||
end
|
||||
|
||||
-- 获取使用牌的合法额外目标(【借刀杀人】等带副目标的卡牌除外)
|
||||
---@param data CardUseStruct @ 使用事件的data
|
||||
---@param bypass_distances boolean? @ 是否无距离关系的限制
|
||||
---@param use_AimGroup boolean? @ 某些场合需要使用AimGroup,by 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 targets ServerPlayer[] @ 可选的目标范围
|
||||
|
@ -2913,7 +2933,7 @@ end
|
|||
|
||||
--- 让一名玩家获得一张牌
|
||||
---@param player integer|ServerPlayer @ 要拿牌的玩家
|
||||
---@param cid integer|Card @ 要拿到的卡牌
|
||||
---@param cid integer|Card|integer[] @ 要拿到的卡牌
|
||||
---@param unhide? boolean @ 是否明着拿
|
||||
---@param reason? CardMoveReason @ 卡牌移动的原因
|
||||
---@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
|
||||
local losts = {} ---@type boolean[]
|
||||
local triggers = {} ---@type Skill[]
|
||||
local lost_piles = {} ---@type integer[]
|
||||
for _, skill in ipairs(skill_names) do
|
||||
if string.sub(skill, 1, 1) == "-" then
|
||||
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(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
|
||||
else
|
||||
local sk = Fk.skills[skill]
|
||||
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
|
||||
-- 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])
|
||||
end
|
||||
end
|
||||
|
||||
if #lost_piles > 0 then
|
||||
self:moveCards({
|
||||
ids = lost_piles,
|
||||
from = player.id,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- 判定
|
||||
|
@ -3236,7 +3271,7 @@ function Room:retrial(card, player, judge, skillName, exchange)
|
|||
end
|
||||
|
||||
--- 弃置一名角色的牌。
|
||||
---@param card_ids integer[] @ 被弃掉的牌
|
||||
---@param card_ids integer[]|integer @ 被弃掉的牌
|
||||
---@param skillName? string @ 技能名
|
||||
---@param who ServerPlayer @ 被弃牌的人
|
||||
---@param thrower? ServerPlayer @ 弃别人牌的人
|
||||
|
|
|
@ -134,7 +134,7 @@ fk.IceDamage = 4
|
|||
---@field public additionalEffect? integer
|
||||
|
||||
---@class CardEffectEvent
|
||||
---@field public from integer
|
||||
---@field public from? integer
|
||||
---@field public to integer
|
||||
---@field public subTargets? integer[]
|
||||
---@field public tos TargetGroup
|
||||
|
|
|
@ -112,6 +112,7 @@ local jink = fk.CreateBasicCard{
|
|||
suit = Card.Heart,
|
||||
number = 2,
|
||||
skill = jinkSkill,
|
||||
is_passive = true,
|
||||
}
|
||||
|
||||
extension:addCards({
|
||||
|
@ -468,6 +469,7 @@ local nullification = fk.CreateTrickCard{
|
|||
suit = Card.Spade,
|
||||
number = 11,
|
||||
skill = nullificationSkill,
|
||||
is_passive = true,
|
||||
}
|
||||
|
||||
extension:addCards({
|
||||
|
|
Loading…
Reference in New Issue