feedback6 R21F: enemy animation states, the client part of PR #275 by @xcdoge re-implemented — the resolver gives an enemy every numbered skill clip (anims.skills; the caller's first index stays the primary, operators unchanged, 宴's PREFAB_SPINE_ROLES fix kept), the stun spellings Stun_1 and Dizzy_Begin/Loop/End, a separate run role (Run_Begin/Loop/End); the renderer walks an enemy faster than moveSpeed 1 on its Run cycle (猎狗pro, 深池侦察犬 ×2; [ASSUMED] threshold), swaps an enemy's skill clip on a cast event (UnitView.setSkillSlot; a form in force is kept), and plays the 重生 of 巨大的丑东西 (Revive, then the fleeing 大祭司 Idle_2 / Move_2 / Stun_2) and 自在 (Revive_01); data/assets.json regenerated offline: 18 enemies gain anims.skills, 6 anims.run, 3 a stun clip (巨大的丑东西, 斩胄之剑, 破胄之锤), nothing else; the sim's cast slot (a new event) is not part of it; golden: unchanged
This commit is contained in:
1 parent
14df10a5a3
commit
76a2db7dd0
11 files changed
+315
-19
No files matched your search
@@ -378,3 +378,49 @@ test('render/app.js hands the `form` of a sim fx (shared/protocol.js fxForm) wit
|
||||
assert.match(src, /if \(!\(late > 0\)\) fx\.simFx\(/);
|
||||
assert.match(src, /interp\.takeEvents\(renderT, EVS, renderT - 1\.5, LATE\);/);
|
||||
});
|
||||
|
||||
// PR #275 (@xcdoge): 巨大的丑东西 (kitUglyThing: KO ⇒ 10 s 重生 ⇒ the fleeing 大祭司) and 自在 (kitXi: KO ⇒ 5 s 重生 ⇒ the
|
||||
// same model, stronger) had no clip set, so their 重生 played no clip. The Revive clip is the 重生's change clip; the sim
|
||||
// reports the 重生 as a stun (archetypes.js reborn), so 巨大的丑东西's 重生 holds the 大祭司's idle after its 8.67 s clip.
|
||||
test('“巨大的丑东西” / “自在”: the 重生 plays the skeleton’s Revive clip, then the second form', () => {
|
||||
const mc = FORMS.enemy_1512_mcmstr;
|
||||
assert.equal(mc.reborn.change, 'Revive', 'the 8.67 s Revive clip opens the 10 s 重生');
|
||||
assert.equal(mc.reborn.next, 'form2');
|
||||
assert.equal(mc.reborn.roles.stun.loop, 'Idle_2', 'the rest of the 重生 (a sim stun) holds the 大祭司 idle, not the first form’s Stun_1');
|
||||
assert.equal(mc.form2.roles.idle, 'Idle_2');
|
||||
assert.equal(mc.form2.roles.move.loop, 'Move_2');
|
||||
assert.equal(mc.form2.roles.stun.loop, 'Stun_2', 'the 大祭司 stunned: its own clip');
|
||||
assert.equal(mc.form2.roles.attack, null, 'the 大祭司 never attacks');
|
||||
const xi = FORMS.enemy_1517_xi;
|
||||
assert.equal(xi.reborn.change, 'Revive_01', 'the 5.33 s Revive_01 is the 5 s 重生');
|
||||
assert.deepEqual(xi.form2.roles, {}, 'the same model, only stronger (reborn.atk)');
|
||||
});
|
||||
|
||||
// Every clip a form set names must exist in that model's manifest entry: a typo is a silently missing animation.
|
||||
test('every clip FORMS names exists in its model’s manifest', () => {
|
||||
const clipsOf = (v, out = new Set()) => {
|
||||
if (typeof v === 'string') { if (v) out.add(v); return out; }
|
||||
if (Array.isArray(v)) { for (const x of v) clipsOf(x, out); return out; }
|
||||
if (v && typeof v === 'object') for (const [k, x] of Object.entries(v)) if (k !== 'via' && k !== 'next') clipsOf(x, out);
|
||||
return out;
|
||||
};
|
||||
const entries = (id) => {
|
||||
const out = [];
|
||||
const e = assets.enemies?.[id]?.spine; if (e) out.push(e.front || e);
|
||||
const t = assets.tokens?.[id]?.spine; if (t) out.push(t);
|
||||
const c = assets.chars?.[id]?.spine; if (c) out.push(...[c.front, c.back].filter(Boolean));
|
||||
return out;
|
||||
};
|
||||
const bad = [];
|
||||
let checked = 0;
|
||||
for (const [id, forms] of Object.entries(FORMS)) {
|
||||
const es = entries(id);
|
||||
if (!es.length) continue;
|
||||
checked++;
|
||||
for (const [form, spec] of Object.entries(forms)) {
|
||||
for (const clip of clipsOf(spec)) if (!es.some((e) => e.animations && Object.hasOwn(e.animations, clip))) bad.push(`${id}.${form}: ${clip}`);
|
||||
}
|
||||
}
|
||||
assert.ok(checked > 10, `the table was walked (${checked} models)`);
|
||||
assert.deepEqual(bad, [], 'clips named by a form set but missing from the manifest');
|
||||
});
|
||||
Reference in new issue
Block a user