fix(render): show exact remaining ammunition
Reference: lolink’s numeric ammunition display in https://github.com/Yilegendoflink/garrison-protocol/commit/071ed14aa27d5ebf54e57447c958f2681737403c. This implementation uses the existing PIXI HUD and exact snapshot counts; no fork code is copied.
This commit is contained in:
4 files changed
+57
-1
No files matched your search
@@ -40,6 +40,11 @@ Visuals: oblique perspective board (tilted ~30°, trapezoid tiles), raised highl
|
||||
|
||||
## 10. Client UI (screens & components)
|
||||
|
||||
Active ammunition skills also show `remaining/capacity` below the segmented skill bar, using the exact `b.snap ammo`
|
||||
integers, including extra rounds and refills. The label stays readable when cells are too narrow to separate. It hides
|
||||
with the ammunition row, during preparation and after death; elemental gauges sit below it. This is a display-only
|
||||
readout, not a change to ammunition consumption or skill duration.
|
||||
|
||||
Look & feel: research 06 §11–§12 (near-black green-grey panels, mint `#4ed8af` accents, gold prices, bracket frames, hexagon badges, bond discs, 7-segment countdown). Fonts: Noto Sans SC (Google Fonts with local fallback), Bender/Novecento self-hosted for numerals and Latin micro-labels.
|
||||
|
||||
Screens: **Title** (season art backdrop, 开始 → nickname), **Lobby** (独立模拟 / 同盟模拟 → create or join; recent rooms; a 设置 button left of 玩法说明 opens the title screen's settings dialog, GitHub #238), **Room** (4 seats, invite code + copy link, difficulty picker (host), the co-op 「AI 队友最后选择」 switch (host, off by default; read-only for the others), add/remove AI, ready, start; the same 设置 button), **Briefing** (INFO_CHECK: stage, factions, disabled bonds, banned chess, boss silhouette, ready x/4), **Band draft** (grid of 40 bands with icon/name/LP/effect; turn order list; skip; 查看禁用盟约与干员 under the order list opens the briefing's bond rows, legend and banned operators again in a read-only dialog — one component, `ui/matchInfo.js`, §21.28), **Game** (Pixi field + DOM HUD: top bar with round/phase capsule (the kill counter reads 已解决 / the round's own enemies: a leak counts, a split child or summon is in neither number, §14)/LP (live during the own battle, §17.5)/timer/ready, and beside the latency a 观战席 capsule (eye + count, a roster with the host's ✕) while the room has spectators (GitHub #120); bond strip + popups; left team panel with LP/status/emote bubbles, click to watch (a boss round frames the viewer's pair in green, §15); bottom shop bar (level card, 3–5 chess cards — a slot the upgrade just opened is a bare frame until the next refresh, GitHub #332 —, item card, funds, refresh, freeze, collapse; 准备就绪 folds it and cancelling unfolds it, GitHub #138); detail panel on click/hover (header with bonds, then 特质 — the operator's own effect — right under it, 特性, stats (live: the battle's own numbers / the start-of-battle ones in prep, §18.5), skill, module, equipped items, talents: §17.5); enemy preview drawer (next round enemies with counts, factions; its 本局信息 tab lists the briefing's disabled bonds and banned operators, §21.28); 机变 overlay (the official card: framed icon + name, the effect text — data `descRaw` — under it, the taker's round avatar at the top-right; two taps, §18.2, §20.7); merge reward overlay (the promotion reward, or a special offer — 凯瑟琳's 定向投放 … — as item / chess cards under its source's label, with the count of offers queued behind it, §21.2); phase banners; the round's result box at SETTLE (the official 「作战结束」 + 「全员无伤!」 / 「生命值减少 −N」 for the viewer's own LP — after a 联防 the server's charge, `m.public.uniteResult.losses` — with the matching BATTLEOVER sound; none in the boss rounds or for a viewer not in the round; GitHub #235, PR #112); ticker (by broadcast priority, highest first; a leader-damage line plays only in its round and a player's newer one replaces the older, §21.10); toasts; emote wheel; settings (volume, quality, show damage numbers)), **Result** (victory/defeat, rounds, per-player lineup, stats, titles, back to room).
|
||||
|
||||
@@ -274,3 +274,10 @@ enemy: picking emitted a unit click with no definition, instead of the tile clic
|
||||
engine-canvas regression retains the card/100 HP assertions and also covers another field refresh, live HP loss and
|
||||
closing the card when the crate is destroyed. Device metadata supplied by the new entry still wins; changing fields
|
||||
does not carry old devices across. Gameplay and allied targeting rules are unchanged.
|
||||
|
||||
### 29.15 Ammunition readout
|
||||
|
||||
Active ammunition skills show remaining rounds and magazine capacity below the segmented bar (§10), including
|
||||
Laterano's extra rounds and refills. The renderer reads the existing exact snapshot counts rather than estimating
|
||||
them from SP. Missing ammunition snapshots, preparation and death hide the number. Dense magazines retain their
|
||||
numeric readout when the renderer omits cell separators. Combat rules and golden results are unchanged.
|
||||
@@ -760,6 +760,7 @@ export class UnitView {
|
||||
this.blockIcon.visible = false;
|
||||
this.ctx.layers.groundFx.addChild(this.blockIcon);
|
||||
this.countText = null;
|
||||
this.ammoText = null;
|
||||
this.itemPips = [];
|
||||
}
|
||||
|
||||
@@ -1413,7 +1414,8 @@ export class UnitView {
|
||||
}
|
||||
// 狼影 pips under the bars (b.snap `wolves`), then the element gauge row under them (b.snap `elem`); redeploy ring above a
|
||||
// knocked-down operator (b.snap `down`)
|
||||
const rowsTop = cy + bh / 2 + (showSp ? spH + 1.5 : 0);
|
||||
let rowsTop = cy + bh / 2 + (showSp ? spH + 1.5 : 0);
|
||||
rowsTop += this._updateAmmoText(showSp && !!ammo, x0 + bw / 2, rowsTop, s);
|
||||
const wolfH = this._updateWolfPips(showBars && !this.isEnemy && !!this.wolves, x0, rowsTop, s);
|
||||
const coinH = this._updateCoins(showBars && !this.isEnemy && !!this.coins, x0, rowsTop + wolfH, s);
|
||||
this._updateElementBar(showBars && !!this.el, x0, bw, rowsTop + wolfH + coinH + 1, spH, s, t);
|
||||
@@ -1441,6 +1443,22 @@ export class UnitView {
|
||||
}
|
||||
}
|
||||
|
||||
/** Exact snapshot counts, including extra rounds and refills; readable even when the cells are too narrow. */
|
||||
_updateAmmoText(show, x, top, s) {
|
||||
let text = this.ammoText;
|
||||
if (!show) { if (text) text.visible = false; return 0; }
|
||||
if (!text) {
|
||||
text = this.ammoText = new this.P.Text('', { fontFamily: 'Bender, Oxanium, sans-serif', fontSize: 16, fontWeight: '700', fill: COLORS.ammo, stroke: '#0b0f0e', strokeThickness: 3 });
|
||||
text.anchor.set(0.5, 0);
|
||||
this.hud.addChild(text);
|
||||
}
|
||||
text.text = `${this.ammo[0]}/${this.ammo[1]}`;
|
||||
text.scale.set(clamp(s * 0.2, 10, 16) / 16);
|
||||
text.position.set(x, top + 3);
|
||||
text.visible = true;
|
||||
return text.height + 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* The thin dark separators between the ammo bar's cells (see _updateHud): one at every round boundary of the `n`-round
|
||||
* magazine across the bar `x0` … `x0 + bw` at height `y`, `h` tall; none while a cell would be under AMMO_BAR.minCell px
|
||||
|
||||
@@ -96,6 +96,32 @@ describe('interp: ammo / wolves / neg', () => {
|
||||
});
|
||||
|
||||
describe('UnitView: the ammo skill\'s segmented bar', () => {
|
||||
test('user sees exact ammunition through spending, refills and skill end, even without cell separators', () => {
|
||||
// Given a magazine too dense to count its cells, When snapshots change, Then the readout stays exact.
|
||||
const v = view();
|
||||
try {
|
||||
for (const [left, max] of [[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);
|
||||
assert.equal(v.ammoCuts.filter((c) => c.visible).length, 0);
|
||||
}
|
||||
// When the skill ends or an older peer omits the row, Then no stale number remains.
|
||||
v.sync(sample({ sp: 6, spMax: 24 }));
|
||||
frames(v);
|
||||
assert.equal(v.ammoText.visible, false);
|
||||
v.sync(sample({ flags: UF.SKILL, ammo: [5, 14] }));
|
||||
v.prep = true;
|
||||
frames(v);
|
||||
assert.equal(v.ammoText.visible, false);
|
||||
v.prep = false;
|
||||
v.die();
|
||||
frames(v);
|
||||
assert.equal(v.ammoText.visible, false);
|
||||
} finally { v.destroy(); }
|
||||
});
|
||||
|
||||
test('replaces the SP bar while the magazine runs: a yellow cell per round, the fill is rounds left / magazine, a thin cut at every boundary', () => {
|
||||
const v = view();
|
||||
v.sync(sample({ flags: UF.SKILL, sp: 22.3, spMax: 24, ammo: [13, 14] }));
|
||||
|
||||
Reference in new issue
Block a user