diff --git a/server/sim/content/kits/ops/op-phatom.js b/server/sim/content/kits/ops/op-phatom.js index 50d24bb4..d683b43b 100644 --- a/server/sim/content/kits/ops/op-phatom.js +++ b/server/sim/content/kits/ops/op-phatom.js @@ -9,9 +9,11 @@ // character_table / skill_table / battle_equip_table / token_table (zh_CN, as built into backups.json); PRTS 傀影 (镜中虚影 // 备注 "虚影再部署与拥有复数召唤物的干员相同,即“部署召唤物后立刻开始计算再部署时间,结束后可以部署下一个”"; 血色乐章 备注 "触发消耗 // 层数效果的行动节点为“成功造成伤害后”。若造成的伤害被伤判效果归零则不会消耗层数"; 夜幕突袭 备注 "不可对空"; EXE-X "撤退时返还大量 -// (80%)该次部署费用"); PRTS 分支特性信息 处决者; the client's battle data — charpack char_250_phatom (Talents/1: +// (80%)该次部署费用"); PRTS 镜中虚影 (备注 "持有禁疗", "可以且优先攻击自身阻挡的单位", "退场时返还1个可部署的镜中虚影", "傀影退场时 +// 强制撤退场上的镜中虚影"); PRTS 分支特性信息 处决者; the client's battle data — charpack char_250_phatom (Talents/1: // charge_token[born] (RechargeToken on his deployment) + die_to_kill_token (KillTokens when he leaves), CommonAbilities -// KillTokens; modes S2 / S3), token prefab token_10007_phatom_twin (charge_token[finish]; the same modes), the skill +// KillTokens; modes S2 / S3), token prefab token_10007_phatom_twin (CommonAbilities: charge_token[finish] carrying abnormal +// flag 7 = 禁疗; Talents/2: phatom_twin_t, RESPAWN_TIME + respawn_time; the same modes), the skill // prefabs skchr_phatom_1 / _2 / _3 and sktok_phatom_1 / _2 / _3 (S1: evade_physical + phatom_s_1[shield_physical] = a // shield_physical of hp_ratio × max HP, both `duration`; S2: phatom_s_2 = `times` stacked phatom_s_2[atk] (ATK MULTIPLIER // `atk`), one stack finished per ON_AFTER_OUTPUT_DAMAGE; S3: RandomCastAbility of the sluggish / stun / root attack @@ -31,14 +33,16 @@ // - T1 镜中虚影 "可以使用一个属性更强的虚影,虚影拥有和自己一样的技能,拥有独立的再部署时间": the twin is a hand piece the player // places (a talent summon: tokens `placeable`, not owner-range); it deploys with the board after the operators and fights // with its own stats (the variant of his form; EXE-Y stage 3: "属性进一步增强" = the byModule stats) as an executor (melee, -// blocks 1, ground-only), its copy of his picked skill (bySkill sktok_phatom_1 / 2 / 3) running at each of its own -// deployments. When he leaves the field (knocked out, withdrawn, forced out) it is killed (KillTokens); a fallen twin comes -// back on its tile — paying its cost (5 DP) as a deployment, the 卫戍 auto redeploy of a placed summon [ASSUMED, as 鸿雪's -// 打字机 / 凯尔希's Mon3tr] — once its redeploy time has passed since its LAST DEPLOYMENT (PRTS 备注: the timer starts at the -// summon's deployment) and only while he stands (it is recharged by his deployment and killed by his leaving) -// [ASSUMED: his redeployment does not cut the twin's own timer short — the client's RechargeToken NORMAL; the 凯尔希 kit -// reads the same charge_token[born] as "readies at once"]. EXE-Y stage 2+ "本体和虚影同时在场时,攻击力各+10%": the twin's -// hidden module talent `atk` — ATK +atk on both while both are on the field. +// blocks 1, ground-only) holding 禁疗 (PRTS; the client's flag 7 — the data's `abnormal` lacks it [ASSUMED: given here, as +// the 凯尔希 / 机械师 kits do for their summons]), its copy of his picked skill (bySkill sktok_phatom_1 / 2 / 3) running at +// each of its own deployments. When he leaves the field (knocked out, withdrawn, forced out) it is withdrawn (PRTS "强制 +// 撤退"; the client's KillTokens); a twin off the field comes back on its tile — paying its cost (5 DP) as a deployment, +// the 卫戍 auto redeploy of a placed summon [ASSUMED, as 鸿雪's 打字机 / 凯尔希's Mon3tr] — once its redeploy time has +// passed since its LAST DEPLOYMENT (PRTS 备注: the timer starts at the summon's deployment) and only while he stands (it +// is recharged by his deployment, withdrawn by his leaving) [ASSUMED: his redeployment does not cut the twin's own timer +// short — the client's RechargeToken NORMAL; 虚影精通 / EXE-X would matter little otherwise; the 凯尔希 kit reads the same +// charge_token[born] as "readies at once"]. EXE-Y stage 2+ "本体和虚影同时在场时,攻击力各+10%": the twin's hidden module +// talent `atk` — ATK +atk on both while both are on the field. // - T2 虚影精通 "虚影的再部署时间-10秒" (EXE-X stage 3: −16): the twin's own talent respawn_time, added to its 45 s. // - S1 暗夜魅影 (被动, at each deployment): physical dodge `prob` and a 屏障 of hp_ratio × max HP that absorbs physical damage // only (shieldType 'phys'), both for `duration` (10) s. @@ -148,8 +152,8 @@ function deployEffect(battle, u, rec, key) { } /** - * The twin's kit (`owner` = 傀影): its skill copy at each of its deployments, EXE-Y stage 2+'s shared ATK, and its own - * redeploys (killed with him; back on its tile once its timer has run from its last deployment, he stands and the DP is + * The twin's kit (`owner` = 傀影): 禁疗, its skill copy at each of its deployments, EXE-Y stage 2+'s shared ATK, and its own + * redeploys (withdrawn with him; back on its tile once its timer has run from its last deployment, he stands and the DP is * there). */ function twinKit(t, owner) { @@ -160,6 +164,7 @@ function twinKit(t, owner) { return { skill: { kind: 'passive' }, install(battle, unit) { + battle.addBuff(unit, { key: 'phatom:twin:abnormal', flags: { noHeal: true }, persist: true, allowDead: true }); // 持有禁疗 if (rec?.id === TS2) installOpusUse(battle, unit, key); battle.on('deploy', (c) => { if (c.unit !== unit || c.move || !up(unit)) return; @@ -194,7 +199,7 @@ export default { [S3]: { kind: 'passive' }, }, talents: [ - { install(battle, unit) { // 镜中虚影: his twin pieces run the twin's kit; killed when he leaves; EXE-Y stage 2+: both +ATK + { install(battle, unit) { // 镜中虚影: his twin pieces run the twin's kit; withdrawn when he leaves; EXE-Y stage 2+: both +ATK for (const t of twinsOf(battle, unit)) { if (t.alive || t.deployed) continue; if (t.kit) battle.offOwner(t); // a piece set up before him: drop its generic kit's hooks @@ -202,7 +207,8 @@ export default { } battle.on('death', (c) => { if (c.unit !== unit) return; - for (const t of twinsOf(battle, unit)) if (t.alive) battle.kill(t, null); // KillTokens + // "傀影退场时强制撤退场上的镜中虚影" (KillTokens): a withdrawal, not a knock-out + for (const t of twinsOf(battle, unit)) if (t.alive) battle.retreat(t, { reason: 'retreat' }); }, { owner: unit }); // 本体和虚影同时在场时,攻击力各+N%: the twin's hidden module talent (his text when no twin def resolves) const both = num(tokTalentBb(battle.tokenDef(TWIN, unit), 'atk').atk, /本体和虚影同时在场时/.test(t0desc) diff --git a/test/content/op_phatom.test.js b/test/content/op_phatom.test.js index 1dffc1a4..ea64b855 100644 --- a/test/content/op_phatom.test.js +++ b/test/content/op_phatom.test.js @@ -68,7 +68,7 @@ function redeployNow(h, u) { assert.ok(h.b.redeploy(u, { free: true }), `${u.name} redeployed`); } -test('傀影 in every 自选 form: his operator kit (all three skills authored), the form\'s stats + module attributes, 1-1, blocks 1, melee ground-only, 18 s redeploy, 维多利亚, no 特质; his placed 镜中虚影 deploys after him with the variant\'s stats and its copy of his skill', () => { +test('傀影 in every 自选 form: his operator kit (all three skills authored), the form\'s stats + module attributes, 1-1, blocks 1, melee ground-only, 18 s redeploy, 维多利亚, no 特质; his placed 镜中虚影 deploys after him with the variant\'s stats, 禁疗 and its copy of his skill', () => { assert.equal(OPERATOR_KITS[PHATOM], KITS[PHATOM]); for (const f of FORMS_ALL) { const [tier, elite, mod] = f; @@ -87,6 +87,7 @@ test('傀影 in every 自选 form: his operator kit (all three skills authored), assert.deepEqual([t.base.maxHp, t.base.atk, t.base.def, t.base.respawnTime, t.base.cost], [tw.stats.maxHp, tw.stats.atk, tw.stats.def, 45, 5], `${label(f)}: twin stats`); assert.deepEqual([t.def.skill.id, t.skill.kind], [tw.skillOf(skill).skillId, 'passive'], `${label(f)}: twin skill copy`); assert.deepEqual([t.s.blockCnt, t.profile.attack, t.profile.canHitFly, t.liveRangeGrid], [1, 'melee', false, [[0, 0], [0, 1]]], `${label(f)}: twin executor`); + assert.ok(t.s.flags.noHeal, `${label(f)}: twin 持有禁疗`); done(h); } } @@ -203,13 +204,17 @@ test('S3 夜幕突袭 (被动): at each deployment 210 % / 240 % ATK physical to } }); -test('T1 镜中虚影 / T2 虚影精通: the twin is killed when he leaves the field and comes back on its tile 35 s (45 − 10; EXE-X stage 3: 45 − 16) after its LAST deployment, paying 5 DP, only while he stands', () => { +test('T1 镜中虚影 / T2 虚影精通: the twin (禁疗: no heal reaches it) is withdrawn when he leaves the field and comes back on its tile 35 s (45 − 10; EXE-X stage 3: 45 − 16) after its LAST deployment, paying 5 DP, only while he stands', () => { for (const f of [[5, false, null], [5, true, EX], [6, true, EX]]) { const [tier, elite, mod] = f; const cut = twinOf(tier, elite, mod).talents.find((x) => x.bb.respawn_time != null).bb.respawn_time; assert.equal(cut, tier === 6 && mod === EX ? -16 : -10, label(f)); const { h, u, t } = field({ tier, elite, mod, skill: 1, dp: 50 }); const wait = 45 + cut; + // 禁疗: an ally heal does not reach it + t.hp = 100; + h.b.heal(u, t, 500); + assert.equal(t.hp, 100, `${label(f)}: 禁疗`); // knocked out while he stands: back `wait` s after its deployment at 0 s, 5 DP paid h.run(5); h.b.kill(t, null); @@ -219,10 +224,11 @@ test('T1 镜中虚影 / T2 虚影精通: the twin is killed when he leaves the f assert.ok(h.runUntil(() => t.alive, 1.5), `${label(f)}: back at ${wait} s`); assert.deepEqual([t.tileR, t.tileC, h.b.players[0].dp], [12, 4, 45], `${label(f)}: its tile, 5 DP`); const at = h.b.time; - // he is knocked out: the twin is killed with him (KillTokens) and waits for him + // he is knocked out: the twin is withdrawn with him (强制撤退 — KillTokens) and waits for him h.b.kill(u, null); u.respawnAt = Infinity; // he stays down until the test brings him back - assert.ok(!t.alive, `${label(f)}: killed with him`); + assert.ok(!t.alive, `${label(f)}: withdrawn with him`); + assert.equal(h.hooksOf('death').filter((c) => c.unit === t).slice(-1)[0].reason, 'retreat', `${label(f)}: a withdrawal, not a knock-out`); h.run(wait + 1); assert.ok(!t.alive && !u.alive, `${label(f)}: no return while he is down`); assert.ok(h.b.redeploy(u, { free: true }));