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

+5 -3
View File
@@ -61,7 +61,8 @@
// view.holdPiece(uid, {row,col} | null) keep a dropped prep piece standing on a tile while its direction is
// chosen / the move is in flight (null releases; setPrep then settles it)
// view.setPieceDir(uid, dir) show a prep piece facing UP|RIGHT|DOWN|LEFT (wheel preview / stored dir)
// view.setSettings({ damageNumbers, officialDamageNumbers, quality: 'high'|'medium'|'low' }) ; view.resize() ; view.destroy()
// view.setSettings({ damageNumbers, officialDamageNumbers, ammoCount, quality: 'high'|'medium'|'low' }) ; view.resize() ; view.destroy()
// (ammoCount: 设置「弹药数量」, the count under an ammo skill's bar — PR #552)
// view.stats() → { fps, frameMs, cpuMs, renderMs, units, particles, impostor, impostorAtlas, boardArt, … } (dev / perf)
//
// Enemy preview pen (research 09 §2.2 / 08 §4.2, render/pen.js): in prep the next round's enemies idle in the pen
@@ -157,7 +158,7 @@ export { releaseGl } from './app/host.js';
/**
* Create the battlefield view inside `host` (an element sized by CSS; the canvas fills it).
* @param {HTMLElement} host
* @param {{ data?: any, assets?: any, audio?: any, settings?: { damageNumbers?: boolean, officialDamageNumbers?: boolean, quality?: string }, signal?: AbortSignal,
* @param {{ data?: any, assets?: any, audio?: any, settings?: { damageNumbers?: boolean, officialDamageNumbers?: boolean, ammoCount?: boolean, quality?: string }, signal?: AbortSignal,
* padding?: object|((kind:string, size:{width:number,height:number}) => object),
* hud?: {top:number,bottom:number}|((kind:'prep'|'bossPrep', size:{width:number,height:number}, o:{shop:boolean}) => {top:number,bottom:number}|null) }} [opts]
*/
@@ -170,7 +171,7 @@ export async function createFieldView(host, options = {}) {
signal?.throwIfAborted();
const assets = resolveAssets(opts.assets);
const data = makeData(opts.data);
const settings = { damageNumbers: true, officialDamageNumbers: false, quality: 'high', ...(opts.settings || {}) };
const settings = { damageNumbers: true, officialDamageNumbers: false, ammoCount: false, quality: 'high', ...(opts.settings || {}) };
// the 3D board (three.js + the official art) loads in parallel with everything else
const boardPref = boardPreference(opts.board);
const want3d = boardPref !== '2d' && webgl2Available(boardPref === '3d');
@@ -1982,6 +1983,7 @@ export async function createFieldView(host, options = {}) {
const q = settings.quality;
if (typeof s.damageNumbers === 'boolean') settings.damageNumbers = s.damageNumbers;
if (typeof s.officialDamageNumbers === 'boolean') settings.officialDamageNumbers = s.officialDamageNumbers;
if (typeof s.ammoCount === 'boolean') settings.ammoCount = s.ammoCount; // the units read it every frame (ctx.settings)
if (s.quality === 'high' || s.quality === 'medium' || s.quality === 'low') settings.quality = s.quality;
if (q !== settings.quality) { board3d?.setQuality?.(settings.quality); resize(); }
},
+31 -4
View File
@@ -2,7 +2,8 @@
//
// UnitView = shadow sprite (shadow layer) + body container (depth-sorted unit layer: elite aura, Spine actor or
// the avatar-in-rarity-diamond fallback) + HUD container (bar layer: HP bar with delayed "ghost" damage, SP bar
// with ready glow / draining skill bar, tier chip, status icons, blocked marker). The fallback shows at once and
// with ready glow / draining skill bar — an ammo skill's segmented magazine, with its count under it when 设置「弹药数量」
// is on (PR #552) —, tier chip, status icons, blocked marker). The fallback shows at once and
// cross-fades to the Spine model when it has loaded (the game never blocks on Spine); an optional local-client model
// (assets.js spineEntry `fallback`, DESIGN §13: 灼热源石虫 / 炽焰源石虫) falls back to the web model first; that web alias
// (the plain 源石虫) is drawn tinted toward the slug's own colours (ALIAS_TINT). A model that failed or timed out is
@@ -462,7 +463,7 @@ function bar(P, parent, color, alpha = 1) {
* Shared context handed to every view by app.js (`audio`: the audio manager of public/js/audio.js — prep landings, land()).
* @typedef {{ P: any, layers: { shadow: any, units: any, bars: any, groundFx: any },
* assets: any, audio?: any, cam: () => any, heightAt: (r:number,c:number)=>number, fx: any, time: () => number,
* settings: { damageNumbers: boolean, quality: string } }} ViewCtx
* settings: { damageNumbers: boolean, quality: string, ammoCount?: boolean } }} ViewCtx
*/
export class UnitView {
@@ -805,6 +806,7 @@ export class UnitView {
this.blockIcon.visible = false;
this.ctx.layers.groundFx.addChild(this.blockIcon);
this.countText = null;
this.ammoText = null; // 设置「弹药数量」: the magazine's count under the ammo bar (_updateAmmoCount)
this.itemPips = [];
}
@@ -1496,8 +1498,10 @@ export class UnitView {
ic.position.set(x - ((icons.length - 1) * (isz + 2)) / 2 + i * (isz + 2), iy);
}
// 狼影 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);
// knocked-down operator (b.snap `down`). 设置「弹药数量」 (off by default) first puts the magazine's count right under
// the ammo bar, and the rows below move down by its height
let rowsTop = cy + bh / 2 + (showSp ? spH + 1.5 : 0);
rowsTop += this._updateAmmoCount(showSp && !!ammo && this.ctx.settings?.ammoCount === true, 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);
@@ -1525,6 +1529,29 @@ export class UnitView {
}
}
/**
* 设置「弹药数量」 (PR #552 by @IceCodeNew; off by default — the official HUD has no such number, the maintainer's decision
* of 2026-10-13): the rounds left / the rounds in the magazine (b.snap `ammo`: the exact counts, 拉特兰's extra rounds
* and refills included) centred at `x` under the ammo bar, its top at `top`, in the bar's yellow — readable even where the
* cells are too narrow for their cuts. Built on first use, hidden otherwise (the skill over, the prep view, a death, an
* older snapshot without the row). Returns the height it takes (0 while hidden) so the rows under the bars move down.
*/
_updateAmmoCount(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);
}
const label = `${this.ammo[0]}/${this.ammo[1]}`;
if (text.text !== label) text.text = label;
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