feedback1f WT: 起飞 review 2 — 暴虐兵长's 暴击 splash and the ticks of debuffs already on her reach an airborne 蒂比
PRTS 萨卡兹悖谬暴虐兵长 技能0 暴击 "对目标和周围4格的我方单位造成攻击力150%的物理普通伤害(无视无法选择,无视迷彩)": the splash passes ignoreSelect. The ticks of a debuff a ground enemy put on her before she took off (出血, 沙狱, the burning DoT, 淤困, 【自然涌动】) select nobody (PRTS 异常效果: 无法选择 effects only act on selection), so they pass ignoreSelect too; a shot already in flight is still refused [ASSUMED]. Comments in tier2.js and SIM.md no longer say that nothing of a ground enemy damages her.
This commit is contained in:
1 parent
2ec51158d0
commit
a973bbe35f
7 files changed
+120
-36
No files matched your search
+12
-8
@@ -209,10 +209,11 @@ an airborne ally (起飞, flag `liftoff`: 蒂比's skills) never for a ground en
|
|||||||
PRTS 术语释义 起飞 "无法被不同阵营行动方式为地面的单位选中"; flyers, 近地悬浮 and 浮空 enemies still pick it; content picks
|
PRTS 术语释义 起飞 "无法被不同阵营行动方式为地面的单位选中"; flyers, 近地悬浮 and 浮空 enemies still pick it; content picks
|
||||||
and one-shot areas that bypass `canTargetAlly` — 控潮术师 / 腐败骑士's 周围四格, 陷落雪祀's chain, the 碎铳之簧 bounce, the
|
and one-shot areas that bypass `canTargetAlly` — 控潮术师 / 腐败骑士's 周围四格, 陷落雪祀's chain, the 碎铳之簧 bounce, the
|
||||||
自行炮 shells, “帝国的甲胄”'s barrage, 鼠王's 沙狱, 烹泉's death blast and steam — filter it too; not selections, so they
|
自行炮 shells, “帝国的甲胄”'s barrage, 鼠王's 沙狱, 烹泉's death blast and steam — filter it too; not selections, so they
|
||||||
still reach it (`ignoreSelect`): abilities PRTS marks "无视无法选择" (【污染秽蚀】, 假想敌:铳's 【盲信之誓】), direct picks
|
still reach it (`ignoreSelect`): abilities PRTS marks "无视无法选择" (【污染秽蚀】, 假想敌:铳's 【盲信之誓】, 萨卡兹悖谬
|
||||||
(碎铳之簧's 法术护盾 counter on its attacker — PRTS 异常效果 "'直接选中'的能力…不受这些仅在选择时生效的异常效果制约") and
|
暴虐兵长's 暴击 splash), direct picks (碎铳之簧's 法术护盾 counter on its attacker — PRTS 异常效果 "'直接选中'的能力…不受这些
|
||||||
the blasts of flying units credited to the ground 胄 (刺胄之弹, 斩胄之剑 / 破胄之锤 — whose 掷剑 / 掷锤 pick "(无视无法选择)");
|
仅在选择时生效的异常效果制约"), the blasts of flying units credited to the ground 胄 (刺胄之弹, 斩胄之剑 / 破胄之锤 — whose
|
||||||
auras of ground enemies still apply [ASSUMED]);
|
掷剑 / 掷锤 pick "(无视无法选择)") and the ticks of a debuff a ground enemy put on it before it took off (出血, 沙狱,
|
||||||
|
burning DoTs, 淤困, 【自然涌动】 — a tick selects nobody); auras of ground enemies still apply [ASSUMED]);
|
||||||
a stealthed ally (隐匿, 排气格栅) only for the enemy it blocks — our operators keep 隐匿 while blocking (PRTS 作战机制
|
a stealthed ally (隐匿, 排气格栅) only for the enemy it blocks — our operators keep 隐匿 while blocking (PRTS 作战机制
|
||||||
§隐匿; 索敌的概念: a blocked enemy "强行无视对方可选性" attacks its blocker); a camouflaged one (迷彩, flag `camou`: ba.camou
|
§隐匿; 索敌的概念: a blocked enemy "强行无视对方可选性" attacks its blocker); a camouflaged one (迷彩, flag `camou`: ba.camou
|
||||||
"不阻挡时不成为敌方普通攻击的目标") likewise (PRTS 异常效果: neither anomaly is "阻挡时解除") [ASSUMED: enemy skills and
|
"不阻挡时不成为敌方普通攻击的目标") likewise (PRTS 异常效果: neither anomaly is "阻挡时解除") [ASSUMED: enemy skills and
|
||||||
@@ -384,7 +385,9 @@ A `rangeExtend` on a `persist` never-expiring buff is **permanent**: it also wid
|
|||||||
untargetable blockFly noMove noSp burstLock hidden attract float noDisplace isolated camou liftoff` (`float` = 近地悬浮 (an air
|
untargetable blockFly noMove noSp burstLock hidden attract float noDisplace isolated camou liftoff` (`float` = 近地悬浮 (an air
|
||||||
unit, `Unit.isFlying`), `liftoff` = an ally's 起飞 (蒂比's skills; gamedata_const ba.liftoff "不阻挡地面敌人且不会被地面敌人攻击,
|
unit, `Unit.isFlying`), `liftoff` = an ally's 起飞 (蒂比's skills; gamedata_const ba.liftoff "不阻挡地面敌人且不会被地面敌人攻击,
|
||||||
可以阻挡飞行敌人"): it blocks no ground enemy (`Battle._blockerFor`) and has 对地规避 — no ground enemy (not `isFlying`)
|
可以阻挡飞行敌人"): it blocks no ground enemy (`Battle._blockerFor`) and has 对地规避 — no ground enemy (not `isFlying`)
|
||||||
selects it, damages it or puts a status on it (`targeting.js evadesGround`); flyers, 近地悬浮 and 浮空 enemies still do,
|
selects it, so no selected damage or status of one lands on it (`targeting.js evadesGround`), while what selects nobody
|
||||||
|
still does (`ignoreSelect` / 无来源: 无视无法选择 abilities, direct picks, flying units' blasts, a debuff's ticks); flyers,
|
||||||
|
近地悬浮 and 浮空 enemies still select it,
|
||||||
with no 对空 check, since it stays a ground unit on its tile (`unit.ground` unchanged; PRTS 行动方式 "起飞的干员仍然是地面单位"),
|
with no 对空 check, since it stays a ground unit on its tile (`unit.ground` unchanged; PRTS 行动方式 "起飞的干员仍然是地面单位"),
|
||||||
`noDisplace` = 失衡免疫 (`displace()` moves nothing), `isolated` = 孤立 ("无法被同阵营选中": no ally
|
`noDisplace` = 失衡免疫 (`displace()` moves nothing), `isolated` = 孤立 ("无法被同阵营选中": no ally
|
||||||
ability selects it — no heal, buff, aura or talent pick from another ally (`battle.allySelectable` / `alliesFor`,
|
ability selects it — no heal, buff, aura or talent pick from another ally (`battle.allySelectable` / `alliesFor`,
|
||||||
@@ -490,7 +493,7 @@ HP left is refused (damage.js `hasHp`: a lethal hit's `damaged` hook runs before
|
|||||||
element?, defIgnoreFlat, defIgnorePct, resIgnoreFlat, resIgnorePct, mul=1, canDodge (phys/arts), isSkill, isSplash,
|
element?, defIgnoreFlat, defIgnorePct, resIgnoreFlat, resIgnorePct, mul=1, canDodge (phys/arts), isSkill, isSplash,
|
||||||
isAttack, attackId, ignoreSleep, ignoreSelect, sourceless, tags[], cancel }` (`ignoreSelect`: no selection 无法选择
|
isAttack, attackId, ignoreSleep, ignoreSelect, sourceless, tags[], cancel }` (`ignoreSelect`: no selection 无法选择
|
||||||
effects stop — an ability "无视无法选择", a direct pick such as a counter on the attacker, a flying unit's blast credited to
|
effects stop — an ability "无视无法选择", a direct pick such as a counter on the attacker, a flying unit's blast credited to
|
||||||
a ground leader — it reaches an airborne 起飞 ally whatever its source; `sourceless`: 无来源 damage — the source's stats add
|
a ground leader, the tick of a debuff already on the unit — it reaches an airborne 起飞 ally whatever its source; `sourceless`: 无来源 damage — the source's stats add
|
||||||
nothing and the hooks get `source: null` plus `credit` = the source, which keeps the stats and the kill; a `loseHp` whose
|
nothing and the hooks get `source: null` plus `credit` = the source, which keeps the stats and the kill; a `loseHp` whose
|
||||||
`from` is 无来源 is 无来源 too; element bursts, leader-part transfers and 坚守 thorns use it — content damage that has a
|
`from` is 无来源 is 无来源 too; element bursts, leader-part transfers and 坚守 thorns use it — content damage that has a
|
||||||
responsible unit should pass it as `source` with `sourceless: true` rather than `source: null`, which credits nobody)
|
responsible unit should pass it as `source` with `sourceless: true` rather than `source: null`, which credits nobody)
|
||||||
@@ -500,8 +503,9 @@ for "本次攻击" procs that must roll once per attack. **Dodge** from several
|
|||||||
`s.dodgePhys` = 1 − Π(1 − pᵢ) (a single source keeps its exact value).
|
`s.dodgePhys` = 1 − Π(1 − pᵢ) (a single source keeps its exact value).
|
||||||
Order: invulnerable / asleep / 对地规避? (a ground enemy's damage or element fill on an airborne 起飞 ally — flag `liftoff`,
|
Order: invulnerable / asleep / 对地规避? (a ground enemy's damage or element fill on an airborne 起飞 ally — flag `liftoff`,
|
||||||
`targeting.js evadesGround` — is refused unless `ignoreSelect` or 无来源: it cannot select her, so its splash and area
|
`targeting.js evadesGround` — is refused unless `ignoreSelect` or 无来源: it cannot select her, so its splash and area
|
||||||
abilities skip her and a shot in flight or a DoT ticking when she took off lands on nothing [ASSUMED for the last two:
|
abilities skip her and a shot in flight when she took off lands on nothing [ASSUMED: PRTS 伤害流程 7 "取消掉隐匿/无敌状态
|
||||||
PRTS 伤害流程 7 "取消掉隐匿/无敌状态下的攻击" read for 对地规避]; checked before `hit` only — 蒂比 S2 takes off inside the
|
下的攻击" read for 对地规避]; the ticks of a debuff already on her select nobody and land (content passes `ignoreSelect`;
|
||||||
|
PRTS 异常效果: 无法选择 effects "仅在选择时生效"); checked before `hit` only — 蒂比 S2 takes off inside the
|
||||||
`hit` of the attack that set it off, which resolves as usual) → **`hit`** (mutate `dmg`, set `dmg.cancel`) → dodge
|
`hit` of the attack that set it off, which resolves as usual) → **`hit`** (mutate `dmg`, set `dmg.cancel`) → dodge
|
||||||
(`rng()`) → mitigation (phys
|
(`rng()`) → mitigation (phys
|
||||||
`max(A − max(0, D×(1−defIgnorePct) − defIgnoreFlat), 5 %A)`, arts `max(A×(1 − R′/100), 5 %A)`, elemental
|
`max(A − max(0, D×(1−defIgnorePct) − defIgnoreFlat), 5 %A)`, arts `max(A×(1 − R′/100), 5 %A)`, elemental
|
||||||
|
|||||||
@@ -55,8 +55,8 @@
|
|||||||
// Every leader (tag boss) ignores 侵蚀 gauge damage ("最终攻势中,敌方领袖不会受到侵蚀损伤").
|
// Every leader (tag boss) ignores 侵蚀 gauge damage ("最终攻势中,敌方领袖不会受到侵蚀损伤").
|
||||||
// An airborne (起飞) operator is no selection of a ground leader or part (对地规避: canTargetAlly, the damage pipeline,
|
// An airborne (起飞) operator is no selection of a ground leader or part (对地规避: canTargetAlly, the damage pipeline,
|
||||||
// the 碎铳之簧 bounce); still reach it (`ignoreSelect`): the 刺胄之弹 / 剑 / 锤 blasts (flying units, 无来源 DoT; 掷剑 /
|
// the 碎铳之簧 bounce); still reach it (`ignoreSelect`): the 刺胄之弹 / 剑 / 锤 blasts (flying units, 无来源 DoT; 掷剑 /
|
||||||
// 掷锤 pick their operator "(无视无法选择)"), the 盲信之誓 chains ("无视无法选择") and the 法术护盾 counter on its attacker
|
// 掷锤 pick their operator "(无视无法选择)"), the 盲信之誓 chains ("无视无法选择"), the 法术护盾 counter on its attacker
|
||||||
// (a direct pick).
|
// (a direct pick) and the ticks of a debuff already on it (【自然涌动】: a tick selects nobody).
|
||||||
// LP effects ('lpLoss' hook + result.lpLoss) must be applied by the match (see the report of this module's owner).
|
// LP effects ('lpLoss' hook + result.lpLoss) must be applied by the match (see the report of this module's owner).
|
||||||
// fx kinds: 'beam' 'shell' 'explode' 'telegraph' 'charge' 'link' 'dash' 'column' 'tide' 'rockfall' 'tentacle' 'equip'
|
// fx kinds: 'beam' 'shell' 'explode' 'telegraph' 'charge' 'link' 'dash' 'column' 'tide' 'rockfall' 'tentacle' 'equip'
|
||||||
// 'sword' 'vest' 'blink' 'summon' 'grow' 'phase' 'lpLoss' (x, y + extra {id, r, tiles, kind, tx, ty …}).
|
// 'sword' 'vest' 'blink' 'summon' 'grow' 'phase' 'lpLoss' (x, y + extra {id, r, tiles, kind, tx, ty …}).
|
||||||
@@ -1076,7 +1076,7 @@ function kitDeer(ab, e) {
|
|||||||
b.fx('beam', { x: e2.x, y: e2.y, from: e2.id, to: t.id, kind: 'naturalSurge', dur });
|
b.fx('beam', { x: e2.x, y: e2.y, from: e2.id, to: t.id, kind: 'naturalSurge', dur });
|
||||||
b.applyStatus(t, 'stun', { duration: dur, source: e2 });
|
b.applyStatus(t, 'stun', { duration: dur, source: e2 });
|
||||||
b.addBuff(t, { key: 'boss:surge', duration: dur, interval: 1, visible: true,
|
b.addBuff(t, { key: 'boss:surge', duration: dur, interval: 1, visible: true,
|
||||||
onTick: ({ battle, unit }) => battle.dealDamage(e2, unit, { amount: e2.s.atk * (lasso.bb.atk_scale ?? 0), type: 'arts', canDodge: false, tags: ['enemyAbility'] }) });
|
onTick: ({ battle, unit }) => battle.dealDamage(e2, unit, { amount: e2.s.atk * (lasso.bb.atk_scale ?? 0), type: 'arts', canDodge: false, ignoreSelect: true, tags: ['enemyAbility'] }) });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,9 +32,11 @@
|
|||||||
// ground enemy (对地规避, targeting.js evadesGround): targetsNear / allTargets skip it through canTargetAlly, the picks
|
// ground enemy (对地规避, targeting.js evadesGround): targetsNear / allTargets skip it through canTargetAlly, the picks
|
||||||
// and one-shot areas that bypass it (周围四格 additions, chain and bounce jumps, shells, barrages, 沙狱, death blasts)
|
// and one-shot areas that bypass it (周围四格 additions, chain and bounce jumps, shells, barrages, 沙狱, death blasts)
|
||||||
// filter it, and the engine refuses a ground enemy's damage and statuses on it. Not selections, so they still reach
|
// filter it, and the engine refuses a ground enemy's damage and statuses on it. Not selections, so they still reach
|
||||||
// it (`ignoreSelect`): abilities PRTS marks "无视无法选择" (【污染秽蚀】, 【盲信之誓】), direct picks of the attacker
|
// it (`ignoreSelect`): abilities PRTS marks "无视无法选择" (【污染秽蚀】, 【盲信之誓】, 萨卡兹悖谬暴虐兵长's 暴击 splash),
|
||||||
// (碎铳之簧's counter — PRTS 异常效果 "'直接选中'的能力…不受这些仅在选择时生效的异常效果制约") and the blasts of flying
|
// direct picks of the attacker (碎铳之簧's counter — PRTS 异常效果 "'直接选中'的能力…不受这些仅在选择时生效的异常效果
|
||||||
// units credited to a ground leader (刺胄之弹, 斩胄之剑 / 破胄之锤). Auras of ground enemies still apply [ASSUMED].
|
// 制约"), the blasts of flying units credited to a ground leader (刺胄之弹, 斩胄之剑 / 破胄之锤) and the ticks of a debuff
|
||||||
|
// already on it (出血, 沙狱, “庞贝”'s burning, 淤困, 【自然涌动】 — a tick selects nobody). Auras of ground enemies still
|
||||||
|
// apply [ASSUMED].
|
||||||
//
|
//
|
||||||
// Special types (factions.json):
|
// Special types (factions.json):
|
||||||
// FLY — engine (FLY motion, ranged-only targeting). Flyer kits below (御4, 护障, 寒霜, 萨科塔之翼/眼, 黑云 …).
|
// FLY — engine (FLY motion, ranged-only targeting). Flyer kits below (御4, 护障, 寒霜, 萨科塔之翼/眼, 黑云 …).
|
||||||
@@ -889,7 +891,10 @@ function statue(ab) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bleeding on hit (逐腐兽): arts damage per second, removed by healing. */
|
/**
|
||||||
|
* Bleeding on hit (逐腐兽): arts damage per second, removed by healing. A tick selects nobody (`ignoreSelect`): it keeps
|
||||||
|
* hurting an ally that took off (起飞) after the bleed landed; so do the other enemy debuff DoTs (沙狱, burnDot, 淤困).
|
||||||
|
*/
|
||||||
function bleed(ab) {
|
function bleed(ab) {
|
||||||
const dmg = T(ab, 'Bleeding.attack@bleeding_damage') ?? 0, dur = T(ab, 'Bleeding.attack@duration') ?? 0;
|
const dmg = T(ab, 'Bleeding.attack@bleeding_damage') ?? 0, dur = T(ab, 'Bleeding.attack@duration') ?? 0;
|
||||||
return {
|
return {
|
||||||
@@ -897,7 +902,7 @@ function bleed(ab) {
|
|||||||
if (!(dmg > 0 && dur > 0)) return;
|
if (!(dmg > 0 && dur > 0)) return;
|
||||||
b.addBuff(c.target, {
|
b.addBuff(c.target, {
|
||||||
key: 'ab:bleed', duration: dur, refresh: 'replace', interval: 1, visible: true,
|
key: 'ab:bleed', duration: dur, refresh: 'replace', interval: 1, visible: true,
|
||||||
onTick: ({ battle, unit }) => battle.dealDamage(e, unit, { amount: dmg, type: 'arts', canDodge: false, tags: ['enemyAbility', 'bleed'] }),
|
onTick: ({ battle, unit }) => battle.dealDamage(e, unit, { amount: dmg, type: 'arts', canDodge: false, ignoreSelect: true, tags: ['enemyAbility', 'bleed'] }),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1321,6 +1326,11 @@ function kitRush(ab) {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 萨卡兹悖谬暴虐兵长: blocked only by a blocker with ≥ 3 free block; its first hit also strikes the units around its target
|
||||||
|
* (PRTS 技能0 暴击 "对目标和周围4格的我方单位造成攻击力150%的物理普通伤害(无视无法选择,无视迷彩)※此技能仅能触发一次"):
|
||||||
|
* the splash ignores 无法选择, so it reaches an airborne 起飞 ally too (`ignoreSelect`).
|
||||||
|
*/
|
||||||
function kitFirstAoe(ab) {
|
function kitFirstAoe(ab) {
|
||||||
const scale = T(ab, 'AOEAttack.atk_scale') ?? 0;
|
const scale = T(ab, 'AOEAttack.atk_scale') ?? 0;
|
||||||
return [blockWeight(3), {
|
return [blockWeight(3), {
|
||||||
@@ -1328,7 +1338,9 @@ function kitFirstAoe(ab) {
|
|||||||
if (a.done) return;
|
if (a.done) return;
|
||||||
a.done = true;
|
a.done = true;
|
||||||
b.fx('explode', { x: c.target.x, y: c.target.y, r: 1, kind: 'aoeAttack' });
|
b.fx('explode', { x: c.target.x, y: c.target.y, r: 1, kind: 'aoeAttack' });
|
||||||
for (const u of b.alliesInRadius(c.target.x, c.target.y, 1)) if (u !== c.target) hurt(b, e, u, e.s.atk * scale, 'phys');
|
for (const u of b.alliesInRadius(c.target.x, c.target.y, 1)) {
|
||||||
|
if (u !== c.target) hurt(b, e, u, e.s.atk * scale, 'phys', { ignoreSelect: true, tags: ['aoeAttack'] });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -1382,7 +1394,7 @@ function kitParasite(ab) {
|
|||||||
b.addBuff(host, {
|
b.addBuff(host, {
|
||||||
// "受到的元素损伤提高至130%": a 元素损伤 multiplier on the element hit (ensureParasiteHook), not 元素伤害
|
// "受到的元素损伤提高至130%": a 元素损伤 multiplier on the element hit (ensureParasiteHook), not 元素伤害
|
||||||
key: 'ab:parasite', visible: true, interval: 1, data: { src: e, spread: PARASITE_SPREAD, epMul: elMul },
|
key: 'ab:parasite', visible: true, interval: 1, data: { src: e, spread: PARASITE_SPREAD, epMul: elMul },
|
||||||
onTick: ({ battle, unit }) => { if (e.alive) battle.dealDamage(e, unit, { amount: e.s.atk * scale, type: 'arts', canDodge: false, tags: ['enemyAbility', 'parasite'] }); },
|
onTick: ({ battle, unit }) => { if (e.alive) battle.dealDamage(e, unit, { amount: e.s.atk * scale, type: 'arts', canDodge: false, ignoreSelect: true, tags: ['enemyAbility', 'parasite'] }); },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
death(c, b, e, a) { if (a.host) b.removeBuff(a.host, 'ab:parasite'); },
|
death(c, b, e, a) { if (a.host) b.removeBuff(a.host, 'ab:parasite'); },
|
||||||
@@ -1618,7 +1630,7 @@ function kitLeaderMisc(key, ab, e) {
|
|||||||
dealt(c, b, e2) {
|
dealt(c, b, e2) {
|
||||||
if (!(dot.dur > 0)) return;
|
if (!(dot.dur > 0)) return;
|
||||||
b.addBuff(c.target, { key: 'ab:burnDot', duration: dot.dur, refresh: 'replace', interval: dot.iv, visible: true,
|
b.addBuff(c.target, { key: 'ab:burnDot', duration: dot.dur, refresh: 'replace', interval: dot.iv, visible: true,
|
||||||
onTick: ({ battle, unit }) => battle.dealDamage(e2, unit, { amount: dot.dmg, type: 'arts', canDodge: false, tags: ['enemyAbility'] }) });
|
onTick: ({ battle, unit }) => battle.dealDamage(e2, unit, { amount: dot.dmg, type: 'arts', canDodge: false, ignoreSelect: true, tags: ['enemyAbility'] }) });
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
iv: T(ab, 'rangedamage.interval') ?? 10,
|
iv: T(ab, 'rangedamage.interval') ?? 10,
|
||||||
@@ -1677,7 +1689,7 @@ function kitLeaderMisc(key, ab, e) {
|
|||||||
for (const u of b.alliesInRadius(x, y, SANDSTORM_RADIUS)) {
|
for (const u of b.alliesInRadius(x, y, SANDSTORM_RADIUS)) {
|
||||||
if (evadesGround(e2, u)) continue;
|
if (evadesGround(e2, u)) continue;
|
||||||
b.addBuff(u, { key: 'ab:sandStorm', duration: dur, refresh: 'replace', interval: 1, visible: true, mods: { atkPct: ss.bb.atk ?? 0 },
|
b.addBuff(u, { key: 'ab:sandStorm', duration: dur, refresh: 'replace', interval: 1, visible: true, mods: { atkPct: ss.bb.atk ?? 0 },
|
||||||
onTick: ({ battle, unit }) => battle.dealDamage(e2, unit, { amount: ss.bb.damage ?? 0, type: 'arts', canDodge: false, tags: ['enemyAbility', 'sandStorm'] }) });
|
onTick: ({ battle, unit }) => battle.dealDamage(e2, unit, { amount: ss.bb.damage ?? 0, type: 'arts', canDodge: false, ignoreSelect: true, tags: ['enemyAbility', 'sandStorm'] }) });
|
||||||
}
|
}
|
||||||
}, { cond: (b, e2) => allTargets(b, e2).length > 0 }),
|
}, { cond: (b, e2) => allTargets(b, e2).length > 0 }),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ function onDefaultSkill(chess) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 起飞 of 蒂比's skills (gamedata_const ba.liftoff "不阻挡地面敌人且不会被地面敌人攻击,可以阻挡飞行敌人"): the skill's flags
|
* 起飞 of 蒂比's skills (gamedata_const ba.liftoff "不阻挡地面敌人且不会被地面敌人攻击,可以阻挡飞行敌人"): the skill's flags
|
||||||
* `liftoff` (no ground enemy blocked — Battle._blockerFor; 对地规避 — targeting.js evadesGround: no ground enemy selects or
|
* `liftoff` (no ground enemy blocked — Battle._blockerFor; 对地规避 — targeting.js evadesGround: no ground enemy selects
|
||||||
* damages her) and `blockFly` (blocks flyers at the air radius). She stays a ground unit on her tile (PRTS 行动方式 "起飞的
|
* her, while what selects nobody still lands — 无视无法选择 abilities, direct picks, flying units' blasts, 无来源 damage, the
|
||||||
|
* ticks of a debuff already on her) and `blockFly` (blocks flyers at the air radius). She stays a ground unit on her tile (PRTS 行动方式 "起飞的
|
||||||
* 干员仍然是地面单位": `unit.ground` unchanged — 隐德来希's 血镰, 地面干员 bonds / items still count her).
|
* 干员仍然是地面单位": `unit.ground` unchanged — 隐德来希's 血镰, 地面干员 bonds / items still count her).
|
||||||
*/
|
*/
|
||||||
const LIFTOFF_FLAGS = Object.freeze({ blockFly: true, liftoff: true });
|
const LIFTOFF_FLAGS = Object.freeze({ blockFly: true, liftoff: true });
|
||||||
@@ -597,7 +598,7 @@ export default {
|
|||||||
// ---------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------
|
||||||
// 2_13 蒂比 紧急赶场通知 (AUTO): "受到攻击后触发" — any incoming enemy attack sets it off (the kit is the only
|
// 2_13 蒂比 紧急赶场通知 (AUTO): "受到攻击后触发" — any incoming enemy attack sets it off (the kit is the only
|
||||||
// trigger: the engine rule is disabled so non-attack damage never fires it) and a physical/arts one is dodged; takes
|
// trigger: the engine rule is disabled so non-attack damage never fires it) and a physical/arts one is dodged; takes
|
||||||
// off for the duration (起飞, LIFTOFF_FLAGS: she blocks no ground enemy and none attacks or damages her): skill range,
|
// off for the duration (起飞, LIFTOFF_FLAGS: she blocks no ground enemy and none selects her — 对地规避): skill range,
|
||||||
// ATK +atk, attacks become 3 shots, blocks flying (not ground) enemies. Trait "起飞后能够阻挡2个飞行敌人": flying enemies
|
// ATK +atk, attacks become 3 shots, blocks flying (not ground) enemies. Trait "起飞后能够阻挡2个飞行敌人": flying enemies
|
||||||
// are blocked only while airborne. 片场工作指南: if not attacked for stack_time s, the next physical/arts attack is
|
// are blocked only while airborne. 片场工作指南: if not attacked for stack_time s, the next physical/arts attack is
|
||||||
// dodged (prob); every attack restarts that timer.
|
// dodged (prob); every attack restarts that timer.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// server/sim/damage.js — damage & heal pipeline, shields, dodge, element gauges (DESIGN §5.5).
|
// server/sim/damage.js — damage & heal pipeline, shields, dodge, element gauges (DESIGN §5.5).
|
||||||
//
|
//
|
||||||
// dealDamage order: (element → gauge path) | invulnerable? / 对地规避 (a ground enemy's damage to an airborne 起飞 ally —
|
// dealDamage order: (element → gauge path) | invulnerable? / 对地规避 (a ground enemy's damage to an airborne 起飞 ally —
|
||||||
// targeting.js evadesGround; not `ignoreSelect` damage) → 'hit' hook (mutable DamageInfo, may set cancel)
|
// targeting.js evadesGround; not `ignoreSelect` damage: no selection, e.g. a debuff's tick) → 'hit' hook (mutable DamageInfo, may set cancel)
|
||||||
// → dodge (phys/arts, canDodge) → mitigation (phys: DEF, arts: RES, true: none)
|
// → dodge (phys/arts, canDodge) → mitigation (phys: DEF, arts: RES, true: none)
|
||||||
// → × source dmgDealtMul (× phys/artsDealtMul) × target dmgTakenMul (not for 元素伤害) × type-taken mul × dmg.mul
|
// → × source dmgDealtMul (× phys/artsDealtMul) × target dmgTakenMul (not for 元素伤害) × type-taken mul × dmg.mul
|
||||||
// → 限伤 (leaders in boss / hidden battles: a hit of ceil(final) ≥ BOSS_HIT_LIMIT is cancelled, see leaderHitCancelled)
|
// → 限伤 (leaders in boss / hidden battles: a hit of ceil(final) ≥ BOSS_HIT_LIMIT is cancelled, see leaderHitCancelled)
|
||||||
@@ -85,7 +85,8 @@ export function makeDamageInfo(d = {}) {
|
|||||||
noSp: !!d.noSp,
|
noSp: !!d.noSp,
|
||||||
ignoreSleep: !!d.ignoreSleep,
|
ignoreSleep: !!d.ignoreSleep,
|
||||||
// no selection 无法选择 effects stop (an ability that "无视无法选择" such as PRTS 【污染秽蚀】, a direct pick, a flying
|
// no selection 无法选择 effects stop (an ability that "无视无法选择" such as PRTS 【污染秽蚀】, a direct pick, a flying
|
||||||
// unit's blast credited to a ground leader): reaches an airborne 起飞 ally whatever the source's 行动方式
|
// unit's blast credited to a ground leader, the tick of a debuff already on the unit): reaches an airborne 起飞 ally
|
||||||
|
// whatever the source's 行动方式
|
||||||
ignoreSelect: !!d.ignoreSelect,
|
ignoreSelect: !!d.ignoreSelect,
|
||||||
sourceless: !!d.sourceless,
|
sourceless: !!d.sourceless,
|
||||||
attackId: d.attackId ?? 0,
|
attackId: d.attackId ?? 0,
|
||||||
@@ -100,10 +101,11 @@ function sleepBlocks(target, source, dmg) {
|
|||||||
/**
|
/**
|
||||||
* 起飞 (flag `liftoff`): a ground enemy's damage never reaches the airborne ally — it cannot select it (对地规避, PRTS 作战机制
|
* 起飞 (flag `liftoff`): a ground enemy's damage never reaches the airborne ally — it cannot select it (对地规避, PRTS 作战机制
|
||||||
* "AOE的判定是对攻击范围内的每个可以被选中的敌人进行判定"), so its splash, area abilities and element fills skip it, and a shot
|
* "AOE的判定是对攻击范围内的每个可以被选中的敌人进行判定"), so its splash, area abilities and element fills skip it, and a shot
|
||||||
* already in flight or a DoT ticking when it took off lands on nothing [ASSUMED: PRTS 伤害流程 7 "取消掉隐匿/无敌状态下的攻击"
|
* already in flight when it took off lands on nothing [ASSUMED: PRTS 伤害流程 7 "取消掉隐匿/无敌状态下的攻击" read for
|
||||||
* read for 对地规避]. Checked before the `hit` hook only: 蒂比 S2 takes off inside the hook of the hit that set it off, which
|
* 对地规避]. Checked before the `hit` hook only: 蒂比 S2 takes off inside the hook of the hit that set it off, which then
|
||||||
* then resolves as usual (dodged if physical / arts — PRTS 备注). Sourceless damage and `ignoreSelect` damage (no
|
* resolves as usual (dodged if physical / arts — PRTS 备注). Sourceless damage and `ignoreSelect` damage still land: no
|
||||||
* selection: 无视无法选择, direct picks, flying units' blasts — targeting.js evadesGround) still land.
|
* selection (targeting.js evadesGround) — 无视无法选择 abilities, direct picks, flying units' blasts, and the ticks of a
|
||||||
|
* debuff already on it (PRTS 异常效果: 无法选择 effects "仅在选择时生效"; the debuff's mods stay too).
|
||||||
*/
|
*/
|
||||||
function liftoffEvades(target, source, dmg) {
|
function liftoffEvades(target, source, dmg) {
|
||||||
return !!target.s.flags.liftoff && !dmg.ignoreSelect && evadesGround(source, target);
|
return !!target.s.flags.liftoff && !dmg.ignoreSelect && evadesGround(source, target);
|
||||||
|
|||||||
@@ -90,9 +90,11 @@ export function canTargetAlly(e, a, ranged) {
|
|||||||
* pipeline and Battle.applyStatus (a ground enemy's area damage, statuses and hits under way skip it — PRTS 作战机制 "AOE
|
* pipeline and Battle.applyStatus (a ground enemy's area damage, statuses and hits under way skip it — PRTS 作战机制 "AOE
|
||||||
* 的判定是对攻击范围内的每个可以被选中的敌人进行判定"), and the content picks and one-shot areas that bypass canTargetAlly
|
* 的判定是对攻击范围内的每个可以被选中的敌人进行判定"), and the content picks and one-shot areas that bypass canTargetAlly
|
||||||
* (chain / bounce jumps, 周围四格 additions, shells, barrages, 沙狱, death blasts). Not selections, so they still reach it:
|
* (chain / bounce jumps, 周围四格 additions, shells, barrages, 沙狱, death blasts). Not selections, so they still reach it:
|
||||||
* sourceless damage and DamageInfo / applyStatus `ignoreSelect` — abilities that "无视无法选择" (【污染秽蚀】, 【盲信之誓】),
|
* sourceless damage and DamageInfo / applyStatus `ignoreSelect` — abilities that "无视无法选择" (【污染秽蚀】, 【盲信之誓】,
|
||||||
* direct picks (碎铳之簧's counter on its attacker: PRTS 异常效果 "'直接选中'的能力…不受这些仅在选择时生效的异常效果制约")
|
* 萨卡兹悖谬暴虐兵长's 暴击 splash), direct picks (碎铳之簧's counter on its attacker: PRTS 异常效果 "'直接选中'的能力…不受这些
|
||||||
* and the blasts of flying units credited to a ground leader (刺胄之弹, 斩胄之剑 / 破胄之锤).
|
* 仅在选择时生效的异常效果制约"), the blasts of flying units credited to a ground leader (刺胄之弹, 斩胄之剑 / 破胄之锤) and
|
||||||
|
* the ticks of a debuff a ground enemy put on it before it took off (出血, 沙狱, burning DoTs, 【自然涌动】: a tick selects
|
||||||
|
* nobody). A shot already in flight is refused [ASSUMED] (damage.js).
|
||||||
*/
|
*/
|
||||||
export function evadesGround(src, a) {
|
export function evadesGround(src, a) {
|
||||||
return !!(a && a.s && a.s.flags.liftoff && src && src.side === 'enemy' && !src.isFlying);
|
return !!(a && a.s && a.s.flags.liftoff && src && src.side === 'enemy' && !src.isFlying);
|
||||||
|
|||||||
@@ -9,14 +9,15 @@
|
|||||||
// the targets its abilities add (控潮术师's 周围四格, chain / bounce jumps) and its area damage and statuses skip her;
|
// the targets its abilities add (控潮术师's 周围四格, chain / bounce jumps) and its area damage and statuses skip her;
|
||||||
// - flyers, 近地悬浮 and 浮空 enemies still select her (she stays a ground unit: no 对空 check);
|
// - flyers, 近地悬浮 and 浮空 enemies still select her (she stays a ground unit: no 对空 check);
|
||||||
// - what is no selection still reaches her: sourceless damage, abilities that "无视无法选择" (【污染秽蚀】 at the
|
// - what is no selection still reaches her: sourceless damage, abilities that "无视无法选择" (【污染秽蚀】 at the
|
||||||
// low-ground rate — she is still on her low tile; 【盲信之誓】), direct picks (碎铳之簧's counter on its attacker —
|
// low-ground rate — she is still on her low tile; 【盲信之誓】; 萨卡兹悖谬暴虐兵长's 暴击 splash), direct picks
|
||||||
// PRTS 异常效果 "'直接选中'的能力…不受这些仅在选择时生效的异常效果制约") and flying units' blasts (刺胄之弹, 斩胄之剑);
|
// (碎铳之簧's counter on its attacker — PRTS 异常效果 "'直接选中'的能力…不受这些仅在选择时生效的异常效果制约"), flying
|
||||||
|
// units' blasts (刺胄之弹, 斩胄之剑) and the ticks of a debuff put on her before she took off (出血, 沙狱, burning);
|
||||||
// a ground enemy's zone does not (集团军重型火炮 【燃烧区域】 "碰撞不受迷彩制约,不可对空": 迷彩 only);
|
// a ground enemy's zone does not (集团军重型火炮 【燃烧区域】 "碰撞不受迷彩制约,不可对空": 迷彩 only);
|
||||||
// - she stays a 地面单位 for ally rules (隐德来希 S2 puts a 血镰 on her; PRTS 备注 "被添加血镰的单位处于起飞时,血镰可对空").
|
// - she stays a 地面单位 for ally rules (隐德来希 S2 puts a 血镰 on her; PRTS 备注 "被添加血镰的单位处于起飞时,血镰可对空").
|
||||||
// Cause: 起飞 was `unit.ground = false` (an operator on a high tile): melee enemies could not reach her (she released them),
|
// Cause: 起飞 was `unit.ground = false` (an operator on a high tile): melee enemies could not reach her (she released them),
|
||||||
// but every ranged ground enemy kept shooting her and the AoE / skills of ground enemies hit her.
|
// but every ranged ground enemy kept shooting her and the AoE / skills of ground enemies hit her.
|
||||||
// [ASSUMED] a ground enemy's damage already under way (a shot in flight, a DoT ticking) is cancelled too (PRTS 作战机制 伤害
|
// [ASSUMED] a ground enemy's shot already in flight is cancelled too (PRTS 作战机制 伤害流程 7 "取消掉隐匿/无敌状态下的攻击",
|
||||||
// 流程 7 "取消掉隐匿/无敌状态下的攻击", read for 对地规避); auras of ground enemies (光环, field-wide debuffs) still apply.
|
// read for 对地规避); auras of ground enemies (光环, field-wide debuffs) still apply.
|
||||||
|
|
||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
@@ -358,3 +359,65 @@ test('F3 review: one-shot area abilities of ground enemies skip an airborne 蒂
|
|||||||
done(h);
|
done(h);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------------------
|
||||||
|
// Second review round: 萨卡兹悖谬暴虐兵长's 暴击 splash ("无视无法选择") and the ticks of debuffs already on her.
|
||||||
|
|
||||||
|
const WDRRL = 'enemy_1320_wdrrl_2'; // 萨卡兹悖谬暴虐兵长 (ground, block ≥ 3): 暴击 "对目标和周围4格…(无视无法选择,无视迷彩)"
|
||||||
|
const NHSTLK = 'enemy_1270_nhstlk'; // 逐腐兽 (ground melee): 出血 on hit — arts per second
|
||||||
|
const LSLIME = 'enemy_1050_lslime'; // “庞贝” (ground): its hits leave a burning DoT
|
||||||
|
|
||||||
|
test('F3 review 2: 萨卡兹悖谬暴虐兵长\'s 暴击 splash ("无视无法选择") reaches an airborne 蒂比 next to its blocker', REAL, () => {
|
||||||
|
const h = makeBattle({
|
||||||
|
stageId: 'act2autochess_m02', seed: 3, autoFinish: false, timeLimit: 400, hooks: ['damaged'], captureNoisy: true,
|
||||||
|
units: [{ chessId: TIPPI[0], row: 9, col: 5, carryState: { sp: 999 }, skillIndex: skillIndex(TIPPI[0], S1) }, { chessId: BAIT, row: 9, col: 4 }],
|
||||||
|
enemies: [{ key: WDRRL, route: { motion: 'WALK', start: [9, 8], end: [9, 1], checkpoints: [] }, mods: { hpMul: 1e3, atkMul: 0.3 } }],
|
||||||
|
});
|
||||||
|
const u = h.unit(TIPPI[0]), bait = h.unit(BAIT);
|
||||||
|
let e = null, airAtBlock = null;
|
||||||
|
for (let i = 0; i < 14 * 30; i++) { // it reaches 角峰 at ≈ 9 s, S1 lasts 32 s
|
||||||
|
h.step();
|
||||||
|
e ??= h.b.enemies.find((x) => x.defId === WDRRL) ?? null;
|
||||||
|
if (e && airAtBlock == null && e.blockedBy) airAtBlock = !!u.s.flags.liftoff;
|
||||||
|
}
|
||||||
|
assert.ok(e && e.blockedBy === bait, '角峰 (block 3) blocks it; she cannot (block < 3)');
|
||||||
|
assert.equal(airAtBlock, true, 'she is airborne when it reaches 角峰');
|
||||||
|
const splash = h.hooksOf('damaged').filter((c) => c.target === u && c.source === e);
|
||||||
|
assert.equal(splash.length, 1, `its one-off splash lands on her: ${splash.map((c) => Math.round(c.amount))}`);
|
||||||
|
assert.ok(splash[0].amount > 0 && !splash[0].dmg.isAttack && splash[0].dmg.ignoreSelect);
|
||||||
|
assert.ok(u.s.flags.liftoff, 'while airborne');
|
||||||
|
assert.ok(h.hooksOf('damaged').some((c) => c.target === bait && c.source === e && c.dmg.isAttack), 'its attack itself is on 角峰');
|
||||||
|
done(h);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('F3 review 2: a debuff a ground enemy put on 蒂比 before she took off keeps ticking (no selection); its new attacks still skip her', REAL, () => {
|
||||||
|
// 鼠王 【沙狱】 (centred near 角峰 and her), 逐腐兽's 出血, “庞贝”'s burning DoT: attached on the ground, then S1
|
||||||
|
const cases = [
|
||||||
|
{ key: MOUSEK, pos: [9, 8], buff: 'ab:sandStorm', bait: true, fire: (h, m) => { m.mem.ab.list.find((a) => a && a.cd === m.mem.ab.sk.SandStorm.cd && a.fire).left = 0.05; } },
|
||||||
|
{ key: NHSTLK, pos: [9, 5.3], buff: 'ab:bleed' },
|
||||||
|
{ key: LSLIME, pos: [9, 7], buff: 'ab:burnDot' },
|
||||||
|
];
|
||||||
|
for (const k of cases) {
|
||||||
|
const units = [{ chessId: TIPPI[0], row: 9, col: 5, carryState: { sp: 0 }, skillIndex: skillIndex(TIPPI[0], S1) }];
|
||||||
|
if (k.bait) units.push({ chessId: BAIT, row: 10, col: 5 });
|
||||||
|
const h = makeBattle({
|
||||||
|
stageId: 'act2autochess_m02', seed: 3, autoFinish: false, timeLimit: 400, hooks: ['damaged'], captureNoisy: true, units,
|
||||||
|
enemies: [{ key: k.key, pos: k.pos, mods: { hpMul: 1e4, speedMul: 0, atkMul: k.bait ? 0.01 : 0.05 } }, { key: LAZERD, pos: [9, 7.2], mods: { hpMul: 1e4, speedMul: 0, atkMul: 0 } }],
|
||||||
|
});
|
||||||
|
const u = h.unit(TIPPI[0]);
|
||||||
|
h.step();
|
||||||
|
const e = h.b.enemies.find((x) => x.defId === k.key);
|
||||||
|
k.fire?.(h, e);
|
||||||
|
assert.ok(h.runUntil(() => u.findBuff(k.buff), 10), `${k.key}: ${k.buff} on her`);
|
||||||
|
assert.ok(!u.s.flags.liftoff, `${k.key}: before she takes off`);
|
||||||
|
u.skill.gainSp(999, 'init');
|
||||||
|
assert.ok(h.runUntil(() => u.s.flags.liftoff, 3), `${k.key}: takes off`);
|
||||||
|
const up = h.b.time;
|
||||||
|
h.run(4);
|
||||||
|
assert.ok(u.s.flags.liftoff && u.findBuff(k.buff), `${k.key}: still airborne with the debuff`);
|
||||||
|
const air = h.hooksOf('damaged').filter((c) => c.target === u && c.source === e && tAt(c) > up + 1e-6);
|
||||||
|
assert.ok(air.filter((c) => !c.dmg.isAttack).length >= 3, `${k.key}: its ticks land while airborne: ${air.length}`);
|
||||||
|
assert.deepEqual(air.filter((c) => c.dmg.isAttack).map((c) => Math.round(c.amount)), [], `${k.key}: no new attack on her`);
|
||||||
|
done(h);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user