feedback1d WN: a field opened after 暴鸰's drop draws it without its bomb (UnitInfo.form)
The bomb-less mode lived only in the client that saw the fx 'phase' event. UnitInfo now carries an enemy's recorded mode (unit.form, set by kitBombd at the release), and the renderer keeps it on the unit info, so a field watched or rejoined later starts on the *_2 clips.
This commit is contained in:
1 parent
901d5b9613
commit
cc57afa8c4
6 files changed
+17
-3
No files matched your search
+1
-1
@@ -963,7 +963,7 @@ Unknown subprofessions fall back to the profession default (test `professions.te
|
||||
`['heal', tgt, amount]`, `['skill', id, 1|0]`, `['die', id, reason]`, `['leak', id]`, `['status', id, key, 1|0]`,
|
||||
`['fx', kind, x, y, extra]` (`hitCap` `{ id, n }`: a leader's hit cancelled by 限伤 — the renderer draws nothing),
|
||||
`['layer', playerId, bondId, n]` (n = the layers actually added, capped at 999), `['bounty', playerId, coins]`.
|
||||
- `UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?, skillIndex? }` (`skillIndex`: an ally's equipped skill, DESIGN §16)
|
||||
- `UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?, skillIndex?, form? }` (`skillIndex`: an ally's equipped skill, DESIGN §16; `form`: an enemy's mode recorded by content as `unit.form` — 暴鸰 `'bombed'` after its drop — for views built later)
|
||||
(`dir` = the unit direction, allies meaningful, enemies 'RIGHT'; `facing` = its horizontal sign for sprite flipping)
|
||||
(`spine`/`avatar` are asset ids from data).
|
||||
- flags: UF bits (blocked 1, stunned 2, frozen 4, stealth 8 — 隐匿 or an ally's 迷彩 — skill 16, shield 32, invuln 64, cold 128, sleep 256, flying 512);
|
||||
|
||||
@@ -1296,6 +1296,8 @@ export async function createFieldView(host, options = {}) {
|
||||
// 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,
|
||||
moduleId: typeof u.moduleId === 'string' ? u.moduleId : undefined,
|
||||
// an enemy's mode already taken (UnitInfo.form: 暴鸰 'bombed'): a field opened later draws it (units.js FORMS)
|
||||
form: typeof u.form === 'string' ? u.form : undefined,
|
||||
};
|
||||
infos.set(info.id, info);
|
||||
return info;
|
||||
|
||||
@@ -1502,6 +1502,7 @@ function kitBombd(ab) {
|
||||
if (!e.alive) return;
|
||||
const atk = e.s.atk;
|
||||
b._ev(['atk', e.id, t.id, 'droneBomb']);
|
||||
e.form = 'bombed'; // UnitInfo.form: a view built later draws it bomb-less
|
||||
b.fx('phase', { x: e.x, y: e.y, id: e.id, kind: 'bombed' });
|
||||
b.addProjectile({ from: e, target: t, speed: PROJECTILE_SPEEDS.droneBomb, visual: 'droneBomb', source: e, hitDead: true,
|
||||
onHit: (c) => land(b, e, atk, c.target, c.x, c.y) });
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// server/sim/snapshot.js — compact serialization for clients (DESIGN §8.2).
|
||||
//
|
||||
// b.snap = { fieldId, t, units: [[id, x, y, hp, maxHp, sp, spMax, flags, anim]], dp, killed, total }
|
||||
// UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, skillIndex?, moduleId? }
|
||||
// dir = 'UP'|'RIGHT'|'DOWN'|'LEFT' (allies: the deploy direction, sim/dir.js); facing = its horizontal sign (±1).
|
||||
// UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, skillIndex?, moduleId?, form? }
|
||||
// dir = 'UP'|'RIGHT'|'DOWN'|'LEFT' (allies: the deploy direction, sim/dir.js); facing = its horizontal sign (±1);
|
||||
// form = an enemy's current mode (`unit.form`, the last fx 'phase' kind content recorded — 暴鸰 'bombed'), so a view
|
||||
// built after the change (a field opened mid-battle) draws it (render/units.js FORMS).
|
||||
// flags bits & anim codes come from shared/constants.js (UF / ANIM).
|
||||
|
||||
import { UF, ANIM } from '../../shared/constants.js';
|
||||
@@ -33,6 +35,7 @@ export function unitInfo(u) {
|
||||
motion: u.motion === 'FLY' ? 'FLY' : undefined,
|
||||
boss: u.isBoss ? true : undefined,
|
||||
uid: u.uid ?? undefined,
|
||||
form: typeof u.form === 'string' ? u.form : 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,
|
||||
// DESIGN §16: an elite ally's equipped module (uniEquipId | 'none'; display only — a teammate's unit in a shared
|
||||
|
||||
@@ -88,6 +88,13 @@ describe('D4 暴鸰: the drone lets go of its bomb on screen', () => {
|
||||
assert.equal(v.atkInterval, 5, 'a one-off drop leaves the attack rhythm alone');
|
||||
});
|
||||
|
||||
test('render/app.js keeps UnitInfo.form (a field opened after the drop) and hands the atk kind to the view', () => {
|
||||
const src = readFileSync(path.join(ROOT, 'public/js/render/app.js'), 'utf8');
|
||||
assert.match(src, /function addInfo\(u\)[\s\S]{0,2000}form: typeof u\.form === 'string' \? u\.form : undefined/);
|
||||
assert.match(src, /src\.onAttack\?\.\(tgt, now, e\[3\]\)/);
|
||||
assert.match(src, /v\.windUp\(t - upcomingT, e\[3\]\)/);
|
||||
});
|
||||
|
||||
test('the drop draws a falling bomb at the sim\'s speed and the mode change draws nothing of its own', () => {
|
||||
assert.ok(PROJ.droneBomb, 'PROJ.droneBomb');
|
||||
assert.equal(PROJ.droneBomb.speed, PROJECTILE_SPEEDS.droneBomb);
|
||||
|
||||
@@ -70,6 +70,7 @@ describe('D4 暴鸰: the bomb leaves the drone and lands', () => {
|
||||
assert.ok(Math.abs(relT - castAt - RELEASE) <= TICK + 1e-9, `released ${(relT - castAt).toFixed(3)} s after the trigger (OnAttack ${RELEASE})`);
|
||||
const phase = log.find(([, ev]) => ev[0] === 'fx' && ev[1] === 'phase');
|
||||
assert.ok(phase && phase[1][4].kind === 'bombed' && Math.abs(phase[0] - relT) < 1e-9, 'the model drops to its bomb-less mode at the release');
|
||||
if (drone.alive) assert.equal(b.fieldMeta().units.find((u) => u.id === drone.id).form, 'bombed', 'a field opened later draws it bomb-less');
|
||||
assert.ok(hitAt != null, 'the bomb lands');
|
||||
assert.equal(firstHit.id, relEv[2], 'on the operator it was dropped on');
|
||||
assert.ok(hitAt - relT > TICK, `then flies (${(hitAt - relT).toFixed(3)} s at ${PROJECTILE_SPEEDS.droneBomb} tiles/s)`);
|
||||
|
||||
Reference in new issue
Block a user