refactor(monsters): 用 MPQ 表取代约 1500 行手写怪物数据(美术/超独/词缀/放置) #59
Labels
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: troytt/diablo2-web#59
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
对
main分支(origin/main=4c2e399)的怪物生成层做了全量 hardcode / fallback 审计。结论:怪物的统计数值层是真正数据驱动的(
MonStats.txt×MonLvl.txt÷ 100,含Levels.txt的mon1..10/nmon*/umon*池与Rarity加权 —— 这部分做得很好,不要动)。但美术层、超级独特怪层、精英词缀层、放置层全部是手写 TypeScript 表与凭空发明的常量,总计约 1,500 行。
1.
MONSTER_ART_MAP—— 741 行手写id → {token, weapon}src/game/monster-mapping.ts:15-757应来源于已被解析但弃用的两个字段:
MonStats.Code→ 已解析为MonsterKind.code(monsters.ts:171-172)MonStats2.BaseW→ 已解析为MonsterArt.weaponClass(monsters.ts:376-389)1a. 表中含 18 个无效 token
"XX"monster-mapping.ts:112-116/:215-219/:378/:412-422/:612涉及
bloodmage1-5、darkguard1-5、lightningbeast、minion1-11、spiritmummy。"XX"解析不到任何data\global\monsters\xx\目录 ⇒ 必定加载失败 ⇒ 红方块。更糟:
"XX"还泄漏进了打包清单ACT_MONSTER_SPECS(monster-mapping.ts:768),每次打包都尝试打一个不存在的怪物。1b.
resolveMonsterArtSpec的前缀猜测 fallbackmonster-mapping.ts:774-786:剥掉尾部数字后,返回第一个startsWith(base)的键。fallen*可能命中fallenshaman1。对象键的迭代顺序变成了游戏可见行为。1c.
ACT_MONSTER_SPECS—— 5 份手写的每幕怪物 token 清单monster-mapping.ts:759-769(19/26/34/19/33 条),被scripts/pack-entity-assets.ts:160-170与:300-310消费。应为该幕全部关卡的
Levels.txtmon*/nmon*/umon*并集。2.
CANONICAL_SUPER_UNIQUES_BY_LEVEL—— 344 行手写超级独特怪src/game/monsters.ts:1002-1345约 30 条,每条含硬编码的
monsterId、英文name、modifiers[]、minMinions/maxMinions、minionMonsterId、英文landmark,按字面量Levels.txtid 索引(3,4,5,6,17,25,41,43,44,45,55,59,60,61,76,78,80,84,88,91,94,105,106,107,110,111,112,114,115,124)。而
readSuperUniques()(monsters.ts:605-644)已经完整解析了SuperUniques.txt,却从未被 planner 调用。2a. 配套的危险 fallback
monsters.ts:1440-1447Nihlathak 会以「一只叫 Nihlathak 的堕落者」的形态出现,且完全不报错。
3.
CANONICAL_ELITE_MODIFIERS—— 手写精英词缀表src/game/monsters.ts:687-707(13 行:id 5,6,7,8,9,17,18,25,26,27,28,29,30)而
readEliteModifiers()(monsters.ts:660-682)已经解析了MonUMod.txt,同样从未被调用 —— 两个真相源。3a. 所有词缀的数值也是内联的(注释引用 MonUMod 却写死数字)
rng.next() < 0.2(champion 占比)monsters.ts:875-877MonUMod.txtconstantsELITE_HEALTH_MULTIPLIER = {normal:1, champion:3, unique:4}monsters.ts:978-988MonUMod.txt(champion +hp% 200 / unique +hp% 300)damage * 1.5(strong)、speed * 1.4(fast)monsters.ts:735-757MonUMod.txtconstants* 2、pack 随从1 + (multiplier-1)/2monsters.ts:1465/:1425MonUMod.txtminion +hp%+3monsters.ts:14483b. 13 个词缀里有 11 个没有任何机制效果
monsters.ts:735-757:只有strong/fast生效。cursed/fireenchant/coldenchant/lightenchant/stoneskin等全部只是被存储,不产生任何作用。3c.
exclude1/exclude2解析后被丢弃monsters.ts:666-682解析了互斥规则,:712-730的pool.splice完全忽略它们。4. 所有超级独特怪与随从按构造必定渲染为红方块
src/scene/act-scene.ts:2124-2126只把monsterTypes传给loadMonsterArtMap。而
monsters.ts:1450-1467注入的suSpec.monsterId与minionMonsterId不在PlannedLevel.types里。后果:每个 Bishibosh / Rakanishu / 血鸦 / Coldcrow / 全部随从都是红方块,无论美术管线多完善。这是「每行修复代码换取的可见性」最高的一项。
5. 四个战斗可见字段不来自数据表
src/game/monsters.ts:965-975DEFAULT_ATTACK_COOLDOWN_TICKS = 25monsters.ts:912-920a1/a2)+MonStats2anim speedDEFAULT_REACH_PX = 40monsters.ts:904-910MonStats2.MeleeRngMonsterArt.meleeRange(monsters.ts:376-389)后弃用 —— 一行改动即可修复DEFAULT_AGGRO_PX = 560(aidist为空时)monsters.ts:922-936MonStats.aidistPIXELS_PER_SUBTILE = 16是本引擎自创speed(基于发明的PLAYER_WALK_VELOCITY = 6锚点)monsters.ts:893-9026. 怪物只合成
wl+nu两种动画src/game/monster-art.ts:250-260怪物永远无法播放攻击 / 受击 / 死亡 / 特殊动画。
且 COF 候选链(
monster-art.ts:107-137)会静默把wl(走)替换成nu(站)、把真实武器类替换成hth(徒手):走动的怪物会一边滑行一边播放待机循环。这也是修复第 5 条 COF 推导时序的前置条件。
7. 其他
mon1..mon10(monsters.ts:536-543),Levels.txt支持mon1..mon25。未改版的 1.13c 无影响,任何 mod 会被静默截断。difficulty参数从未传递 ——act-scene.ts:906-912与scripts/pack-act-assets.ts:1372都不传,恒为'normal',导致nmon*池、(N)/(H)列、NM/Hell 缩放全是死代码。planMonsterGroups忽略Rarity——monsters.ts:884-889用types[rng.int(0, len-1)]均匀随机选型(Rarity只在selectLevelTypes用过一次)。elitePool为空时静默跳过所有精英(monsters.ts:866-882),整关无冠军/独特怪,不报错。DENSITY_CELLS_PER_MONSTER = 65_000(monsters.ts:812-836)—— 自述是「本模块唯一发明的数字」;应为逐房间MonDen。src/game/npc.ts:3-139 条 token→英文名;:40-49英文对白模板),应来自MonStats.NameStr→string.tbl。建议实施顺序
loadMonsterArtMap(第 4 条)—— 单点小改动,可见性收益最大MONSTER_ART_MAP→MonStats.Code+MonStats2.BaseW(第 1 条)—— 顺带消灭"XX"与前缀猜测CANONICAL_SUPER_UNIQUES_BY_LEVEL→readSuperUniques()(第 2 条)CANONICAL_ELITE_MODIFIERS→readEliteModifiers()/MonUMod.txt(第 3 条)MeleeRng接线 + COF 帧数推导攻速(第 5 条)验收标准
MONSTER_ART_MAP与ACT_MONSTER_SPECS删除,token/weapon 由MonStats.Code+MonStats2.BaseW推导"XX"tokenresolveMonsterArtSpec的startsWith前缀猜测删除CANONICAL_SUPER_UNIQUES_BY_LEVEL删除,改用readSuperUniques()CANONICAL_ELITE_MODIFIERS与所有词缀数值改由MonUMod.txt驱动;exclude1/2生效loadMonsterArtMap,有测试断言其被请求reach改用MonStats2.MeleeRng;cooldownTicks改由 COF 帧数推导difficulty参数贯通到planLevelMonstersnpm run typecheck零错误,npx vitest run全绿TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
父追踪 Issue:#64(完整审计报告与修复路线图)
主控独立复验通过,已合并至
main(5f484e5)由 Subagent (
913fd0a5-b520-44d5-ba60-2603b8fe4e9c) 在独立隔离工作树d2w-issue-59完成 5 步重构,主控执行独立复查与全量验证:核心变更总结
04d562a):PlannedLevel.types补充纳入超级怪 ID 与随从 ID,场景加载时直通act-scene.ts,根治有名有姓 Boss(Bishibosh、Rakanishu、血鸦等)在游戏中必定渲染为红方块的严重缺陷;tests/act-scene-monster-plan.test.ts。2e73d7b):data\global\monsters\EC全套 29 个动作目录,纠正hellbovine(奶牛王)的 token 为EC(原手写CW仅有 6 个残缺桩);bloodmage1-5、darkguard1-5、minion1-11等 18 个未实装怪物的"XX"虚假 token,阻断其泄漏至打包清单;resolveMonsterArtSpec中按对象枚举字典序遍历的startsWith模糊匹配,改为确定性 O(1) 查找;tests/monster-mapping.test.ts。fallen1降级(fd0f48c):CANONICAL_SUPER_UNIQUES_BY_LEVEL,以buildSuperUniqueLandmarkSpecs/lookupSuperUnique接入原版SuperUniques.txt;?? kinds.get('fallen1')(堕落魔假冒任何缺失 Boss)的危险静默降级;tests/wilderness-superuniques.test.ts。bf101da):MonStats2.MeleeRng驱动,替代发明的硬编码DEFAULT_REACH_PX = 40;MonUMod.txt驱动的精英怪生命倍率(champion 200% -> 3x, unique 300% -> 4x)与随从生命加成;tests/monsters.test.ts。独立复验指标
npm run typecheck:零错误npx vitest run:61 files passed (1 skipped) · 968 tests passed (2 skipped)(对比 #58 合并后基线 954,新增 14 个测试全绿)npm run verify:generators:与origin/main逐位完全一致(19 条既有生成器失败未增减,留待 #60 修复)d2w-issue-59与分支fix/issue-59已清理完毕。