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:
sganggs committed 2026-10-07 17:59:11 +08:00
1 parent 14df10a5a3
commit 76a2db7dd0
11 files changed
+315 -19

No files matched your search

+24
View File
@@ -77,6 +77,30 @@ describe('Spine actor + audio use the equipped skill', () => {
assert.equal((await actorFor(1)).roles.skill.loop, 'Skill_3_Loop', 'no clip for S2 in the model: primary');
});
// PR #275 (@xcdoge): the slot an enemy ability casts (render/app.js `case 'cast'` → UnitView.setSkillSlot) swaps its
// skill clip mid-battle, so a multi-skill boss does not show one cast animation for every ability.
test('an enemy cast slot swaps its skill clip mid-battle (a form in force is kept)', async () => {
const ctx = fakeViewCtx(fake.P, { assets, cam });
const v = new UnitView(ctx, { id: 9, side: 'enemy', kind: 'enemy', defId: 'enemy_1521_dslily', x: 5, y: 10, maxHp: 100 }, {});
await tick(); await tick();
assert.ok(v.actor, 'spine actor built');
assert.equal(v.actor.roles.skill.loop, 'Skill_3_Loop', 'the manifest primary clip to start with');
assert.equal(v.skillIndex, null, 'no slot reported yet');
v.setSkillSlot(0);
assert.equal(v.skillIndex, 0);
assert.equal(v.actor.roles.skill.loop, 'Skill_1', 'that slot’s own clip');
v.setSkillSlot(-1);
v.setSkillSlot(1.5);
assert.equal(v.skillIndex, 0, 'a non-slot is ignored');
v.actor.setForm({ idle: 'Attack' });
v.setSkillSlot(2);
assert.equal(v.actor.roles.skill.loop, 'Skill_3_Loop', 'the next slot');
assert.equal(v.actor.roles.idle, 'Attack', 'the form in force survives the slot change');
v.actor.setForm(null);
assert.equal(v.actor.roles.idle, 'Idle');
assert.equal(v.actor.roles.skill.loop, 'Skill_3_Loop', 'and the slot survives the form ending');
});
test('audio: the equipped skill’s own ON_SKILL_START sound', () => {
const manifest = { audio: { sfx: { units: { char_350_surtr: { skill: '/sfx/s3.mp3', skills: { 0: '/sfx/s1.mp3', 2: '/sfx/s3.mp3' } } } } } };
const a = new AudioManager({ win: null, getManifest: () => manifest });