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

+6 -3
View File
@@ -166,7 +166,8 @@ export function applyRoleFix(roles, fix, durations) {
/**
* Spine metadata of extracted models (findLocalEnemyModels / findLocalTokenModels), parsed like processModels does — but
* read only: the atlas is sized / pma-normalized in memory (extract.py already writes it so), nothing on disk changes.
* Roles are resolved for skill index 0, like the web enemy and token models, then LOCAL_SPINE_ROLES applied.
* Roles are resolved for skill index 0 (an enemy also for its numbered skill clips), like the web enemy and token models,
* then LOCAL_SPINE_ROLES applied.
* @param {string} root absolute public/assets directory
* @param {Record<string, { dir: string, skel: string, atlas: string, pngs: string[] }>} found
* @returns {Promise<{ meta: Record<string, LocalSpineMeta>, problems: string[] }>}
@@ -192,7 +193,7 @@ export async function localSpineMeta(root, found) {
const sk = parseSkel(await readFile(join(root, m.skel)), info.regions);
if (!sk.animations.length) throw new Error('skeleton has no animations');
if (sk.missingRegions?.length) problems.push(`${id}: ${sk.missingRegions.length} attachment(s) not in atlas (e.g. ${sk.missingRegions[0]})`);
const fixed = applyRoleFix(resolveRoles(sk.animations, { skillIndices: [0], durations: sk.durations }), LOCAL_SPINE_ROLES[id], sk.durations);
const fixed = applyRoleFix(resolveRoles(sk.animations, { skillIndices: [0], numberedSkills: id.startsWith('enemy_'), durations: sk.durations }), LOCAL_SPINE_ROLES[id], sk.durations);
if (fixed.missing.length) problems.push(`${id}: role fix not applied (clip missing) for ${fixed.missing.join(', ')}`);
meta[id] = {
skel: base(m.skel), atlas: base(m.atlas), textures: info.pages, pma: true,
@@ -330,7 +331,9 @@ export async function processModels(models, { root, dl, cachePath, download = tr
if (!sk.animations.length) { problems.push(`${m.key}: skeleton has no animations`); continue; }
const key = String(m.key);
const id = key.startsWith('enemy:') ? key.slice('enemy:'.length) : key.startsWith('op:') ? key.split(':')[1] : null; // op:<char>:front|back
const fixed = applyRoleFix(resolveRoles(sk.animations, { skillIndices: m.skillIndices, durations: sk.durations }), id ? PREFAB_SPINE_ROLES[id] : undefined, sk.durations);
// an enemy's numbered skill clips are resolved too (anims.skills: the clip of each skill slot it casts, PR #275);
// operators keep the pool's indices (their equipped skill picks among them)
const fixed = applyRoleFix(resolveRoles(sk.animations, { skillIndices: m.skillIndices, numberedSkills: key.startsWith('enemy:'), durations: sk.durations }), id ? PREFAB_SPINE_ROLES[id] : undefined, sk.durations);
if (fixed.missing.length) problems.push(`${m.key}: role fix not applied (clip missing) for ${fixed.missing.join(', ')}`);
const anims = fixed.roles;
entries.set(m.key, {