0.2.6-dev: port PR #552 by IceCodeNew — an optional 「弹药数量」 setting (default off: the official HUD has no such number, the maintainer's decision of 2026-10-13) shows an ammo skill's rounds left / magazine under its bar, from the exact b.snap ammo counts; sanitised with the other settings, applied by the field view live, all four language packs, the diagnostics line; the simplified board has no ammo bar; DESIGN §31.9; golden: unchanged

This commit is contained in:
sganggs committed 2026-10-13 04:43:39 +08:00
1 parent 231da0efbc
commit 461a7747e1
16 files changed
+244 -17

No files matched your search

+87
View File
@@ -3,6 +3,7 @@
// [[id, left, max]] and `neg` [[id, fill]] beside the unit tuples and samples them from the older snapshot; render/units.js
// draws the ammo skill's segmented yellow bar in place of the SP bar, 伺夜's 狼影 as a row of diamonds and the 我执 pool
// as the red bar over the drained HP bar; an ammo skill has no standing aura (headless fake PIXI, test/render/fakepixi.js).
// 设置「弹药数量」 (PR #552 by @IceCodeNew, off by default) adds the magazine's count under the ammo bar.
// The server side is test/sim/hud-readouts.test.js and test/content/op_hsgma2.test.js.
// Run: node --test test/render/hud-readouts.test.js
@@ -166,6 +167,92 @@ describe('UnitView: the ammo skill\'s segmented bar', () => {
});
});
// 设置「弹药数量」 (PR #552 by @IceCodeNew, DESIGN §31.9): the official HUD has no number under the ammo bar, so the count is an
// option, off by default (the maintainer's decision of 2026-10-13); the settings side is test/ui/ammo-count.test.js
describe('UnitView: the optional ammo count under the bar (设置「弹药数量」)', () => {
const counting = (on = true) => view({}, { settings: { damageNumbers: true, quality: 'high', ammoCount: on } });
const coinY = (v) => v.coinHud.root.position.y;
test('off (the default): nothing is drawn under the magazine, and the rows below stay where they were', () => {
const v = view();
const w = counting(false);
try {
for (const u of [v, w]) { u.sync(sample({ flags: UF.SKILL, ammo: [13, 14], coins: [3, 10] })); frames(u); }
assert.equal(v.ammoText, null, 'never built without the setting');
assert.equal(w.ammoText, null, 'nor with it off');
assert.equal(v.hud.children.some((c) => c instanceof fake.P.Text && /\d+\/14/.test(c.text)), false);
const plain = view();
plain.sync(sample({ coins: [3, 10] }));
frames(plain);
assert.equal(coinY(v), coinY(w));
assert.ok(coinY(v) > coinY(plain), 'the magazine row (thicker than the SP bar) is all that sits above the purse');
plain.destroy();
} finally { v.destroy(); w.destroy(); }
});
test('on: the exact count through spending, refills and capacity changes, also where the cells are too narrow for cuts', () => {
const v = counting();
try {
for (const [left, max] of [[13, 14], [12, 14], [137, 240], [136, 240], [260, 260], [0, 260]]) {
v.sync(sample({ flags: UF.SKILL, ammo: [left, max] }));
frames(v);
assert.equal(v.ammoText?.text, `${left}/${max}`);
assert.equal(v.ammoText.visible, true);
if (max >= 240) assert.equal(v.ammoCuts.filter((c) => c.visible).length, 0, 'a dense magazine draws no cuts, the count still reads');
}
const bw = barWidth(v);
assert.ok(Math.abs(v.ammoText.position.x - (v.spFill.position.x + bw / 2)) < 1e-9, 'centred under the bar');
assert.ok(v.ammoText.position.y > v.spFill.position.y, 'below the magazine');
assert.equal(v.ammoText.anchor.x, 0.5);
assert.equal(v.ammoText.style.fill, COLORS.ammo, 'in the bar\'s yellow');
} finally { v.destroy(); }
});
test('on: hidden when the skill ends, without the snapshot row, in the prep view, after death and for enemies; the rows below make room', () => {
const v = counting();
try {
v.sync(sample({ flags: UF.SKILL, ammo: [5, 14], coins: [3, 10] }));
frames(v);
const withCount = coinY(v);
v.sync(sample({ sp: 6, spMax: 24, coins: [3, 10] })); // the magazine is spent: the SP bar again
frames(v);
assert.equal(v.ammoText.visible, false, 'no stale number after the skill');
assert.ok(withCount > coinY(v), 'the purse row moved down by the count\'s height while it showed');
v.sync(sample({ flags: UF.SKILL, ammo: [5, 14] }));
v.prep = true;
frames(v);
assert.equal(v.ammoText.visible, false, 'the prep view draws no bars');
v.prep = false;
frames(v);
assert.equal(v.ammoText.visible, true);
v.die();
frames(v);
assert.equal(v.ammoText.visible, false, 'a dead unit has no bars');
} finally { v.destroy(); }
const e = view({ id: 2, side: 'enemy', kind: 'enemy', defId: 'enemy_x' }, { settings: { damageNumbers: true, quality: 'high', ammoCount: true } });
try {
e.sync(sample({ hp: 500, flags: UF.SKILL, ammo: [3, 4] }));
frames(e);
assert.equal(e.ammoText, null, 'enemies have no magazine, so no count');
} finally { e.destroy(); }
});
test('turning the setting off takes the count away at once (the field view shares its settings object with the units)', () => {
const v = counting();
try {
v.sync(sample({ flags: UF.SKILL, ammo: [9, 14] }));
frames(v);
assert.equal(v.ammoText.visible, true);
v.ctx.settings.ammoCount = false;
frames(v);
assert.equal(v.ammoText.visible, false);
v.ctx.settings.ammoCount = true;
frames(v);
assert.equal(v.ammoText.visible, true);
} finally { v.destroy(); }
});
});
describe('UnitView: 伺夜\'s 狼影 pips', () => {
const pack = (extra = {}) => view({ kind: 'token', defId: 'token_10028_vigil_wolf', ...extra });