diablo2-web/README.md

164 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# d2web
从零实现的**暗黑破坏神 II 引擎**(纯 TypeScript + Vite + WebGL2,**零运行时依赖**),在浏览器里读取
用户自备的经典版 D2 归档(MPQ),渲染等距地图、驱动角色走动,并支持把地图离线烘焙成 web 原生格式。
- **仓库不含任何暴雪资源**:没有 `.mpq` / `.dc6` / `.dt1` / 图集 PNG;原版数据要自己放进 `samples/d2/`。
- **代码可复现一切**:格式解码、地图构建、资源包烘焙、角色合成都在这份代码里,脚本可重跑。
- 路线图、实现细节、验证方法论与未完成项在 [`ROADMAP.md`](ROADMAP.md)(README 只做总览与部署使用)。
## 总览
| 能力 | 说明 |
| --- | --- |
| **等距地图渲染** | DS1 + DT1 投影(2:1 dimetric,格 80×40 px),地板 / 墙 / **屋顶** 三层分别绘制;逐格按 `RarityFrameIndex` 加权随机选瓦片变体;画家序排序 |
| **碰撞** | 每格 5×5 个 sub-tile 标志位(一层内所有层做 OR 合并,含 `type 13` 影子层),角色按 sub-tile 判定走位 |
| **真实关卡** | 已烘焙 **365 张地图**,覆盖 5 个 act:35 个预置关卡(罗格营地、修道院、大教堂、地下墓穴、鲁高因、库拉斯特码头、哈洛加斯…)+ **70 个随机迷宫 + 31 个野外关卡**(生成逻辑移植自 D2MOO,按官方参数表复现) |
| **真·女法师** | `d2char.mpq` 的 DCC + COF 逐层合成(8 方向;行走 `SOWLHTH`、站立 `SONUHTH`),不是占位棋子 |
| **资源包(离线烘焙)** | 索引色 PNG 图集页(≤2048²,PLTE + tRNS 取自该 act 的 `pal.pl2`)+ `scene.json`(绘制列表、碰撞 RLE、出生点、对象元数据);首屏只加载视口需要的页,其余后台补 |
| **按需读归档** | 也可不烘焙,直接用 HTTP Range 只拉需要的 MPQ 字节当场解码(`?live=1`,慢,作为回退路径) |
| **三级关卡选择器** | 章节 → 场景 → 细分场景(如 第一章 / 罗格营地 / 北);中文场景名见 `src/game/level-names-zh.ts` |
**尚未实现**:真实地图上的怪物还是占位配置——怪物种类、密度、精英怪、超级唯一怪都尚未从
`Levels.txt` / `MonStats.txt` / `SuperUniques.txt` 读取(见 M7);关卡之间还不连通(见 M8);
战斗数值不是暗黑2 的公式(见 M9);可破坏物品的美术(位置与美术成员已进包,DCC 帧未接)。
完整的里程碑排期见 [`ROADMAP.md`](ROADMAP.md)。
### 代码结构
```
src/mpq/ MPQ v1 容器:hash/block 表、Storm 加密、分扇区编解码、PKWARE implode/explode、Range 读取
src/formats/ dc6 / ds1 / dt1 / pal / pl2 / cel / pcx / tbl / sprite / dcc / cof / bitstream
src/game/ 等距场景与碰撞、关卡表解析、DCC+COF 角色合成、对象与物品解析、确定性 RNG
src/render/ WebGL2 渲染器(多图集;纹理切换时 flush 以保画家序)
src/scene/ 页面级装配(资源包优先,失败回退到直读归档)
src/net/ src/sim/ 联机中继与确定性锁步(夹具地图上的 2–4 人合作)
scripts/ 资源包烘焙、格式/数据/部署校验、无头浏览器检查、夹具生成
*.html 五个入口页面:acts / index / map / walk / net
```
## 部署与使用
### 1. 前置:自备游戏数据
把你自己合法拥有的**经典版暗黑破坏神 II(1.13c)** 数据放进 `samples/d2/`,至少需要
`d2data.mpq`、`d2char.mpq`、`d2exp.mpq`、`Patch_D2.mpq`。资源包模式(页面默认)只需要
`samples/d2-packs/`——它不在 Git 里(63 MB 的索引 PNG 每次重烘都会让仓库再长一份),
按下面的「资源包从哪来」下载解包后,**没有原版数据也能看地图**。
`samples/d2/` 通常做成软链接,指向一个**磁盘上的持久目录**。切勿链到 `/tmp`——它是内存盘,
重启即清空,链接会变死链而角色/怪物退化成占位方块。本机的实际摆放、清单与逆向参考用的原版
DLL 见 [`docs/LOCAL_DATA.md`](docs/LOCAL_DATA.md)。
### 2. 资源包从哪来(不在 Git 里)
图集包(62 个地图块 / 35 关,索引色 PNG + `scene.json`)**不在本仓库里**——它是 ~66 MB 的索引色
PNG,每次重烘几乎全部文件都变,而 PNG 无法被 git 的 delta 压缩,随仓库分发会让每烘焙一次仓库再长
~60 MB。它放在**图库仓库**,那里刻意**只保留一个提交**(每次重烘 orphan 提交 + force push),
体积恒定在「一份快照」:
```bash
git clone https://git.projectdiablo2.cn/troytt/diablo2-web-assets.git samples/d2-packs
```
只想要一份压缩包(不需要 git):
```bash
curl -O https://www.laiseek.xyz/diablo2/assets/d2-packs-2026-09-14.tar.xz
sha256sum d2-packs-2026-09-14.tar.xz # 应为 9cdfd0c19ac68de842c6725070b65d056e25f5bcc77f36d6d9a29cd9f1b431e8
tar xJf d2-packs-2026-09-14.tar.xz # 解出 samples/d2-packs/
```
仓库里只保留 `.gitignore` 规则与本文档;要自己生成包就照第 3 步 `npm run pack:data` 从原版 MPQ
烘焙,再用 `npm run publish:packs` 发布到图库仓库(脚本会重建单提交历史)。
### 3. 本地跑起来
```bash
npm install # 仅 vite / typescript / @types/node
tsx scripts/make-fixtures.ts samples/fixtures # 夹具:DC6 + 调色板 + 文本表
tsx scripts/make-map-fixtures.ts samples/fixtures # 夹具:DT1 + DS1 + 夹具 MPQ
npm run pack:data # 从 samples/d2/ 烘焙 62 个地图块 → samples/d2-packs
npm run dev # http://127.0.0.1:5173/acts.html
```
`pack:data` 需要原版 MPQ(约 2 分钟);只用上面下载来的 `samples/d2-packs/` 可以跳过它,
代价是 `?live=1` 与重新烘焙不可用。
### 4. 页面与操作
| 页面 | 用途 |
| --- | --- |
| `/acts.html` | **主页面**:全屏画布 + 三级选择器(章节 / 场景 / 细分场景)+ HUD |
| `/index.html` | MPQ 资源检查器(头、存储标志、压缩掩码分布、成员列表、精灵预览) |
| `/walk.html?sample=<你的.mpq>` | 可行走演示(D1 试玩版 `spawn.mpq` 已验证) |
| `/net.html?data=samples/fixtures&ws=ws://127.0.0.1:8787&peers=3&peer=0` | 2–4 人联机(先 `npm run net-server`) |
主页面查询参数:
```
?act=1..5 章节
?level=<地图块 label> 直接指定细分场景,如 33-act-1-cathedral-cathy3
?quadrant=<DS1 名> 直读归档模式下指定该关卡的某个 DS1
?live=1 不走资源包,直接按 HTTP Range 读 MPQ 解码(慢)
?pack=<目录> 换一个资源包目录 ?base=<目录> 换一个归档目录
```
操作:**WASD / 方向键**移动(按屏幕方向),**滚轮 或 `+` / `-`** 缩放,`0` 复位;画布随窗口自适应并铺满视口。
### 5. 部署到服务器
```bash
npm run build:game # → dist-game/(base=/diablo2/)
rm -rf /var/www/d2web && mkdir -p /var/www/d2web && cp -r dist-game/. /var/www/d2web/
rm -rf /var/www/d2packs && mkdir -p /var/www/d2packs && cp -r samples/d2-packs/. /var/www/d2packs/
mkdir -p /var/www/d2data && ln -f samples/d2/*.mpq /var/www/d2data/ # 只有 ?live=1 用得到
```
nginx(`/etc/nginx/sites-enabled/default`):
```nginx
location = /diablo2 { return 301 /diablo2/; }
location = /diablo2/ { rewrite ^ /diablo2/acts.html last; }
location /diablo2/ { alias /var/www/d2web/; autoindex off; add_header Cache-Control 'no-store'; }
location /diablo2/packs/ { # 资源包:可缓存
alias /var/www/d2packs/;
autoindex off;
add_header Cache-Control 'public, max-age=3600';
}
location ~ ^/diablo2/data/(?<d2file>[A-Za-z0-9_.-]+\.mpq)$ { # 只暴露 .mpq,不列目录
alias /var/www/d2data/$d2file;
autoindex off;
add_header Accept-Ranges bytes;
add_header Cache-Control 'public, max-age=3600';
}
```
要点:归档必须支持 **Range**(否则页面会退化成整文件下载);`/root` 是 0700,所以用硬链接暴露给 nginx。
部署后跑 `npm run verify:deploy` 自检。
### 6. 自检命令
```bash
npm run verify:deploy # 线上:页面 / 资源包索引 / 图集 / Range 206 / 旧入口 410
npm run verify:packs # 资源包与"现读现解"逐项比对(绘制序、碰撞栅格逐字节、每帧像素哈希)
npm run verify:tiles # 每个槽位画的是不是该有的瓦片类型(地面=type 0)+ 位置公式
npm run verify:alignment # 墙 / 地面基线、屋顶偏移与引擎公式一致
npm run verify:object-lookup # DS1 对象 id → token/mode 与 Objects.txt 交叉核对
npm run verify:acts # 关卡表 → DS1 → DT1 → 等距场景 + 碰撞
npm run verify:d2 # 真 MPQ 数据端到端解码
npm run verify:implode # PKWARE implode 解码器(全成员)
npm run typecheck # tsc --noEmit
```
## 数据与许可
- 仓库**不含**暴雪美术:原版 MPQ、`reference/` 下的第三方参考源码都不入库(规则写在 `.gitignore` 与
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md));`samples/fixtures/`(本项目自产)与
`samples/d2-packs/`(派生数据)只作为 `/diablo2/assets/` 下的归档发布,不进 Git 历史。
图集包每次重烘都会让 Git 仓库再长 ~60 MB(PNG 无法 delta 压缩),所以不再入库。
- 使用本引擎需要你自己合法拥有的游戏数据。将来落地的生成类关卡(迷宫 / 野外)会在页面与文档中标注为
**"按官方参数表复现,非引擎原版布局"**(生成逻辑移植自 D2MOO)。