《明日方舟》是一款魔物主题的策略手游。在游戏中,玩家将管理一艘满载“魔物干员”的方舟,为调查来源神秘的矿石灾难而踏上旅途。在这个宽广而危机四伏的世界中,你或许会看到废土中的城市废墟,或许会看到仿若幻境的亚人国度,或许会遭遇无法解读的神秘,或许参与无比残酷的战争。在有关幻想与异种生命的世界中,体验史诗与想象,情感与牵绊!——鹰角网络
当游戏中实装一个新干员时,玩家希望了解该干员的能力强度,以决定是否投资获取该干员。 游戏中使用稀有度表示干员的强度,然而,随着游戏运营时间的推移,干员数量也不断增长,同一稀有度的干员越来越多,并产生了一定幅度的强度膨胀,不再能简单使用稀有度来评价数百位干员的强度。面对玩家的困惑,本文提出一个干员强度评价体系,希望能够更有效地评价每位干员。
前人已经提出了多种强度评价方法,并利用这些方法产出了许多有价值的测评。然而,现有方法存在一些问题:
本文提出的方案——泛用型环境自适应干员强度评价体系(以下简称UOEP)旨在解决上述问题,具有以下特性:
UOEP是一个可广泛用于明日方舟干员、随着游戏更新不断发展、涵盖各种环境和干员各项能力、一键自动计算干员强度的评价体系。
然而,目前的UOEP也存在一定的局限性:
如上文所述,UOEP评价干员的各项能力,包括输出、增伤、承伤、保护、治疗、元素、回费、控制,其主要评价指标分别为:
UOEP使用Excel计算干员的伤害数据。在了解计算方法前,我们先学习一些基础知识,包括帧对齐、伤害类型、伤害乘区、友方增益。
明日方舟是一个每秒30
帧的游戏,游戏中的任何操作均需要在对应的帧上完成。举例来说,史尔特尔
的理论攻击间隔为1.25
秒,但实际在游戏中并非如此。攻击间隔秒数1.25
对应的帧数1.25*30=37.5
并不是一个整数,由于操作被限制在30
帧上,其必须被舍入为整数。游戏中采用的舍入方法为四舍五入,采用此规则,37.5
将取整为38
,因此史尔特尔的实际攻击间隔约为38/30≈1.2667
秒,此操作被称为帧对齐。需要注意的是,明日方舟的部分代码存在会损失精度的浮点数运算,游戏过程中帧数会产生一定波动,与计算相差几帧,这一差距可能导致干员在技能期间的攻击次数多一次或少一次,实际伤害将与计算结果出现偏差。
帧对齐的计算方法如下所示,ROUND(value, 0)
函数表示将value
四舍五入取整:
ROUND(1.25*30,0)/30
不考虑治疗的情况下,当前版本游戏中我方伤害类型有物理、法术、元素、真实,敌方伤害类型有物理、法术、真实。每种伤害的结算方式不同,基本公式如下,其中MAX()
函数表示取两数中更大的值:
MAX(攻击力-敌方防御力,攻击力*0.05)
攻击力*MAX(100-敌方法术抗性,5)/100
攻击力*MAX(100-敌方元素抗性,0)/100
攻击力
物理和法术伤害需要结算敌方单位的双抗,且均有最低5%
的保底伤害,元素伤害需要结算敌方单位的元素抗性,且没有保底伤害,真实伤害能够无视敌方的抗性。
在游戏中,伤害的结算公式相比上述基本公式更加复杂,因为干员拥有提升攻击力、削弱敌方双抗等能力。经过补充的计算公式如下,其中MEDIAN(value, low, high)
函数表示将value
限制在最小值low
和最大值high
之间:
MAX(
(((攻击力+A1+...+An)*(1+B1+...+Bn)+C1+...+Cn)*D1*...*Dn+E1+...+En)*0.05,
(((攻击力+A1+...+An)*(1+B1+...+Bn)+C1+...+Cn)*D1*...*Dn+E1+...+En)-MAX(((敌方防御力-(X1+...+Xn))*Y1*...*Yn-(P1+...+Pn))*Q1*...*Qn,0)
)*(F1*...*Fn)
(((攻击力+A1+...+An)*(1+B1+...+Bn)+C1+...+Cn)*D1*...*Dn+E1+...+En)*(MEDIAN(100-((敌方法术抗性-(X1+...+Xn))*Y1*...*Yn-(P1+...+Pn))*Q1*...*Qn,5,100)/100)*(F1*...*Fn)
(((攻击力+A1+...+An)*(1+B1+...+Bn)+C1+...+Cn)*D1*...*Dn+E1+...+En)*(MEDIAN(100-敌方元素抗性,0,100)/100)*(F1*...*Fn)
(((攻击力+A1+...+An)*(1+B1+...+Bn)+C1+...+Cn)*D1*...*Dn+E1+...+En)*(MEDIAN(100-敌方损伤抵抗,0,100)/100)*(G1*...*Gn)
(((攻击力+A1+...+An)*(1+B1+...+Bn)+C1+...+Cn)*D1*...*Dn+E1+...+En)*(F1*...*Fn)
公式中存在一系列伤害乘区,其含义和相关例子在下表列出。
乘区 | 含义 | 举例 | 举例干员能力描述 |
---|---|---|---|
A |
攻击力直接加算 | 乌尔比安 二天赋血脉的哺养 |
每次击倒一名敌人时攻击力提高30 |
B |
攻击力直接乘算 | Mon3tr 一天赋自我修复 |
重构体周围友方单位的攻击力+15% |
C |
攻击力最终加算 | 浊心斯卡蒂 二技能同葬无光之愿 |
获得相当于浊心斯卡蒂60%攻击力的鼓舞 |
D |
攻击力最终乘算 | 娜仁图亚 一技能旋刃 |
造成相当于攻击力190%的物理伤害 |
E |
伤害加算 | 逻各斯 二天赋剜魂具辞 |
使目标在5秒内受到的法术伤害提高150点 |
F |
伤害最终乘算 | 塞雷娅 三技能钙质化 |
附近所有敌军受到的法术伤害+55% |
G |
元素损伤最终乘算 | 塑心 二天赋精神逆构 |
使攻击范围内敌人受到的凋亡损伤提高33% |
X |
敌方双抗加算 | 假日威龙陈 三技能“假日风暴” |
地面敌人经过时防御力-220 |
Y |
敌方双抗乘算 | 缄默德克萨斯 二技能阵雨连绵 |
使命中目标在10秒内法术抗性-30% |
P |
敌方双抗数值无视 | 史尔特尔 一天赋熔火 |
无视攻击目标26法术抗性 |
Q |
敌方双抗比例无视 | 提丰 一天赋锐如兽牙 |
连续攻击时最高无视防御力的60% |
在上述多个伤害乘区中,并非所有加成效果都来自干员自身,而是来自其他友方单位。如Mon3tr
一天赋自我修复
提供的效果为重构体周围友方单位的攻击力+15%
,可对多名友方干员生效,则这些友方干员的伤害公式中的攻击力直接乘算乘区均会出现Mon3tr
提供的增益。
此外,并非所有加成效果均可直接叠加,部分增益效果具有同类属性取最高
的性质,如浊心斯卡蒂
的鼓舞
,当多名干员同时提供鼓舞
时,只有一名干员的鼓舞
增益会生效。目前具备该性质的增益效果包括:
精力充沛
、鼓舞
、偷取
、冻结
、脆弱
、物理脆弱
、法术脆弱
、元素脆弱
、技力光环
鼓舞
、庇护
、偷取
、虚弱
、寒冷
在基础知识中,我们已经了解伤害公式中存在的乘区,以及友方增益效果。应用已学习的知识,考虑娜仁图亚
携带一技能旋刃
造成相当于攻击力190%
的物理伤害,由于其一天赋“我见,我得”
累计可偷取
敌人250
点攻击力和200
点防御力,且攻击间隔为1
秒,理想情况下其DPS公式为:
MAX(
(((攻击力)+250)*1.9)*0.05,
(((攻击力)+250)*1.9)-MAX(((敌方防御力-200)),0)
)/1
为了计算吃拐率,UOEP需要将所有可能的友方增益都加入伤害公式中,使公式的复杂度迅速提高。加入所有增益的公式如下所示。注意到其中娜仁图亚偷取
了敌方200
点防御力,由于偷取
具有同类属性取最高
的性质,因此友方附加的敌方防御力加算_偷取
增益效果需要和娜仁图亚自身提供的200
取MAX()
运算。此外,友方可能附加攻击速度加成,使得娜仁图亚的攻击间隔小于1
秒并变为非整数帧数,此时需要进行帧对齐。
MAX(
(((攻击力+攻击力直接加算)*(1+攻击力直接乘算+攻击力直接乘算_狙击干员+MAX(攻击力直接乘算_精力充沛))+250+攻击力最终加算+MAX(攻击力最终加算_鼓舞))*1.9+伤害加算)*0.05,
(((攻击力+攻击力直接加算)*(1+攻击力直接乘算+攻击力直接乘算_狙击干员+MAX(攻击力直接乘算_精力充沛))+250+攻击力最终加算+MAX(攻击力最终加算_鼓舞))*1.9+伤害加算)-MAX(((敌方防御力-(敌方防御力加算+MAX(敌方防御力加算_偷取,200)))*敌方防御力乘算),0)
)*(伤害最终乘算*MAX(伤害最终乘算_脆弱)*MAX(伤害最终乘算_物理脆弱))/(ROUND(1/((100+攻击速度直接加算+攻击速度直接加算_狙击干员+攻击速度直接加算_高台干员)/100)*30,0)/30)
加入增益后,公式的长度膨胀了4倍多,注意到这只是最简单的伤害公式,大多数干员的公式都比它复杂。这里我们至少遇到两个问题:
同类属性取最高
的增益效果时,出错的可能性显著提高。为了应对上述问题,本文提出了UOEP公式语言(UOEP Formula Language, UOEP-FL)。UOEP-FL是一个领域专用语言,是Excel公式语言的严格子集。UOEP-FL定义了用于编写伤害公式的基本语法块,编译器将会读取用户使用基本语法块编写的公式,并将所有增益注入公式中,输出带增益的公式。用户只需维护不含任何友方增益效果的公式,无需维护编译生成的增益公式,有效提高了公式的可维护性。换句话说,在上一节中,由第一个公式转换到第二个公式的过程将由机器自动完成,用户只需要编写第一个公式,而无需操心第二个。
以下是一个使用UOEP-FL编写的伤害公式实例,该公式计算逻各斯
携带Δ模组时一技能殁亡
的周期DPS:
=(
N("法术伤害")+
(
(((BaseAttackRE03D)*(1+1))+150)*(MEDIAN(100-((EnemyResistanceMajor-10)),5,100)/100)+
0.6*(1/(EnemyCountMinor+1))*(((BaseAttackRE03D)*(1+1))*0.6+150)*(MEDIAN(100-((EnemyResistanceMajor-10)),5,100)/100)
)/(ROUND(1.6*30,0)/30)+
N("元素伤害")+
(
(800)*(MEDIAN(100-EnemyElementalResistanceMajor,0,100)/100)*15+
0.6*(1/(EnemyCountMinor+1))*(((BaseAttackRE03D)*(1+1))*0.6)*(MEDIAN(100-EnemyElementalResistanceMajor,0,100)/100)*ROUND(15/(ROUND(1.6*30,0)/30)+0.5,0)
)/(
N("凋亡损伤爆发期间时间")+
15+
N("凋亡损伤累积至爆发所需时间")+
ROUNDUP(EnemyMaxElementMajor/(
(((N("@InjuryDark")+BaseAttackRE03D)*(1+1))+150)*(MEDIAN(100-((EnemyResistanceMajor-10)),5,100)/100)*0.08*(MEDIAN(100-EnemyInjuryResistanceMajor,0,100)/100)+
0.6*(1/(EnemyCountMinor+1))*(((N("@InjuryDark")+BaseAttackRE03D)*(1+1))*0.6+150)*(MEDIAN(100-((EnemyResistanceMajor-10)),5,100)/100)*0.08*(MEDIAN(100-EnemyInjuryResistanceMajor,0,100)/100)
),0)*(ROUND(1.6*30,0)/30)
)
)
编译器生成的增益公式如下:
=IF(OR(ISNUMBER(SEARCH("RE03",BuffSourceIds))),0,(
N("法术伤害")+
(
(((BaseAttackRE03D+BuffDamageAttackFirstValue)*(1+1+(N("@MonoEnergizedAttack")+MAX(BuffDamageMonoEnergizedAttackFirstRatio))+BuffDamageAttackFirstRatio+BuffDamageAttackFirstRatioCaster)+(N("@MonoEncouragedAttack")+MAX(BuffDamageMonoEncouragedAttackFinalValue))+BuffDamageAttackFinalValue)+150+BuffDamageMagicalGainValue)*(MEDIAN(100-((EnemyResistanceMajor-(10+(N("@MonoEnemyFrozenResistance")+MAX(BuffDamageMonoEnemyFrozenResistanceLossValue))+BuffDamageEnemyResistanceLossValue))*BuffDamageEnemyResistanceLossFinalRatio),5,100)/100)*(1*(N("@MonoEnemyVulnerable")+MAX(BuffDamageMonoEnemyVulnerableFinalRatio))*(N("@MonoEnemyVulnerableMagical")+MAX(BuffDamageMonoEnemyVulnerableMagicalFinalRatio))*BuffDamageMagicalFinalRatio)+
0.6*(1/(EnemyCountMinor+1))*(((BaseAttackRE03D+BuffDamageAttackFirstValue)*(1+1+(N("@MonoEnergizedAttack")+MAX(BuffDamageMonoEnergizedAttackFirstRatio))+BuffDamageAttackFirstRatio+BuffDamageAttackFirstRatioCaster)+(N("@MonoEncouragedAttack")+MAX(BuffDamageMonoEncouragedAttackFinalValue))+BuffDamageAttackFinalValue)*0.6+150+BuffDamageMagicalGainValue)*(MEDIAN(100-((EnemyResistanceMajor-(10+(N("@MonoEnemyFrozenResistance")+MAX(BuffDamageMonoEnemyFrozenResistanceLossValue))+BuffDamageEnemyResistanceLossValue))*BuffDamageEnemyResistanceLossFinalRatio),5,100)/100)*(1*(N("@MonoEnemyVulnerable")+MAX(BuffDamageMonoEnemyVulnerableFinalRatio))*(N("@MonoEnemyVulnerableMagical")+MAX(BuffDamageMonoEnemyVulnerableMagicalFinalRatio))*BuffDamageMagicalFinalRatio)
)/(ROUND(1.6/((100+BuffDamageAttackSpeedFirstValue+BuffDamageAttackSpeedFirstValueRanged)/100)*30,0)/30)+
N("元素伤害")+
(
(800)*(MEDIAN(100-EnemyElementalResistanceMajor,0,100)/100)*(1*(N("@MonoEnemyVulnerableElemental")+MAX(BuffDamageMonoEnemyVulnerableElementalFinalRatio,BuffDamageMonoEnemyVulnerableElementalFinalRatioDark))*BuffDamageElementalFinalRatio)*15+
0.6*(1/(EnemyCountMinor+1))*(((BaseAttackRE03D+BuffDamageAttackFirstValue)*(1+1+(N("@MonoEnergizedAttack")+MAX(BuffDamageMonoEnergizedAttackFirstRatio))+BuffDamageAttackFirstRatio+BuffDamageAttackFirstRatioCaster)+(N("@MonoEncouragedAttack")+MAX(BuffDamageMonoEncouragedAttackFinalValue))+BuffDamageAttackFinalValue)*0.6+BuffDamageElementalGainValue)*(MEDIAN(100-EnemyElementalResistanceMajor,0,100)/100)*(1*(N("@MonoEnemyVulnerableElemental")+MAX(BuffDamageMonoEnemyVulnerableElementalFinalRatio,BuffDamageMonoEnemyVulnerableElementalFinalRatioDark))*BuffDamageElementalFinalRatio)*ROUND(15/(ROUND(1.6/((100+BuffDamageAttackSpeedFirstValue+BuffDamageAttackSpeedFirstValueRanged)/100)*30,0)/30)+0.5,0)
)/(
N("凋亡损伤爆发期间时间")+
15+
N("凋亡损伤累积至爆发所需时间")+
ROUNDUP(EnemyMaxElementMajor/(
(((N("@InjuryDark")+BaseAttackRE03D+BuffDamageAttackFirstValue)*(1+1+(N("@MonoEnergizedAttack")+MAX(BuffDamageMonoEnergizedAttackFirstRatio))+BuffDamageAttackFirstRatio+BuffDamageAttackFirstRatioCaster)+(N("@MonoEncouragedAttack")+MAX(BuffDamageMonoEncouragedAttackFinalValue))+BuffDamageAttackFinalValue)+150+BuffDamageMagicalGainValue)*(MEDIAN(100-((EnemyResistanceMajor-(10+(N("@MonoEnemyFrozenResistance")+MAX(BuffDamageMonoEnemyFrozenResistanceLossValue))+BuffDamageEnemyResistanceLossValue))*BuffDamageEnemyResistanceLossFinalRatio),5,100)/100)*(1*(N("@MonoEnemyVulnerable")+MAX(BuffDamageMonoEnemyVulnerableFinalRatio))*(N("@MonoEnemyVulnerableMagical")+MAX(BuffDamageMonoEnemyVulnerableMagicalFinalRatio))*BuffDamageMagicalFinalRatio)*0.08*(MEDIAN(100-EnemyInjuryResistanceMajor,0,100)/100)*(1*BuffDamageInjuryFinalRatio*BuffDamageInjuryDarkFinalRatio)+
0.6*(1/(EnemyCountMinor+1))*(((N("@InjuryDark")+BaseAttackRE03D+BuffDamageAttackFirstValue)*(1+1+(N("@MonoEnergizedAttack")+MAX(BuffDamageMonoEnergizedAttackFirstRatio))+BuffDamageAttackFirstRatio+BuffDamageAttackFirstRatioCaster)+(N("@MonoEncouragedAttack")+MAX(BuffDamageMonoEncouragedAttackFinalValue))+BuffDamageAttackFinalValue)*0.6+150+BuffDamageMagicalGainValue)*(MEDIAN(100-((EnemyResistanceMajor-(10+(N("@MonoEnemyFrozenResistance")+MAX(BuffDamageMonoEnemyFrozenResistanceLossValue))+BuffDamageEnemyResistanceLossValue))*BuffDamageEnemyResistanceLossFinalRatio),5,100)/100)*(1*(N("@MonoEnemyVulnerable")+MAX(BuffDamageMonoEnemyVulnerableFinalRatio))*(N("@MonoEnemyVulnerableMagical")+MAX(BuffDamageMonoEnemyVulnerableMagicalFinalRatio))*BuffDamageMagicalFinalRatio)*0.08*(MEDIAN(100-EnemyInjuryResistanceMajor,0,100)/100)*(1*BuffDamageInjuryFinalRatio*BuffDamageInjuryDarkFinalRatio)
),0)*(ROUND(1.6/((100+BuffDamageAttackSpeedFirstValue+BuffDamageAttackSpeedFirstValueRanged)/100)*30,0)/30)
)
))
下面介绍UOEP-FL的基本语法块。
N("@...")
(N("@...")+...)
注解是公式中的重要部分,可以告知编译器该部分的值属于哪个增益。例如,在上一节中,娜仁图亚
一天赋“我见,我得”
累计可偷取
敌人200
点防御力,由于偷取
为同类属性取最高
,且友方增益效果也可以偷取
,我们需要告诉编译器200
是偷取
得到的,以便编译器注入友方增益时,对其取MAX()
:通过注解可以编写(N("@MonoEnemyStolenDefense")+200)
,编译器会将其转换为MAX(200,BuffDamageMonoEnemyStolenDefenseLossValue
。
目前支持的注解及其含义如下。
注解 | 含义 |
---|---|
@MonoEncouragedAttack |
伤害-鼓舞-攻击力数值提升(最终加算) |
@MonoEnergizedAttack |
伤害-精力充沛-攻击力比例提升(直接乘算) |
@MonoEnemyStolenDefense |
伤害-偷取-敌方防御力数值降低(最终加算) |
@MonoEnemyFrozenResistance |
伤害-冻结-敌方法术抗性降低(直接加算) |
@MonoEnemyVulnerable |
伤害-脆弱-敌方受到的物理法术真实伤害比例提升(最终乘算) |
@MonoEnemyVulnerablePhysical |
伤害-物理脆弱-敌方受到的物理伤害比例提升(最终乘算) |
@MonoEnemyVulnerableMagical |
伤害-法术脆弱-敌方受到的法术伤害比例提升(最终乘算) |
@MonoEnemyVulnerableElemental |
伤害-元素脆弱-敌方受到的元素伤害比例提升(最终乘算) |
@MonoSkillPointAutomatic |
伤害-技力光环-技力自然回复速度提升(直接加算) |
@MonoEncouragedHealth |
保护-鼓舞-生命上限数值提升(最终加算) |
@MonoEncouragedDefense |
保护-鼓舞-防御力数值提升(最终加算) |
@MonoShelter |
保护-庇护-受到的物理和法术伤害比例降低(最终乘算) |
@MonoEnemyStolenAttack |
保护-偷取-敌方攻击力数值降低(最终加算) |
@MonoEnemyWeakenedAttack |
保护-虚弱-敌方攻击力比例降低(最终乘算) |
@MonoEnemyStolenAttackSpeed |
保护-偷取-敌方攻击速度数值降低(最终加算) |
@MonoEnemyColdAttackSpeed |
保护-寒冷-敌方攻击速度数值降低(直接加算) |
注解 | 含义 |
---|---|
@True |
真实伤害 |
@InjurySanity |
神经损伤 |
@InjuryFire |
灼燃损伤 |
@InjuryDark |
凋亡损伤 |
@AttackSpeed |
攻击速度 |
@SkillAutomatic |
自然回复技能回转时长 |
@SkillOffensive |
攻击回复技能回转时长 |
@SkillOffensiveAttackCount |
攻击回复技能回转期间普通攻击次数 |
注解 | 含义 |
---|---|
@EnemyAttackSpeedLoss |
敌方攻击速度数值降低(直接加算) |
@DamageLoss |
受到的伤害比例降低(最终乘算) |
@Evasion |
闪避和减命中(最终乘算) |
@CrystalBarrier |
琉璃璧效果(林 ) |
@ProjectileRemoval |
弹道消除效果(余 ) |
@BlockCount |
强制修改该单位的阻挡数(玛恩纳 ) |
@PositionMelee |
强制将该单位设为地面单位(Mon3tr ) |
@PositionRanged |
强制将该单位设为高台单位 |
伤害基本语法块包括物理、法术、元素、真实伤害,以及元素损伤。可以在各个伤害乘区(即代码中的...
)处加入具体数值,其中使用的变量名及其含义如下。
伤害基本语法块及相关变量:
MAX(
(((BaseAttackGG01+...)*(1+...)+...)*...+...)*0.05,
(((BaseAttackGG01+...)*(1+...)+...)*...+...)-MAX(((BaseDefenseMajor-(...))*...-...)...,0)
)*(...)
(((BaseAttackGG01+...)*(1+...)+...)*...+...)*(MEDIAN(100-((BaseResistanceMajor-(...))*...-(...))*...,5,100)/100)*(...)
(((BaseAttackGG01+...)*(1+...)+...)*...+...)*(MEDIAN(100-EnemyElementalResistanceMajor,0,100)/100)*(...)
(((N("@InjuryDark")+BaseAttackGG01+...)*(1+...)+...)*...+...)*(MEDIAN(100-EnemyInjuryResistanceMajor,0,100)/100)*(...)
(((N("@True")+BaseAttackGG01+...)*(1+...)+...)*...+...)*(...)
变量名 | 含义 |
---|---|
BaseAttackGG01 |
ID为GG01 的干员(推进之王)的基础攻击力 |
BaseDefenseMajor |
主目标的防御力 |
BaseResistanceMajor |
主目标的法术抗性 |
EnemyElementalResistanceMajor |
主目标的元素抗性 |
EnemyInjuryResistanceMajor |
主目标的损伤抵抗 |
结合帧对齐的知识,我们可以得出攻击间隔的基本语法块,其中理论攻击间隔可以进行加算和乘算,攻击速度可以在默认值100
的基础上进行增减。然而,在复杂情况下,攻击间隔是可变的,例如伊内丝
二技能暗夜无明
和忍冬
三技能隐狐之艺
期间的攻击速度不断改变,这种情况下不能再使用定长的攻击间隔语法块,需要改用手动攻击速度语法块,编译器也会正确注入相关的增益。
(ROUND((1.25+...)*.../((100+...)/100)*30,0)/30)
(N("@AttackSpeed")+280+...)
在计算技能的周期DPS时,我们需要考虑技能的回转时长。由于技力光环增益的存在,技能的回转可以被缩短,需要使用注解让编译器注入对应的友方技力增益。其中,在没有增益时,自然回复技能的回转时长等于消耗的技力数,攻击回复技能的时长等于消耗的技力数×攻击间隔。计算强力击类攻回技能的周期DPS时,需要提供技能回转期间的普攻次数,该次数会受到攻击间隔和友方增益的影响,因此需要为编译器提供攻击间隔(此处使用0*攻击间隔
在告知编译器的情况下不影响公式计算结果),编译器会将包含攻击间隔的增益片段注入公式。
(N("@SkillAutomatic")+20/(1+...))
(N("@SkillOffensive")+2*(ROUND(...)/30))
(N("@SkillOffensiveAttackCount")+2+0*(ROUND(...)/30))
在部分情况下,我们需要在一个公式中同时计算多名友方单位的输出,召唤类干员如死芒
的所有技能均包含本体和召唤物的输出,元素干员组合如塑心
+妮芙
的输出由两名干员同时在场组成。UOEP-FL支持公式中出现任意数量的干员,且编译器会自动分析公式中的片段属于哪位干员提供的伤害,并注入对应类别的增益,如塑心
+妮芙
组合中塑心
的部分会注入辅助职业的增益,妮芙
的部分会注入术师职业的增益。
然而,并非在所有情况下都能如此顺利。部分公式的基本语法块默认写法并未指出该部分属于哪位干员,这种情况下需要人工在该语法块中指出。例如,对于如下计算死芒
普攻DPS的公式,同时包含了本体和召唤物的伤害:
=(
(
(((BaseAttackDB01)))*(MEDIAN(100-EnemyResistanceMajor,5,100)/100)
)/(ROUND(1.6*30,0)/30)+
(
(((BaseSummonAttackDB01)*(1+1)))*(MEDIAN(100-EnemyResistanceMajor,5,100)/100)
)/(ROUND(1.8*30,0)/30)
)
编译器将报错ValueError: Expect a subject in (ROUND(1.6*30,0)/30)
,提示找不到攻击间隔(ROUND(1.6*30,0)/30)
的主人。这时需要用户手动在其末尾用0*BaseAttackDB01
告知编译器该攻击间隔的从属单位为死芒本体,如下所示,即可正确通过编译。
=(
(
(((BaseAttackDB01)))*(MEDIAN(100-EnemyResistanceMajor,5,100)/100)
)/(ROUND(1.6*30,0)/30+0*BaseAttackDB01)+
(
(((BaseSummonAttackDB01)*(1+1)))*(MEDIAN(100-EnemyResistanceMajor,5,100)/100)
)/(ROUND(1.8*30,0)/30+0*BaseSummonAttackDB01)
)
与输出公式计算DPS不同,承伤公式计算的是我方单位受到的DPH。提供一个承伤公式的模板,所有承伤公式均可使用该模板编写。使用的变量名及其含义在下方列出。
=MAX(IFS(
EnemyDamageType=LiteralPhysical,MAX(
(EnemyDamagePerHit)*0.05,
(EnemyDamagePerHit)-((BaseDefenseGG01))
),
EnemyDamageType=LiteralMagical,(EnemyDamagePerHit)*(MEDIAN(100-((BaseResistanceGG01)),5,100)/100),
EnemyDamageType=LiteralTrue,(EnemyDamagePerHit)
),0)*(N("@ProjectileRemoval")+IF(EnemyRanged,0.8,1))+0*((BaseHealthGG01))+0*(N("@EnemyAttackSpeedLoss")+0)+0*(N("@CrystalBarrier")+200)+0*(N("@BlockCount")+3)+N("@PositionMelee")+N("@PositionRanged")
变量名 | 含义 |
---|---|
EnemyDamageType |
敌方伤害类型 |
EnemyDamagePerHit |
敌方每击伤害 |
EnemyRanged |
敌方可远程攻击 |
BaseDefenseGG01 |
ID为GG01 的干员(推进之王)的基础防御力 |
BaseResistanceGG01 |
ID为GG01 的干员(推进之王)的基础法术抗性 |
BaseHealthGG01 |
ID为GG01 的干员(推进之王)的基础生命值 |
LiteralPhysical |
物理伤害字面量 |
LiteralMagical |
法术伤害字面量 |
LiteralTrue |
真实伤害字面量 |
弹道消除:可以使用@ProjectileRemoval
表示敌方远程弹道消除效果,该增益目前由余
三技能灶里乾坤
提供,为可选项。
(N("@ProjectileRemoval")+IF(EnemyRanged,0.8,1))
敌方攻击速度数值降低:可以使用@EnemyAttackSpeedLoss
表示减攻速效果,后跟直接加算值,为可选项。
0*(N("@EnemyAttackSpeedLoss")+0)
强制修改阻挡数和位置:可以使用@BlockCount
表示阻挡数,@PositionMelee
表示修改为地面单位,@PositionRanged
表示修改为高台单位,均为可选项。
0*(N("@BlockCount")+3)
N("@PositionMelee")
N("@PositionRanged")
琉璃璧:可以使用@CrystalBarrier
表示琉璃璧效果,后跟吸收伤害的最大值,该增益目前由林
一天赋计出万全
提供,为可选项。
0*(N("@CrystalBarrier")+200)
需要Python 3.10或以上版本。在Windows环境下,建议使用Windows Terminal,键入以下命令可看到帮助说明。
PS C:\> python3 arknights_formula_helper.py --help
usage: arknights_formula_helper.py [-h] [--from {damage,endurance}] [--to {buff,decay,enemy}]
[--minify {auto,off,max}] [--file] [--dry-run]
Effortlessly tailor your Arknights formulas to various battlefields with all possible buffs
options:
-h, --help show this help message and exit
--from {damage,endurance}
formula source type
--to {buff,decay,enemy}
formula target type
--minify {auto,off,max}
whether to minify the generated formula
--file read input from `input.txt` and write output to `output.txt`
--dry-run do not write the disk
参数 | 说明 |
---|---|
--help |
显示帮助 |
--from damage |
输入输出公式(默认值) |
--from endurance |
输入承伤公式 |
--to buff |
输出增益公式(默认值) |
--to decay |
输出增益衰减公式,用于输出衰减 承伤衰减 |
--to enemy |
输出增益实战公式,用于输出实战 承伤实战 |
--minify auto |
按需压缩公式,仅在公式长度超过阈值时压缩 |
--minify off |
不压缩公式 |
--minify max |
最大程度压缩公式 |
--file |
从文件读写,而非从控制台读写 |
--dry-run |
空跑,不将结果写入磁盘 |
交互式使用样例如下,输入公式后,按Ctrl + Z
结束输入,编译器将输出增益后的公式。不输入公式的情况下,按Ctrl + Z
可退出,也可输入:quit
退出。输入:clear
可以清屏。
PS C:\> python3 arknights_formula_helper.py
Formula >
============================================================
=(
(
(((BaseAttackDB01)))*(MEDIAN(100-EnemyResistanceMajor,5,100)/100)
)/(ROUND(1.6*30,0)/30+0*BaseAttackDB01)+
(
(((BaseSummonAttackDB01)*(1+1)))*(MEDIAN(100-EnemyResistanceMajor,5,100)/100)
)/(ROUND(1.8*30,0)/30+0*BaseSummonAttackDB01)
)
^Z
------------------------------------------------------------
=IF(OR(BuffDamageApplyToSingleAllyOnly,BuffDamageApplyToNonSummonAllyOnly,ISNUMBER(SEARCH("DB01",BuffSourceIds))),0,(
(
(((BaseAttackDB01+BuffDamageAttackFirstValue)*(1+(N("@MonoEnergizedAttack")+MAX(BuffDamageMonoEnergizedAttackFirstRatio))+BuffDamageAttackFirstRatio+BuffDamageAttackFirstRatioCaster)+(N("@MonoEncouragedAttack")+MAX(BuffDamageMonoEncouragedAttackFinalValue))+BuffDamageAttackFinalValue)+BuffDamageMagicalGainValue)*(MEDIAN(100-MAX(((EnemyResistanceMajor-(0+(N("@MonoEnemyFrozenResistance")+MAX(BuffDamageMonoEnemyFrozenResistanceLossValue))+BuffDamageEnemyResistanceLossValue))*BuffDamageEnemyResistanceLossFinalRatio),0),5,100)/100)*(1*(N("@MonoEnemyVulnerable")+MAX(BuffDamageMonoEnemyVulnerableFinalRatio))*(N("@MonoEnemyVulnerableMagical")+MAX(BuffDamageMonoEnemyVulnerableMagicalFinalRatio))*BuffDamageMagicalFinalRatio)
)/(ROUND(1.6/((100+BuffDamageAttackSpeedFirstValue+BuffDamageAttackSpeedFirstValueRanged)/100)*30,0)/30+0*BaseAttackDB01)+
(
(((BaseSummonAttackDB01+BuffDamageAttackFirstValue)*(1+1+(N("@MonoEnergizedAttack")+MAX(BuffDamageMonoEnergizedAttackFirstRatio,BuffDamageMonoEnergizedAttackFirstRatioMelee))+BuffDamageAttackFirstRatio+BuffDamageAttackFirstRatioMelee)+(N("@MonoEncouragedAttack")+MAX(BuffDamageMonoEncouragedAttackFinalValue))+BuffDamageAttackFinalValue)+BuffDamageMagicalGainValue)*(MEDIAN(100-MAX(((EnemyResistanceMajor-(0+(N("@MonoEnemyFrozenResistance")+MAX(BuffDamageMonoEnemyFrozenResistanceLossValue))+BuffDamageEnemyResistanceLossValue))*BuffDamageEnemyResistanceLossFinalRatio),0),5,100)/100)*(1*(N("@MonoEnemyVulnerable")+MAX(BuffDamageMonoEnemyVulnerableFinalRatio))*(N("@MonoEnemyVulnerableMagical")+MAX(BuffDamageMonoEnemyVulnerableMagicalFinalRatio))*BuffDamageMagicalFinalRatio)
)/(ROUND(1.8/((100+BuffDamageAttackSpeedFirstValue+BuffDamageAttackSpeedFirstValueMelee)/100)*30,0)/30+0*BaseSummonAttackDB01)
))
============================================================
查看数据使用方法
受到时间和工作量限制,文档必有许多不详之处,将在未来版本中改进,希望您能谅解。