feedback2 WI: a running 坚固维式重锤 不死 window stops a dollkeeper's switch to its 替身 (fatal priority −99, the lock stays at −100); the switch flag is trait.dollSwitching; the 替身's own max HP is stated as sourced (review of #44)

This commit is contained in:
sganggs committed 2026-10-04 00:26:13 +08:00
1 parent 29784db4d9
commit cfe85c6fee
8 files changed
+62 -31

No files matched your search

+9 -4
View File
@@ -20,8 +20,10 @@
// either quality satisfies them; lent items count as carried. Both are evaluated live, at the moment of use.
//
// Hook priorities (non-default): 'fatal' — consumable death savers run LAST, so a skill's / talent's own undying (kits
// use 10 … −60) never wastes a charge: 坚固维式重锤's lock (异常效果 不死, once per deployment — deploymentOf; its running
// windows held by one battle-level hook) at PRIO_REVIVE −100, then the M3茧甲 revive at PRIO_RESPAWN −101 — PRTS
// use 10 … −60) never wastes a charge: 坚固维式重锤's lock (异常效果 不死, once per deployment — deploymentOf) at
// PRIO_REVIVE −100 and its running windows, held by one battle-level hook, at PRIO_UNDYING_HELD −99 — before a 傀儡师's
// switch to its 替身 (professions.js, −100: PRTS 分支特性信息 傀儡师 "受到足以致命的伤害且未持有不死的情况下"), while the
// lock itself still comes after that switch (as in 0.1.1); then the M3茧甲 revive at PRIO_RESPAWN −101 — PRTS
// 卫戍协议:盟约 下半/PRTS盟约记录 备注 "“复活”的实现方式为:受益者因移动之外的原因退场时下次部署的再部署时间和费用归零": a
// revive acts on a knock-out, which a 不死 prevents, so the lock always comes first whatever the equip order (player
// report F1 after 0.1.0: with the 茧甲 equipped first the revive ran first and the first lethal hit showed no lock);
@@ -65,6 +67,8 @@ const chance = (battle, p) => p > 0 && (p >= 1 || battle.rng() < p);
const ENEMY_RECORD = /^enemy_/;
/** 'fatal' priorities (see header). */
export const PRIO_REVIVE = -100;
/** A running 坚固 window (不死 held): before a 傀儡师's switch to its 替身 at −100 — see header. */
export const PRIO_UNDYING_HELD = PRIO_REVIVE + 1;
/** The items' 复活 (M3茧甲): acts on a knock-out, so after every 不死 (PRIO_REVIVE) — see header. */
export const PRIO_RESPAWN = PRIO_REVIVE - 1;
const PRIO_FREE_UNDYING = 20;
@@ -277,9 +281,10 @@ function hammerAcquire(battle, rt, u) {
const hs = hammerState(battle, rt, u);
hs.refs++;
if (hs.scope) return hs;
// every running 坚固 window of the battle (holdsUndying): one hook that no grant owns, registered before any lock hook
// every running 坚固 window of the battle (holdsUndying): one hook that no grant owns, ahead of every lock hook and of a
// 傀儡师's switch to its 替身 (PRIO_UNDYING_HELD: a 本体 holding 不死 does not switch)
if (!rt.undyingHook) {
rt.undyingHook = battle.on('fatal', (c) => { if (!c.prevented && holdsUndying(battle, c.unit)) c.prevented = true; }, { priority: PRIO_REVIVE });
rt.undyingHook = battle.on('fatal', (c) => { if (!c.prevented && holdsUndying(battle, c.unit)) c.prevented = true; }, { priority: PRIO_UNDYING_HELD });
}
const S = new Scope(battle, u, `item:hammer#${u.id}`);
hs.scope = S;
+2 -2
View File
@@ -1700,11 +1700,11 @@ const KITS = {
{ install(battle, unit) { // 拥抱自我 (the 替身 fighting: not during a switch animation)
const slow = num(t0.move_speed), scale = num(t0.atk_scale);
whileOn(battle, unit, AURA_IV, () => {
if (!unit.trait.doll || unit.trait.dollSwitch || !slow) return;
if (!unit.trait.doll || unit.trait.dollSwitching || !slow) return;
for (const e of battle.unitsInGrid(unit, aroundGrid, { side: 'enemy' })) if (!e.isFlying) battle.addBuff(e, { key: 'ghost2:embrace', duration: AURA_DUR, mods: { moveMul: Math.max(0, 1 + slow) } });
});
whileOn(battle, unit, 1, () => {
if (!unit.trait.doll || unit.trait.dollSwitch || !(scale > 0)) return;
if (!unit.trait.doll || unit.trait.dollSwitching || !(scale > 0)) return;
for (const e of battle.unitsInGrid(unit, aroundGrid, { side: 'enemy' })) if (!e.isFlying) battle.dealDamage(unit, e, { amount: unit.s.atk * scale, type: 'arts', tags: ['talent', 'embrace'] });
});
} },
+19 -13
View File
@@ -93,10 +93,12 @@ const installCharger = (battle, unit) => {
/**
* 傀儡师 (dollkeeper) trait "受到致命伤时不撤退,切换成<替身>作战(替身阻挡数为0),持续20秒后自身再次替换<替身>" with the
* branch rules of PRTS 分支特性信息 傀儡师: a lethal hit on the <本体> (one it survives with 不死 does not count) starts a
* switch animation, after which the operator fights as its <替身> for the trait's 20 s (bb duration) holding 阻回; then
* it switches back the same way. A switch animation clears the operator's Buffs and resets its HP to the max of the form
* it switches to; during one the operator holds 不死, 无敌, 阻回, 禁疗, 孤立, 强制缴械 and is immune to 眩晕 / 冻结 / 睡眠.
* branch rules of PRTS 分支特性信息 傀儡师: a lethal hit on the <本体> (one it survives with 不死 does not count: "受到足以致命的
* 伤害且未持有不死的情况下" — a skill's / talent's own 不死 runs earlier, a running 坚固维式重锤 window just before this hook,
* items/battle.js PRIO_UNDYING_HELD −99) starts a switch animation, after which the operator fights as its <替身> for the
* trait's 20 s (bb duration) holding 阻回; then it switches back the same way. A switch animation clears the operator's
* Buffs and resets its HP to the max of the form it switches to; during one the operator holds 不死, 无敌, 阻回, 禁疗, 孤立,
* 强制缴械 and is immune to 眩晕 / 冻结 / 睡眠.
* The 替身 blocks nothing from the start of the switch to it until the switch back starts; a lethal hit on it knocks the
* operator out. Content may switch the operator at once (hook `dollSwitch` { unit, reason, done }: 归溟幽灵鲨 S2 "技能结束
* 后立刻切换为<替身>"). A kit's `dollNoAttack` profile flag (归溟幽灵鲨, PRTS 特性备注 "<替身>不进行普通攻击") disarms the 替身
@@ -104,10 +106,14 @@ const installCharger = (battle, unit) => {
* 技能范围 — likewise); 风丸's 替身 attacks (PRTS: "<替身>状态下可对空").
* The form is the unit's model state (`unit.form` 'doll', snapshot.js unitInfo; fx 'substitute' / 'swap' / 'dollEnd' with
* `form`, shared/protocol.js fxForm): the client draws the 替身 on the skeleton's *_B clips (render/units.js FORMS).
* The 替身's max HP is the operator's own (PRTS: HP "重设…至最大值"; the trait blackboard's 替身 HP bonus `max_hp` is 0 for
* both 傀儡师 — the elite PUM-Y module's +20 % aside — and 风丸's 纸偶 token has her max HP at every level), or its 替身
* token's (风丸).
* [ASSUMED]: a switch animation lasts DOLL_SWITCH (the skeletons' 1 s Start_B — the 替身 appearing — and Start_2 — the
* 本体 back); "清除自身一切Buff" ends the running skill and removes the statuses (buffs with a catalogue `status`) — the
* remake's bond / item / talent effects are buffs too and stay; a dollkeeper without a 替身 token of its own (归溟幽灵鲨) keeps
* its own max HP (PRTS: HP "重设…至最大值"; character_table has no 替身 of hers, and 风丸's 纸偶 token has her HP).
* 本体 back), also on a direct switch (`dollSwitch`); "清除自身一切Buff" ends the running skill and removes the statuses
* (buffs with a catalogue `status`) — every other buff stays, the remake's bond / item / talent effects (buffs too)
* and other units' timed buffs and shields alike; 无敌 is damage immunity only (as for every ally 无敌 here — PRTS's
* "无法被不同阵营选中" is not modelled for it).
*/
export const DOLL_SWITCH = 1;
const DOLL_SWITCH_IMMUNE = new Set(['stun', 'freeze', 'sleep']);
@@ -127,10 +133,10 @@ const installDollkeeper = (battle, unit) => {
const sk = unit.skill;
if (sk && sk.active && sk.kind !== 'passive') sk.end('substitute');
for (const b of unit.buffs.slice()) if (b.status) battle.removeBuff(unit, b);
unit.trait.dollSwitch = true;
const done = () => { unit.trait.dollSwitch = false; };
unit.trait.dollSwitching = true;
const done = () => { unit.trait.dollSwitching = false; };
battle.addBuff(unit, {
key: 'trait:dollSwitch', duration: DOLL_SWITCH, onExpire: done, onRemove: done,
key: 'trait:dollSwitching', duration: DOLL_SWITCH, onExpire: done, onRemove: done,
flags: { invulnerable: true, noSp: true, noHeal: true, isolated: true, disarm: true },
});
};
@@ -166,18 +172,18 @@ const installDollkeeper = (battle, unit) => {
};
battle.on('fatal', (ctx) => {
if (ctx.unit !== unit || ctx.prevented) return;
if (unit.trait.dollSwitch) { ctx.prevented = true; return; } // 不死 while switching (a 流失 ignores 无敌)
if (unit.trait.dollSwitching) { ctx.prevented = true; return; } // 不死 while switching (a 流失 ignores 无敌)
if (unit.trait.doll) return; // the 替身 is knocked out
ctx.prevented = enter();
}, { owner: unit, priority: -100 });
battle.on('dollSwitch', (ctx) => { if (ctx.unit === unit && !ctx.done) ctx.done = enter(); }, { owner: unit });
battle.on('beforeStatus', (ctx) => {
if (ctx.target === unit && unit.trait.dollSwitch && DOLL_SWITCH_IMMUNE.has(ctx.status)) ctx.cancel = true;
if (ctx.target === unit && unit.trait.dollSwitching && DOLL_SWITCH_IMMUNE.has(ctx.status)) ctx.cancel = true;
}, { owner: unit });
battle.on('death', (ctx) => {
if (ctx.unit !== unit) return;
unit.trait.doll = false;
unit.trait.dollSwitch = false;
unit.trait.dollSwitching = false;
// knocked out as the 替身: its death clip has played (the 'die' event came first); the redeploy is the 本体 again
if (unit.form) { unit.form = null; battle.fx('dollEnd', at({ form: null })); }
}, { owner: unit });