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
@@ -4,7 +4,7 @@
|
||||
|
||||
import { EVENT_BUFFER_CAP } from '../constants.js';
|
||||
import { elementView } from '../damage.js';
|
||||
import { unitInfo, snapshotUnits } from '../snapshot.js';
|
||||
import { unitInfo, snapshotUnits, ammoView, wolfView, negView } from '../snapshot.js';
|
||||
|
||||
export class BattleEvents {
|
||||
fx(kind, params = {}) {
|
||||
@@ -37,7 +37,11 @@ export class BattleEvents {
|
||||
* down: [[id, respawnAt, respawnTime, state, row, col]] — operators that left the field waiting to redeploy (isDown): the
|
||||
* game time their respawn timer ends, its length (s), constants.js DOWN_STATE and the tile they lie on (and
|
||||
* come back on: _layBody — where they fell, or their home);
|
||||
* elem: [[id, element, fill, cooldownEnd, cooldown]] — the element gauge each unit shows (damage.js elementView).
|
||||
* elem: [[id, element, fill, cooldownEnd, cooldown]] — the element gauge each unit shows (damage.js elementView);
|
||||
* ammo: [[id, rounds left, rounds in the magazine]] — a running ammo skill, whole rounds (snapshot.js ammoView): the segmented bar;
|
||||
* wolves: [[id, 狼影 left, the talent's maximum]] — 伺夜's 狼群 (snapshot.js wolfView): the pips under the HP bar;
|
||||
* neg: [[id, fill]] — the share of its cap a negative-HP pool holds (snapshot.js negView; 斩业星熊's 我执): the red bar.
|
||||
* The last three are display only: no sim state reads them, and the nine-field unit tuples are unchanged.
|
||||
*/
|
||||
snapshot() {
|
||||
const snap = {
|
||||
@@ -60,13 +64,22 @@ export class BattleEvents {
|
||||
(down || (down = [])).push([u.id, r2(u.respawnAt), r2(Math.max(0, u.respawnAt - u.deathAt)), this._downState(u), ...this.restTile(u)]);
|
||||
}
|
||||
if (down) snap.down = down;
|
||||
let elem = null;
|
||||
let elem = null, ammo = null, wolves = null, neg = null;
|
||||
for (const u of this.units) {
|
||||
if (!u.alive || !u.deployed || u.hidden) continue;
|
||||
const v = elementView(u, this.time);
|
||||
if (v) (elem || (elem = [])).push([u.id, v[0], v[1], v[2], v[3]]);
|
||||
const am = ammoView(u);
|
||||
if (am) (ammo || (ammo = [])).push([u.id, am[0], am[1]]);
|
||||
const wv = wolfView(u);
|
||||
if (wv) (wolves || (wolves = [])).push([u.id, wv[0], wv[1]]);
|
||||
const ng = negView(u);
|
||||
if (ng) (neg || (neg = [])).push([u.id, ng]);
|
||||
}
|
||||
if (elem) snap.elem = elem;
|
||||
if (ammo) snap.ammo = ammo;
|
||||
if (wolves) snap.wolves = wolves;
|
||||
if (neg) snap.neg = neg;
|
||||
return snap;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ export default {
|
||||
skill: null,
|
||||
trait: { hitsFn: (b, w) => Math.max(1, w.mem.wolves || 1) },
|
||||
install(battle, w) {
|
||||
w.mem.wolfCapacity = maxWolves; // (b.snap `wolves`, snapshot.js wolfView: the pips under the HP bar)
|
||||
const tal = w.def.talents || [];
|
||||
const wb = tal[0]?.bb ?? {};
|
||||
const per = num(wb.block_cnt, num(wb['vigil_wolf_t_1_enhance[trigger].block_cnt'], 1));
|
||||
|
||||
@@ -180,6 +180,9 @@ export default {
|
||||
const capOf = () => num(t0.max_minus_hp_ratio, 2) * unit.s.maxHp;
|
||||
const quiet = num(t0['hsgma2_t_1[heal].interval'], 5);
|
||||
const regen = num(t0['hsgma2_t_1[heal].hp_recovery_per_sec_by_max_hp_ratio']);
|
||||
// the pool as a share of its cap, read when a snapshot is taken (b.snap `neg`, snapshot.js negView): the green HP bar
|
||||
// sits on the 1-HP floor in 我执, so the client draws the pool as the red bar — display only
|
||||
unit.negFill = () => (unit.mem.hsEgo && unit.mem.hsEgo.pool > 0 ? unit.mem.hsEgo.pool / Math.max(1e-9, capOf()) : 0);
|
||||
const enter = (pool) => {
|
||||
unit.mem.hsEgo = { pool: Math.max(0, pool), lastHurt: battle.time };
|
||||
battle.addBuff(unit, { key: EGO, status: 'healFree', flags: { noHeal: true, healFree: true }, tags: ['talent'] });
|
||||
|
||||
@@ -773,6 +773,7 @@ function wolfPack(bb, raw, def) {
|
||||
skill: null,
|
||||
trait: { hitsFn: (b, u) => Math.max(1, u.mem.shadows ?? 1) },
|
||||
install(battle, unit) {
|
||||
unit.mem.wolfCapacity = maxShadows; // (b.snap `wolves`, snapshot.js wolfView: the pips under the HP bar)
|
||||
// initial shadows: 伺夜's talent text ("初始两只"), else one below the maximum
|
||||
const ot = (ownerOf(unit)?.def?.talents || []).map((t) => t.description || '').join(' ');
|
||||
const mi = ot.match(/初始(两|二|\d+)只/);
|
||||
|
||||
+44
-1
@@ -3,7 +3,8 @@
|
||||
// b.snap = { fieldId, t, units: [[id, x, y, hp, maxHp, sp, spMax, flags, anim]], dp, killed, total }
|
||||
// (hp of a countdown summon — unit.countdown, content/tokens.js startCountdown — is maxHp × the share of its life left)
|
||||
// UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?,
|
||||
// form?, skillIndex?, moduleId?, items?, standInFor?, diy? } (standInFor = the replaced operator's charId of a 补位
|
||||
// form?, skillIndex?, ammoSkill?, moduleId?, items?, standInFor?, diy? } (ammoSkill = an ally whose skill is an ammo
|
||||
// magazine: the HP bar shows its rounds as cells, b.snap `ammo`; standInFor = the replaced operator's charId of a 补位
|
||||
// stand-in; diy = a 自选 piece's pick { charId, skillIndex, uniEquipId } — defId is its slot, spine / avatar the operator's;
|
||||
// form = the unit's current model form — an enemy's, content/enemies/helpers.js setForm:
|
||||
// 掠海漂移体 'crawl', 暴鸰 'bombed', 转译基底·α's forms …; a 傀儡师 fighting as its 替身 'doll', professions.js — a view built
|
||||
@@ -48,6 +49,9 @@ export function unitInfo(u) {
|
||||
uid: u.uid ?? undefined,
|
||||
// DESIGN §16: the equipped skill's index (the renderer / audio pick that skill's Spine clip and sound)
|
||||
skillIndex: u.side === 'ally' && Number.isInteger(d.skill?.index) ? d.skill.index : undefined,
|
||||
// an ally whose skill is an ammo magazine: the renderer shows it as the segmented bar under the HP bar (b.snap `ammo`) and
|
||||
// draws no sustained skill aura for it — known from the unit's first appearance, before any snapshot or skill event
|
||||
ammoSkill: u.side === 'ally' && u.skill?.kind === 'ammo' ? true : undefined,
|
||||
// DESIGN §16: an elite ally's equipped module (uniEquipId | 'none'; display only — a teammate's unit in a shared
|
||||
// field shows its owner's module in the detail card)
|
||||
moduleId: u.side === 'ally' && d.golden && typeof d.loadout?.moduleId === 'string' ? d.loadout.moduleId : undefined,
|
||||
@@ -118,6 +122,45 @@ export function unitTuple(u, t) {
|
||||
return [u.id, r2(u.x), r2(u.y), hp, maxHp, r1(sp), spMax, flagsOf(u), animOf(u, t)];
|
||||
}
|
||||
|
||||
/**
|
||||
* The three optional HUD readouts of b.snap (events.js snapshot(): `ammo`, `wolves`, `neg`; each only for a living,
|
||||
* deployed, visible unit and only when it has one). They ride beside the 9-field tuples — the tuple, and the SP
|
||||
* fraction the ammo skill still puts in it, stay as they are.
|
||||
*
|
||||
* `ammoView(u)` = `[rounds left, rounds in the magazine]` of a running ammo skill (whole numbers; the magazine is the
|
||||
* activation's real total — extra bullets and refills included — that unitTuple's SP fraction runs on): the segmented
|
||||
* yellow bar under the HP bar (隐现's S2 holds 14 rounds, skill_table skchr_inside_2 `attack@trigger_time`).
|
||||
*/
|
||||
export function ammoView(u) {
|
||||
const sk = u.skill;
|
||||
if (u.side !== 'ally' || !sk || !sk.active || sk.kind !== 'ammo') return null;
|
||||
const left = Math.max(0, Math.ceil(sk.ammoLeft - 1e-9));
|
||||
return [left, Math.max(1, Math.ceil((sk.ammoMax || sk.ammo || 0) - 1e-9), left)];
|
||||
}
|
||||
|
||||
/**
|
||||
* `wolfView(u)` = `[狼影 left, the talent's maximum]` of 伺夜's 狼群 pack (content/tokens.js wolfPack and the managed kit
|
||||
* chess_char_3_19-vigil.js keep the count in `mem.shadows` and the maximum in `mem.wolfCapacity`): the pips under the HP
|
||||
* bar. A pack in its 战术点形态 is not alive and shows nothing.
|
||||
*/
|
||||
export function wolfView(u) {
|
||||
const cap = u.mem?.wolfCapacity, n = u.mem?.shadows;
|
||||
if (u.side !== 'ally' || !(cap >= 1) || !Number.isFinite(n)) return null;
|
||||
const max = Math.round(cap);
|
||||
return [Math.max(0, Math.min(max, Math.round(n))), max];
|
||||
}
|
||||
|
||||
/**
|
||||
* `negView(u)` = the share (0.01–1) of its cap that a unit's negative-HP pool holds, null without a pool: 斩业星熊's 我执
|
||||
* (kits/ops/op-hsgma2.js sets `unit.negFill`, the pool read when the snapshot is taken) — the red bar over the drained HP
|
||||
* bar. The pool itself, and the 1-HP floor she stands on, are the sim's and are not touched.
|
||||
*/
|
||||
export function negView(u) {
|
||||
if (typeof u.negFill !== 'function') return null;
|
||||
const v = u.negFill();
|
||||
return v > 0 ? Math.min(1, Math.max(0.01, r2(v))) : null;
|
||||
}
|
||||
|
||||
/** Units included in a snapshot: deployed & visible, plus recently dead ones (DIE animation). */
|
||||
export function snapshotUnits(units, t) {
|
||||
const out = [];
|
||||
|
||||
@@ -84,6 +84,9 @@ export class Unit {
|
||||
// a countdown summon's life on the field ({ from, until } battle times; content/tokens.js startCountdown, cleared by
|
||||
// every deployment): its bar shows the time left (snapshot.js unitTuple), its HP never moves (无敌 + 禁疗)
|
||||
this.countdown = null;
|
||||
// a unit with a negative-HP pool (斩业星熊's 我执, kits/ops/op-hsgma2.js) sets this to a function returning the share of the
|
||||
// pool's cap it holds (0–1): b.snap's `neg` list (snapshot.js negView) draws it as the red bar; display only
|
||||
this.negFill = null;
|
||||
// knocked out, the unit lies — and redeploys — on its home tile instead of where it fell (Battle._layBody) while content
|
||||
// holds this: 乌尔比安 moved by his S3 (the owner's decision of 2026-10-07, a deviation from PRTS's "where it fell");
|
||||
// every deployment clears it (battle/deploy.js _deploy)
|
||||
|
||||
Reference in new issue
Block a user