里程碑 M10 · 重要程度 ★★★★☆ · 风险 中 · 依赖:M9
完整路线图见ROADMAP.md
背景
| 项 | 当前(demo-data.ts) |
真实 D2 |
|---|---|---|
| 物品基类 | 3(剑 / 帽 / 药水) | ~500 |
| 词缀 | 2 | ~1000 |
更关键的是掉落模型:rollDrop 现在的实现完全不是 D2 的模型。D2 的掉落是 TreasureClassEx.txt 定义的递归树,每个节点按权重向下展开。
改动清单
- [MODIFY]
src/game/acts.ts—— 加载Armor.txt、Weapons.txt、Misc.txt、MagicPrefix.txt、MagicSuffix.txt、UniqueItems.txt、SetItems.txt、TreasureClassEx.txt、ItemTypes.txt、Runes.txt、Gems.txt - [MODIFY]
src/game/items.ts——itemBaseFromRow/affixFromRow列映射逐列核对真实表。新增:品质等级(普通/魔法/稀有/套装/暗金/符文之语)、插槽、镶嵌、耐久、需求(力量/敏捷/等级) - [NEW]
src/game/treasure.ts——TreasureClassEx.txt掉落树 - [NEW]
src/game/runeword.ts—— 符文之语
注意
物品结构的任何变动都会影响 M13(.d2s 存档互通)——.d2s 的物品段是位对齐的变长编码,
字段一旦定错,往返比对就过不去。建议在本里程碑设计数据结构时就预留 .d2s 需要的全部字段
(GUID、品质、插槽数、已镶嵌物品、耐久、个性化名称等)。
验收
- 打死怪掉出带词缀的稀有装备,属性生效
- 新增
scripts/verify-items.ts:全量物品表列映射断言 TreasureClassEx递归树无环、各节点概率和为 1- 符文之语可合成且属性正确
里程碑 M11 · 重要程度 ★★★★☆ · 风险 中 · 依赖:M9, M10
完整路线图见ROADMAP.md
背景
当前只有法师(Sorceress)一个可渲染角色、2 个技能(attack / firebolt)。
真实 D2 是 7 职业 × 30 技能 = 210 个。
这是整个路线图里最长的尾巴。
好消息:角色渲染管线已通
7 个职业的 COF token 分别是:
| token | 职业 |
|---|---|
am |
亚马逊 |
ba |
野蛮人 |
dz |
德鲁伊 |
ne |
死灵法师 |
pa |
圣骑士 |
so |
法师(已实现) |
as |
刺客 |
loadCharacterSheet 的路径约定是 <root><component>/<token><component><variant><animation><weapon>.dcc,
COMPONENTS = ['hd','tr','lg','ra','la','rh','lh','sh','s1','s2']。
换 token 即可,比听起来便宜得多。
改动清单
- [MODIFY]
src/game/acts.ts—— 加载CharStats.txt、Skills.txt、SkillDesc.txt - [NEW]
src/game/classes.ts—— 7 职业定义与 COF token - [NEW]
src/game/skill-tree.ts—— 每职业 3 系 × 30 技能,含协同加成(synergies)、前置依赖、技能点分配 - [MODIFY]
src/game/skills.ts—— 从 2 个 demo 技能扩到 210 个的技能效果实现
交付建议
技能效果实现是纯工作量,建议分批:
- 第一批:每职业最常用的 ~5 个技能(共 ~35 个),保证每个职业都能玩
- 第二批:补齐常用 build 涉及的技能
- 第三批:长尾
不建议一次性做完 210 个再合入——那会是一个几千行、无法 review 的改动。
验收
- 切换到野蛮人,技能树可加点,跳跃 / 呐喊生效
- 7 职业 × 8 方向 × 全动画的 COF 解析零失败
- 技能树前置依赖图无环
- 协同加成计算正确
里程碑 M12 · 重要程度 ★★★★☆ · 风险 低 · 依赖:M6(建议插队到 M8 之后)
完整路线图见ROADMAP.md
建议插队
💡 这个里程碑值得提前到 M8 之后做。
- UI 素材在
data\\global\\ui\\下,是 DC6,解码器已经就绪- 风险最低(纯表现层,不碰模拟)
- 血球 / 蓝球 + 背包界面带来的感知提升远超工作量——这是「突然看起来像暗黑 2 了」的那个节点
代价:M9 / M10 落地后 UI 要返工一轮数据绑定。考虑到 UI 层与数据层本来就该解耦,这个代价可接受。
改动清单
- [NEW]
src/ui/inventory.ts—— 背包 10×4 网格 + 装备槽 - [NEW]
src/ui/character-sheet.ts—— 角色面板 - [NEW]
src/ui/skill-tree-panel.ts—— 技能树面板 - [NEW]
src/ui/belt.ts—— 腰带 - [NEW]
src/ui/globes.ts—— 血球 / 蓝球 - [NEW]
src/ui/hotkeys.ts—— 技能快捷键 - [NEW]
src/ui/font.ts—— D2 位图字体(data\\local\\font\\)
注意
联机场景(net-scene.ts)目前没有背包 UI、没有血蓝球(单人场景有)。
虽然联机不在交付路径上,但新写的 UI 组件应该与场景解耦,将来能直接复用。
验收
- 界面截图与原版并排对比
- UI DC6 解码零失败
- 背包拖拽、装备穿脱、腰带快捷使用均正常
- 截图回归测试(浏览器侧判定靠状态字段,不靠看图——把状态挂到
window.__d2web*上由 CDP 读取)
里程碑 M13 · 重要程度 ★★★★☆ · 风险 中高 · 依赖:M8, M10, M11
完整路线图见ROADMAP.md
背景
Master 明确要求支持读取真实 .d2s 存档并与官方游戏互通。
当前 src/game/save.ts 只解析 签名 / 版本 / 名字 / 职业 / 等级 / 校验和,
其余段原样保留字节(这是对的——见下方铁律)。
为什么排在 M11 之后
.d2s 的数据段涵盖:
| 段 | 依赖的里程碑 |
|---|---|
| 角色属性(stats) | M9 |
| 全部 30 个技能的加点(skills) | M11 |
| 背包 / 身上 / 腰带 / 仓库的全部物品(items) | M10 |
| 任务进度(quests) | M14 |
| 传送点解锁状态(waypoints) | M8 |
| 雇佣兵 / 尸体 / 铁傀儡 | M7 |
没有 M8 / M10 / M11 就无法做有意义的往返。
改动清单
- [MODIFY]
src/game/save.ts—— 逐段实现:header(765 B)quests(298 B)waypoints(81 B)npc(51 B)stats(变长位域)skills(32 B)items(变长位域)corpse/mercenary/iron golem
- [NEW]
src/formats/d2s-bits.ts—— 物品段是位对齐而非字节对齐的变长编码,需要独立的位读写器 - [NEW]
scripts/verify-d2s.ts—— 往返差分:读入真实存档 → 解析 → 重新序列化 → 逐字节与原文件比对
⚠️ 安全约束
铁律 8:解码器宁可拒绝也不猜。
猜错的
.d2s解码器会静默产出坏存档,而坏存档可能损坏 Master 的真实角色。在往返比对逐字节通过之前,写入路径必须默认禁用。
校验和必须重算正确,否则官方客户端拒绝加载。
验收
- 用本引擎读取官方 1.13c 存档,角色属性 / 技能 / 背包完全正确
scripts/verify-d2s.ts往返逐字节比对通过- 反向写出的存档能被官方客户端正常加载,且角色无损
- 对无法确认的字段保持原样保留字节,不做「看起来合理」的解析
针对室外荒野生成层(Levels.txt.DrlgType == 3),彻底消除与原版暗黑 2(D2Common.dll / DRLGOUTDOORS)的结构性分歧,包括控制点小径、移除凭空发明的十字矮墙、还原真实峭壁边界状态机、SpawnAct12Waypoint小站与神殿拓扑放置、接缝门洞空间对齐与地表过渡瓦片。
M17 首批 5 个 issue 已完成。第二批:以 1.13c D2Common.dll 为黄金标准的 DRLG 差异修复 (P0-P3),依据审计报告 113c-divergence-audit.md。
实现打怪掉落物品
复刻暗黑破坏神 II (v1.13c) 原版打怪掉落物品全链路机制(D2Game.dll D2GAME_DropMonsterItems @ 0x6FC32D60 & D2Common.dll ITEM_Drop @ 0x6FD40000):
- 全伤害源击杀事件与单次掉落门控:修复
tickCombatMulti清空前置world.events导致普攻/瞬发技能/投射物击杀不触发掉落的生命周期缺陷,保证每只怪物死亡瞬间严格触发且仅触发一次掉落结算。 - 真实怪物与暗金怪 TreasureClass (TC) 全量接入:补齐
MonStats.txt(735 行普通/蓝怪/金怪TreasureClass1..4)与SuperUniques.txt(尸火、毕须博须、拉卡尼休、血鸟、女伯爵及五大关底 Boss)在运行时的真实 TC 映射与mlvl升级,彻底消除全部回退到Act 1 H2H A的缺陷。 - 地面战利品与金币堆实体散落及弹跳动画:怪物掉落的金币与装备/药水/卷轴/符文/宝石统一在死亡坐标生成
GroundItemEntity,沿 2:1 等距菱形网格防重叠螺旋散落(规避墙体与虚空阻挡),触发原版flippy抛物线弹跳动画、星芒闪烁与落地音效。 - 掉落
Item与UiInventoryItem背包/装备栏无损桥接:将executeDropPipeline产出的底层Item(含普通/超强/魔法前后缀/稀有双词缀/绿色套装/暗金装备)完整转换为含invFile贴图、中文化名称与词缀属性(formatItemTooltip)、可穿戴槽位(allowedSlots)的UiInventoryItem,实现 Alt 标签显示、点击拾取入包(或金币累加)、背包悬停详情与右键穿戴闭环。 - 全链路确定性单测与无头浏览器实机验证:覆盖普通怪/精英怪/暗金怪/Boss 的确定性掉落单测及
acts.html实机击杀→掉落→Alt 标签→拾取→背包装备展示端到端走查。
实装暗黑破坏神 II:毁灭之王 v1.13c 原版城镇商人(Vendor NPC)库存生成、物品交易/收购/修理/赌博定价引擎与四分页商店 UI。
核心 Ground Truth 权威对标 (D2MOO + 官方 MPQ 数据表)
- 商人全局物品缓存 (
SUnitProxy.cpp):SUNITPROXY_InitializeNpcControl/SUNITPROXY_FillGlobalItemCache/SUNITPROXY_FillIGlobaltemCacheRecordForNpc- 严格映射 17 位商人 ID (
nNpcId = 0..16:阿卡拉、基德、恰西、法拉、雷山德、卓格南、赫拉铁力Hralti*、阿尔科、奥玛斯、艾尔吉斯、艾席拉、凯恩、海尔布、贾梅拉、马拉、拉苏克、安亚/尼拉塞克),分离常驻商品pPermCache(PermStoreItem = 1) 与动态随机库存pItemCache。
- 按商人 ID、游戏难度与角色等级动态生成出售物品 (
SUnitNpc.cpp):D2GAME_NPC_FillStoreInventory_6FCC7100&D2GAME_NPC_GenerateStoreItem_6FCC6A60- 商店物品等级公式:
ilvl = max(1, min(99, cLvl + 5)),普通难度受 Act 1..5 幕数上限[12, 20, 28, 36, 45]钳制,噩梦/地狱难度无幕数上限。 ilvl < 25时滚动普通/劣质/超强底材;ilvl >= 25时非固定库存 100% 升级为魔法品质 (ITEMQUAL_MAGIC) 且保底下限提升 (nMinItems = (rand & 1) + 2)。cLvl > 25时在噩梦/地狱难度严格执行扩展级 (UberCode) / 精华级 (UltraCode) 升级骰子以及NightmareUpgrade/HellUpgrade药水替换。
- 1.13c 交易价格与收购价格计算引擎 (
Items.cpp):ITEMS_CalculateTransactionCost (0x6FD9B1C0)及辅助函数0x6FD9AB90/0x6FD9ACE0/0x6FD9CB50。- 精确计算购买价 (
BUY)、商人收购价 (SELL)、修理费 (REPAIR) 与赌博价 (GAMBLE):涵盖防御比例缩放、Staffmods 单技能高额加价(售价/1024vs 收购价/4096)、词缀与ItemStatCost.txt逐点属性加价、镶嵌物半价累加、无形/职业专属收购价/4惩罚、Npc.txt汇率与任务折扣、各难度最高收购上限(普通5k/10k/15k/20k/25k、噩梦30k、地狱35k)及减价属性 (STAT_ITEM_REDUCEDPRICES)。
- 城镇 NPC 交互菜单与四分页商店 UI:
- 原版风格 NPC 交互菜单(交谈 / 交易 / 赌博 / 修理 / 鉴定),左侧 10×10 四分页商店面板(护甲 / 武器 I / 武器 II / 杂物)与右侧玩家背包联动交易。
把 diablo2-web 扩展出一个可登录 PvPGN、进入 D2GS 1.13c 服务端游戏的纯客户端模式。D2GS 由原版 D2Game.dll 驱动,服务端权威:战斗结算、掉落、刷怪、AI、物品生成全部在服务端;客户端只承担 D2Client 职责——按种子生成地图、渲染表现、本地移动预测、把 UI 操作转成上行包并以服务端回执为唯一真相。单机模式保留不动。
本里程碑先作规划,按 P0–P7 拆 issue 后再实施。
一、现状差距(代码审计结论)
| 类别 | 内容 |
|---|---|
| ✅ 可复用 | MPQ / DCC / DC6 / DT1 / COF 解码,WebGL 渲染与图集(实体 / 飞弹 / 叠加层 / UI),UI 面板,字体,物品 Tooltip 与 i18n,自动地图,bnet.html 七阶段前置流程 UI(src/frontend/flow-state.ts) |
| ⚠️ 需剥离 / 解耦 | GameEngine、战斗、掉落、刷怪、AI、词缀生成在 D2GS 下都是服务端职责;src/scene/act-scene.ts(10,753 行)与它们直接耦合 |
| ❌ 不可复用 | src/net/(锁步 P2P + relay):其前提“D2 联机不是服务端权威”(lockstep.ts 顶部注释)与 D2GS 模型相反 |
| 🔴 关键路径 | D2GS 只下发地图种子(0x03 D2GS_LOADACT:act / mapId / area)与房间坐标(0x07 / 0x08),客户端必须用同一种子逐位一致生成 DRLG。现 D2MOO 1:1 移植仅覆盖 Act I(DRLG_AllocDrlg 对其他幕直接 throw),且只用于离线烘焙;运行时从固定烘焙副本中挑选(WORLD_VARIANT_SEED) |
| 🆕 全新 | BNCS / MCP / D2GS 三层协议,D2GS 下行 Huffman 解压,约 150 种下行包 / 约 100 种上行包,物品位流,客户端单位表与移动预测,D2Client 侧技能 / 飞弹表现函数,WS↔TCP 桥 |
二、连接链路
浏览器 ⇄ WS↔TCP 桥 ⇄ bnetd(BNCS,TCP 6112)→ d2cs(MCP,TCP 6113)→ D2GS(TCP 4000)。d2dbs 是服务端内部组件,客户端不接触。D2GS 只接受经 MCP_JOINGAME 授权(game hash + token)的连接,不能绕过 realm 直连。
三、架构选型
- 运行形态:推荐「浏览器 + 自建 WS↔TCP 桥」(只透传字节、目标白名单、防开放代理);备选 Electron / Tauri 原生 TCP(免桥,但失去纯 Web 部署)。另建 Node headless 协议栈,用于抓包回放与在线冒烟(与浏览器共用同一份协议代码)。
- 场景架构:推荐抽出只读
WorldView接口,单机GameEngine与联机ClientWorld各自实现,渲染与 UI 只依赖接口。不推荐在act-scene.ts内加if (net)分支(耦合继续加深),也不推荐另写一套联机场景(渲染代码重复两份)。
四、阶段划分
P0 联机基建与测试床(#508)
- 自建测试服:PvPGN(bnetd + d2cs + d2dbs)+ D2GS 1.13c(Windows 程序,可 Wine;需原版 1.13c 文件)。
- WS↔TCP 桥;传输抽象(Node 直连 TCP / 浏览器走桥)。
- 抓包回放框架:录制原版 1.13c
Game.exe↔ D2GS 会话,作为解析器黄金用例。
P1 登录链路(BNCS + MCP)(#509)
- BNCS:
SID_AUTH_INFO (0x50)、SID_PING (0x25)、SID_AUTH_CHECK (0x51)(CheckRevision + D2 / LoD 双 CD-Key 哈希)、SID_LOGONRESPONSE2 (0x3A)、SID_QUERYREALMS2 (0x40)、SID_LOGONREALMEX (0x3E)。 - MCP:
MCP_STARTUP (0x01)、MCP_CHARLIST2 (0x19)、MCP_CHARCREATE (0x02)、MCP_CHARLOGON (0x07)、MCP_CREATEGAME (0x03)、MCP_JOINGAME (0x04)、MCP_GAMELIST (0x05)。 - 接线
bnet_connecting / bnet_login / char_create / char_select / bnet_lobby各阶段;选角画面按 statstring 渲染角色外观。
P2 D2GS 协议核心(#510)
- 握手:
0xAF(压缩协商)→ 上行0x68 D2GS_GAMELOGON→0x6B进入游戏环境;0x6D/0x8F心跳。 - 下行包固定码表 Huffman 解压 + 长度头分帧;定长 / 变长包长表以 1.13c 客户端二进制为准。
- 包分发框架;未知包 ID / 长度不符一律 fail-fast,并落盘原始字节。
P3 客户端世界(Act I)→ 档 A(#511)
- DRLG 运行时化:收到
0x03后在 Worker 中按种子与难度(取自0x01)生成当前幕;0x07/0x08增删房间;房间瓦片改用运行时 DT1 图集(替代按场景烘焙的页)。 - 单位表:
0x59 / 0x5B / 0x5C玩家、0xAC怪物与 NPC、0x51物件、0x09关卡传送点、0x0A移除。 - 移动:本地玩家预测(移植 D2Common 寻路,D2MOO
Path/)+0x15纠偏;他人0x0F / 0x10、怪物0x67 / 0x68 / 0x69 / 0x6D插值与动作模式同步(复用 animdata / anim-dispatcher)。 - 外观:
0x59+ 已装备物品 → COF 组件合成(复用char-appearance.ts);聊天(下行0x26/ 上行0x15)。
P4 物品与界面交互(#512)
0x9C(世界)/0x9D(持有)物品位流解码,对标 D2Common 物品位流编解码(D2MOOItems.cpp,需核对 1.13c 差异),映射到现有Item以复用 Tooltip。- 背包 / 仓库 / 方块 / 腰带 / 光标持物改为「上行请求 → 服务端
0x9D回执」:0x16拾取、0x17丢弃、0x18–0x1F容器与装备栏移动、0x20使用、0x23–0x26腰带、0x50丢金币。 - NPC:
0x13交互、0x2F / 0x30开关对话、0x32–0x35买 / 卖 / 鉴定 / 修理;传送点0x49;回城门与关卡入口交互。 - 属性与技能:
0x1D–0x20属性、0x94技能等级;加点0x3A / 0x3B、选技能0x3C。
P5 战斗表现与状态 → 档 B(#513)
0x4C / 0x4D单位施法 → 执行 Skills.txtcltstfunc / cltdofunc对应的客户端技能函数与cltmissile*;客户端飞弹按 Missiles.txtpCltDoFunc / pCltHitFunc / CltSubMissile* / CltHitSubMissile*演算(纯表现,伤害只认服务端);复用现有 missile / overlay 图集与渲染。0x0C受击、死亡与尸体模式、0x95生命法力、0xA7–0xA9状态(含状态属性列表)与光环 / 诅咒叠加层。- 上行
0x05–0x11左 / 右键技能(坐标 / 单位 / shift 变体)。
P6 DRLG 第二至五幕(可与 P3–P5 并行)(#514)
- 前置:#496 #497 #498 #499(第二至五幕户外关卡)、#502(瓦片变体规则)。另需:
DRLG_AllocDrlg第二、三幕种子滚动(staff / boss tomb、jungle interlink)、全部 maze / preset 关卡走移植路径、运行时(非烘焙)生成;tools/d2moo-oracle扩展到全部 136 关卡。 - 瓦片变体规则已定案(单机与联机统一,见 #502):两种模式不搞例外,一律使用 D2 原版选法(
DRLGROOMTILE_GetTileCache:按房间种子pDrlgRoom.pSeed+D2CMP_10081_GetTileRarity抽取,drlg-roomtile.ts已实现),DRLG 关卡不再用 SplitMix64pickVariant二次重抽,从根本上消除变体间子格碰撞差异导致的预测分叉。
P7 五幕完整对等 → 档 C(#515)
- 各幕特殊关卡与物件表现(塔拉夏古墓、秘法圣殿、崔凡克、混沌避难所、
0xA4巴尔波次、远古人等;服务端权威,客户端只做表现)。 - 任务状态(
0x28 / 0x29)→ NPC 对话与任务日志;传送点菜单0x63。 - 组队(
0x8B–0x8D、0x7F / 0x90)、交易(0x77–0x79)、雇佣兵(0x81、0x9E–0xA0)。 - 音效(
0x2C、技能 / NPC 语音);断线、超时与重连;大量单位时的性能。
五、验收
- 档 A(技术验证):两名玩家同进罗格营地,互相可见走动与聊天;在线 oracle——每个
0x07房间坐标 100% 命中本地 DRLG 房间;headless 回放全部抓包零解析错误;进房 60 分钟不断线。 - 档 B(Act I 可玩):Act I 捡 / 丢 / 穿脱 / 买卖 / 存取 / 鉴定 / 传送 / 打怪全流程,上行包字节与原版
Game.exe同操作抓包一致;七职业技能的施法 / 飞弹 / 命中表现与原版同屏录像逐帧对齐(抽帧比对)。 - 档 C(五幕完整):
tools/d2moo-oracle+scripts/drlg-diff.ts对 136 关卡 × 多种子 × 3 难度全阶段 0 差异,且在线0x07命中率 100%;本客户端与原版Game.exe同局通关五幕,全程无断线、无解析错误、无位置分叉。 - 全程:
npm run typecheck0 errors、vitest全量通过、单机模式 136 关卡 headless 审计零回归。
六、粗估人力(1 名熟练工程师全职,单位:人周)
| 阶段 | 人周 | 累计 |
|---|---|---|
| P0 基建与测试床 | 1–2 | |
| P1 登录链路 | 2–3 | |
| P2 D2GS 协议核心 | 2–3 | |
| P3 客户端世界(Act I) | 4–6 | 档 A ≈ 9–14 周(约 2–3.5 人月) |
| P4 物品与界面交互 | 5–7 | |
| P5 战斗表现与状态 | 6–9 | 档 B ≈ 20–30 周(约 5–7.5 人月) |
| P6 DRLG 第二至五幕 | 6–10 | 可与 P3–P5 并行 |
| P7 五幕完整对等 | 14–22 | 档 C ≈ 40–62 周(约 10–15.5 人月) |
agent 驱动可大幅压缩编码时间;真正的瓶颈在对真实 D2GS 的抓包比对与在线验证。
七、风险
- DRLG 逐位一致(最大风险):任何分叉都会导致碰撞与位置和服务端不一致。缓解:D2MOO oracle 逐阶段比对 + D2GS 在线
0x07oracle(后者代表真实 1.13cD2Common.dll)。 - 客户端技能 / 飞弹函数:D2Client 侧逻辑以 1.13c 反汇编为准;D2MOO 以 D2Common / D2Game 为主,客户端函数覆盖度待确认,是工作量最大的不确定项。
- Warden(
0xAE):若服务端启用需单独评估;测试服默认关闭。 - 版本校验:CheckRevision 需要真实 1.13c 客户端文件,或在测试服放宽 PvPGN 版本检查。
- WS↔TCP 桥安全:生产部署需鉴权、限流与目标白名单。
act-scene.ts解耦回归:拆分期间单机模式须保持全量测试通过。
八、不在范围(YAGNI)
Battle.net 聊天频道 / 好友 / 天梯榜 UI、1.14+ 与 D2R 兼容、D2GS 服务端改造、Warden 模块执行、任何外挂式功能(如全图显示)。
约束
严格以 1.13c 反汇编(D2Client.dll / D2Common.dll / D2Net.dll)与官方 MPQ 数据表为准绳,禁止编造规则与静默降级兜底;客户端不做任何权威结算;所有改动遵守 worktree 隔离规则。
复刻捡起/丢弃物品
复刻原版功能
1、人物可以从箱子或者背包丢装备到地上(包括金币)
2、物品在地上会以一个小图标显示,并且有闪光的动画效果
3、按alt键显示物品名称(以颜色区别稀有度)
4、点击小图标捡东西进包裹
5、如果包裹慢了显示一个物品被抛起的动画。