fix(render): show Swire Alter coin balance
This commit is contained in:
8 files changed
+140
-4
No files matched your search
@@ -1399,6 +1399,7 @@ Unknown subprofessions fall back to the profession default (test `professions.te
|
|||||||
readouts of `snapshot.js` `ammoView` (an ally's running ammo skill: whole rounds, the magazine = `ammoMax`), `wolfView` (伺夜's 狼群:
|
readouts of `snapshot.js` `ammoView` (an ally's running ammo skill: whole rounds, the magazine = `ammoMax`), `wolfView` (伺夜's 狼群:
|
||||||
`mem.shadows` of `mem.wolfCapacity`) and `negView` (`unit.negFill()`, set by 斩业星熊's T1 业火: the 我执 pool as a share of its cap);
|
`mem.shadows` of `mem.wolfCapacity`) and `negView` (`unit.negFill()`, set by 斩业星熊's T1 业火: the 我执 pool as a share of its cap);
|
||||||
UnitInfo `ammoSkill` marks an ally whose skill is an ammo magazine (DESIGN §8.2).
|
UnitInfo `ammoSkill` marks an ally whose skill is an ammo magazine (DESIGN §8.2).
|
||||||
|
Optional `coins` carries 琳琅诗怀雅's balance; its wire and display contract is in [DESIGN §8.2](design/network.md#82-match).
|
||||||
`fieldMeta()` lists the knocked-out operators too (a client joining mid-battle shows them; DESIGN §18.3).
|
`fieldMeta()` lists the knocked-out operators too (a client joining mid-battle shows them; DESIGN §18.3).
|
||||||
`stand: [[id, until]]` (only when non-empty) = the game time each enemy's attack recovery ends (`atkStandUntil`: it
|
`stand: [[id, until]]` (only when non-empty) = the game time each enemy's attack recovery ends (`atkStandUntil`: it
|
||||||
stands for the rest of its attack clip, §1.2 `attackStand`) — enemies alive, deployed, visible, neither feared nor stunned;
|
stands for the rest of its attack clip, §1.2 `attackStand`) — enemies alive, deployed, visible, neither feared nor stunned;
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ S→C:
|
|||||||
listed enemy's recovery was cut or ignored (失衡, fear, stun, hiding, leaving): an interval with a cut inside it stays
|
listed enemy's recovery was cut or ignored (失衡, fear, stun, hiding, leaving): an interval with a cut inside it stays
|
||||||
linear. A newest snapshot showing a death, a stun or a block is not extrapolated. Old snapshots without them
|
linear. A newest snapshot showing a death, a stun or a block is not extrapolated. Old snapshots without them
|
||||||
interpolate linearly; nothing is inferred from the attack animation.
|
interpolate linearly; nothing is inferred from the attack animation.
|
||||||
|
- Optional `b.snap.coins: [[id, balance]]` carries 琳琅诗怀雅's whole-coin balance (`mem.coins`), including zero, for living,
|
||||||
|
deployed, visible allies. The client samples it at the older snapshot boundary, as with `wolves`; malformed rows and
|
||||||
|
unknown ids are ignored, and an absent entry clears the display. A coin icon and integer sit below HP/SP and above the
|
||||||
|
element gauge, hidden in prep and while down. The icon reuses the coin FX texture from `render/textures.js`.
|
||||||
|
[ASSUMED] HUD placement/style; no coin mechanics change.
|
||||||
- `b.ev` — `{ fieldId, ev: [ ... ] }` event tuples: `['spawn', UnitInfo]`, `['atk', srcId, tgtId, projKind]`, `['dmg', tgtId, amount, type]`, `['heal', tgtId, amount]`, `['skill', id, on:0|1]`, `['die', id, reason]` (reason `'killed'`, `'retreat'`, `'expired'` …; only `'killed'` plays an operator's knock-down sound), `['status', id, key, on]`, `['fx', kind, x, y, extra]` (`'hitCap' {id, n}` = a cancelled 限伤 hit, drawn as nothing, §20.12; `extra.form` = the unit's model form from then on (an enemy's mode, a 傀儡师's 替身 §22.11) — `shared/protocol.js fxForm`, state like a spawn: never dropped by the client, §21.4), `['layer', playerId, bondId, n]`, `['bounty', playerId, coins]`, `['deploy', id]`
|
- `b.ev` — `{ fieldId, ev: [ ... ] }` event tuples: `['spawn', UnitInfo]`, `['atk', srcId, tgtId, projKind]`, `['dmg', tgtId, amount, type]`, `['heal', tgtId, amount]`, `['skill', id, on:0|1]`, `['die', id, reason]` (reason `'killed'`, `'retreat'`, `'expired'` …; only `'killed'` plays an operator's knock-down sound), `['status', id, key, on]`, `['fx', kind, x, y, extra]` (`'hitCap' {id, n}` = a cancelled 限伤 hit, drawn as nothing, §20.12; `extra.form` = the unit's model form from then on (an enemy's mode, a 傀儡师's 替身 §22.11) — `shared/protocol.js fxForm`, state like a spawn: never dropped by the client, §21.4), `['layer', playerId, bondId, n]`, `['bounty', playerId, coins]`, `['deploy', id]`
|
||||||
- `m.toast {kind, text}` · `m.ticker {text, id, type, priority, playerId}` (the strip plays the highest priority first, §21.10) · `m.emote {playerId, id}` · `m.result {...}`
|
- `m.toast {kind, text}` · `m.ticker {text, id, type, priority, playerId}` (the strip plays the highest priority first, §21.10) · `m.emote {playerId, id}` · `m.result {...}`
|
||||||
- `m.unitStats {seq, round, units: [unitStatsEntry]}` — the answer to `g.unitStats` (`seq` echoed): every own board operator / summon by uid with `{id, uid, defId, hp, alive, maxHp, atk, def, res, interval, blockCnt, moveSpeed, range?, base: {…same}}` and `dir?` (shared/protocol.js `unitStatsEntry`; `range` = an ally's live range grid, facing RIGHT, never a target-selection grid, §21.16; `dir` = the unit's facing now, UP|RIGHT|DOWN|LEFT — the snapshot tuples carry none and UnitInfo `dir` is the facing at send time — which the detail card's range overlay rotates by, GitHub PR #281) as its next battle starts (§18.5)
|
- `m.unitStats {seq, round, units: [unitStatsEntry]}` — the answer to `g.unitStats` (`seq` echoed): every own board operator / summon by uid with `{id, uid, defId, hp, alive, maxHp, atk, def, res, interval, blockCnt, moveSpeed, range?, base: {…same}}` and `dir?` (shared/protocol.js `unitStatsEntry`; `range` = an ally's live range grid, facing RIGHT, never a target-selection grid, §21.16; `dir` = the unit's facing now, UP|RIGHT|DOWN|LEFT — the snapshot tuples carry none and UnitInfo `dir` is the facing at send time — which the detail card's range overlay rotates by, GitHub PR #281) as its next battle starts (§18.5)
|
||||||
|
|||||||
@@ -1060,6 +1060,10 @@ players alive when the fight starts; no change.)
|
|||||||
我执), `test/render/hud-readouts.test.js` (14: interp stepping / malformed rows / absent list, the cells, the pips, the red bar, the
|
我执), `test/render/hud-readouts.test.js` (14: interp stepping / malformed rows / absent list, the cells, the pips, the red bar, the
|
||||||
aura, and the chain sim → snapshot → buffer → view) — all fail on the parent; a headless-Chrome look at the real field view
|
aura, and the chain sim → snapshot → buffer → view) — all fail on the parent; a headless-Chrome look at the real field view
|
||||||
(`notes/shot-hud.mjs`, not committed) shows the three readouts as intended. Docs: design/network.md, SIM.md §9, design/client.md, PLAYING.md, `types/core.js`.
|
(`notes/shot-hud.mjs`, not committed) shows the three readouts as intended. Docs: design/network.md, SIM.md §9, design/client.md, PLAYING.md, `types/core.js`.
|
||||||
|
- 琳琅诗怀雅's coin balance uses the same snapshot/readout path. A player screenshot showed her HP/SP bars without a coin
|
||||||
|
count; the kit already tracked `mem.coins`, but snapshots omitted it. The optional `coins` field supplies the HUD's
|
||||||
|
coin icon and integer, including zero ([DESIGN §8.2](../design/network.md#82-match)). The icon reuses the coin FX texture;
|
||||||
|
the inspected `entry/img_coin` is an event ticket, not her currency. [ASSUMED] placement/style; generation, spending and skills are unchanged.
|
||||||
|
|
||||||
### 27.43 A renderer that loses the 12 s race no longer mounts its canvas late — the attempt is aborted (PR #302 by @siruimei07) — `ui/fieldHost.js mountFieldView / useFieldView`, `render/app.js createFieldView`, `render/app/host.js withTimeout`
|
### 27.43 A renderer that loses the 12 s race no longer mounts its canvas late — the attempt is aborted (PR #302 by @siruimei07) — `ui/fieldHost.js mountFieldView / useFieldView`, `render/app.js createFieldView`, `render/app/host.js withTimeout`
|
||||||
- **Seen**: `mountFieldView` raced `createFieldView` against a 12 s timeout and on failure removed every child of the host, but the factory
|
- **Seen**: `mountFieldView` raced `createFieldView` against a 12 s timeout and on failure removed every child of the host, but the factory
|
||||||
|
|||||||
@@ -29,10 +29,11 @@
|
|||||||
// * `down` [[id, respawnAt, respawnTime, state, row?, col?]] — knocked-out operators waiting to redeploy (they are no
|
// * `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
|
// 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()
|
// * 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:
|
// 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)
|
// `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
|
// `wolves` [[id, 狼影 left, the talent's maximum]] → sample().wolves [left, maximum] | null
|
||||||
|
// `coins` [[id, whole-coin balance]] → sample().coins number | null (zero is a balance)
|
||||||
// `neg` [[id, fill]] (0.01–1: the negative-HP pool's share of its cap) → sample().neg number (0: none)
|
// `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
|
// 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 我执).
|
// clears the readout (render/units.js: the segmented ammo bar, the wolf pips, the red bar of 斩业星熊's 我执).
|
||||||
@@ -117,6 +118,13 @@ export function normalizeSnapshot(snap) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ammo = countList(snap.ammo, units), wolves = countList(snap.wolves, units);
|
const ammo = countList(snap.ammo, units), wolves = countList(snap.wolves, units);
|
||||||
|
let coins = null;
|
||||||
|
if (Array.isArray(snap.coins)) {
|
||||||
|
for (const e of snap.coins) {
|
||||||
|
if (!Array.isArray(e) || !units.has(e[0]) || !Number.isSafeInteger(e[1]) || e[1] < 0) continue;
|
||||||
|
(coins || (coins = new Map())).set(e[0], e[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
let neg = null;
|
let neg = null;
|
||||||
if (Array.isArray(snap.neg)) {
|
if (Array.isArray(snap.neg)) {
|
||||||
for (const e of snap.neg) {
|
for (const e of snap.neg) {
|
||||||
@@ -147,7 +155,7 @@ export function normalizeSnapshot(snap) {
|
|||||||
(standCut || (standCut = new Map())).set(e[0], e[1]);
|
(standCut || (standCut = new Map())).set(e[0], e[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { t, units, down, ammo, wolves, neg, stand, standCut, raw: snap };
|
return { t, units, down, ammo, wolves, coins, neg, stand, standCut, raw: snap };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -356,7 +364,7 @@ export class SnapshotBuffer {
|
|||||||
const stamp = A.t;
|
const stamp = A.t;
|
||||||
for (const [id, a] of A.units) {
|
for (const [id, a] of A.units) {
|
||||||
let o = out.get(id);
|
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, ammo: null, wolves: null, neg: 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, coins: null, neg: 0 }; out.set(id, o); }
|
||||||
const b = B ? B.units.get(id) : null;
|
const b = B ? B.units.get(id) : null;
|
||||||
if (b) {
|
if (b) {
|
||||||
const dx = b[1] - a[1], dy = b[2] - a[2];
|
const dx = b[1] - a[1], dy = b[2] - a[2];
|
||||||
@@ -402,6 +410,7 @@ export class SnapshotBuffer {
|
|||||||
// the HP-bar readouts come with the older snapshot, like flags (whole rounds step with the skill flag)
|
// 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.ammo = A.ammo ? A.ammo.get(id) || null : null;
|
||||||
o.wolves = A.wolves ? A.wolves.get(id) || null : null;
|
o.wolves = A.wolves ? A.wolves.get(id) || null : null;
|
||||||
|
o.coins = A.coins?.get(id) ?? null;
|
||||||
o.neg = A.neg ? A.neg.get(id) || 0 : 0;
|
o.neg = A.neg ? A.neg.get(id) || 0 : 0;
|
||||||
o.seen = stamp;
|
o.seen = stamp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -477,6 +477,8 @@ export class UnitView {
|
|||||||
this.sp = 0; this.spMax = 0;
|
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.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.wolves = null; // [狼影 left, the talent's maximum] of a 狼群 (sync; b.snap `wolves`)
|
||||||
|
this.coins = null; // Swire's balance, including zero; null without a purse
|
||||||
|
this.coinHud = null;
|
||||||
this.neg = 0; // the share of its cap a negative-HP pool holds, 0 = none (sync; b.snap `neg`, 斩业星熊's 我执)
|
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.flags = 0; this.anim = ANIM.IDLE;
|
||||||
this.statuses = new Set();
|
this.statuses = new Set();
|
||||||
@@ -825,6 +827,7 @@ export class UnitView {
|
|||||||
this.hp = hp;
|
this.hp = hp;
|
||||||
this.sp = s.sp; this.spMax = s.spMax;
|
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;
|
this.ammo = s.ammo || null; this.wolves = s.wolves || null; this.neg = s.neg > 0 ? Math.min(1, s.neg) : 0;
|
||||||
|
this.coins = s.coins ?? null;
|
||||||
const prevFlags = this.flags;
|
const prevFlags = this.flags;
|
||||||
this.flags = s.flags | 0;
|
this.flags = s.flags | 0;
|
||||||
this.anim = s.anim | 0;
|
this.anim = s.anim | 0;
|
||||||
@@ -1412,7 +1415,8 @@ export class UnitView {
|
|||||||
// knocked-down operator (b.snap `down`)
|
// knocked-down operator (b.snap `down`)
|
||||||
const rowsTop = cy + bh / 2 + (showSp ? spH + 1.5 : 0);
|
const rowsTop = cy + bh / 2 + (showSp ? spH + 1.5 : 0);
|
||||||
const wolfH = this._updateWolfPips(showBars && !this.isEnemy && !!this.wolves, x0, rowsTop, s);
|
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);
|
const coinH = this._updateCoins(showBars && !this.isEnemy && this.coins !== null, x0, rowsTop + wolfH, s);
|
||||||
|
this._updateElementBar(showBars && !!this.el, x0, bw, rowsTop + wolfH + coinH + 1, spH, s, t);
|
||||||
this._updateDownRing(!prep && !!this.down && !this.alive, x, this.screen.y - DOWN_LOOK.height * s, 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)
|
// blocked marker at the feet (enemies held by a blocker)
|
||||||
const blocked = !prep && this.alive && this.isEnemy && (this.flags & UF.BLOCKED);
|
const blocked = !prep && this.alive && this.isEnemy && (this.flags & UF.BLOCKED);
|
||||||
@@ -1493,6 +1497,29 @@ export class UnitView {
|
|||||||
return d + 2 + WOLF_PIPS.pad;
|
return d + 2 + WOLF_PIPS.pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** [ASSUMED] Coin icon + integer below the bars; the balance comes only from snapshots, not transient FX. */
|
||||||
|
_updateCoins(show, x, top, s) {
|
||||||
|
let r = this.coinHud;
|
||||||
|
if (!show) { if (r) r.root.visible = false; return 0; }
|
||||||
|
const P = this.P;
|
||||||
|
if (!r) {
|
||||||
|
const root = new P.Container();
|
||||||
|
const icon = new P.Sprite(fxAtlas().tex.coin);
|
||||||
|
icon.width = icon.height = 16;
|
||||||
|
const text = new P.Text('', { fontFamily: 'Bender, Oxanium, sans-serif', fontSize: 16, fontWeight: '700', fill: '#ffd04a', stroke: '#0b0f0e', strokeThickness: 3 });
|
||||||
|
text.position.set(19, -2);
|
||||||
|
root.addChild(icon, text);
|
||||||
|
this.hud.addChild(root);
|
||||||
|
r = this.coinHud = { root, icon, text };
|
||||||
|
}
|
||||||
|
r.text.text = String(this.coins);
|
||||||
|
const size = clamp(s * 0.2, 10, 16);
|
||||||
|
r.root.scale.set(size / 16);
|
||||||
|
r.root.position.set(x, top + 3);
|
||||||
|
r.root.visible = true;
|
||||||
|
return size + 5;
|
||||||
|
}
|
||||||
|
|
||||||
/** True while the shown gauge is in its 爆发冷却 (b.snap `elem` carries the cooldown's end and length). */
|
/** True while the shown gauge is in its 爆发冷却 (b.snap `elem` carries the cooldown's end and length). */
|
||||||
elementCooling() {
|
elementCooling() {
|
||||||
return !!this.el && this.elDur > 0 && this.elUntil > 0;
|
return !!this.el && this.elDur > 0 && this.elUntil > 0;
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ export class BattleEvents {
|
|||||||
if (wv) (wolves || (wolves = [])).push([u.id, wv[0], wv[1]]);
|
if (wv) (wolves || (wolves = [])).push([u.id, wv[0], wv[1]]);
|
||||||
const ng = negView(u);
|
const ng = negView(u);
|
||||||
if (ng) (neg || (neg = [])).push([u.id, ng]);
|
if (ng) (neg || (neg = [])).push([u.id, ng]);
|
||||||
|
// Swire's purse is initialized on deploy; zero is a balance, absence is no purse.
|
||||||
|
if (u.side === 'ally' && Number.isSafeInteger(u.mem.coins) && u.mem.coins >= 0) {
|
||||||
|
(snap.coins || (snap.coins = [])).push([u.id, u.mem.coins]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (elem) snap.elem = elem;
|
if (elem) snap.elem = elem;
|
||||||
if (ammo) snap.ammo = ammo;
|
if (ammo) snap.ammo = ammo;
|
||||||
|
|||||||
@@ -288,6 +288,91 @@ describe('an ammo skill has no standing aura', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('the chain: sim → snapshot → SnapshotBuffer → UnitView', () => {
|
describe('the chain: sim → snapshot → SnapshotBuffer → UnitView', () => {
|
||||||
|
test('user coin HUD: Given real Swire skills, When coins accumulate and are spent, Then the visible balance includes zero', () => {
|
||||||
|
for (const skillIndex of [0, 1, 2]) {
|
||||||
|
const h = makeBattle({
|
||||||
|
units: [{ chessId: 'chess_char_3_04_a', row: 10, col: 5, skillIndex }],
|
||||||
|
flags: { dpInit: 99, dpMax: 99, dpPerSec: 3 }, timeLimit: 120,
|
||||||
|
});
|
||||||
|
h.step();
|
||||||
|
const u = h.unit('chess_char_3_04_a');
|
||||||
|
const v = new UnitView(fakeViewCtx(fake.P, { cam }), renderInfo(unitInfo(u)));
|
||||||
|
const buffer = new SnapshotBuffer({ delay: 0 });
|
||||||
|
const show = (expected) => {
|
||||||
|
const snap = h.b.snapshot();
|
||||||
|
assert.deepEqual(snap.coins, [[u.id, expected]]);
|
||||||
|
buffer.push(snap, snap.t);
|
||||||
|
v.sync(buffer.sample(snap.t).get(u.id), snap.t);
|
||||||
|
frames(v);
|
||||||
|
assert.equal(v.coinHud.root.visible, true);
|
||||||
|
assert.equal(v.coinHud.text.text, String(expected));
|
||||||
|
assert.ok(v.coinHud.root.position.y > v.hpBg.position.y);
|
||||||
|
};
|
||||||
|
if (skillIndex === 2) {
|
||||||
|
show(0);
|
||||||
|
assert.ok(u.skill.activate('test', { free: true }));
|
||||||
|
h.step();
|
||||||
|
show(1);
|
||||||
|
h.run(3);
|
||||||
|
show(2);
|
||||||
|
u.skill.end('manual');
|
||||||
|
h.step();
|
||||||
|
show(0);
|
||||||
|
} else if (skillIndex === 1) {
|
||||||
|
show(0);
|
||||||
|
assert.ok(h.runUntil(() => u.mem.coins === 3, 60));
|
||||||
|
show(3);
|
||||||
|
const bomb = h.b.allyUnits.find((a) => a.alive && a.defId === 'token_10031_swire2_gdtrap');
|
||||||
|
h.b.retreat(bomb, { reason: 'expired', permanent: true });
|
||||||
|
assert.ok(h.runUntil(() => u.mem.coins === 2, 5));
|
||||||
|
show(2);
|
||||||
|
} else {
|
||||||
|
show(1);
|
||||||
|
h.run(3);
|
||||||
|
show(2);
|
||||||
|
h.b.dealDamage(null, u, { amount: u.s.maxHp * 0.5, type: 'true' });
|
||||||
|
assert.ok(h.runUntil(() => u.mem.coins === 1, 2));
|
||||||
|
show(1);
|
||||||
|
}
|
||||||
|
v.prep = true;
|
||||||
|
frames(v);
|
||||||
|
assert.equal(v.coinHud.root.visible, false);
|
||||||
|
v.prep = false;
|
||||||
|
v.sync(sample());
|
||||||
|
frames(v);
|
||||||
|
assert.equal(v.coinHud.root.visible, false, 'an old snapshot clears the balance');
|
||||||
|
u.hidden = true;
|
||||||
|
assert.equal(h.b.snapshot().coins, undefined, 'hidden units do not publish a purse');
|
||||||
|
u.hidden = false;
|
||||||
|
u.deployed = false;
|
||||||
|
assert.equal(h.b.snapshot().coins, undefined, 'undeployed units do not publish a purse');
|
||||||
|
u.deployed = true;
|
||||||
|
h.b.retreat(u, { reason: 'expired', permanent: true });
|
||||||
|
assert.equal(h.b.snapshot().coins, undefined);
|
||||||
|
v.sync(sample({ coins: 0 }));
|
||||||
|
frames(v);
|
||||||
|
assert.equal(v.coinHud.root.visible, true);
|
||||||
|
v.die();
|
||||||
|
frames(v);
|
||||||
|
assert.equal(v.coinHud.root.visible, false);
|
||||||
|
assert.deepEqual(h.b.errors, []);
|
||||||
|
v.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user coin HUD: Given adjacent snapshots, When sampling a spend, Then zero arrives only at the snapshot boundary', () => {
|
||||||
|
const buf = new SnapshotBuffer({ delay: 0 });
|
||||||
|
buf.push({ t: 1, units: [tuple(1)], coins: [[1, 2], [1, -1], [1, 0.5], [9, 4], null] }, 0);
|
||||||
|
buf.push({ t: 2, units: [tuple(1)], coins: [[1, 0]] }, 0.5);
|
||||||
|
buf.push({ t: 3, units: [tuple(1)] }, 1);
|
||||||
|
const out = buf.sample(1.99);
|
||||||
|
assert.equal(out.get(1).coins, 2);
|
||||||
|
buf.sample(2, out);
|
||||||
|
assert.equal(out.get(1).coins, 0);
|
||||||
|
buf.sample(3, out);
|
||||||
|
assert.equal(out.get(1).coins, null);
|
||||||
|
});
|
||||||
|
|
||||||
test('隐现 S2: 14 cells, 13 after the first round, one more gone per attack; no aura; the plain SP bar after the last round', () => {
|
test('隐现 S2: 14 cells, 13 after the first round, one more gone per attack; no aura; the plain SP bar after the last round', () => {
|
||||||
const h = makeBattle({
|
const h = makeBattle({
|
||||||
defs: { enemies: { enemy_dummy: enemyRec({ key: 'enemy_dummy', hp: 1e7, speed: 0 }) } },
|
defs: { enemies: { enemy_dummy: enemyRec({ key: 'enemy_dummy', hp: 1e7, speed: 0 }) } },
|
||||||
|
|||||||
@@ -121,6 +121,7 @@
|
|||||||
* @property {ElemSnap[]} [elem]
|
* @property {ElemSnap[]} [elem]
|
||||||
* @property {CountSnap[]} [ammo]
|
* @property {CountSnap[]} [ammo]
|
||||||
* @property {CountSnap[]} [wolves]
|
* @property {CountSnap[]} [wolves]
|
||||||
|
* @property {[number, number][]} [coins] Swire's whole-coin balance, including zero
|
||||||
* @property {NegSnap[]} [neg]
|
* @property {NegSnap[]} [neg]
|
||||||
* @property {[number, number][]} [stand]
|
* @property {[number, number][]} [stand]
|
||||||
* @property {[number, number][]} [standCut]
|
* @property {[number, number][]} [standCut]
|
||||||
|
|||||||
Reference in new issue
Block a user