Go to file
troytt dd775c8156 fix(client): keep unit animation facing aligned with movement direction
The walk/run clip often faced away from the direction of travel:

- deltaToDir16 used 0 = +X with rounding, while 1.13c clips start at
  south-west; units further reduced it with dir16 % 8 and the renderer
  with % dirCount, so 8-direction monsters showed the wrong row.
- re-pathing every frame while the mouse was held made the first path
  point the centre of the current cell, so the unit stepped back and
  turned around before moving on.
- server position packets rubber-banded the local player towards a
  lagging position, and spawnOrSnap reset the facing to 0.

Unit facing is now the 1.13c dir64 (D2Common 0x6FD5CF40), set once per
path segment and kept while idle, mapped to the clip row with the D2CMP
and moveToWorldSubTile paths from the rounded cell. The local player
follows D2Client's position gate 0x6FADA1B0: out-of-tolerance positions
send C2S 0x5F instead of moving the predictor, 0x15 is the authoritative
snap, and replay follows the recorded positions.
2026-10-02 06:38:35 +00:00
.gitea/workflows test: 引入 Vitest 测试框架、覆盖率统计与 CI 流水线 (fixes #14) 2026-09-14 12:25:28 +00:00
docs feat(drop): embed MonStats and SuperUniques with 1.13c multi-diff TC parity (Fixes #410, #411) 2026-09-25 13:44:35 +00:00
public feat(frontend,netproto): implement 1.13c Battle.net Create New Account flow 2026-10-02 06:29:03 +00:00
samples/fixtures feat(skills): authentic 1.13c Cast Overlay system and elemental spell animations (Fixes #386) 2026-09-23 03:53:57 +00:00
src fix(client): keep unit animation facing aligned with movement direction 2026-10-02 06:38:35 +00:00
tests fix(client): keep unit animation facing aligned with movement direction 2026-10-02 06:38:35 +00:00
tools fix(client): keep unit animation facing aligned with movement direction 2026-10-02 06:38:35 +00:00
.gitignore feat(client,baker): implement incremental SHA-256 Service Worker asset caching 2026-10-01 01:09:17 +00:00
AGENTS.md fix(drlg,pack): pack XX minion art, fix L123 var3 waypoint, and pin exact DRLG tile variants (#500, #501, #502) 2026-09-28 09:23:12 +00:00
PROJECT.md feat(client,netproto): complete D2GS v1.13c online gameplay loops (R1-R5: item tooltips, container ops, NPC dialogue/travel, mercenaries, warps/portals/SkillCast/death, party/trade/quest panels) 2026-10-02 06:08:32 +00:00
README.md docs: add dedicated module READMEs and rewrite root README with single-client guide 2026-10-01 13:31:15 +00:00
ROADMAP.md chore: 移除 index.html、net.html、walk.html 历史原型入口并优化根路径路由 2026-09-18 12:04:07 +00:00
TEST_INFRA.md feat(client,netproto): complete D2GS v1.13c online gameplay loops (R1-R5: item tooltips, container ops, NPC dialogue/travel, mercenaries, warps/portals/SkillCast/death, party/trade/quest panels) 2026-10-02 06:08:32 +00:00
TEST_READY.md feat(client,netproto): complete D2GS v1.13c online gameplay loops (R1-R5: item tooltips, container ops, NPC dialogue/travel, mercenaries, warps/portals/SkillCast/death, party/trade/quest panels) 2026-10-02 06:08:32 +00:00
THIRD_PARTY_NOTICES.md 初始提交:从零实现的暗黑破坏神 II WebGL2 引擎(TypeScript + Vite,零运行时依赖) 2026-09-13 18:10:28 +08:00
package-lock.json chore(scripts): 使用 tsx 运行 TypeScript 脚本,解除对特定 Node 运行时的依赖 (fixes #12) 2026-09-14 11:44:43 +00:00
package.json chore(tools): drop acts.html-only single-player browser verifiers 2026-10-01 06:04:04 +00:00
play.html feat(client): formula-derived viewport profiles + 1068x600 / 1280x720 2026-10-02 06:22:40 +00:00
tsconfig.baker.json refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
tsconfig.client.json refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
tsconfig.common.json refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
tsconfig.json refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
tsconfig.netproto.json refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
tsconfig.server.json refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
tsconfig.sw.json feat(client,baker): implement incremental SHA-256 Service Worker asset caching 2026-10-01 01:09:17 +00:00
vite.config.ts feat(client): render boot resource download progress bar with percentage and current filename 2026-10-01 01:19:27 +00:00
vitest.config.ts refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00

README.md

Diablo II: Lord of Destruction (v1.13c) — Web Port

1. 项目概述

本项目是 《暗黑破坏神 II:毁灭之王》(Diablo II: Lord of Destruction v1.13c) 的高精度 Web 移植实现。项目严格遵循原版 1.13c 反编译二进制汇编(D2Common.dll、D2Client.dll、D2Game.dll、D2Net.dll、Bnclient.dll、D2MCPClient.dll、Fog.dll)与官方 MPQ 数据表(Levels.txt、LvlTypes.txt、SuperUniques.txt、Skills.txt、TreasureClassEx.txt 等)作为唯一事实基准(Ground Truth):

  • 1:1 原生网络协议兼容:完整实现 BNCS(战网登录/聊天,端口 6112)、MCP(Realm 角色管理/建房加房,端口 6113)与 D2GS(Huffman 压缩帧、167 个 S2C 操作码、C2S 指令及物品比特流,端口 4000),浏览器客户端可通过轻量 TCP-to-WebSocket 桥直接连入原版 C++ PvPGN + D2GS (1.13c) 战网服务器。
  • 种子驱动的确定性 DRLG 地图重建:1:1 复刻 D2Common.dll 64 位进位乘法随机数 (0x6FDAA9E0) 与三大 DRLG 拓扑算法(Preset 预设、Maze 地牢迷宫、Outdoors 野外生成),客户端仅凭 D2GS 下发的 32 位 mapSeed 即可在浏览器端还原与 C++ 服务端逐格一致的瓦片地图与碰撞网格。
  • WebGL2 高性能等距渲染与离线资产管线:通过 Node.js 离线烘焙管线 (src/baker) 将原版 MPQ 中的 DT1/DS1 瓦片、DC6/DCC 角色与怪物动画、COF 复合图层、PL2 调色板与 TBL 字符串表预编译为静态图集与二进制包,配合 Service Worker 增量缓存与 WebGL2 合批渲染实现 60fps 流畅体验。
  • 严格的模块化架构隔离:全仓按原版 DLL 职责划分为五大核心代码模块与独立工具集,由多套独立 tsconfig.*.json 与架构边界测试(tests/arch/boundaries.test.ts)强制保障零跨层污染。

2. 模块概述与 README 链接

项目源码按职责边界严格拆分为以下模块,点击各模块的 README.md 可查看详细的子目录结构、核心算法与验证命令:

模块路径 对标原版组件 运行环境与边界约束 核心职责简述 详细文档
src/common D2Common.dll Storm.dll D2Lang.dll 纯 TypeScript (ES2022),零 DOM / 零 Node.js 依赖 MPQ 与原生二进制格式解析器 (DC6/DCC/DT1/DS1/COF/PL2/TBL)、D2DataRegistry 数据表中心、1.13c DRLG 随机地图生成算法、64 位 D2Rng 与烘焙校验契约 (pack-contract) src/common/README.md
src/netproto D2Net.dll Bnclient.dll D2MCPClient.dll Fog.dll 纯协议栈模块,零外部模块依赖,唯一入口 src/netproto/index.ts BNCS (6112) / MCP (6113) / D2GS (4000) 二进制协议编解码、XSHA-1 与 CheckRevision 认证、Fog.dll Huffman 解压、167 个 S2C 操作码解析、物品比特流与 WebSocket/TCP 传输层抽象 src/netproto/README.md
src/client D2Client.dll D2Gfx.dll D2Win.dll D2Launch.dll 纯浏览器客户端 (DOM + WebGL2),零 Node / 零 server / 零 baker 依赖 唯一页面入口 play.html、战网登录/选人/大厅前端界面、OnlineSession 与 ClientWorld 状态镜像、种子驱动的实时 DRLG 地图重建、移动预测与内插、WebGL2 渲染器、全套经典 HUD 面板与抓包检查器 src/client/README.md
src/server D2Game.dll 纯确定性模拟 (ES2022 + WebWorker),禁 DOM / 禁 Node / 禁非确定性随机与时钟 25fps (40ms/tick) 权威游戏循环、1.13c 战斗伤害与抗性管线、7 职业 221 个技能执行器、投射物与光环引擎、怪物 AI 与房间流式激活、TC 掉落管线与 .d2s 存档读写 src/server/README.md
src/baker 离线资源构建管线 纯 Node.js CLI 环境 (types: ["node"]),禁 DOM / 禁 client / 禁 server 从原版 1.13c MPQ 归档中提取并烘焙瓦片图集 (tiles)、角色/怪物/NPC 实体 (entities)、投射物 (missiles)、覆盖层 (overlays)、HUD/前端 UI、动画表 (animdata) 与 SHA-256 资产清单 (asset-manifest.json) src/baker/README.md
tools/ 开发者与 QA 工具集 Node.js / Playwright CLI 无头协议测试机器人 (d2-bot.ts)、.d2cap 抓包离线重放器 (netproto-replay.ts)、.d2s 存档全解锁工具 (d2s-unlock.ts)、全谱系 136 关卡浏览器审计 (audit-levels-browser.ts) 与 DRLG 差分对比工具 tools/README.md

模块依赖拓扑图

          ┌──────────────────┐          ┌────────────────────┐
          │    src/common    │          │    src/netproto    │
          │ (Pure TS Base)   │          │ (1.13c Wire Stack) │
          └────────┬─────────┘          └─────────┬──────────┘
                   │                              │
         ┌─────────┼──────────────────┐           │
         ▼         ▼                  ▼           ▼
┌─────────────┐ ┌──────────────┐   ┌─────────────────────────┐
│  src/baker  │ │  src/server  │◄──┤       src/client        │
│ (Node CLI)  │ │ (D2Game Sim) │   │ (Browser WebGL2 Client) │
└─────────────┘ └──────────────┘   └─────────────────────────┘
                   *(注:src/client 与 src/server 之间完全解耦,仅各自依赖 src/common 与 src/netproto)*

3. 单客户端模式使用方法(Single-Client Mode Guide)

在单客户端模式下,Web 前端作为纯粹的 1.13c 在线客户端运行(play.html),所有战网账号认证、角色存档、房间管理与游戏权威战斗逻辑均由远端原版 PvPGN (bnetd + d2cs + d2dbs) + D2GS (v1.13c) 服务器承载。

完整跑通单客户端模式包含以下三个核心步骤:


3.1 第一步:在 D2GS / PvPGN 机器上设置 TCP-to-WebSocket 桥

由于浏览器只支持 WebSocket (ws:// / wss://) 而不支持直接建立原始 TCP Socket,需要在运行 PvPGN 与 D2GS 的服务器(或同内网网关机)上部署 websockify + Nginx 反向代理,将浏览器的三路 WebSocket 子路径桥接到对应的原生 TCP 端口:

浏览器 Web 客户端 (src/netproto WsStream)
  │
  ├─ wss://<host>/d2net/bnet   ──► Nginx :443 ──► websockify 127.0.0.1:7001 ──► PvPGN bnetd (TCP :6112)
  ├─ wss://<host>/d2net/realm  ──► Nginx :443 ──► websockify 127.0.0.1:7002 ──► PvPGN d2cs  (TCP :6113)
  └─ wss://<host>/d2net/game   ──► Nginx :443 ──► websockify 127.0.0.1:7003 ──► D2GS 1.13c  (TCP :4000)

1) 安装 websockify

在 Linux 网关/宿主机(例如 Ubuntu/Debian)上安装 websockify:

sudo apt-get update && sudo apt-get install -y websockify nginx

2) 配置 systemd 守护服务管理 3 个桥接端口

创建环境变量配置目录 /etc/d2ws 及三个端点的配置文件(若 D2GS 运行在另一台内网 Windows 机器上,将 TARGET 中的 127.0.0.1 替换为对应的内网 IP 即可):

sudo mkdir -p /etc/d2ws

# 1. BNCS (bnetd, TCP 6112)
sudo tee /etc/d2ws/bnet.env >/dev/null <<'EOF'
LISTEN=127.0.0.1:7001
TARGET=127.0.0.1:6112
EOF

# 2. MCP Realm (d2cs, TCP 6113)
sudo tee /etc/d2ws/realm.env >/dev/null <<'EOF'
LISTEN=127.0.0.1:7002
TARGET=127.0.0.1:6113
EOF

# 3. D2GS Game Server (D2GS.exe 1.13c, TCP 4000)
sudo tee /etc/d2ws/game.env >/dev/null <<'EOF'
LISTEN=127.0.0.1:7003
TARGET=127.0.0.1:4000
EOF

创建模板单元文件 /etc/systemd/system/d2ws@.service 并启动服务:

sudo tee /etc/systemd/system/d2ws@.service >/dev/null <<'EOF'
[Unit]
Description=Diablo II WebSocket-to-TCP Bridge (%i)
After=network.target

[Service]
Type=simple
EnvironmentFile=/etc/d2ws/%i.env
ExecStart=/usr/bin/websockify --heartbeat=30 ${LISTEN} ${TARGET}
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now d2ws@bnet d2ws@realm d2ws@game

3) 配置 Nginx /d2net/* WebSocket 反向代理

在 Nginx 站点配置(如 /etc/nginx/sites-enabled/default)的 server 块中加入 /d2net/ 路由规则:

# BNCS (端口 6112)
location = /d2net/bnet {
    proxy_pass http://127.0.0.1:7001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
    proxy_buffering off;
}

# MCP Realm (端口 6113)
location = /d2net/realm {
    proxy_pass http://127.0.0.1:7002;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
    proxy_buffering off;
}

# D2GS Game Server (端口 4000)
location = /d2net/game {
    proxy_pass http://127.0.0.1:7003;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
    proxy_buffering off;
}

重载 Nginx 配置并验证服务状态:

sudo nginx -t && sudo systemctl reload nginx
systemctl is-active d2ws@bnet d2ws@realm d2ws@game

提示:客户端的 createWsBridgeResolver(位于 src/netproto/transport/endpoint.ts)会自动根据协议握手阶段返回的端口(6112、6113、4000)将连接重写至 <wsBridgeUrl>/bnet、/realm 与 /game,因此无需修改 D2GS 内部下发的 IP,只需确保 PvPGN 的 realm.conf / address_translation.conf 中配置的端口保持标准 6113 与 4000 即可。


3.2 第二步:如何烘焙离线美术资源(Offline Asset Baking)

Web 客户端运行所需的瓦片地图图集、角色/怪物 DCC 精灵、HUD 面板与动画速度表均通过 src/baker 从原版 1.13c MPQ 离线烘焙生成到 public/ 目录。

1) 安装项目依赖

npm install

2) 放置原版 1.13c MPQ 文件

将原版 Diablo II: Lord of Destruction v1.13c 的 MPQ 文件放入(或软链接至)samples/d2/ 目录:

mkdir -p samples/d2
# 确保以下 4 个核心 MPQ 存在(音频 MPQ 如 d2sfx.mpq / d2music.mpq / d2xmusic.mpq 可选):
ls -lh samples/d2/
# d2data.mpq   d2char.mpq   d2exp.mpq   Patch_D2.mpq

3) 执行全量烘焙与契约校验

# 一键烘焙全部离线资源到 public/act-packs/ 并生成 public/asset-manifest.json
npm run bake -- all

# 烘焙完成后执行严格完整性校验(验证 233+3 个 DT1 瓦片、实体图集与 0 缺失文件)
npm run bake:verify

如果需要单独重新烘焙某一部分资源,也可以使用细粒度命令:

  • npm run pack:tiles:仅烘焙全部 5 幕瓦片图集与 drlg-bundle.{json,bin}
  • npm run pack:frontend:仅烘焙战网登录、角色选择与大厅前置界面资源
  • npm run pack:ui:仅烘焙游戏内 HUD 面板、技能图标与鼠标指针
  • npm run pack:animdata:仅提取 AnimData.d2 动画帧率表
  • npm run pack:entities:仅烘焙怪物、SuperUniques(含随从)与场景交互物图集
  • npm run pack:missiles / npm run pack:overlays:仅烘焙投射物或光环/状态覆盖层特效
  • npm run pack:manifest:重新扫描 public/ 并更新 public/asset-manifest.json(触发客户端 Service Worker 增量更新)

3.3 第三步:如何启动服务与连接游戏

方式 A:本地开发模式启动(Vite Dev Server)

npm run dev
  • 启动后浏览器访问 http://127.0.0.1:5173/play.html(访问根路径 / 也会自动重写到 /play.html)。
  • 默认代理配置:vite.config.ts 已内置 /d2net WebSocket 代理(默认转发至远端测试服 wss://www.laiseek.xyz/d2net/*),因此本地启动后可直接登录进入游戏。
  • 自定义切换 D2GS 服务器地址:
    1. URL 参数方式:访问 http://127.0.0.1:5173/play.html?wsBridge=wss://your-d2gs-domain.com/d2net(或本地桥 ws://127.0.0.1:8080/d2net)。
    2. UI 设置面板方式:点击画面顶部 32px 工具栏右侧的 ⚙ Settings 按钮,修改 WS Bridge URL 并保存刷新。

方式 B:生产环境构建与静态部署

# 1. 全仓类型检查(同时校验 common / netproto / server / client / baker 五套 tsconfig)
npm run typecheck

# 2. 构建生产静态产物(输出至 dist/,若需部署在 /diablo2/ 子路径下可执行 npm run build:game 输出至 dist-game/)
npm run build

# 3. 本地预览生产构建包
npm run preview

在生产环境 Nginx 中,只需将构建生成的 dist/ 目录挂载为静态站点根目录(或子路径 /diablo2/),并与 3.1 节 中的 /d2net/{bnet,realm,game} WebSocket 反向代理配置在同一个域名下,即可通过浏览器直接访问完整的在线暗黑 II 体验。

方式 C:使用命令行无头 Bot 快速验证服务连通性

无需打开浏览器,可在终端使用 tools/d2-bot.ts 直接测试 TCP 桥与 D2GS 建房/进图流程:

npm run bot -- --bridge wss://your-d2gs-domain.com/d2net --user <账号> --pass <密码> --char <角色名>