diff --git a/server/sim/content/kits/tier3.js b/server/sim/content/kits/tier3.js index 25a6c6ea..f8154762 100644 --- a/server/sim/content/kits/tier3.js +++ b/server/sim/content/kits/tier3.js @@ -405,8 +405,9 @@ const KITS = { // closing it spends every coin on random ground enemies of range 2-4 in front and those she blocks (atk_scale phys + // a small push, radial despite the text's 向前 — PRTS 备注 "推开效果为径向推动"; client charpack char_1033_swire2: // the RandomGold ability (Skill_3_End) carries swire2_s_3[knockback] of template knockback[relative]; 地面敌方单位, - // 弹道不可对空). Auto-close (the mode casts everything itself; the player's "主动关闭" is not available): once the - // purse is full (10) and a coin target stands there. 精锐 module MER-Y: ATK +4 % per trait payment (≤ 5 stacks). + // 弹道不可对空). It does not close itself when the purse is full (PRTS 卫戍协议/帮助 「通常不会自动关闭技能」; the skill + // text is 「可随时主动关闭」; GitHub #89-1). The player closes it: skill.end('manual'), which spends the coins. + // 精锐 module MER-Y: ATK +4 % per trait payment (≤ 5 stacks). chess_char_3_04_a: (bb, chess, def) => { const d = defOf(chess, def); const t0 = talentBb(d, 0), t1 = talentBb(d, 1); @@ -493,7 +494,7 @@ const KITS = { skills: altSkills(chess, d, bb, { [S1]: () => ({ kind: 'passive' }), // (coins → heals: installS1) [S3]: (s) => { - const cash = num(s.bb.atk_scale, 1), force = num(s.bb.force, 0), full = num(s.bb.sp, 10); + const cash = num(s.bb.atk_scale, 1), force = num(s.bb.force, 0); // the 【金币标记】 targets: ground enemies on range 2-4 in front of her (range_table "2-4") and every unit she blocks const coinMarks = (battle, unit) => { const list = enemiesOn(battle, unit, gridKeys(SWIRE2_COIN_GRID, unit), 0, { ...unit.profile, canHitFly: false }); @@ -503,9 +504,8 @@ const KITS = { return { kind: 'toggle', attack: { hits: 2 }, - onTick({ battle, unit, skill }) { - if ((unit.mem.coins ?? 0) >= full && coinMarks(battle, unit).length) skill.end('manual'); - }, + // PRTS 卫戍协议/帮助 「通常不会自动关闭技能」; 千金一掷 「可随时主动关闭」 (GitHub #89-1). A full purse + // with a markable enemy does not end it. skill.end('manual') is the close that spends the coins. onEnd({ battle, unit, reason }) { if (reason !== 'manual' || !unit.alive) return; const n = unit.mem.coins ?? 0; diff --git a/test/sim/feedback3-swire-s3.test.js b/test/sim/feedback3-swire-s3.test.js new file mode 100644 index 00000000..ef89a17e --- /dev/null +++ b/test/sim/feedback3-swire-s3.test.js @@ -0,0 +1,60 @@ +// test/sim/feedback3-swire-s3.test.js — GitHub #89-1: 琳琅诗怀雅 S3 千金一掷 used to call skill.end('manual') +// from onTick once her coins were full and a ground enemy stood in the coin range. PRTS 卫戍协议/帮助 技能操作 +// 「通常不会自动关闭技能」 and the skill text 「可随时主动关闭技能;携带此技能时金币上限为10」: she keeps it +// open. The close is still skill.end('manual') — the same call the old onTick used, and feedback1d-push.test.js — +// which spends the coins and pushes. No markable target still ends and does not throw. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; +import { hasGeneratedData } from '../../server/sim/simdata.js'; + +const REAL = { skip: !hasGeneratedData() }; +const ID = 'chess_char_3_04_a'; +const foe = enemyRec({ key: 'e_sw', hp: 1e9, atk: 0, speed: 0, def: 0, res: 0, mass: 1 }); + +function battle(withEnemy) { + return makeBattle({ + defs: { enemies: { e_sw: foe } }, + units: [{ chessId: ID, row: 10, col: 5, skillIndex: 2 }], + enemies: withEnemy ? [{ key: 'e_sw', pos: [10, 6] }] : [], + hooks: ['damaged', 'skillEnd'], autoFinish: false, timeLimit: 30, + }); +} +const arm = (h) => { + h.step(); + const u = h.unit(ID); + assert.equal(u.skill.id, 'skchr_swire2_3'); + assert.ok(u.skill.active || u.skill.activate('test', { free: true })); + return u; +}; + +test('琳琅诗怀雅 S3 stays open with a full purse and a coin target; skill.end(\'manual\') still spends', REAL, () => { + const h = battle(true); + const u = arm(h); + const full = 10; // S3 blackboard sp: the purse cap 「金币上限为10」 + u.mem.coins = full; + h.run(6); + assert.equal(u.skill.active, true, 'several seconds do not close it'); + assert.equal(u.mem.coins, full, 'the coins are still hers'); + const e = h.enemies()[0]; + const hp0 = e.hp; + u.skill.end('manual'); + assert.equal(u.skill.active, false); + assert.equal(u.mem.coins, 0, 'the close spends every coin'); + assert.ok(e.hp < hp0, 'and the coins hit the enemy in range'); + assert.equal(h.b.errors.length, 0); + checkInvariants(h.b); +}); + +test('琳琅诗怀雅 S3: closing with no markable target still ends and spends, and does not throw', REAL, () => { + const h = battle(false); + const u = arm(h); + u.mem.coins = 4; + assert.doesNotThrow(() => u.skill.end('manual')); + assert.equal(u.skill.active, false); + assert.equal(u.mem.coins, 0); + assert.equal(h.hooksOf('damaged').length, 0, 'nothing to pay'); + assert.equal(h.b.errors.length, 0); + checkInvariants(h.b); +});