diff --git a/lua/client/client.lua b/lua/client/client.lua index f091edb590ddff500e694b7de1a75c3f443a926e..36de236d29a8822ed2b74e34c8e7f31a3a73cf7f 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -1116,6 +1116,17 @@ fk.client_callback["AddSkillUseHistory"] = function(self, data) updateLimitSkill(playerid, Fk.skills[skill_name]) end +fk.client_callback["AddSkillBranchUseHistory"] = function(self, data) + local playerid, skill_name, branch, time = data[1], data[2], data[3], data[4] + local player = self:getPlayerById(playerid) + player:addSkillBranchUseHistory(skill_name, branch, time) + + -- 真的有分支会改变状态吗……? + -- local skill = Fk.skills[skill_name] + -- if not skill then return end + -- updateLimitSkill(playerid, Fk.skills[skill_name]) +end + fk.client_callback["SetSkillUseHistory"] = function(self, data) local id, skill_name, time, scope = data[1], data[2], data[3], data[4] local player = self:getPlayerById(id) @@ -1126,6 +1137,18 @@ fk.client_callback["SetSkillUseHistory"] = function(self, data) updateLimitSkill(id, Fk.skills[skill_name]) end +fk.client_callback["SetSkillBranchUseHistory"] = function(self, data) + local id, skill_name, branch, time, scope = + data[1], data[2], data[3], data[4], data[5] + local player = self:getPlayerById(id) + player:setSkillBranchUseHistory(skill_name, branch, time, scope) + + -- 真的有分支会改变状态吗……? + -- local skill = Fk.skills[skill_name] + -- if not skill then return end + -- updateLimitSkill(id, Fk.skills[skill_name]) +end + fk.client_callback["AddVirtualEquip"] = function(self, data) local cname = data.name local player = self:getPlayerById(data.player) diff --git a/lua/core/events/skill.lua b/lua/core/events/skill.lua index 9a4df10597e43d5ecbe7f17fc65b8d069ec260a8..b3b644dd7ffb8dcf608a56e6f1b90607fab72b5f 100644 --- a/lua/core/events/skill.lua +++ b/lua/core/events/skill.lua @@ -13,6 +13,7 @@ ---@field public no_indicate? boolean @ 发动时是否不显示指示线 ---@field public audio_index? number @ 发动时是否播放特定编号台词 ---@field public anim_type? AnimationType|string @ 发动时是否播放特定动画 +---@field public history_branch? string @ 发动时是否将技能发动历史归类到某个分支 --- 技能使用的数据 ---@class SkillUseData: SkillUseDataSpec, TriggerData @@ -21,7 +22,7 @@ SkillUseData = TriggerData:subclass("SkillUseData") ---@class SkillEffectDataSpec ---@field public skill_cb fun():any @ 实际技能函数 ---@field public who ServerPlayer @ 技能发动者 ----@field public skill Skill @ 发动的技能 +---@field public skill UsableSkill @ 发动的技能 ---@field public skill_data SkillUseData @ 技能数据 ---@field public prevented? boolean @ 防止执行技能效果(仅用于触发技、主动技、转化技) ---@field public trigger_break? boolean @ 停止继续触发此时机(仅用于触发技) diff --git a/lua/core/player.lua b/lua/core/player.lua index acb76764dfb5f632accbe72aaca1967046a95031..6ca061b3aee71923d20740631cd0d2e0d2c17042 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -35,6 +35,7 @@ ---@field public special_cards table @ 类似“屯田”的“田”的私人牌堆 ---@field public cardUsedHistory table @ 用牌次数历史记录 ---@field public skillUsedHistory table @ 发动技能次数的历史记录 +---@field public skillBranchUsedHistory table> @ 发动技能某分支次数的历史记录 ---@field public buddy_list integer[] @ 队友列表,或者说自己可以观看别人手牌的那些玩家的列表 ---@field public equipSlots string[] @ 装备栏列表 ---@field public sealedSlots string[] @ 被废除的装备栏列表 @@ -123,6 +124,7 @@ function Player:initialize() self.cardUsedHistory = {} self.skillUsedHistory = {} + self.skillBranchUsedHistory = {} self.buddy_list = {} end @@ -838,22 +840,42 @@ function Player:addSkillUseHistory(skill_name, num) end end +--- 增加玩家使用特定技能分支的历史次数。 +---@param skill_name string @ 技能名 +---@param branch string @ 技能分支名,不写则默认改变某技能**所有分支**的历史次数 +---@param num? integer @ 次数 默认1 +function Player:addSkillBranchUseHistory(skill_name, branch, num) + num = num or 1 + assert(type(num) == "number" and num ~= 0) + + self.skillBranchUsedHistory[skill_name] = self.skillBranchUsedHistory[skill_name] or {} + self.skillBranchUsedHistory[skill_name][branch] = self.skillBranchUsedHistory[skill_name][branch] or {0, 0, 0, 0} + local t = self.skillBranchUsedHistory[skill_name][branch] + for i, _ in ipairs(t) do + t[i] = t[i] + num + end +end + --- 设定玩家使用特定技能的历史次数。 +--- `num`和`scope`均不写则为清空特定区域的历史次数 ---@param skill_name? string @ 技能名,不写则默认改变所有技能的历史次数 ---@param num? integer @ 次数 默认0 ----@param scope? integer @ 查询历史范围 +---@param scope? integer @ 查询历史范围,若你填了num则必须填具体时机 function Player:setSkillUseHistory(skill_name, num, scope) skill_name = skill_name or "" if num == nil and scope == nil then if skill_name ~= "" then self.skillUsedHistory[skill_name] = {0, 0, 0, 0} + self:setSkillBranchUseHistory(skill_name) else self.skillUsedHistory = {} + self:setSkillBranchUseHistory() end return end num = num or 0 + assert(scope) if skill_name == "" then for _, v in pairs(self.skillUsedHistory) do v[scope] = num @@ -865,6 +887,83 @@ function Player:setSkillUseHistory(skill_name, num, scope) self.skillUsedHistory[skill_name][scope] = num end +--- 设定玩家使用特定技能分支的历史次数。 +--- `num`和`scope`均不写则为清空特定区域的历史次数 +---@param skill_name? string @ 技能名,不写则默认改变**所有技能**之所有分支的历史次数 +---@param branch? string @ 技能分支名,不写则默认改变某技能**所有分支**的历史次数 +---@param num? integer @ 次数 默认0 +---@param scope? integer @ 查询历史范围 +function Player:setSkillBranchUseHistory(skill_name, branch, num, scope) + skill_name = skill_name or "" + if num == nil and scope == nil then + if skill_name ~= "" then + if branch then + self.skillBranchUsedHistory[skill_name][branch] = {0, 0, 0, 0} + else + self.skillBranchUsedHistory[skill_name] = {} + end + else + self.skillBranchUsedHistory = {} + end + return + end + + num = num or 0 + assert(scope) + if skill_name == "" then + for _, v in pairs(self.skillBranchUsedHistory) do + if branch then + v[branch][scope] = num + else + for _, history in pairs(v) do + history[scope] = num + end + end + end + else + self.skillBranchUsedHistory[skill_name] = self.skillBranchUsedHistory[skill_name] or {} + if branch then + self.skillBranchUsedHistory[skill_name][branch] = self.skillBranchUsedHistory[skill_name][branch] or {0, 0, 0, 0} + self.skillBranchUsedHistory[skill_name][branch][scope] = num + else + for _, history in pairs(self.skillBranchUsedHistory[skill_name]) do + history[scope] = num + end + end + end +end + +--- 清空玩家使用特定技能的历史次数 +---@param skill_name string @ 技能名,若为主技能则同时清空所有技能效果和分支的历史次数 +---@param scope? integer @ 清空的历史范围,不填则全部清空 +function Player:clearSkillHistory(skill_name, scope) + local skill = Fk.skills[skill_name] + local skel = skill:getSkeleton() + if skel and skel.name == skill_name then + if scope then + for _, effect in ipairs(skel.effect_names) do + self:setSkillUseHistory(effect, 0, scope) + end + else + for _, effect in ipairs(skel.effect_names) do + self:setSkillUseHistory(effect) + end + end + self:setSkillBranchUseHistory(skill_name) + return + end + + if scope then + for _, effect in ipairs(skill_name) do + self:setSkillUseHistory(effect, 0, scope) + end + else + for _, effect in ipairs(skill_name) do + self:setSkillUseHistory(effect) + end + end +end + --- 获取玩家使用特定牌的历史次数(只算计入次数的部分)。 ---@param cardName string @ 牌名 ---@param scope? integer @ 查询历史范围,默认Turn @@ -879,11 +978,20 @@ end --- 获取玩家使用特定技能的历史次数。 ---@param skill_name string @ 技能(skill skeleton)名 ---@param scope? integer @ 查询历史范围,默认Turn -function Player:usedSkillTimes(skill_name, scope) +---@param branch? string @ 不查询主技能使用次数,改为查询分支所属的次数限制 +function Player:usedSkillTimes(skill_name, scope, branch) if not self.skillUsedHistory[skill_name] then return 0 end scope = scope or Player.HistoryTurn + + if branch then + if not self.skillBranchUsedHistory[skill_name] or not self.skillBranchUsedHistory[skill_name][branch] then + return 0 + end + + return self.skillBranchUsedHistory[skill_name][branch][scope] + end return self.skillUsedHistory[skill_name][scope] end diff --git a/lua/core/skill_skeleton.lua b/lua/core/skill_skeleton.lua index e6b746db0d02d1956e2f3f2f270fc526f7871701..5b8cf2e64e8d4bf56919cbceb13fc79dd35c5321 100644 --- a/lua/core/skill_skeleton.lua +++ b/lua/core/skill_skeleton.lua @@ -7,7 +7,7 @@ ---@field public anim_type? string|AnimationType @ 技能类型定义 ---@field public global? boolean @ 决定是否是全局技能 ---@field public dynamic_desc? fun(self: Skill, player: Player, lang: string): string? @ 动态描述函数 ----@field public derived_piles? string|string[] @ 与某效果联系起来的私人牌堆名,失去该效果时将之置入弃牌堆(@deprecated) +---@field public derived_piles? string|string[] @deprecated @ 与某效果联系起来的私人牌堆名,失去该效果时将之置入弃牌堆 ---@field public audio_index? table|integer @ 此技能效果播放的语音序号,可为int或int表 ---@field public extra? table @ 塞进技能里的各种数据 @@ -20,21 +20,28 @@ ---@field public dynamic_name? fun(self: SkillSkeleton, player: Player, lang?: string): string @ 动态名称函数 ---@field public dynamic_desc? fun(self: SkillSkeleton, player: Player, lang?: string): string? @ 动态描述函数 ---@field public derived_piles? string | string[] @ 与该技能联系起来的私人牌堆名,失去该技能时将之置入弃牌堆 +---@field public max_phase_use_time? integer | fun(self: SkillSkeleton, player: Player): integer? @ 该技能的最大使用次数——阶段 +---@field public max_turn_use_time? integer | fun(self: SkillSkeleton, player: Player): integer? @ 该技能的最大使用次数——回合 +---@field public max_round_use_time? integer | fun(self: SkillSkeleton, player: Player): integer? @ 该技能的最大使用次数——轮次 +---@field public max_game_use_time? integer | fun(self: SkillSkeleton, player: Player): integer? @ 该技能的最大使用次数——本局游戏 +---@field public max_branches_use_time? table?> | fun(self: SkillSkeleton, player: Player): table?>? @ 该技能的最大使用次数——任意标签(内部有独立的时段细分) ---@field public mode_skill? boolean @ 是否为模式技能(诸如斗地主的“飞扬”和“跋扈”) ---@field public extra? table @ 塞进技能里的各种数据 ---@class SkillSkeleton : Object, SkillSkeletonSpec ----@field public effects Skill[] 技能对应的所有效果 ----@field public effect_names string[] 技能对应的效果名 ----@field public effect_spec_list ([any, any, any])[] 技能对应的效果信息 +---@field public effects Skill[] @ 该技能对应的所有效果 +---@field public effect_names string[] @ 该技能对应的效果名 +---@field public effect_spec_list ([any, any, any])[] @ 该技能对应的效果信息 ---@field public ai_list ([string, any, string, boolean?])[] ---@field public tests fun(room: Room, me: ServerPlayer)[] ---@field public dynamicName fun(self: SkillSkeleton, player: Player, lang?: string): string @ 动态名称函数 ---@field public dynamicDesc fun(self: SkillSkeleton, player: Player, lang?: string): string @ 动态描述函数 ----@field public derived_piles? string[] @ 与一个技能同在的私有牌堆名,失去时弃置其中的所有牌 +---@field public derived_piles? string[] @ 与该技能同在的私有牌堆名,失去时弃置其中的所有牌 +---@field public max_use_time table @ 该技能在各时机内最大的使用次数 +---@field public max_branches_use_time? table?> | fun(self: SkillSkeleton, player: Player): table?>? @ 该技能的最大使用次数——任意标签(内部有独立的时段细分) ---@field public addTest fun(self: SkillSkeleton, fn: fun(room: Room, me: ServerPlayer)) @ 测试函数 ----@field public onAcquire fun(self: SkillSkeleton, player: ServerPlayer, is_start: boolean) ----@field public onLose fun(self: SkillSkeleton, player: ServerPlayer, is_death: boolean) +---@field public onAcquire fun(self: SkillSkeleton, player: ServerPlayer, is_start: boolean) @ 获得技能时执行的函数 +---@field public onLose fun(self: SkillSkeleton, player: ServerPlayer, is_death: boolean) @ 失去技能时执行的函数 ---@field public addEffect fun(self: SkillSkeleton, key: "distance", data: DistanceSpec, attribute: nil): SkillSkeleton ---@field public addEffect fun(self: SkillSkeleton, key: "prohibit", data: ProhibitSpec, attribute: nil): SkillSkeleton ---@field public addEffect fun(self: SkillSkeleton, key: "atkrange", data: AttackRangeSpec, attribute: nil): SkillSkeleton @@ -43,9 +50,9 @@ ---@field public addEffect fun(self: SkillSkeleton, key: "filter", data: FilterSpec, attribute: nil): SkillSkeleton ---@field public addEffect fun(self: SkillSkeleton, key: "invalidity", data: InvaliditySpec, attribute: nil): SkillSkeleton ---@field public addEffect fun(self: SkillSkeleton, key: "visibility", data: VisibilitySpec, attribute: nil): SkillSkeleton ----@field public addEffect fun(self: SkillSkeleton, key: "active", data: ActiveSkillSpec, attribute: nil): SkillSkeleton +---@field public addEffect fun(self: SkillSkeleton, key: "active", data: ActiveSkillSpec, attribute: SkillAttribute?): SkillSkeleton ---@field public addEffect fun(self: SkillSkeleton, key: "cardskill", data: CardSkillSpec, attribute: nil): SkillSkeleton ----@field public addEffect fun(self: SkillSkeleton, key: "viewas", data: ViewAsSkillSpec, attribute: nil): SkillSkeleton +---@field public addEffect fun(self: SkillSkeleton, key: "viewas", data: ViewAsSkillSpec, attribute: SkillAttribute?): SkillSkeleton local SkillSkeleton = class("SkillSkeleton") @@ -87,6 +94,14 @@ function SkillSkeleton:initialize(spec) self.extra = spec.extra or {} + self.max_use_time = { + spec.max_phase_use_time, + spec.max_turn_use_time, + spec.max_round_use_time, + spec.max_game_use_time, + } + self.max_branches_use_time = spec.max_branches_use_time + --Notify智慧,当不存在main_skill时,用于创建main_skill。看上去毫无用处 fk.readCommonSpecToSkill(self, spec) end @@ -189,9 +204,12 @@ function SkillSkeleton:createSkill() return main_skill end ----@class TrigSkelAttribute ----@field public is_delay_effect? boolean ---- 若为true,则不贴main_skill +---@class SkillAttribute +---@field public check_effect_limit? boolean @ 若为true,则自带判断效果次数(若不填can_use,则默认带限定“本阶段”的次数判定) +---@field public check_skill_limit? boolean @ 若为true,则自带判断技能次数 + +---@class TrigSkelAttribute: SkillAttribute +---@field public is_delay_effect? boolean @ 若为true,则不贴main_skill ---@alias TrigFunc fun(self: TriggerSkill, event: TriggerEvent, target: ServerPlayer?, player: ServerPlayer, data: any): any ---@class TrigSkelSpec: { @@ -229,16 +247,42 @@ function SkillSkeleton:createTriggerSkill(_skill, idx, key, attr, spec) sk.global = spec.global end if spec.can_trigger then + local tmp_func if table.contains(_skill.tags, Skill.Wake) then - sk.triggerable = function(_self, event, target, player, data) + if spec.can_wake then + sk.canWake = spec.can_wake + end + tmp_func = function(_self, event, target, player, data) return spec.can_trigger(_self, event, target, player, data) and sk:enableToWake(event, target, player, data) end else - sk.triggerable = spec.can_trigger + tmp_func = spec.can_trigger end - if table.contains(_skill.tags, Skill.Wake) and spec.can_wake then - sk.canWake = spec.can_wake + sk.triggerable = function(_self, event, target, player, data) + if attr.check_effect_limit then + for scope, _ in pairs(_self.max_use_time) do + if not _self:withinTimesLimit(player, scope) then + return false + end + end + end + if attr.check_skill_limit then + if #_skill.max_use_time == 0 and _skill.max_branches_use_time then + -- 写死的时机table,考虑到历史记录也是写死在这的,姑且先这样吧 + for _, scope in ipairs({Player.HistoryGame, Player.HistoryRound, Player.HistoryTurn, Player.HistoryPhase}) do + if not _skill:withinBranchTimesLimit(player, nil, scope) then + return false + end + end + end + for scope, _ in pairs(_skill.max_use_time) do + if not _skill:withinTimesLimit(player, scope) then + return false + end + end + end + return tmp_func(_self, event, target, player, data) end end if spec.on_trigger then sk.trigger = spec.on_trigger end @@ -447,6 +491,9 @@ function fk.readInteractionToSkill(skill, spec) end end +---@param _skill SkillSkeleton +---@param idx integer +---@param attr SkillAttribute ---@param spec ActiveSkillSpec ---@return ActiveSkill function SkillSkeleton:createActiveSkill(_skill, idx, key, attr, spec) @@ -456,14 +503,43 @@ function SkillSkeleton:createActiveSkill(_skill, idx, key, attr, spec) local skill = ActiveSkill:new(new_name, #_skill.tags > 0 and _skill.tags[1] or Skill.NotFrequent) fk.readUsableSpecToSkill(skill, spec) - if spec.can_use then - skill.canUse = function(curSkill, player) - return spec.can_use(curSkill, player) and curSkill:isEffectable(player) + local spec_can_use = spec.can_use + if not spec_can_use then + spec_can_use = ActiveSkill.canUse + end + + ---@param curSkill ViewAsSkill + ---@param player Player + skill.canUse = function(curSkill, player, card, extra_data) + if not curSkill:isEffectable(player) then return end + if attr.check_effect_limit then + for scope, _ in pairs(curSkill.max_use_time) do + if not curSkill:withinTimesLimit(player, scope) then + return false + end + end + end + if attr.check_skill_limit then + if #_skill.max_use_time == 0 and _skill.max_branches_use_time then + -- 写死的时机table,考虑到历史记录也是写死在这的,姑且先这样吧 + for _, scope in ipairs({Player.HistoryGame, Player.HistoryRound, Player.HistoryTurn, Player.HistoryPhase}) do + if not _skill:withinBranchTimesLimit(player, nil, scope) then + return false + end + end + end + for scope, _ in pairs(_skill.max_use_time) do + if not _skill:withinTimesLimit(player, scope) then + return false + end + end end + return spec_can_use(curSkill, player, card, extra_data) end if spec.card_filter then skill.cardFilter = spec.card_filter end if spec.target_filter then skill.targetFilter = spec.target_filter end if spec.feasible then skill.feasible = spec.feasible end + if spec.on_cost then skill.onCost = spec.on_cost end if spec.on_use then skill.onUse = spec.on_use end if spec.prompt then skill.prompt = spec.prompt end if spec.target_tip then skill.targetTip = spec.target_tip end @@ -501,6 +577,9 @@ function SkillSkeleton:createCardSkill(_skill, idx, key, attr, spec) return skill end +---@param _skill SkillSkeleton +---@param idx integer +---@param attr SkillAttribute ---@param spec ViewAsSkillSpec ---@return ViewAsSkill function SkillSkeleton:createViewAsSkill(_skill, idx, key, attr, spec) @@ -530,14 +609,51 @@ function SkillSkeleton:createViewAsSkill(_skill, idx, key, attr, spec) if type(spec.pattern) == "string" then skill.pattern = spec.pattern end + + ---@param curSkill ViewAsSkill + ---@param player Player + local timeCheck = function(curSkill, player) + if attr.check_effect_limit then + for scope, _ in pairs(curSkill.max_use_time) do + if not curSkill:withinTimesLimit(player, scope) then + return false + end + end + end + if attr.check_skill_limit then + if #_skill.max_use_time == 0 and _skill.max_branches_use_time then + -- 写死的时机table,考虑到历史记录也是写死在这的,姑且先这样吧 + for _, scope in ipairs({Player.HistoryGame, Player.HistoryRound, Player.HistoryTurn, Player.HistoryPhase}) do + if not _skill:withinBranchTimesLimit(player, nil, scope) then + return false + end + end + end + for scope, _ in pairs(_skill.max_use_time) do + if not _skill:withinTimesLimit(player, scope) then + return false + end + end + end + return true + end + if type(spec.enabled_at_play) == "function" then skill.enabledAtPlay = function(curSkill, player) - return spec.enabled_at_play(curSkill, player) and curSkill:isEffectable(player) + return timeCheck(curSkill, player) and spec.enabled_at_play(curSkill, player) and curSkill:isEffectable(player) + end + else + skill.enabledAtPlay = function(curSkill, player) + return timeCheck(curSkill, player) and ViewAsSkill.enabledAtPlay(curSkill, player) end end if type(spec.enabled_at_response) == "function" then skill.enabledAtResponse = function(curSkill, player, cardResponsing) - return spec.enabled_at_response(curSkill, player, cardResponsing) and curSkill:isEffectable(player) + return timeCheck(curSkill, player) and spec.enabled_at_response(curSkill, player, cardResponsing) and curSkill:isEffectable(player) + end + else + skill.enabledAtResponse = function(curSkill, player, cardResponsing) + return timeCheck(curSkill, player) and ViewAsSkill.enabledAtResponse(curSkill, player, cardResponsing) end end if spec.prompt then skill.prompt = spec.prompt end @@ -556,7 +672,13 @@ function SkillSkeleton:createViewAsSkill(_skill, idx, key, attr, spec) if spec.click_count then skill.click_count = spec.click_count end if type(spec.enabled_at_nullification) == "function" then - skill.enabledAtNullification = spec.enabled_at_nullification + skill.enabledAtNullification = function(curSkill, player, cardData) + return timeCheck(curSkill, player) and spec.enabled_at_nullification(curSkill, player, cardData) and curSkill:isEffectable(player) + end + else + skill.enabledAtNullification = function(curSkill, player, cardData) + return timeCheck(curSkill, player) and ViewAsSkill.enabledAtNullification(curSkill, player, cardData) + end end skill.handly_pile = spec.handly_pile @@ -567,6 +689,8 @@ function SkillSkeleton:createViewAsSkill(_skill, idx, key, attr, spec) skill.mute_card = not (string.find(skill.pattern, "|") or skill.pattern == "." or string.find(skill.pattern, ",")) end + if spec.on_cost then skill.onCost = spec.on_cost end + return skill end @@ -672,6 +796,106 @@ function SkillSkeleton:getDynamicDescription(player, lang) return self.dynamicDesc and self:dynamicDesc(player, lang) end +-- 获得技能的最大使用次数 +---@param player Player @ 使用者 +---@param scope integer @ 查询历史范围(默认为回合) +---@param to? Player @ 目标 +---@return number? @ 最大使用次数,nil就是无限 +function SkillSkeleton:getMaxUseTime(player, scope, to) + scope = scope or Player.HistoryTurn + local time = self.max_use_time[scope] + local ret = time + if type(time) == "function" then + ret = time(self, player, to) + end + if ret == nil then return nil end + return ret +end + +-- 获得技能的最大使用次数(基于某个分支) +---@param player Player @ 使用者 +---@param branch string @ 分支名(没有后缀) +---@param scope integer @ 查询历史范围(默认为回合) +---@param to? Player @ 目标 +---@return number? @ 最大使用次数,nil就是无限 +function SkillSkeleton:getBranchMaxUseTime(player, branch, scope, to) + scope = scope or Player.HistoryTurn + local times_table + if type(self.max_branches_use_time) == "function" then + times_table = self:max_branches_use_time(player) + else + times_table = self.max_branches_use_time + end + if not times_table then return nil end + + local ret = times_table[branch][scope] + if ret == nil then return nil end + return ret +end + +-- 获得技能的剩余使用次数 +---@param player Player @ 使用者 +---@param scope integer @ 查询历史范围(默认为回合) +---@param to? Player @ 目标 +---@return number? @ 剩余使用次数,nil就是无限 +function SkillSkeleton:getRemainUseTime(player, scope, to) + scope = scope or Player.HistoryTurn + + local limit = self:getMaxUseTime(player, scope, to) + if limit == nil then return nil end + + return math.max(0, limit - player:usedSkillTimes(self.name, scope)) +end + +-- 判断一个角色是否在技能的次数限制内 +---@param player Player @ 使用者 +---@param scope? integer @ 查询历史范围(默认为回合) +---@param to? Player @ 目标 +---@return boolean? +function SkillSkeleton:withinTimesLimit(player, scope, to) + scope = scope or Player.HistoryTurn + if not self:withinBranchTimesLimit(player, nil, scope) then return false end + + local limit = self:getMaxUseTime(player, scope, to) + if limit == nil then return true end + + return self:getRemainUseTime(player, scope, to) > 0 +end + +-- 判断一个角色是否在技能的**所有分支**次数限制内 +---@param player Player @ 使用者 +---@param branch? string @ 查询分支范围(无则检查所有分支) +---@param scope? integer @ 查询历史范围(默认为回合) +---@param to? Player @ 目标 +---@return boolean? +function SkillSkeleton:withinBranchTimesLimit(player, branch, scope, to) + scope = scope or Player.HistoryTurn + local times_table + if type(self.max_branches_use_time) == "function" then + times_table = self:max_branches_use_time(player) + else + times_table = self.max_branches_use_time + end + if not times_table then return true end + + if branch then + local limit = (times_table[branch] or {})[scope] + return not (limit and player:usedSkillTimes(self.name, scope, branch) >= limit) + end + + local has_limit = false + for target_branch, limits in pairs(times_table) do + local ret = (limits or {})[scope] + if ret ~= nil then + has_limit = true + if player:usedSkillTimes(self.name, scope, target_branch) < ret then + return true + end + end + end + return not has_limit +end + ---@param spec SkillSkeletonSpec ---@return SkillSkeleton function fk.CreateSkill(spec) diff --git a/lua/core/skill_type/active.lua b/lua/core/skill_type/active.lua index b417474018f912bd4083c7b6fda2183bdb8032e0..d7e092fc88012cad7b4220853f324ba70571f609 100644 --- a/lua/core/skill_type/active.lua +++ b/lua/core/skill_type/active.lua @@ -196,6 +196,12 @@ function ActiveSkill:prompt(player, selected_cards, selected_targets, extra_data ------- } +---@param player ServerPlayer +---@param skillData SkillUseData +function ActiveSkill:onCost(player, skillData) + return {} +end + ---@param room Room ---@param cardUseEvent SkillUseData function ActiveSkill:onUse(room, cardUseEvent) end diff --git a/lua/core/skill_type/usable_skill.lua b/lua/core/skill_type/usable_skill.lua index 924ce1a2ccf609b8e5782e59b526f1d17779786e..772a6c4c3d688a232616ee4bf60a8e77ee7b99fc 100644 --- a/lua/core/skill_type/usable_skill.lua +++ b/lua/core/skill_type/usable_skill.lua @@ -7,9 +7,10 @@ --]] ---@class UsableSkill : Skill ----@field public max_use_time integer[] +---@field public max_use_time table @ 一个效果的最大可用次数 +---@field public history_branch? string | fun(self: UsableSkill, player: ServerPlayer, data: SkillUseData):string? @ 发动时是否将技能发动历史归类到某个分支 ---@field public expand_pile? string | integer[] | fun(self: UsableSkill, player: Player): integer[]|string? @ 额外牌堆,牌堆名称或卡牌id表 ----@field public derived_piles? string | string[] @ 与某效果联系起来的私人牌堆名,失去该效果时将之置入弃牌堆(@deprecated) +---@field public derived_piles? string | string[] @deprecated @ 与某效果联系起来的私人牌堆名,失去该效果时将之置入弃牌堆 ---@field public times? fun(self: UsableSkill, player: Player): integer local UsableSkill = Skill:subclass("UsableSkill") @@ -22,7 +23,7 @@ end -- 获得技能的最大使用次数 ---@param player Player @ 使用者 ----@param scope integer @ 查询历史范围(默认为回合) +---@param scope? integer @ 查询历史范围(默认为回合) ---@param card? Card @ 卡牌 ---@param to? Player @ 目标 ---@return number? @ 最大使用次数,nil就是无限 @@ -48,7 +49,7 @@ end -- 判断一个角色是否在技能的次数限制内 ---@param player Player @ 使用者 ----@param scope integer @ 查询历史范围(默认为回合) +---@param scope? integer @ 查询历史范围(默认为回合) ---@param card? Card @ 牌,若没有牌,则尝试制造一张虚拟牌 ---@param card_name? string @ 牌名 ---@param to? Player @ 目标 @@ -67,9 +68,6 @@ function UsableSkill:withinTimesLimit(player, scope, card, card_name, to) local limit = self:getMaxUseTime(player, scope, card, to) if not limit then return true end - for _, skill in ipairs(status_skills) do - if skill:bypassTimesCheck(player, self, scope, card, to) then return true end - end if not card_name then if card then @@ -79,6 +77,10 @@ function UsableSkill:withinTimesLimit(player, scope, card, card_name, to) end end + for _, skill in ipairs(status_skills) do + if skill:bypassTimesCheck(player, self, scope, card, to) then return true end + end + return player:usedCardTimes(card_name, scope) < limit or (card and not not card:hasMark(MarkEnum.BypassTimesLimit)) or not not player:hasMark(MarkEnum.BypassTimesLimit) or diff --git a/lua/core/skill_type/view_as.lua b/lua/core/skill_type/view_as.lua index f000a07a4211ad0ec633f6830bf032d2fae3c607..266e1fb9d0323c9ae80f08a41908e432f9cc0849 100644 --- a/lua/core/skill_type/view_as.lua +++ b/lua/core/skill_type/view_as.lua @@ -116,6 +116,12 @@ function ViewAsSkill:feasible(player, targets, selected_cards, card) return false end +---@param player ServerPlayer +---@param skillData SkillUseData +function ViewAsSkill:onCost(player, skillData) + return {} +end + ---@param room Room ---@param cardUseEvent SkillUseData ---@param params? handleUseCardParams diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index b21e37dabd8d2d278328c33ecf2f7bb63c3d1e9b..20db6ec01efd13e7d7a7185b8e1a0eccc565868b 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -80,6 +80,7 @@ function fk.readUsableSpecToSkill(skill, spec) skill.is_delay_effect = not not spec.is_delay_effect skill.late_refresh = not not spec.late_refresh skill.click_count = not not spec.click_count + skill.history_branch = spec.history_branch end function fk.readStatusSpecToSkill(skill, spec) @@ -90,15 +91,15 @@ function fk.readStatusSpecToSkill(skill, spec) end ---@class UsableSkillSpec: SkillSpec ----@field public main_skill? UsableSkill ----@field public max_use_time? integer[] +---@field public main_skill? UsableSkill @ 该技能是否为某技能的主框架 +---@field public max_phase_use_time? integer|fun(self: SkillSkeleton, player: Player): integer? @ 该技能效果的最大使用次数——阶段 +---@field public max_turn_use_time? integer|fun(self: SkillSkeleton, player: Player): integer? @ 该技能效果的最大使用次数——回合 +---@field public max_round_use_time? integer|fun(self: SkillSkeleton, player: Player): integer? @ 该技能效果的最大使用次数——轮次 +---@field public max_game_use_time? integer|fun(self: SkillSkeleton, player: Player): integer? @ 该技能效果的最大使用次数——本局游戏 +---@field public history_branch? string|fun(self: UsableSkill, player: ServerPlayer, data: SkillUseData):string? @ 裁定本技能发动时(on_cost->on_use)将技能历史额外添加到某处分支下(内部有独立的时段细分),无法约束本技能是否可用 ---@field public expand_pile? string | integer[] | fun(self: UsableSkill, player: ServerPlayer): integer[]|string? @ 额外牌堆,牌堆名称或卡牌id表 ---@field public derived_piles? string | string[] @ 与某效果联系起来的私人牌堆名,失去该效果时将之置入弃牌堆(@deprecated) ----@field public max_phase_use_time? integer @ 每阶段使用次数上限 ----@field public max_turn_use_time? integer @ 每回合使用次数上限 ----@field public max_round_use_time? integer @ 每回合使用次数上限 ----@field public max_game_use_time? integer @ 整场游戏使用次数上限 ----@field public times? integer | fun(self: UsableSkill, player: Player): integer +---@field public times? integer | fun(self: UsableSkill, player: Player): integer @ 显示在主动技按钮上的发动次数数字 ---@field public min_target_num? integer ---@field public max_target_num? integer ---@field public target_num? integer @@ -114,6 +115,7 @@ end ---@field public card_filter? fun(self: ActiveSkill, player: Player, to_select: integer, selected: integer[], selected_targets: Player[]): any @ 判断卡牌能否选择 ---@field public target_filter? fun(self: ActiveSkill, player: Player?, to_select: Player, selected: Player[], selected_cards: integer[], card: Card?, extra_data: UseExtraData|table?): any @ 判定目标能否选择 ---@field public feasible? fun(self: ActiveSkill, player: Player, selected: Player[], selected_cards: integer[], card: Card): any @ 判断卡牌和目标是否符合技能限制 +---@field public on_cost? fun(self: UsableSkill, player: ServerPlayer, data: SkillUseData):CostData|table @ 自定义技能的消耗信息 ---@field public on_use? fun(self: ActiveSkill, room: Room, skillUseEvent: SkillUseData): any ---@field public prompt? string|fun(self: ActiveSkill, player: Player, selected_cards: integer[], selected_targets: Player[]): string @ 提示信息 ---@field public interaction? fun(self: ActiveSkill, player: Player): table? @ 选项框 @@ -142,8 +144,9 @@ end ---@field public card_filter? fun(self: ViewAsSkill, player: Player, to_select: integer, selected: integer[], selected_targets: Player[]): any @ 判断卡牌能否选择 ---@field public target_filter? fun(self: ViewAsSkill, player: Player?, to_select: Player, selected: Player[], selected_cards: integer[], card: Card?, extra_data: UseExtraData|table?): any @ 判定目标能否选择 ---@field public feasible? fun(self: ViewAsSkill, player: Player, selected: Player[], selected_cards: integer[], card: Card): any @ 判断卡牌和目标是否符合技能限制 ----@field public on_use? fun(self: ActiveSkill, room: Room, skillUseEvent: SkillUseData, card: Card, params: handleUseCardParams?): UseCardDataSpec|string? +---@field public on_use? fun(self: ViewAsSkill, room: Room, skillUseEvent: SkillUseData, card: Card, params: handleUseCardParams?): UseCardDataSpec|string? ---@field public view_as fun(self: ViewAsSkill, player: Player, cards: integer[]): Card? @ 判断转化为什么牌 +---@field public on_cost? fun(self: ViewAsSkill, player: ServerPlayer, data: SkillUseData):CostData|table @ 自定义技能的消耗信息 ---@field public pattern? string ---@field public enabled_at_play? fun(self: ViewAsSkill, player: Player): any ---@field public enabled_at_response? fun(self: ViewAsSkill, player: Player, response: boolean): any diff --git a/lua/server/events/skill.lua b/lua/server/events/skill.lua index f7448cba33cdaf4e2234f4ca8a91fad64b4a29a2..6024100a4094196512c3c6bd851fdf2759b76ffc 100644 --- a/lua/server/events/skill.lua +++ b/lua/server/events/skill.lua @@ -95,9 +95,26 @@ function SkillEffect:main() ) end + local branch = cost_data.history_branch + if not branch then + if type(skill.history_branch) == "function" then + branch = skill:history_branch(player, skill_data) + else + branch = skill.history_branch + end + end + player:addSkillUseHistory(skill.name) if skill.name ~= skill:getSkeleton().name and not skill.is_delay_effect then player:addSkillUseHistory(skill:getSkeleton().name) + + if branch then + player:addSkillBranchUseHistory(skill:getSkeleton().name, branch) + end + else + if branch then + player:addSkillBranchUseHistory(skill.name, branch) + end end end @@ -134,7 +151,7 @@ end ---@param player ServerPlayer @ 发动技能的玩家 ---@param skill Skill @ 发动的技能 ---@param effect_cb fun() @ 实际要调用的函数 ----@param skill_data? table @ 技能的信息 +---@param skill_data? SkillUseDataSpec @ 技能的信息 ---@return SkillEffectData function SkillEventWrappers:useSkill(player, skill, effect_cb, skill_data) ---@cast self Room diff --git a/lua/server/room.lua b/lua/server/room.lua index e04184fb8c97e4296febb8d29a3e5d1b70dfe3b7..fbef33b5162aede06a8e1542e785ed91119f9c33 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -2206,25 +2206,49 @@ function Room:handleUseCardReply(player, data, params) if skill.interaction then skill.interaction.data = data.interaction_data end if skill:isInstanceOf(ActiveSkill) then ---@cast skill ActiveSkill + + local use_spec = { + from = player, + cards = selected_cards, + tos = table.map(targets, Util.Id2PlayerMapper), + } + local use_data = SkillUseData:new(use_spec) + use_data.cost_data = skill:onCost(player, use_data) + if not use_data.cost_data.history_branch then + if type(skill.history_branch) == "function" then + use_data.cost_data.history_branch = skill:history_branch(player, use_data) + else + use_data.cost_data.history_branch = skill.history_branch + end + end + self:useSkill(player, skill, function() - skill:onUse(self, SkillUseData:new { - from = player, - cards = selected_cards, - tos = table.map(targets, Util.Id2PlayerMapper), - }) - end, {tos = table.map(targets, Util.Id2PlayerMapper), cards = selected_cards, cost_data = {}}) + skill:onUse(self, use_data) + end, use_data) return nil elseif skill:isInstanceOf(ViewAsSkill) then ---@cast skill ViewAsSkill --Self = player local useResult local c = skill:viewAs(player, selected_cards) + + local use_spec = { + from = player, + cards = selected_cards, + tos = table.map(targets, Util.Id2PlayerMapper), + } + local use_data = SkillUseData:new(use_spec) + use_data.cost_data = skill:onCost(player, use_data) + if not use_data.cost_data.history_branch then + if type(skill.history_branch) == "function" then + use_data.cost_data.history_branch = skill:history_branch(player, use_data) + else + use_data.cost_data.history_branch = skill.history_branch + end + end + self:useSkill(player, skill, function() - useResult = skill:onUse(self, SkillUseData:new { - from = player, - cards = selected_cards, - tos = table.map(targets, Util.Id2PlayerMapper), - }, c, params) or "" + useResult = skill:onUse(self, use_data, c, params) or "" if type(useResult) == "table" then if params == nil then player.room:useCard(useResult) @@ -2234,7 +2258,7 @@ function Room:handleUseCardReply(player, data, params) useResult.attachedSkillAndUser = { skillName = skill.name, user = player.id, muteCard = skill.mute_card } end end - end, {tos = table.map(targets, Util.Id2PlayerMapper), cards = selected_cards, cost_data = {}}) + end, use_data) return useResult end else @@ -3983,6 +4007,7 @@ function Room:clearHistory (scope) for _, p in ipairs(self.players) do p:setCardUseHistory("", 0, scope) p:setSkillUseHistory("", 0, scope) + p:setSkillBranchUseHistory("", nil, 0, scope) for name, _ in pairs(p.mark) do if name:find(suffix, 1, true) then self:setPlayerMark(p, name, 0) diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index a4fe6693fd4bc9c4a71832c76a774267c54ed4ab..0f9cc9dd0b8cb61c7af517d0ef16ac83075add12 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -454,12 +454,24 @@ function ServerPlayer:addSkillUseHistory(skillName, num) self.room:doBroadcastNotify("AddSkillUseHistory", {self.id, skillName, num}) end +-- 增加技能分支发动次数 +function ServerPlayer:addSkillBranchUseHistory(skillName, branch, num) + Player.addSkillBranchUseHistory(self, skillName, branch, num) + self.room:doBroadcastNotify("AddSkillBranchUseHistory", {self.id, skillName, branch, num}) +end + -- 设置技能已发动次数 function ServerPlayer:setSkillUseHistory(skillName, num, scope) Player.setSkillUseHistory(self, skillName, num, scope) self.room:doBroadcastNotify("SetSkillUseHistory", {self.id, skillName, num, scope}) end +-- 设置技能分支已发动次数 +function ServerPlayer:setSkillBranchUseHistory(skillName, branch, num, scope) + Player.setSkillBranchUseHistory(self, skillName, branch, num, scope) + self.room:doBroadcastNotify("SetSkillBranchUseHistory", {self.id, skillName, branch, num, scope}) +end + --- 设置连环状态 ---@param chained boolean @ true为横置,false为重置 ---@param data any? @ 额外数据