2. 技能本体与技能效果#
在 0.5.5 更新之后,技能的定义被拆分成了技能本体和技能效果两部分:
技能(技能骨架
SkillSkeletron)技能的定义,定义技能的实际名字、标签、次数限制等内容。
效果(技能
Skill)技能发动时实际执行的内容, 定义各技能效果的发动时机、发动条件、效果描述、技能的具体实现等内容。 也就是说,技能效果才是技能的真正内容,而技能本体只是承载技能效果的一个框架。
特别的,第一个技能效果被视为技能的“主效果”,名字与技能本体相同。
在 0.5.5 更新后,技能效果与技能本体分离。
技能效果的定义不再直接包含在技能本体中,
而是通过技能效果列表 effects 与技能本体关联。
因此,虽然实际执行的代码属于各技能效果, 但需要判断角色是否持有技能,或该技能的标签、次数限制等内容时, 仍然需要通过技能本体来进行判断。
2.1. 技能标签#
技能标签是技能的一个重要属性,虽然不怎么影响技能的实际效果,但却是技能的一个重要分类标准。
技能标签的本质是字符串, Skill 类内置了一套枚举,
包括了如锁定技 Skill.Compulsory、
限定技 Skill.Limited 、转换技 Skill.Switch 等的官服标签。
为了方便技能编写,一些技能标签会让制作技能效果时赋予默认效果(这里仅列出部分):
(可被编写者自行覆盖):
Skill.Compulsory锁定技标签:技能发动时机被满足后,在询问技能时(on_cost)默认同意发动技能。Skill.Limited限定技标签:若未设技能次数限制,则默认为“本技能每局游戏限一次”。
(硬编码,不可覆盖):
Skill.Wake觉醒技标签:判断技能标签时等同于同时拥有Skill.Compulsory和Skill.Limited标签。Skill.Switch转换技标签:技能效果中会默认包含一个“转换”效果,发动时自动切换转换技标签。Skill.Permanent持恒技标签:技能判断是否无效时,跳过有此标签技能的判定。
3. 技能的次数限制#
在“重置技能”与“次数+1”在官设和DIY中普及的当下,次数限制本身也在变得越来越复杂。 虽然说本质上是一系列标记,但在实际使用中,只有次数相关的功能能被“重置技能”统一控制。 为了更好地理解技能的次数限制,理解这些规范变得至关重要。
3.1. 四个时段#
技能的发动次数限制主要考察四个时段,在对应时段的对应事件执行后会重置相应的次数:
全局游戏
Player.HistoryGame没有对应事件,所以也不会被自动重置。
轮次
Player.HistoryRound对应轮次事件
GameEvent.Round。回合
Player.HistoryTurn对应回合事件
GameEvent.Turn,在时机“回合结束时fk.TurnEnd”执行后重置。但特别的,目前回合次数限制是唯一一个在回合事件执行前就被重置的次数限制时段。
阶段
Player.HistoryPhase对应阶段事件
GameEvent.Phase在时机“阶段结束时fk.EventPhaseEnd”执行后重置。
3.2. 定义技能的次数限制#
一个技能的次数限制看起来应该是这样的:
{
max_turn_use_time = 1, -- 本技能每回合限一次
max_phase_use_time = 2, -- 本技能每阶段限两次
max_round_use_time = function(self, player) ... end, -- 本技能每轮的次数限制取决于该函数
max_game_use_time = 4, -- 本技能每局限四次
-- 技能效果等其他内容
}
其中, max_turn_use_time、 max_phase_use_time、
max_round_use_time和 max_game_use_time分别对应
回合、阶段、轮次和全局游戏的次数限制。
3.3. 次数检测#
和是否拥有技能一样,有时我们需要手动检测我们发动了几次技能。
对于技能效果来说,应使用player:usedEffectTimes(effect_name, scope)来检测发动了几次该效果。
其中effect_name是技能效果的名字,scope是检测的时段(如Player.HistoryTurn)。
而在 0.5.5 之后,我们则需要用player:usedSkillTimes(skill_name, scope)来检测发动了几次该技能。
3.3.1. 与次数上限比较#
当我们需要比较发动次数与次数上限时,应该使用skel:withinTimesLimit(player, scope)来判断是否在该技能的次数限制内,
虽然说可以同时使用player:usedSkillTimes(skel.name, scope)和skel:getMaxUseTime(player, scope)来进行比较,
但前者会更简洁,而且也能处理诸如“该时段是否存在次数限制”的提前判断。
技能效果的次数限制也可以以此法检测,函数名也不尽相同:
skill:withinTimesLimit(player, scope)来判断是否在该技能效果的次数限制内。skill:getMaxUseTime(player, scope)来获取该技能效果的次数限制。player:usedEffectTimes(effect_name, scope)来获取发动了几次该技能效果。
3.3.2. 自动检测#
SkillSkeletron:addEffect 实际上有三个参数,分别是:
技能分类(包括如"active"的实际分类和如fk.TurnEnd的具体时机)、
技能内容(包括技能效果的具体实现)、
以及我们要重点讲的第三个参数:额外修饰。
额外修饰是一个参数列表,用以在创建技能效果时指导代码应该自动添加哪些内容。 其中就有作为本文核心的次数限制修饰:
"check_effect_limit"在发动技能效果前先检测本效果发动的次数是否超过了某时机的限制。"check_skill_limit"在发动技能效果前先检测本效果所属技能发动的次数是否超过了某时机的限制。
这两者的区别在于,前者只检测当前技能效果的次数限制,而后者则检测整个技能的次数限制。
因此,如果一个技能的其中一个效果拥有自己的次数限制,那么我们应该在这个效果中修饰中设置 "check_effect_limit = true"。
如果我们希望整个技能共享一个次数限制,那么我们应该在每个效果中使用 "check_skill_limit = true"。
3.4. 延迟效果#
在发动效果时,也会为效果所属技能自动记录发动次数,因此技能次数绝大多数时候是一定包含效果的使用次数的。
但有时我们需要在技能效果中发动一个延迟效果(如“结束阶段,若你本回合发动过该技能,则...”),
而这个延迟效果不应增加主技能的使用次数,否则会导致技能实际的可用次数远少于预期。
这时就需要在这个延迟效果中使用 "is_delay_effect = true"来让它不再为主技能增加技能次数。
同时,延迟效果标签也使得该效果在发动时会默认自动发动。
3.5. 技能分支#
由于官服存在一些特殊的技能次数限制(如“每回合每项各限一次”),因此,对于技能本身,还有一套更详细的体系:
local skel = fk.createSkill {
name = "skill_name",
max_branches_use_time = {
["choice1"] = { -- 该次数限制仅限于技能效果分支“choice1”
[Player.HistoryTurn] = 1, -- 本技能的“choice1”分支每回合限一次
},
["choice2"] = { -- 该次数限制仅限于技能效果分支“choice2”
[Player.HistoryPhase] = 1, -- 本技能的“choice2”分支每阶段限一次
},
...
},
-- 其他内容
}
skel:addEffect("active", {
can_use = function(self, player)
return skel:withinBranchTimesLimit(player, "choice2", Player.HistoryPhase) -- 判断角色是否在技能效果分支“choice2”的阶段次数限制内
end, -- 该效果的发动条件
}, {
-- 该效果属于技能效果分支“choice1”,因此会受到“choice1”分支的次数限制
check_skill_limit = true, -- 该效果会受到技能的次数限制
...
})
在这个例子中,我们定义了一个技能骨架 skel,并在其中定义了一个技能效果。
在技能骨架中,我们通过 max_branches_use_time定义了两个技能效果分支“choice1”和“choice2”的次数限制。
在技能效果中,我们通过 check_skill_limit = true让该效果自动受到技能本身的次数限制,
以确保所有分支皆超过次数限制时技能无法发动。
并且在发动条件中通过 skel:withinBranchTimesLimit判断角色是否在“choice2”分支的阶段次数限制内。