From 7a3d53faf6237c45beb8b22869a96936e60df235 Mon Sep 17 00:00:00 2001 From: sganggs Date: Tue, 6 Oct 2026 15:37:49 +0800 Subject: [PATCH] =?UTF-8?q?feedback5=20WE2:=20=E5=A1=91=E5=BF=83's=20per-b?= =?UTF-8?q?attle=20layer=20cap=20is=20the=20trait's=20own,=20whichever=20b?= =?UTF-8?q?ond=20is=20the=20highest=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit garrison_90 (bond_actived_maxstack, "当前已激活且层数最多的盟约层数+1(每场作战至多10层)"): fireGain keeps the instance's count and passes the rest uncapped to gainLayers, so a change of the highest bond no longer grants a fresh 10 / 20 (PR #178 review). Other bond types keep the per-(instance, bond) cap. Local to garrisons/battle.js. Golden: no scenario moved. --- server/sim/content/garrisons/battle.js | 17 ++++++-- test/content/feedback5-we2-garrison90.test.js | 40 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 test/content/feedback5-we2-garrison90.test.js diff --git a/server/sim/content/garrisons/battle.js b/server/sim/content/garrisons/battle.js index 4c533cb3..2924cd56 100644 --- a/server/sim/content/garrisons/battle.js +++ b/server/sim/content/garrisons/battle.js @@ -17,7 +17,8 @@ // (engine statusApplied ctx.entered). Targets: bond_by_id / bond_self (own active bonds) / bond_actived_maxstack; // amounts: by_count / by_charcount_samerow / by_charlevel; conditions character_same_row / // character_same_col (≥ check_count incl. self). Gains go through support.gainLayers with -// reason 'garrison', source = the trait's owner, cap = max_add_count_per_battle per (instance, bond). +// reason 'garrison', source = the trait's owner, cap = max_add_count_per_battle per (instance, bond) +// — per instance for bond_actived_maxstack (塑心: the highest bond may change, the cap does not). // act1autochess_gar_event_addition_cnt (魔王) layerGain: a 'garrison' gain whose source stands on the tile in front // of 魔王 (range_id 1-1) — or was knocked out there this instant (幽灵鲨 "被击倒时"; engine ctx.tile) // — gets +extra_cnt per bond (the extra does not count toward the source's per-battle cap). @@ -189,10 +190,20 @@ export function fireGain(battle, it) { if (!conditionMet(battle, it)) return 0; const bonds = targetBonds(battle, it); if (!bonds.length) return 0; - const n = amountOf(battle, it); + let n = amountOf(battle, it); + // bond_actived_maxstack (塑心 garrison_90 "当前已激活且层数最多的盟约层数+1(每场作战至多10层)"): the cap is the instance's + // own over the battle, whichever bond is the highest at each gain — per (instance, bond) a change of the highest bond + // granted a fresh 10 (PR #178 review). The base amount counts toward it (魔王's extra does not, as in gainLayers). + const whole = it.bbStr.bond_type === 'bond_actived_maxstack' && Number.isFinite(it.cap); + if (whole) { + n = Math.min(Math.floor(n), Math.max(0, Math.floor(it.cap) - (it.wholeUsed ?? 0))); + if (!(n > 0)) return 0; + } const added = S.gainLayers(battle, { - playerId: it.unit.ownerId, bonds, n, requireActive: true, source: it.unit, reason: 'garrison', cap: it.cap, capKey: it.capKey, + playerId: it.unit.ownerId, bonds, n, requireActive: true, source: it.unit, reason: 'garrison', + cap: whole ? Infinity : it.cap, capKey: whole ? null : it.capKey, }); + if (whole && added > 0) it.wholeUsed = (it.wholeUsed ?? 0) + n; if (added > 0) S.fxOn(battle, 'garrison', it.unit, `gar:${it.gid}`, it.key, { n: added }); return added; } diff --git a/test/content/feedback5-we2-garrison90.test.js b/test/content/feedback5-we2-garrison90.test.js new file mode 100644 index 00000000..2952e548 --- /dev/null +++ b/test/content/feedback5-we2-garrison90.test.js @@ -0,0 +1,40 @@ +// test/content/feedback5-we2-garrison90.test.js — 0.2.0 WE2 #30: 塑心's 特质 garrison_90 (bond_actived_maxstack) "<战斗中> +// 开启技能时,同一行每有1名干员,当前已激活且层数最多的盟约层数+1(每场作战至多10层)" (elite +2, at most 20): the per-battle cap is +// the trait's own, whichever bond is the highest at each gain — a change of the highest bond grants no fresh cap +// (server/sim/content/garrisons/battle.js fireGain; PR #178 review). +// Run: node --test test/content/feedback5-we2-garrison90.test.js + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js'; + +const B = (layers = 0) => ({ count: 3, active: true, tier: 1, layers }); + +/** 塑心's trait on 'op' with one row mate (2 in the row: +2 per skill, elite +4). */ +function field(gid, bonds) { + const chess = { op: { ...chessRec({ id: 'op' }), garrisonIds: [gid] }, m1: { ...chessRec({ id: 'm1' }), garrisonIds: [] } }; + const h = makeBattle({ + seed: 3, bonds, defs: { chess, enemies: {} }, autoFinish: false, timeLimit: 400, + units: [{ chessId: 'op', row: 10, col: 4 }, { chessId: 'm1', row: 10, col: 6 }], + }); + h.step(1); + return h; +} +const skill = (h) => h.b.emit('skillStart', { unit: h.unit('op'), skill: h.unit('op').skill, reason: 'test' }); +const gains = (h) => ({ ...h.result().perPlayer.p1.layerGains }); + +test('#30 塑心 garrison_90: at most 10 (elite 20) layers per battle in all, even when the highest bond changes', () => { + for (const [gid, per, cap] of [['garrison_90_a', 2, 10], ['garrison_90_b', 4, 20]]) { + const h = field(gid, { yanShip: B(20), steadShip: B(15) }); + const n1 = Math.floor(cap / per / 2); // half the cap on 炎 (the highest) + for (let i = 0; i < n1; i++) skill(h); + assert.deepEqual(gains(h), { yanShip: n1 * per }, `${gid}: 炎 is the highest`); + // 坚守 becomes the highest (an outside gain of 30), then many more casts + assert.equal(h.b.addLayers('p1', 'steadShip', 30, 'test'), 30); + for (let i = 0; i < 20; i++) skill(h); + const g = gains(h); + assert.equal(g.yanShip, n1 * per, `${gid}: 炎 kept its gain`); + assert.equal(g.steadShip - 30, cap - n1 * per, `${gid}: 坚守 got only what was left of the one cap`); + checkInvariants(h.b); + } +});