feedback7 R22-HUD: the HP bar shows the ammo skill's rounds as yellow cells (隐现 S2: 14), 伺夜's 狼影 left as diamonds and 斩业星熊's 我执 pool as a red bar — one snapshot change for PR #303 and #378 by @IceCodeNew and PR #383 by @Convey123; golden: unchanged
The three PRs patched the same snapshot and HUD lines (Grok's review: do not merge three diffs), so this is one design built from their code and tests; IceCodeNew is the commit author (#303, #378), Convey123 co-author (#383). - b.snap gains three optional, display-only lists beside the unchanged 9-field tuples (snapshot.js ammoView / wolfView / negView, called from battle/events.js snapshot(); a living, deployed, visible ally, only when it has one): ammo [[id, rounds left, magazine]] (whole rounds; the magazine is the activation's real total that unitTuple's SP fraction runs on, extras and refills included), wolves [[id, 狼影 left, talent maximum]] (mem.shadows of the new mem.wolfCapacity, set by tokens.js wolfPack and the managed vigil kit), neg [[id, fill]] (unit.negFill(), a function op-hsgma2.js sets: the pool is read when the snapshot is taken, no per-tick sync). UnitInfo.ammoSkill (an ally whose skill is an ammo magazine) is known from the first appearance: fx events run before the frame's sample, so the aura cannot wait for the first ammo row. - render/interp.js keeps the lists per snapshot (malformed rows / unknown ids dropped) and sample() hands them out from the older snapshot like flags (they step with the skill flag); render/units.js draws them: the ammo magazine replaces the SP bar (a cell per round, thin cuts, exact fill, no cuts under 2 px a cell), a row of diamonds under the bars for the wolves (the element gauge row moves below), the red bar over the drained HP bar for the pool. An ammo skill draws no standing aura (rings.js _aura; the activation burst stays). - PR #378's wolf-head avatars are NOT taken: the count is data (the talent text 至多3只, mem.shadows), the HUD art of the official counter is in no game data we have, so the pips are plain diamonds [ASSUMED look]. The PRs' own tests are folded in and extended (test/sim/hud-readouts.test.js, test/render/hud-readouts.test.js, op_hsgma2.test.js). - Docs: network.md, SIM.md §9, client.md, PLAYING.md, types/core.js. - Golden: unchanged (283 scenarios; the canon hashes units / down / elem only, no sim state moved). - Tests: 4 + 14 new + 1 in op_hsgma2 (12), all fail on the parent; 344 related tests pass; lint 0 errors, typecheck, check:imports, docs tests. Co-authored-by: Convey123 <Convey123@users.noreply.github.com>
This commit is contained in:
1 parent
ae22335cfe
commit
8ae891f1f3
19 files changed
+721
-28
No files matched your search
@@ -37,6 +37,9 @@ export function renderInfo(u) {
|
||||
// DESIGN §16 loadout of an ally (UnitInfo.skillIndex / moduleId): the Spine actor plays that skill's clip, and a
|
||||
// tap hands them to the detail card (a teammate's unit shows its owner's skill / module)
|
||||
skillIndex: Number.isInteger(u.skillIndex) ? u.skillIndex : undefined,
|
||||
// an ally whose skill is an ammo magazine (UnitInfo.ammoSkill): its rounds are the cells under the HP bar, so no sustained
|
||||
// skill aura is drawn for it (render/fx/rings.js _aura)
|
||||
ammoSkill: u.ammoSkill === true ? true : undefined,
|
||||
moduleId: typeof u.moduleId === 'string' ? u.moduleId : undefined,
|
||||
// the ally's equipped item ids (UnitInfo.items, DESIGN §16 / §21.11): the detail card needs them for a teammate's
|
||||
// unit (resolveDetail `unitItems` → the read-only 装备 section and the 变形同构体 pairing chips); the owner's own
|
||||
|
||||
@@ -74,8 +74,13 @@ export class FxRings {
|
||||
this._aura(view, true);
|
||||
}
|
||||
|
||||
/** Active-skill aura: a soft gold glow and a slowly turning hex on the ground under the unit (fading in / out). */
|
||||
/**
|
||||
* Active-skill aura: a soft gold glow and a slowly turning hex on the ground under the unit (fading in / out). An ammo
|
||||
* skill (UnitInfo.ammoSkill) has none: its magazine is the yellow cells under the HP bar, which shrink with every round
|
||||
* — a standing glow would read as a buff that never changes. The activation burst (skill()) is the same for all.
|
||||
*/
|
||||
_aura(view, on) {
|
||||
if (on && view.info?.ammoSkill) return;
|
||||
const P = this.P;
|
||||
let a = this.auras.get(view.id);
|
||||
if (on) {
|
||||
|
||||
@@ -20,14 +20,21 @@
|
||||
// game s behind) are dropped by the caller's choice (`isCosmeticEvent`); state events — an enemy's form fx
|
||||
// included — are always delivered, and a full queue sheds only cosmetic ones.
|
||||
//
|
||||
// Snapshot tuple layout (DESIGN §8.2): [id, x, y, hp, maxHp, sp, spMax, flags, anim]. Two optional lists ride along
|
||||
// (server/sim/battle/events.js snapshot, user playtest #4 items 8 / 9):
|
||||
// Snapshot tuple layout (DESIGN §8.2): [id, x, y, hp, maxHp, sp, spMax, flags, anim]. Optional lists ride along
|
||||
// (server/sim/battle/events.js snapshot; the first two from user playtest #4 items 8 / 9):
|
||||
// * `elem` [[id, element, fill, cooldownEnd, cooldown]] — the element gauge a unit shows: appended to that unit's
|
||||
// normalised tuple (EL…EL_DUR) and handed out by sample() as `el`, `elFill`, `elUntil`, `elDur` (from the older
|
||||
// snapshot, like flags);
|
||||
// * `down` [[id, respawnAt, respawnTime, state, row?, col?]] — knocked-out operators waiting to redeploy (they are no
|
||||
// longer in `units`) and the tile they lie on (where they fell, or their home — sim Battle._layBody; kept only when
|
||||
// both are integers): downAt(time) returns the list of the snapshot at `time`.
|
||||
// both are integers): downAt(time) returns the list of the snapshot at `time`;
|
||||
// * three HP-bar readouts, kept per snapshot beside the tuples (the tuple layout is unchanged) and handed out by sample()
|
||||
// from the older snapshot like flags, so they step with the skill flag instead of sliding:
|
||||
// `ammo` [[id, rounds left, rounds in the magazine]] → sample().ammo [left, magazine] | null (whole numbers only)
|
||||
// `wolves` [[id, 狼影 left, the talent's maximum]] → sample().wolves [left, maximum] | null
|
||||
// `neg` [[id, fill]] (0.01–1: the negative-HP pool's share of its cap) → sample().neg number (0: none)
|
||||
// An entry that is malformed, or names a unit the snapshot does not list, is dropped; a snapshot without the list
|
||||
// clears the readout (render/units.js: the segmented ammo bar, the wolf pips, the red bar of 斩业星熊's 我执).
|
||||
// Game times in both (`cooldownEnd`, `respawnAt`) are on the snapshots' clock, so a view compares them with renderT.
|
||||
|
||||
import { fxForm } from '../../../shared/protocol.js';
|
||||
@@ -66,8 +73,9 @@ export function frameTime(msg) {
|
||||
|
||||
/**
|
||||
* Validate & normalise a b.snap payload. Returns `{ t, units: Map<id, tuple>, down: [[id, respawnAt, respawnTime,
|
||||
* state, row?, col?]] | null, raw }` or null when unusable. Tuples with a non-finite id/x/y are skipped; other numbers default
|
||||
* to 0; a unit's `elem` entry (see header) is appended to its tuple; malformed `elem` / `down` entries are dropped.
|
||||
* state, row?, col?]] | null, ammo, wolves: Map id → [left, max] | null, neg: Map id → fill | null, raw }` or null when unusable.
|
||||
* Tuples with a non-finite id/x/y are skipped; other numbers default to 0; a unit's `elem` entry (see header) is appended to
|
||||
* its tuple; malformed `elem` / `down` / `ammo` / `wolves` / `neg` entries are dropped.
|
||||
*/
|
||||
export function normalizeSnapshot(snap) {
|
||||
if (!snap || typeof snap !== 'object') return null;
|
||||
@@ -90,6 +98,14 @@ export function normalizeSnapshot(snap) {
|
||||
if (tu && tu.length === 9) tu.push(e[1], clamp(finite(e[2]), 0, 1), finite(e[3]), Math.max(0, finite(e[4])));
|
||||
}
|
||||
}
|
||||
const ammo = countList(snap.ammo, units), wolves = countList(snap.wolves, units);
|
||||
let neg = null;
|
||||
if (Array.isArray(snap.neg)) {
|
||||
for (const e of snap.neg) {
|
||||
if (!Array.isArray(e) || !units.has(e[0]) || !(typeof e[1] === 'number' && e[1] > 0)) continue;
|
||||
(neg || (neg = new Map())).set(e[0], Math.min(1, e[1]));
|
||||
}
|
||||
}
|
||||
let down = null;
|
||||
if (Array.isArray(snap.down)) {
|
||||
for (const d of snap.down) {
|
||||
@@ -99,7 +115,22 @@ export function normalizeSnapshot(snap) {
|
||||
(down || (down = [])).push(e);
|
||||
}
|
||||
}
|
||||
return { t, units, down, raw: snap };
|
||||
return { t, units, down, ammo, wolves, neg, raw: snap };
|
||||
}
|
||||
|
||||
/**
|
||||
* `[[id, left, max]]` → Map id → [left, max] for the ids in `units`: whole numbers with 0 ≤ left ≤ max and max ≥ 1 (the
|
||||
* `ammo` and `wolves` lists); null without a valid entry.
|
||||
*/
|
||||
function countList(list, units) {
|
||||
if (!Array.isArray(list)) return null;
|
||||
let m = null;
|
||||
for (const e of list) {
|
||||
if (!Array.isArray(e) || !units.has(e[0]) || !Number.isSafeInteger(e[1]) || !Number.isSafeInteger(e[2])
|
||||
|| e[1] < 0 || e[2] < 1 || e[1] > e[2]) continue;
|
||||
(m || (m = new Map())).set(e[0], [e[1], e[2]]);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
export class SnapshotBuffer {
|
||||
@@ -265,8 +296,9 @@ export class SnapshotBuffer {
|
||||
|
||||
/**
|
||||
* Interpolated state at `time` (default renderT). Fills and returns `out` (a Map id → sample object reused
|
||||
* across calls: `{ id, x, y, hp, maxHp, sp, spMax, flags, anim, vx, vy, seen, el, elFill, elUntil, elDur }` —
|
||||
* `el` = the shown element gauge (null: none), see header). Samples of units no longer present are deleted from `out`.
|
||||
* across calls: `{ id, x, y, hp, maxHp, sp, spMax, flags, anim, vx, vy, seen, el, elFill, elUntil, elDur, ammo, wolves,
|
||||
* neg }` — `el` = the shown element gauge (null: none), `ammo` / `wolves` / `neg` the HP-bar readouts (null / null / 0:
|
||||
* none), see header). Samples of units no longer present are deleted from `out`.
|
||||
*/
|
||||
sample(time = this.renderT, out = new Map()) {
|
||||
const s = this.snaps;
|
||||
@@ -282,7 +314,7 @@ export class SnapshotBuffer {
|
||||
const stamp = A.t;
|
||||
for (const [id, a] of A.units) {
|
||||
let o = out.get(id);
|
||||
if (!o) { o = { id, x: 0, y: 0, hp: 0, maxHp: 0, sp: 0, spMax: 0, flags: 0, anim: 0, vx: 0, vy: 0, seen: 0, el: null, elFill: 0, elUntil: 0, elDur: 0 }; out.set(id, o); }
|
||||
if (!o) { o = { id, x: 0, y: 0, hp: 0, maxHp: 0, sp: 0, spMax: 0, flags: 0, anim: 0, vx: 0, vy: 0, seen: 0, el: null, elFill: 0, elUntil: 0, elDur: 0, ammo: null, wolves: null, neg: 0 }; out.set(id, o); }
|
||||
const b = B ? B.units.get(id) : null;
|
||||
if (b) {
|
||||
const dx = b[1] - a[1], dy = b[2] - a[2];
|
||||
@@ -317,6 +349,10 @@ export class SnapshotBuffer {
|
||||
o.flags = a[7];
|
||||
o.anim = a[8];
|
||||
if (a.length > 9) { o.el = a[9]; o.elFill = a[10]; o.elUntil = a[11]; o.elDur = a[12]; } else if (o.el !== null) { o.el = null; o.elFill = 0; o.elUntil = 0; o.elDur = 0; }
|
||||
// the HP-bar readouts come with the older snapshot, like flags (whole rounds step with the skill flag)
|
||||
o.ammo = A.ammo ? A.ammo.get(id) || null : null;
|
||||
o.wolves = A.wolves ? A.wolves.get(id) || null : null;
|
||||
o.neg = A.neg ? A.neg.get(id) || 0 : 0;
|
||||
o.seen = stamp;
|
||||
}
|
||||
for (const id of out.keys()) if (!A.units.has(id)) out.delete(id);
|
||||
|
||||
@@ -60,9 +60,14 @@ export const COLORS = Object.freeze({
|
||||
hpBoss: 0xff2d55,
|
||||
hpGhost: 0xfff0c8,
|
||||
hpBack: 0x0c0f0e,
|
||||
// the red bar of a negative-HP pool (斩业星熊's 我执, b.snap `neg`) — the same red as an enemy's HP
|
||||
hpNeg: 0xff4b3e,
|
||||
sp: 0x6fd3ff,
|
||||
spReady: 0xffe066,
|
||||
spActive: 0xffb347,
|
||||
// the ammo bar's yellow cells (b.snap `ammo`) and 伺夜's 狼影 pips (b.snap `wolves`)
|
||||
ammo: 0xffd04a,
|
||||
wolf: 0xe8f0ff,
|
||||
shield: 0xdfe8ff,
|
||||
});
|
||||
|
||||
|
||||
@@ -184,6 +184,17 @@ export const ALIAS_TINT = Object.freeze({ enemy_1305_mhslim: 0xffc48a, enemy_130
|
||||
* between `pulse` and 1 at `pulseHz` (real time).
|
||||
*/
|
||||
export const EL_BAR = Object.freeze({ icon: 0.15, min: 8, max: 15, enemy: 0.8, gap: 1, pulse: 0.45, pulseHz: 1.5 });
|
||||
/**
|
||||
* The HP-bar readouts under the bars (b.snap `ammo`, `wolves`, `neg`; render/interp.js header):
|
||||
* - AMMO_BAR — the ammo skill's segmented bar replaces the SP bar: a cell per round, `height` of the HP bar's thickness
|
||||
* (the SP bar's is 0.6), cells drawn only while they are at least `minCell` px wide;
|
||||
* - WOLF_PIPS — 伺夜's 狼影 count as `size` (tiles) wide diamonds, clamped to `min`…`max` px, `gap` px apart, `pad` px
|
||||
* below the rows above; a lit diamond is a 狼影 left, a dim one a spent slot (`dim` alpha).
|
||||
* [ASSUMED] the look: the count is data (skill_table `attack@trigger_time` 14 for 隐现's S2; the talent text 「至多3只」), the
|
||||
* official HUD art of neither is in the game data.
|
||||
*/
|
||||
export const AMMO_BAR = Object.freeze({ height: 0.85, minCell: 2 });
|
||||
export const WOLF_PIPS = Object.freeze({ size: 0.1, min: 5, max: 9, gap: 2, pad: 1.5, dim: 0.25 });
|
||||
|
||||
/**
|
||||
* Enemy modes drawn with another clip set of the same skeleton (the `form` of a sim fx — shared/protocol.js fxForm —
|
||||
@@ -407,6 +418,9 @@ export class UnitView {
|
||||
this.visFacing = this.isEnemy ? -1 : this.facing;
|
||||
this.hp = Number(info.maxHp) || 1; this.maxHp = Number(info.maxHp) || 1; this.ghostHp = this.hp;
|
||||
this.sp = 0; this.spMax = 0;
|
||||
this.ammo = null; // [rounds left, rounds in the magazine] while an ammo skill runs (sync; b.snap `ammo`)
|
||||
this.wolves = null; // [狼影 left, the talent's maximum] of a 狼群 (sync; b.snap `wolves`)
|
||||
this.neg = 0; // the share of its cap a negative-HP pool holds, 0 = none (sync; b.snap `neg`, 斩业星熊's 我执)
|
||||
this.flags = 0; this.anim = ANIM.IDLE;
|
||||
this.statuses = new Set();
|
||||
this.alive = true;
|
||||
@@ -417,6 +431,8 @@ export class UnitView {
|
||||
this.gameT = 0; // battle game time of the frame (render clock), for the redeploy ring's countdown and the element bar's refill
|
||||
this.el = null; this.elFill = 0; this.elUntil = 0; this.elDur = 0; // shown element gauge (sync)
|
||||
this._elBar = null; // { root, disc, bg, fill } sprites of the element gauge row, built on first use
|
||||
this._wolfPips = null; // { root, back[], lit[] } diamonds of the 狼影 row, built on first use
|
||||
this.ammoCuts = []; // the thin separators between the ammo bar's cells (_updateAmmoCuts)
|
||||
this._downRing = null; // { disc, track, arc, text } sprites, built on first use
|
||||
this.alpha = 1; this.fadeIn = this.prep ? 1 : 0;
|
||||
this.lunge = 0; this.lungeDir = { x: 1, y: 0 };
|
||||
@@ -649,6 +665,8 @@ export class UnitView {
|
||||
this.hpBg = bar(P, h, COLORS.hpBack, 0.85);
|
||||
this.hpGhost = bar(P, h, COLORS.hpGhost, 0.9);
|
||||
this.hpFill = bar(P, h, this.isEnemy ? (this.isBoss ? COLORS.hpBoss : COLORS.hpEnemy) : COLORS.hpAlly);
|
||||
// a negative-HP pool (斩业星熊's 我执), drawn over the drained HP bar as the red bar
|
||||
this.negFill = bar(P, h, COLORS.hpNeg);
|
||||
this.shieldBar = bar(P, h, COLORS.shield, 0.95);
|
||||
this.spBg = bar(P, h, COLORS.hpBack, 0.85);
|
||||
this.spFill = bar(P, h, COLORS.sp);
|
||||
@@ -658,7 +676,7 @@ export class UnitView {
|
||||
this.spGlow.blendMode = P.BLEND_MODES.ADD;
|
||||
h.addChild(this.spGlow);
|
||||
// nothing shows until the first HUD update decides (a culled or prep view never draws bars)
|
||||
for (const b of [this.hpBg, this.hpGhost, this.hpFill, this.shieldBar, this.spBg, this.spFill, this.spGlow]) b.visible = false;
|
||||
for (const b of [this.hpBg, this.hpGhost, this.hpFill, this.negFill, this.shieldBar, this.spBg, this.spFill, this.spGlow]) b.visible = false;
|
||||
this.chip = null;
|
||||
// operators only: summon tokens have no tier (hand and field alike)
|
||||
if (!this.isEnemy && !this.isToken && this.info.kind !== 'device') {
|
||||
@@ -744,6 +762,7 @@ export class UnitView {
|
||||
if (hp < this.hp - 0.5 && this.isBoss) this.shake = 0.25;
|
||||
this.hp = hp;
|
||||
this.sp = s.sp; this.spMax = s.spMax;
|
||||
this.ammo = s.ammo || null; this.wolves = s.wolves || null; this.neg = s.neg > 0 ? Math.min(1, s.neg) : 0;
|
||||
const prevFlags = this.flags;
|
||||
this.flags = s.flags | 0;
|
||||
this.anim = s.anim | 0;
|
||||
@@ -1174,24 +1193,32 @@ export class UnitView {
|
||||
this.hpFill.position.set(x0, cy); this.hpFill.width = bw * k; this.hpFill.height = bh;
|
||||
if (!this.isEnemy) this.hpFill.tint = k < 0.3 ? COLORS.hpAllyLow : COLORS.hpAlly;
|
||||
}
|
||||
// 业火 我执: her HP sits on the 1-HP floor and the damage past it fills a pool — the red bar over the drained green one
|
||||
const neg = showHp ? this.neg : 0;
|
||||
this.negFill.visible = neg > 0;
|
||||
if (neg > 0) { this.negFill.position.set(x0, cy); this.negFill.width = bw * neg; this.negFill.height = bh; }
|
||||
const shielded = showHp && (this.flags & UF.SHIELD);
|
||||
this.shieldBar.visible = !!shielded;
|
||||
if (shielded) { this.shieldBar.position.set(x0, cy - bh / 2 - 1); this.shieldBar.width = bw; this.shieldBar.height = Math.max(1.5, bh * 0.35); }
|
||||
// SP
|
||||
const showSp = showBars && !this.isEnemy && this.spMax > 0;
|
||||
// SP — or, while an ammo skill runs, its magazine: yellow cells, one per round, emptying from the right (b.snap `ammo`;
|
||||
// the SP bar's own fraction, ammoLeft / ammoMax, is the same width). Without an `ammo` row the bar is the plain SP bar.
|
||||
const ammo = !this.isEnemy ? this.ammo : null;
|
||||
const showSp = showBars && !this.isEnemy && (this.spMax > 0 || !!ammo);
|
||||
this.spBg.visible = this.spFill.visible = showSp;
|
||||
const spH = Math.max(2, bh * 0.6);
|
||||
const spH = Math.max(2, bh * (ammo ? AMMO_BAR.height : 0.6));
|
||||
let ready = false;
|
||||
let sy = 0;
|
||||
if (showSp) {
|
||||
const active = !!(this.flags & UF.SKILL);
|
||||
const k = clamp(this.sp / this.spMax, 0, 1);
|
||||
const k = ammo ? ammo[0] / ammo[1] : clamp(this.sp / this.spMax, 0, 1);
|
||||
ready = !active && k >= 0.999;
|
||||
const sy = cy + bh / 2 + spH / 2 + 1.5;
|
||||
sy = cy + bh / 2 + spH / 2 + 1.5;
|
||||
this.spBg.position.set(x0 - 1, sy); this.spBg.width = bw + 2; this.spBg.height = spH + 2;
|
||||
this.spFill.position.set(x0, sy); this.spFill.width = bw * k; this.spFill.height = spH;
|
||||
this.spFill.tint = active ? COLORS.spActive : ready ? COLORS.spReady : COLORS.sp;
|
||||
this.spFill.tint = ammo ? COLORS.ammo : active ? COLORS.spActive : ready ? COLORS.spReady : COLORS.sp;
|
||||
this._spY = sy;
|
||||
}
|
||||
this._updateAmmoCuts(showSp && !!ammo, ammo, x0, sy, bw, spH);
|
||||
this.spGlow.visible = ready;
|
||||
if (ready) {
|
||||
const pulse = 0.55 + 0.35 * Math.sin(t * 6);
|
||||
@@ -1222,8 +1249,11 @@ export class UnitView {
|
||||
ic.width = ic.height = isz;
|
||||
ic.position.set(x - ((icons.length - 1) * (isz + 2)) / 2 + i * (isz + 2), iy);
|
||||
}
|
||||
// element gauge row under the bars (b.snap `elem`); redeploy ring above a knocked-down operator (b.snap `down`)
|
||||
this._updateElementBar(showBars && !!this.el, x0, bw, cy + bh / 2 + (showSp ? spH + 1.5 : 0) + 1, spH, s, t);
|
||||
// 狼影 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);
|
||||
const wolfH = this._updateWolfPips(showBars && !this.isEnemy && !!this.wolves, x0, rowsTop, s);
|
||||
this._updateElementBar(showBars && !!this.el, x0, bw, rowsTop + wolfH + 1, spH, s, t);
|
||||
this._updateDownRing(!prep && !!this.down && !this.alive, x, this.screen.y - DOWN_LOOK.height * s, s, t);
|
||||
// blocked marker at the feet (enemies held by a blocker)
|
||||
const blocked = !prep && this.alive && this.isEnemy && (this.flags & UF.BLOCKED);
|
||||
@@ -1248,6 +1278,62 @@ export class UnitView {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* (the fill keeps its exact width). Sprites are built as the magazines need them and hidden when not in use.
|
||||
*/
|
||||
_updateAmmoCuts(show, ammo, x0, y, bw, h) {
|
||||
const cuts = this.ammoCuts;
|
||||
const n = show ? ammo[1] : 0;
|
||||
const step = n > 1 ? bw / n : 0;
|
||||
const want = step >= AMMO_BAR.minCell ? n - 1 : 0;
|
||||
while (cuts.length < want) cuts.push(bar(this.P, this.hud, COLORS.hpBack, 0.9));
|
||||
for (let i = 0; i < cuts.length; i++) {
|
||||
const c = cuts[i];
|
||||
c.visible = i < want;
|
||||
if (c.visible) { c.position.set(x0 + (i + 1) * step - 0.5, y); c.width = 1; c.height = h; }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 伺夜's 狼群 count (b.snap `wolves`, [left, maximum]): a row of diamonds from `x0`, its top at `top`, one per 狼影 the
|
||||
* talent allows, the first `left` of them lit. Built on the first count, hidden without one. Returns the height the row
|
||||
* takes (0 while hidden) so the element gauge row can sit below it.
|
||||
*/
|
||||
_updateWolfPips(show, x0, top, s) {
|
||||
let r = this._wolfPips;
|
||||
if (!show) { if (r) r.root.visible = false; return 0; }
|
||||
const P = this.P;
|
||||
const [left, max] = this.wolves;
|
||||
if (!r) {
|
||||
r = this._wolfPips = { root: new P.Container(), back: [], lit: [] };
|
||||
this.hud.addChild(r.root);
|
||||
}
|
||||
const mk = (list, color, alpha) => {
|
||||
const d = new P.Sprite(white());
|
||||
d.anchor.set(0.5); d.rotation = Math.PI / 4; d.tint = color; d.alpha = alpha;
|
||||
r.root.addChild(d);
|
||||
list.push(d);
|
||||
};
|
||||
while (r.lit.length < max) { mk(r.back, COLORS.hpBack, 0.85); mk(r.lit, COLORS.wolf, 1); }
|
||||
const d = clamp(s * WOLF_PIPS.size, WOLF_PIPS.min, WOLF_PIPS.max); // a diamond's diagonal
|
||||
const side = d / Math.SQRT2;
|
||||
const cy = top + WOLF_PIPS.pad + d / 2 + 1;
|
||||
for (let i = 0; i < r.lit.length; i++) {
|
||||
const on = i < max;
|
||||
const b = r.back[i], l = r.lit[i];
|
||||
b.visible = l.visible = on;
|
||||
if (!on) continue;
|
||||
const cx = x0 + d / 2 + 1 + i * (d + 2 + WOLF_PIPS.gap);
|
||||
b.position.set(cx, cy); b.width = b.height = side + 2;
|
||||
l.position.set(cx, cy); l.width = l.height = side;
|
||||
l.alpha = i < left ? 1 : WOLF_PIPS.dim;
|
||||
}
|
||||
r.root.visible = true;
|
||||
return d + 2 + WOLF_PIPS.pad;
|
||||
}
|
||||
|
||||
/** True while the shown gauge is in its 爆发冷却 (b.snap `elem` carries the cooldown's end and length). */
|
||||
elementCooling() {
|
||||
return !!this.el && this.elDur > 0 && this.elUntil > 0;
|
||||
|
||||
Reference in new issue
Block a user