fix(packer): 修正 monsters 前缀长度,恢复全部 NPC 美术解析 (refs #6)

打包器在切分 MPQ 成员名时,objects 走 `OBJECT_PREFIX.length`,
monsters 却硬编码了一个手数的 `22`。`data\global\monsters\` 的真实
长度是 **21**,于是每个怪物 token 都被多切掉一个字符:

    slice(21) -> "DC"   (正确)
    slice(22) -> "C"    (实际发生的)

`monsterMembers` 因此以 `C`/`E`/`R`/`S`/`J` 这些残缺 token 建索引,
而查表时用的是正确的 `DC`/`JE`/`XR`/`XS`/`0J`,导致**每一次 NPC 美术
查找都必然落空**。由于代码里对"找不到"是静默跳过,这个缺陷不会报错,
只会让 NPC 全部变成没有贴图的空对象——在真实 MPQ 到位之前完全无法察觉。

改为引入 `MONSTER_PREFIX` 常量并由 `.length` 推导,消除手数魔数。

实测(覆盖全部 365 个 scene.json):

    NPC 实例总数            103     103
    其中解析出 COF 成员       0 ->  103
    其中解析出精灵帧          0 ->  103

按 token 分解,修复后全部满额:
0J 82/82、DC 4/4、HR 2/2、JE 4/4、XR 2/2、XS 9/9。

抽查:Deckard Cain 现在解析到
`data\global\monsters\DC\TR\DCTRLITNUHTH.dcc`,帧 43x75,偏移 (-45,-54)。
普通场景物件仍走 `objects\`,未受影响。
This commit is contained in:
troytt 2026-09-14 14:51:49 +00:00
parent cd87c2c5fa
commit 68c7890eee
1 changed files with 7 additions and 2 deletions

View File

@ -64,6 +64,8 @@ const PAGE_SIZE = 2048
const HOT_RADIUS_CELLS = 8
/** Where object art lives. */
const OBJECT_PREFIX = 'data\\global\\objects\\'
/** Where monster and town-NPC art lives. */
const MONSTER_PREFIX = 'data\\global\\monsters\\'
/**
* Mode directories tried in order when picking an object's art.
*
@ -347,9 +349,12 @@ const objectMembers = new Map<string, Map<string, string[]>>()
const monsterMembers = new Map<string, Map<string, string[]>>()
for (const name of allNames) {
const isObj = name.toLowerCase().startsWith(OBJECT_PREFIX.toLowerCase())
const isMon = name.toLowerCase().startsWith('data\\global\\monsters\\')
const isMon = name.toLowerCase().startsWith(MONSTER_PREFIX.toLowerCase())
if (!isObj && !isMon) continue
const prefixLen = isObj ? OBJECT_PREFIX.length : 22 // 'data\\global\\monsters\\'.length
// Derive the length; a hand-counted magic number here was off by one (22 vs
// the real 21), which sliced the first letter off every monster token and
// silently broke all NPC art lookups.
const prefixLen = isObj ? OBJECT_PREFIX.length : MONSTER_PREFIX.length
const rest = name.slice(prefixLen).split('\\')
if (rest.length < 3) continue
const token = (rest[0] ?? '').toUpperCase()