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:
1 parent
231da0efbc
commit
461a7747e1
16 files changed
+244
-17
No files matched your search
@@ -158,6 +158,8 @@
|
||||
"显示伤害数字": "Damage Numbers",
|
||||
"官方伤害数字": "Official Damage Numbers",
|
||||
"只显示达到官方暴击条件的红色数字。关闭时仍显示每一次命中。": "Show only red numbers that meet the official crit rule. While off, every hit still shows a number.",
|
||||
"弹药数量": "Ammo Count",
|
||||
"弹药类技能开启时,在弹药条下方显示剩余弹药 / 总弹药数(如 13/14)。官方界面没有这个数字。": "While an ammo skill runs, shows the rounds left / in total under its ammo bar (e.g. 13/14). The official interface has no such number.",
|
||||
"画面质量": "Graphics Quality",
|
||||
"触屏操作:点击单位选中(撤退 / 出售)· 长按单位或卡牌查看详情 · 拖动部署后滑动选择朝向": "Touch controls: tap a unit to select it (Retreat / Sell) · long-press a unit or card for details · after dragging to deploy, swipe to choose a direction",
|
||||
"快捷键": "Hotkeys",
|
||||
|
||||
@@ -164,6 +164,8 @@
|
||||
"显示伤害数字": "ダメージ数値を表示",
|
||||
"官方伤害数字": "公式ダメージ数値",
|
||||
"只显示达到官方暴击条件的红色数字。关闭时仍显示每一次命中。": "公式のクリティカル条件を満たす赤い数値だけを表示します。オフのときは、これまで通り命中ごとに表示します。",
|
||||
"弹药数量": "弾薬数",
|
||||
"弹药类技能开启时,在弹药条下方显示剩余弹药 / 总弹药数(如 13/14)。官方界面没有这个数字。": "弾薬スキルの発動中、弾薬バーの下に残弾数/総弾数(例:13/14)を表示します。公式の画面にはない表示です。",
|
||||
"画面质量": "画質",
|
||||
"触屏操作:点击单位选中(撤退 / 出售)· 长按单位或卡牌查看详情 · 拖动部署后滑动选择朝向": "タッチ操作:ユニットをタップで選択(撤退 / 売却)· ユニットやカードを長押しで詳細表示 · ドラッグで配置後、スワイプで向きを選択",
|
||||
"快捷键": "ショートカットキー",
|
||||
|
||||
@@ -160,6 +160,8 @@
|
||||
"显示伤害数字": "대미지 수치 표시",
|
||||
"官方伤害数字": "공식 대미지 수치",
|
||||
"只显示达到官方暴击条件的红色数字。关闭时仍显示每一次命中。": "공식 치명타 조건을 만족하는 빨간 숫자만 표시합니다. 끄면 지금까지처럼 모든 타격에 숫자가 표시됩니다.",
|
||||
"弹药数量": "탄약 수",
|
||||
"弹药类技能开启时,在弹药条下方显示剩余弹药 / 总弹药数(如 13/14)。官方界面没有这个数字。": "탄약 스킬이 켜져 있는 동안 탄약 바 아래에 남은 탄약 / 전체 탄약 수(예: 13/14)를 표시합니다. 공식 화면에는 없는 숫자입니다.",
|
||||
"画面质量": "그래픽 품질",
|
||||
"触屏操作:点击单位选中(撤退 / 出售)· 长按单位或卡牌查看详情 · 拖动部署后滑动选择朝向": "터치 조작: 유닛을 탭하여 선택(퇴각 / 판매) · 유닛이나 카드를 길게 눌러 상세 정보 확인 · 드래그로 배치한 뒤 스와이프로 방향 선택",
|
||||
"快捷键": "단축키",
|
||||
|
||||
@@ -162,6 +162,8 @@
|
||||
"显示伤害数字": "顯示傷害數字",
|
||||
"官方伤害数字": "官方傷害數字",
|
||||
"只显示达到官方暴击条件的红色数字。关闭时仍显示每一次命中。": "只顯示達到官方暴擊條件的紅色數字。關閉時仍顯示每一次命中。",
|
||||
"弹药数量": "彈藥數量",
|
||||
"弹药类技能开启时,在弹药条下方显示剩余弹药 / 总弹药数(如 13/14)。官方界面没有这个数字。": "彈藥類技能開啟時,在彈藥條下方顯示剩餘彈藥 / 總彈藥數(如 13/14)。官方介面沒有這個數字。",
|
||||
"画面质量": "畫面品質",
|
||||
"触屏操作:点击单位选中(撤退 / 出售)· 长按单位或卡牌查看详情 · 拖动部署后滑动选择朝向": "觸控操作:點擊單位選中(撤退 / 出售)· 長按單位或卡牌查看詳情 · 拖動部署後滑動選擇朝向",
|
||||
"快捷键": "快捷鍵",
|
||||
|
||||
+1
-1
@@ -282,7 +282,7 @@ export function buildDiagnostics(input) {
|
||||
b.done ? 'done' : b.loading ? 'loading' : 'running', b.paused ? 'paused' : null, Number.isFinite(b.speed) ? `${b.speed}x` : null)}`);
|
||||
}
|
||||
if (settings) {
|
||||
lines.push(`settings: ${join(`quality ${settings.quality}`, settings.damageNumbers ? 'damage numbers' : null, settings.officialDamageNumbers ? 'official damage numbers' : null, settings.muted ? 'muted' : null)}`);
|
||||
lines.push(`settings: ${join(`quality ${settings.quality}`, settings.damageNumbers ? 'damage numbers' : null, settings.officialDamageNumbers ? 'official damage numbers' : null, settings.ammoCount ? 'ammo count' : null, settings.muted ? 'muted' : null)}`);
|
||||
}
|
||||
const tl = trafficLine(trafficTotals);
|
||||
if (tl) lines.push(`traffic: ${tl}`);
|
||||
|
||||
@@ -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(); }
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,14 +23,16 @@ import { DEFAULT_HOTKEYS, sanitizeHotkeys } from './shortcuts.js';
|
||||
export const TEXT_SIZES = Object.freeze(['sm', 'md', 'lg', 'xl']);
|
||||
|
||||
/** keys: the in-match shortcuts' key map (ui/gameLogic/shortcuts.js; settings → 快捷键). voiceLang: VOICE_LANGS.
|
||||
* textSize: TEXT_SIZES (css/theme.css `--t`, applied by ui/settings.js applyTextSize). */
|
||||
export const DEFAULT_SETTINGS = Object.freeze({ bgm: 0.6, sfx: 0.8, voice: 0.8, voiceLang: 'cn', voiceOverrides: Object.freeze({}), muted: false, damageNumbers: true, officialDamageNumbers: false, quality: 'high', textSize: 'sm', keys: DEFAULT_HOTKEYS });
|
||||
* textSize: TEXT_SIZES (css/theme.css `--t`, applied by ui/settings.js applyTextSize). ammoCount: 设置「弹药数量」 — the
|
||||
* rounds left / the magazine under an ammo skill's bar (render/units.js; PR #552), off by default: the official HUD has no
|
||||
* such number (the maintainer's decision of 2026-10-13). */
|
||||
export const DEFAULT_SETTINGS = Object.freeze({ bgm: 0.6, sfx: 0.8, voice: 0.8, voiceLang: 'cn', voiceOverrides: Object.freeze({}), muted: false, damageNumbers: true, officialDamageNumbers: false, ammoCount: false, quality: 'high', textSize: 'sm', keys: DEFAULT_HOTKEYS });
|
||||
const QUALITIES = ['high', 'medium', 'low'];
|
||||
|
||||
/**
|
||||
* Sanitize persisted settings.
|
||||
* @param {any} raw
|
||||
* @returns {{ bgm: number, sfx: number, voice: number, voiceLang: 'cn'|'jp', voiceOverrides: Record<string, string>, muted: boolean, damageNumbers: boolean, officialDamageNumbers: boolean, quality: 'high'|'medium'|'low',
|
||||
* @returns {{ bgm: number, sfx: number, voice: number, voiceLang: 'cn'|'jp', voiceOverrides: Record<string, string>, muted: boolean, damageNumbers: boolean, officialDamageNumbers: boolean, ammoCount: boolean, quality: 'high'|'medium'|'low',
|
||||
* textSize: 'sm'|'md'|'lg'|'xl', keys: Record<'refresh'|'freeze'|'levelUp'|'retreat'|'sell'|'ready', string> }}
|
||||
*/
|
||||
export function sanitizeSettings(raw) {
|
||||
@@ -45,6 +47,7 @@ export function sanitizeSettings(raw) {
|
||||
muted: typeof r.muted === 'boolean' ? r.muted : DEFAULT_SETTINGS.muted,
|
||||
damageNumbers: typeof r.damageNumbers === 'boolean' ? r.damageNumbers : DEFAULT_SETTINGS.damageNumbers,
|
||||
officialDamageNumbers: typeof r.officialDamageNumbers === 'boolean' ? r.officialDamageNumbers : false,
|
||||
ammoCount: typeof r.ammoCount === 'boolean' ? r.ammoCount : DEFAULT_SETTINGS.ammoCount,
|
||||
quality: QUALITIES.includes(r.quality) ? r.quality : DEFAULT_SETTINGS.quality,
|
||||
textSize: TEXT_SIZES.includes(r.textSize) ? r.textSize : DEFAULT_SETTINGS.textSize,
|
||||
keys: sanitizeHotkeys(r.keys),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Player settings (BGM/SFX/voice volume, the voice dub 语音语言, mute, damage numbers, render quality, 文字大小, the
|
||||
// Player settings (BGM/SFX/voice volume, the voice dub 语音语言, mute, damage numbers, the optional 弹药数量 under an ammo
|
||||
// skill's bar — not in the official HUD, off by default, PR #552 —, render quality, 文字大小, the
|
||||
// shortcut keys): a tiny observable store persisted in localStorage (`sp.pref.settings`), applied to the audio manager
|
||||
// and to the document on every change, plus the settings modal — which also holds the language switch (ui/lang.js; kept
|
||||
// apart in `sp.pref.lang`; under it a note while the current language's pack is a machine translation,
|
||||
@@ -29,7 +30,7 @@ import { copyText } from './clipboard.js';
|
||||
|
||||
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
||||
|
||||
/** Settings store: { bgm, sfx, voice, voiceLang, voiceOverrides, muted, damageNumbers, quality, textSize, keys }. */
|
||||
/** Settings store: { bgm, sfx, voice, voiceLang, voiceOverrides, muted, damageNumbers, officialDamageNumbers, ammoCount, quality, textSize, keys }. */
|
||||
export const settingsStore = createStore(sanitizeSettings(loadPref('settings', null)));
|
||||
|
||||
/**
|
||||
@@ -265,6 +266,8 @@ export function SettingsModal({ open, onClose }) {
|
||||
<${Toggle} label=${t('显示伤害数字')} micro="DAMAGE NUMBERS" value=${s.damageNumbers} onChange=${(v) => updateSettings({ damageNumbers: v })} />
|
||||
<${Toggle} label=${t('官方伤害数字')} micro="OFFICIAL NUMBERS" value=${s.officialDamageNumbers} onChange=${(v) => updateSettings({ officialDamageNumbers: v })} />
|
||||
<p class="set-hint">${t('只显示达到官方暴击条件的红色数字。关闭时仍显示每一次命中。')}</p>
|
||||
<${Toggle} label=${t('弹药数量')} micro="AMMO COUNT" value=${s.ammoCount} onChange=${(v) => updateSettings({ ammoCount: v })} />
|
||||
<p class="set-hint">${t('弹药类技能开启时,在弹药条下方显示剩余弹药 / 总弹药数(如 13/14)。官方界面没有这个数字。')}</p>
|
||||
<div class="set-row">
|
||||
<span class="set-row__label">${t('画面质量')}<${MicroLabel}>QUALITY<//></span>
|
||||
<div class="set-seg" role="radiogroup">
|
||||
|
||||
Reference in new issue
Block a user