# 部署指南 目标:在一台家用 Windows 小主机上长期开服,让朋友通过局域网或公网来玩。macOS / Linux / Docker 放在后面。 所有命令都在项目根目录执行。遇到问题先运行 `node tools/doctor.mjs`(只读诊断)。 ## 0. 资源需求 | 项目 | 说明 | |---|---| | 服务器 CPU | 战斗在各玩家浏览器里模拟(DESIGN §14),服务器只负责回合、经济和校验:**每个房间每个作战回合约 1 ms CPU**。AI 队友 / 掉线玩家的战场由服务器模拟:作战开始时 3 个 AI 战场在开发机上约 0.2–0.5 s CPU,小主机上可能要几秒(分成 8 ms 小片执行,不会卡住其他房间)。`SP_VERIFY=all` 会复算每个真人战场,CPU 明显增加,小主机建议保持 `off` 或 `sample`。 | | 服务器内存 | 空闲约 100 MB,每个进行中的对局再增加几 MB。 | | 网络 | 4 人对局中服务器每回合下行约 0.25 MB(DESIGN §14 实测)。首次进入游戏时浏览器要从主机下载所需的图片 / Spine 模型 / 音频(按需加载,之后走浏览器缓存),公网隧道带宽小时第一次会慢一些。 | | 磁盘 | 素材约 550 MB(`public/assets`,含中文约 65 MB、日文约 85 MB 两套干员语音)+ 依赖约 125 MB(`node_modules`;整合包只带运行依赖,约 65 MB);可选的本地提取约 40 MB(`.venv-extract`)+ 70 MB 贴图(见第 6 节)。完整包解压后约 710 MB。 | | 玩家设备 | 支持 WebGL 的现代浏览器(Chrome / Edge / Firefox / Safari 最新版),电脑或手机平板(横屏)。老旧设备可在设置里调低画质或访问 `/?board=2d`。 | 服务器**无状态**:房间和对局只存在内存里,没有数据库和存档,**不需要备份**。重启服务器会结束正在进行的对局(包括断线后本可在 24 小时内回来继续的独立模拟)。 ## 1. Windows 小主机:一步步 ### 1.1 安装与首次启动 1. 安装 Node.js 22 LTS 和 Git(在 PowerShell 或「终端」里;用下面的整合包时不需要 Git): ```powershell winget install OpenJS.NodeJS.LTS winget install Git.Git ``` 装完**关闭并重新打开**终端,`node -v` 应显示 v22 或更高(winget 的 LTS 目前是 v24.x,同样可用)。没有 winget 时从 和 下载安装。 2. 下载,三选一。建议放在一个固定、短、**不在 OneDrive 同步范围内**的目录,例如 `C:\Stronghold-Protocol`: - **完整包(推荐)**:在仓库的 [Releases](https://github.com/sganggs/Stronghold-Protocol/releases) 页面下载最新版本的 `Stronghold-Protocol-v<版本>.zip`(约 505 MB,解压后约 710 MB;已含运行依赖、前端库和全部素材,包括中文、日文两套干员语音和官方 3D 棋盘等本地客户端素材),解压后把里面的 `Stronghold-Protocol` 文件夹放到上述位置。不需要 Git,首次启动也不用再下载素材。素材版权归上海鹰角网络 / Yostar,仅限非商业使用,见 [NOTICE.md](../NOTICE.md)。 - **精简包**:同一页面的 `Stronghold-Protocol-v<版本>-lite.zip`(约 22 MB)。代码、运行依赖和前端库与完整包相同,但不带素材:美术、Spine 模型、音频(含两套干员语音)、字体、表情和「玩法说明」教程图在首次启动时由 setup 从公开镜像下载(约 550 MB,显示进度,可中断续传;镜像设置见下面的「国内镜像下载」)。官方 3D 棋盘等本地客户端素材需要用本机客户端提取,或从同一版本的完整包复制(第 6 节)。适合下载大文件不方便、或想先下一个小包的情况;放置方式同完整包。 - **源码**: ```powershell git clone https://github.com/sganggs/Stronghold-Protocol.git C:\Stronghold-Protocol ``` 3. 双击 `C:\Stronghold-Protocol\scripts\start-windows.bat`。首次会:安装依赖(`npm ci`;整合包已含,跳过)→ 复制前端库(整合包已含,跳过)→ 下载约 550 MB 素材(完整包已含,跳过;精简包和源码在这一步下载,显示进度,中断后再次启动会续传)→ 若检测到本机的明日方舟客户端,询问是否提取官方贴图(可跳过)→ 启动服务器并打开浏览器。 4. 窗口里会打印朋友可用的地址,例如 `http://192.168.1.23:3000`。用另一台设备打开它确认能进入。关闭窗口即停止服务器。 等价的手动命令:`npm ci`、`node tools/setup.mjs`、`npm start`。 #### 国内镜像下载 Setup 默认使用「GitHub 原始源 → jsDelivr」,不查询公网 IP,也不请求 gh-proxy.com。GitHub 下载失败时会提示如何手动开启镜像;仅添加提示,不自动切换到第三方代理。 镜像方法是在完整 GitHub 链接前加 `https://gh-proxy.com/`,例如: ```text https://gh-proxy.com/https://raw.githubusercontent.com/OWNER/REPO/BRANCH/file.png ``` 手动开启后顺序为「前缀镜像 → 原始源 → jsDelivr」。索引、图片、Spine、音频和字体都使用此规则(音频 voice 分支跳过 jsDelivr)。镜像是第三方代理;当前只校验格式和大小,没有内容哈希校验,请自行决定是否信任并启用。npm / pip 依赖不使用 GitHub 前缀。 ```powershell node tools/setup.mjs --asset-source=mirror # 手动优先国内镜像 node tools/setup.mjs --asset-source=direct # 默认:仅原始源和 jsDelivr,不使用前缀代理 $env:SP_ASSET_SOURCE = 'mirror' # 也可用环境变量显式启用 ``` `node tools/fetch-assets.mjs` 同样支持 `--asset-source=direct|mirror`。命令行优先于 `SP_ASSET_SOURCE`。默认镜像前缀为 `https://gh-proxy.com/`,可通过 `SP_GITHUB_PROXY` 指定其他 HTTPS 前缀;仅配置前缀不会启用镜像。将 `SP_GITHUB_PROXY` 设为空字符串(或全空格)可彻底禁用前缀代理,即使选择了 `mirror` 模式;未设置此变量与显式设空不同,前者使用默认前缀。Windows PowerShell 的某些版本会将空值视为删除变量,可设置 `$env:SP_GITHUB_PROXY = ' '` 或使用 `--asset-source=direct` 来明确禁用。前缀只处理 GitHub 下载链接,不重复添加。 镜像请求每个 URL 只尝试一次,响应头超时 8 秒,响应体有独立的空闲超时,失败即尝试原始源。连续 3 次网络错误、HTTP 错误或无效内容会在本次运行中关闭镜像,后续索引、素材和字体共享该状态;正在进行的镜像请求也会中止并回退。成功会清零连续失败次数;404 / 410 是资源不存在,不触发熔断。再次运行脚本会重新尝试手动启用的镜像。原始源的重试、已有文件跳过和 0.1.1 的清单缩减保护保持不变。 从历史下载记录派生的 Spine 补充贴图也按本次设置重新选择来源,禁用后不会沿用旧代理地址。 ### 1.2 防火墙 - 第一次启动时 Windows 会弹出「Windows 安全中心警报」:勾选**专用网络**并点「允许访问」。 - 没弹窗或点错了,用**管理员** PowerShell 添加规则(下面的开机自启脚本也会自动添加): ```powershell netsh advfirewall firewall add rule name="Stronghold Protocol" dir=in action=allow protocol=TCP localport=3000 profile=private,domain ``` - 家里的网络要是「公用网络」,Windows 会拦截入站连接。改成专用(管理员 PowerShell;网卡名用 `Get-NetConnectionProfile` 查看): ```powershell Set-NetConnectionProfile -InterfaceAlias "以太网" -NetworkCategory Private ``` - `node tools/doctor.mjs` 会显示规则是否存在、每个网络的类型,以及朋友可用的地址。 ### 1.3 固定局域网 IP(推荐) 主机 IP 变了,朋友收藏的地址就失效。推荐在**路由器**后台的「DHCP 静态分配 / 地址保留」里把小主机的 MAC 地址绑定到固定 IP(如 `192.168.1.50`)。也可以在 Windows「设置 → 网络和 Internet → 属性 → IP 分配 → 编辑」里手动设置(IP、子网掩码、网关、DNS 与路由器一致,且不要与别的设备冲突)。 ### 1.4 开机自动在后台运行 先关闭 `start-windows.bat` 的窗口(否则端口冲突),然后在项目目录运行(会自动请求管理员权限): ```powershell powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 ``` 它会:运行一次 `tools/setup.mjs` → 把设置写入 `scripts\service.env.cmd`(node.exe 路径、端口等)→ 注册计划任务 **StrongholdProtocol**(开机 20 秒后以 SYSTEM 身份运行 `scripts\run-server.cmd`,无需登录;服务器退出后 5 秒自动重启)→ 添加防火墙规则 → 立即启动并显示状态。日志在 `logs\server.log`(超过 10 MB 自动轮换)。 | 需求 | 命令(都加在 `powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1` 之后) | |---|---| | 换端口 / 其他设置 | `-Port 8080`、`-Verify sample`、`-Combat server`、`-BindHost 127.0.0.1`(只给反向代理用) | | 公用网络也放行 | `-AllowPublicNetwork`(一般不需要;Tailscale 网卡被识别为公用网络时可能需要) | | 查看状态和最近日志 | `-Status` | | 重启(更新代码后) | `-Restart` | | 停止 | `-Stop`(下次开机仍会自动启动) | | 卸载 | `-Uninstall`(删除计划任务、防火墙规则和 `service.env.cmd`) | 建议同时关闭睡眠,否则小主机会在无人操作时休眠:`powercfg /change standby-timeout-ac 0`。
替代方案:用 NSSM 注册成真正的 Windows 服务 ```powershell winget install NSSM.NSSM # 或从 https://nssm.cc 下载 nssm install StrongholdProtocol "C:\Program Files\nodejs\node.exe" server\index.js nssm set StrongholdProtocol AppDirectory C:\Stronghold-Protocol nssm set StrongholdProtocol AppEnvironmentExtra PORT=3000 HOST=:: nssm set StrongholdProtocol AppStdout C:\Stronghold-Protocol\logs\server.log nssm set StrongholdProtocol AppStderr C:\Stronghold-Protocol\logs\server.log nssm start StrongholdProtocol ``` 防火墙规则仍需按 1.2 手动添加。两种方式只选一种。
### 1.5 更新 ```powershell cd C:\Stronghold-Protocol powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 -Stop # 装了开机自启时 git checkout -- data/assets.json # 素材清单由 setup 重新生成,先还原以免 git pull 冲突 git pull npm ci node tools/setup.mjs # 补下载新增的素材(已有文件会跳过) powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 -Restart ``` 没装开机自启的话,最后一步改成重新双击 `start-windows.bat`。 #### 用整合包安装的:更新包 0.2.1 起,Releases 里每个版本除了完整包和精简包,还有更新包 `Stronghold-Protocol-v<版本>-update.zip`:只含比之前的 0.2.x 版本改动过的文件(程序、数据、运行依赖,以及改动过的素材),通常只有几 MB。它用来升级用 0.2.0 及以后的完整包或精简包装好的文件夹(适用的版本写在 Releases 说明里);全新安装、0.1.x 和 GitHub「Download ZIP」源码包请用完整包或精简包,`git clone` 的用上面的 `git pull`。 ```powershell cd C:\Stronghold-Protocol powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 -Stop # 装了开机自启时;没装就关掉服务器窗口 Expand-Archive -Force <下载目录>\Stronghold-Protocol-v<版本>-update.zip C:\ # 解压到安装文件夹的上一层,合并进 C:\Stronghold-Protocol、覆盖同名文件 powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 -Restart ``` 没装开机自启的,最后一步改成双击 `start-windows.bat`。也可以在资源管理器里打开 zip,把其中 `Stronghold-Protocol` 文件夹的全部内容复制到安装文件夹,选「替换目标中的文件」。macOS / Linux:停止服务器后运行 `unzip -o Stronghold-Protocol-v<版本>-update.zip -d <安装文件夹的上一层>`(安装文件夹名为 `Stronghold-Protocol`);不要用访达拖放,它会把同名文件夹整个替换掉。 启动时(双击启动、`npm start`、开机自启的计划任务、NSSM / systemd 都一样)服务器先完成更新:按 `MANIFEST.json` 核对全部程序文件(代码、数据、运行依赖、前端库和说明文档;素材归 setup 管,不在其中),删除 `UPDATE.json` 列出的、新版本不再使用的旧文件(只删内容与旧版本发布时完全一样的文件:自己改过的文件、自己装的内容包、`logs`、`.cache` 都不会动),再把 `UPDATE.json` 改名为 `.update-applied.json`,然后照常启动,日志里有一行「已更新到 v<版本>」。 - **文件夹不是更新包对应的版本**(例如 0.1.x、只解压了一部分、程序文件被改过):服务器**不启动**,提示「这个更新包只能覆盖在 v0.2.0 … 的整合包安装上(检测到 N 个文件与 v<版本> 不一致或缺失)」并举出几个文件(开机自启的,`-Status` 显示的日志里能看到)。新旧文件混在一起运行,容易出现看起来像游戏 bug 的错误,所以宁可不启动。下载完整包重新安装,或把更新包重新完整解压一遍再启动(`UPDATE.json` 还在,会重新检查;在此之前什么都不删)。 - 只有说明文档、`scripts\`、`tools\` 里的文件不一致:照常启动,日志里列出是哪些文件。`UPDATE.json` 损坏:跳过并提示,按现有文件启动。 - **精简包装的也能用**:程序部分与完整包完全相同;更新包带着完整包里改动过的素材,其余素材仍由 setup 补齐。如果更新包带来了新的本地客户端素材清单(`data/local-assets.json`)而这台电脑没有对应的素材,启动时会提示:从同一版本的完整包复制 `public/assets/local/`(第 6 节),或运行 `node tools/setup.mjs --local` 提取,不需要时删掉 `data/local-assets.json` 即可。 - 更新包不联网、不会自动更新,和完整包一样从 Releases 手动下载。`npm run doctor` 的「文件校验 MANIFEST.json」一行随时显示全部程序文件是否与这个版本一致(源码目录没有这个文件,不做校验)。 不用更新包也可以:停止服务器,把新版本的整合包解压到新目录后从那里启动(完整包已含素材;装了开机自启的,在新目录重新运行一次 `install-service-windows.ps1`)。用精简包或 GitHub「Download ZIP」源码包的:解压新版本后,把旧目录里的 `public\assets`、`public\fonts`、`.cache` 和 `data\local-assets.json`(若有)复制过去,可避免重新下载(setup 只补下新增的素材)。 ## 2. 让不在同一网络的朋友加入 ### 2.1 Tailscale / ZeroTier(推荐给家用小主机) 组一个虚拟局域网:不需要公网 IP、不需要改路由器、不暴露到互联网。 - **Tailscale**:主机和朋友都安装 (Windows:`winget install Tailscale.Tailscale`)并登录。朋友用自己的账号时,在 Tailscale 管理后台把这台主机「Share」给他们,或邀请他们加入你的 tailnet。朋友访问 `http://<主机的 100.x.y.z 地址>:3000`(`tailscale ip -4` 查看;开了 MagicDNS 也可以用 `http://<主机名>:3000`)。 - **ZeroTier**:在 创建网络,主机和朋友安装客户端并加入同一个 Network ID,在后台勾选授权成员;访问 `http://<主机的 ZeroTier IP>:3000`。 - 连不上时运行 `node tools/doctor.mjs`:看 VPN 网卡是否被 Windows 识别为「公用网络」,是的话按 1.2 改为专用,或安装自启时加 `-AllowPublicNetwork`。 ### 2.2 cloudflared 临时隧道(朋友什么都不用装) ```powershell winget install --id Cloudflare.cloudflared # macOS: brew install cloudflared cloudflared tunnel --url http://localhost:3000 ``` 把输出的 `https://xxxx.trycloudflare.com` 发给朋友。页面是 https 时客户端自动改用 `wss://`,不需要任何配置;服务器会通过隧道转发的 `CF-Connecting-IP` 识别真实来源(`TRUST_PROXY=auto`)。临时隧道每次启动地址都不同,且没有可用性保证;需要固定地址请使用 Cloudflare 账号 + 自己域名的「命名隧道」。 ### 2.3 路由器端口转发 仅当你有**公网 IPv4**(很多宽带是运营商级 NAT,没有公网 IP,此时请用 2.1 / 2.2): 1. 先按 1.3 固定主机的局域网 IP。 2. 路由器「虚拟服务器 / 端口转发」:外部端口 3000(或任意端口)→ 内部 `主机IP:3000`,TCP。 3. 朋友访问 `http://<你的公网 IP>:外部端口`。 注意:游戏没有账号系统,知道地址的人都能进来。服务器对来自互联网的连接有按网络的数量限制(每个网络最多 64 个连接,房间 / 对局数量也有上限),但仍建议不玩时关掉转发,或优先用 Tailscale。 ### 2.4 反向代理与 HTTPS(有域名时) 必须部署在**域名根路径**(客户端使用 `/data/`、`/vendor/`、`/ws` 等绝对路径,不支持挂在子路径下)。代理需要转发 WebSocket 升级(路径 `/ws`)。建议让服务器只监听本机:`HOST=127.0.0.1`(Windows 自启:`-BindHost 127.0.0.1`)。 **Caddy**(自动申请 HTTPS 证书,WebSocket 无需额外配置): ```caddy game.example.com { reverse_proxy 127.0.0.1:3000 } ``` **Nginx**: ```nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443 ssl http2; # 新版 Nginx(1.25.1 起)写成 listen 443 ssl; 加一行 http2 on; server_name game.example.com; ssl_certificate /etc/letsencrypt/live/game.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/game.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 1h; # WebSocket 长连接 } } ``` **HTTP/2**:客户端由数百个小脚本模块组成(0.2.0 起进入对局时约 340 个),隔着公网时建议让代理以 HTTP/2 提供页面:所有模块走同一条连接,远距离玩家首次进入对局明显更快。Caddy 默认就是 HTTP/2;Nginx 见上面的 `http2`。服务器本身只说 HTTP/1.1,局域网或本机游玩不受影响。 https / wss 说明:页面通过 https 打开时客户端自动连接 `wss://同一域名/ws`;http 时用 `ws://`。服务器本身只提供 http,证书由代理 / 隧道负责。代理与服务器在同一台机器或内网时,`TRUST_PROXY=auto` 会信任它的 `X-Forwarded-For` / `X-Real-IP`;代理在公网另一台机器上时设 `TRUST_PROXY=1`(同时确保游戏端口只对代理开放)。 ## 3. Docker ```bash # A) 构建时下载素材(需要联网,约 550 MB) docker build -t stronghold-protocol --build-arg FETCH_ASSETS=1 . docker run -d --name stronghold -p 3000:3000 --restart unless-stopped stronghold-protocol # B) 不把素材打进镜像:先在宿主机运行 node tools/setup.mjs,然后挂载 docker build -t stronghold-protocol . docker run -d --name stronghold -p 3000:3000 --restart unless-stopped \ -v "$PWD/public/assets:/app/public/assets:ro" stronghold-protocol ``` 镜像从源码(`git clone`)构建,Releases 的整合包不含 `Dockerfile`。镜像基于 `node:22-alpine`,多阶段构建,只含生产依赖;`public/vendor` 在构建时生成。`.dockerignore` 排除了 `public/assets`(不会把宿主机素材打进构建上下文);`public/fonts`、`data/assets.json` 和 `data/local-assets.json` 若存在会被复制进去。环境变量同 README(`-e SP_VERIFY=sample` 等)。健康检查:`GET /healthz`。 docker compose 示例: ```yaml services: stronghold: build: context: . args: { FETCH_ASSETS: "1" } ports: ["3000:3000"] restart: unless-stopped environment: SP_VERIFY: "off" ``` ## 4. macOS / Linux 常驻 - 临时开服:`scripts/start.sh`(或 `npm start`),保持终端窗口打开。macOS 首次会询问是否允许 node 接受传入连接,选「允许」。 - Linux systemd(`/etc/systemd/system/stronghold.service`,路径与用户按实际修改): ```ini [Unit] Description=Stronghold Protocol game server After=network-online.target Wants=network-online.target [Service] WorkingDirectory=/opt/Stronghold-Protocol ExecStart=/usr/bin/node server/index.js Environment=PORT=3000 HOST=:: Restart=always RestartSec=5 User=stronghold [Install] WantedBy=multi-user.target ``` `sudo systemctl daemon-reload && sudo systemctl enable --now stronghold`;日志 `journalctl -u stronghold -f`;防火墙 `sudo ufw allow 3000/tcp`。 ## 5. 排错 | 现象 | 处理 | |---|---| | 任何问题 | `node tools/doctor.mjs`:Node 版本、依赖、素材完整性、端口、局域网地址、防火墙、网络类型 | | `端口已被占用 / EADDRINUSE` | 已经有一个服务器在运行(自启任务?)或其他程序占用 3000:换端口 `scripts\start-windows.bat --port 3001` | | 朋友打不开页面 | 防火墙规则 / 网络类型(1.2);确认用的是 `LAN` 地址而不是 `localhost`;访客 Wi-Fi 常开启「AP 隔离」;不在同一网络请看第 2 节 | | 画面是占位图、没有声音 | 素材没下完:重新运行 `node tools/setup.mjs`(会续传);缺失明细在 `.cache/assets-report.json`。默认仅原始源和 jsDelivr;可用 `--asset-source=mirror` 手动开启前缀镜像(见上文) | | 素材下载很慢 / 失败 | 网络问题可随时中断,重新运行会跳过已完成的文件;`node tools/fetch-assets.mjs --concurrency=4` 降低并发。有文件没下载成功时,素材清单 `data/assets.json` 保持不变(脚本列出缺少的条目并以非零状态结束;游戏里缺的图片用占位图,缺的声音不播放),重新运行即可补齐 | | 表情显示成默认图标、「玩法说明」只有文字要点 | 素材没下载完整:重新运行 `node tools/setup.mjs`(表情和教程图随其他素材一起从公开镜像下载,不需要客户端);缺失明细在 `.cache/assets-report.json` | | 本地提取失败 | 游戏照常运行,只是第 6 节表格里的几样换成替代样式。确认客户端已下载全部资源;Python 版本太新导致依赖安装失败时,安装 Python 3.12 后删除 `.venv-extract` 再运行 `node tools/setup.mjs --local` | | 3D 棋盘没出现 | 需要本地提取的棋盘贴图(`node tools/doctor.mjs` 会显示「3D 棋盘可用」),以及支持 WebGL2 的浏览器。没有客户端的服务器可以从同一版本的整合包复制本地素材(第 6 节) | | 断线 | 同盟模拟 10 分钟内、独立模拟 24 小时内(`config.constants.singleReconnectTime`)用同一浏览器重新打开页面,自动回到原座位。同盟掉线期间按原阵容自动作战、到时自动准备(不会代为购买;想让 AI 代打请用「离开模拟 → 暂离(AI 托管)」);独立模拟不计时,等你回来 | ## 6. 本地客户端素材(可选) `public/assets/local/` 和 `data/local-assets.json` 是从本机安装的《明日方舟》客户端里提取的官方素材(`tools/local-extract`,DESIGN §13):`node tools/setup.mjs` 检测到客户端时会询问是否提取,之后可以用 `node tools/setup.mjs --local` 重新提取,或用 `--game "<…/StreamingAssets/AB/Windows>"` 指定客户端目录。setup 从公开镜像下载的素材不包含这部分,所以在没有客户端的电脑上(例如 Linux 服务器)用源码或精简包部署时不会有它;Releases 的完整包里已经带上了。 没有本地素材时游戏照常运行,只是下面几样换成替代样式: | 内容 | 没有本地素材时 | |---|---| | 官方 3D 棋盘(贴图、模型、地图特效) | 2D 棋盘,地块由程序绘制 | | 部分官方界面图标与底板:交流按钮和表情面板的边框、暂停面板、装备替换窗口、干员调配界面、队友状态与漏怪标记、模组类型图标等 | 样式相近的替代图形、图标或文字 | | 灼热 / 炽焰源石虫的官方模型 | 染成橙色 / 红橙色的普通源石虫 | | 39 个召唤物的官方模型(多数自选召唤物,以及凯瑟琳的爬行号·防护单元、凛御银灰的风雪之眼;公开镜像没有) | 召唤物头像(菱形底板) | 表情(6 套 × 6 个)和「玩法说明」的 19 页教程图公开镜像也有:`node tools/setup.mjs` 会和其他素材一起下载(约 21 MB),不需要客户端;有本地素材时优先显示本地的。 **没有客户端的服务器**想要上表中的官方素材:从**同一版本**的完整包([Releases](https://github.com/sganggs/Stronghold-Protocol/releases))里,把 `public/assets/local/` 文件夹和 `data/local-assets.json` 复制到服务器项目目录下的相同位置。服务器每次请求都会重新读取这两处,不必重启,玩家刷新页面即可。一定要用与服务器代码相同版本的完整包:各版本提取的内容和清单可能不同(例如灼热 / 炽焰源石虫的模型是 0.1.0 之后才加入的,召唤物模型是 0.2.0 加入的),混用其他版本的文件会缺图或用错图。复制后 `node tools/doctor.mjs` 会显示本地素材的条目数和「3D 棋盘可用」。 **0.2.0 之前提取过的**:召唤物模型是 0.2.0 新增的提取项,旧的提取结果里没有(`node tools/setup.mjs` 会提示「缺少新版的自选召唤物模型」)。有客户端的电脑运行 `node tools/setup.mjs --local` 重新提取即可,只想补这一项也可以在提取用的 Python 环境里运行 `tools/local-extract/extract.py --only spine/token`(新文件写入 `public/assets/local/spine/token/`,清单里其他条目保持不变)。 **3D 棋盘贴图的下载量**:每位玩家进入对局时都要从开服的电脑下载 3D 棋盘的 12 张贴图。提取时会给这 12 张各写一份 WebP(颜色贴图有损、质量 95,法线和数据贴图无损),清单里列的是 WebP,同名 PNG 留在旁边给裁切工具和 setup 用。这部分下载量从约 6.7 MB 降到约 2 MB,网速慢的远程联机最明显。只有 PNG 的本地素材(例如在这一改动之前提取的)可以用提取时的 Python 环境运行 `tools/local-extract/extract.py --webp` 就地补上,只需要 Pillow,不需要客户端。 ## 7. 打包发布(维护者) Releases 的 zip(完整包、精简包,0.2.1 起还有更新包)由 `tools/package.mjs` 生成,在**源码仓库**里运行(整合包里没有这个工具): ```bash npm run package -- --dry-run --list # 只检查:列出每个文件和大小,不写任何文件(精简包加 --lite) npm run package -- --out <目录> # 完整包 Stronghold-Protocol-v<版本>.zip npm run package:lite -- --out <目录> # 精简包 Stronghold-Protocol-v<版本>-lite.zip npm run package -- --update --from <旧版本的完整包>[,<…>] --out <目录> # 更新包 Stronghold-Protocol-v<版本>-update.zip ``` - **打进去的**:`git ls-files` 里的 `server/`、`shared/`、`data/`、`public/`(不含 `public/dev/`)、`packs/`(随仓库提交的内容包;只在本机安装、没提交的不打进去)、启动脚本、玩家会运行的工具(setup、vendor、fetch-assets 与 `tools/assets/`、doctor,以及 setup 调用的 `tools/local-extract/` 和 `crop-board-atlas.mjs`)、服务器和 fetch-assets 读取的 4 张研究数据表(`docs/research/` 的 `03-operators`、`05-enemies`、`05-maps`、`07-assets` 四个 JSON)、`package.json` / `package-lock.json`、许可证与说明(`LICENSE`、`NOTICE.md`、`THIRD-PARTY-NOTICES.md`、`README.md`、`CHANGELOG.md`)、`docs/PLAYING.md` 和本文;然后在临时目录里生成 `packs/index.json`(打进去的语言包和内容包的列表,供纯静态托管使用;服务器自己会实时列出,见 [PACKS.md](PACKS.md)),再 `npm ci --omit=dev` 装上运行依赖和 `public/vendor`。完整包再加上 `data/assets.json` 列出的素材、`public/fonts`,以及本地提取的 `public/assets/local/` 和 `data/local-assets.json`。磁盘上有、清单却没列出的文件不打进去(例如 0.2.0 移出自选的焰狐龙梓兰的旧素材)。日文干员语音(`audio.voiceJp`,`public/assets/audio/voice/jp/` 的 2674 个文件,约 85 MB,zip 后约 76 MB)默认也打进完整包;`tools/package.mjs` 里的开关 `FULL_ZIP_JP_VOICE` 改成 `false` 时,完整包(以及由它比较出的更新包)不带这些文件:玩家首次启动时 setup 会像精简包那样下载它们,下载完成前选「日本語」会播中文语音,更新包也不会删除玩家已有的日文语音。 - **不打进去的**:`test/`、维护用的工具(数据构建、golden、botbench、i18n、导入检查、本工具等)、`scripts/make-windows-bundle.mjs`(Windows 便携包,见 [WINDOWS.md](WINDOWS.md))、其他文档、研究笔记和 `docs/img/`、`handoff/`、`.github/`、`types/`、lint / 编辑器 / Docker 配置。和 0.1.x 的整树打包(全部跟踪文件加上 `public/assets` 的全部内容)相比,0.2.0 的完整包少了约 640 个文件、解压后小约 26 MB,zip 小约 8 MB。 - **打包前的检查**(`--dry-run` 也全部做一遍):拒绝名单(`pv`、`review`、`.cache`、`.claude`、`.git`、`logs`、`.env`、`scripts/service.env.cmd`、`handoff`、`test` 等);每个打进去的模块的相对导入、玩家用的 npm 脚本(start / setup / doctor / launch / postinstall / vendor / assets)都指向包里的文件;完整包里 `data/assets.json` 和 `data/local-assets.json` 列出的文件都在(缺了先运行 `node tools/fetch-assets.mjs`);没有只差大小写的两个路径;包里的文件(二进制素材也查)不含个人目录路径(`/Users/…`、`C:\Users\…`、`/home/…`)或本机的账户名(运行时从系统读取;`SP_PACKAGE_SCAN_NAMES=a,b` 可以再加名字);打进去的已跟踪文件没有未提交的改动(重新生成的 `data/assets.json` 要先提交)。有任何问题都会列出原因、以非零状态结束,不写 zip;正式打包时还会核对临时目录里的文件和计划完全一致。 - **MANIFEST.json**:三种包的根目录都有(`npm ci` 之后写入):除素材(`public/assets/`、`public/fonts/`、`data/assets.json`、`data/local-assets.json`,归 setup 管)以外每个文件的大小和 sha256,同一版本的三种包内容相同。`npm run doctor` 和更新包的启动检查(`server/update.js`)用它核对安装。 - **更新包**(0.2.1 起,每个版本都发):`--from` 后面列出**之前每个 0.2.x 版本的完整包**(Releases 上的 zip,或它解压出来、没动过的文件夹;逗号分隔或写多个 `--from`),例如发布 0.2.2 时 `--from Stronghold-Protocol-v0.2.0.zip,Stronghold-Protocol-v0.2.1.zip`。工具照常构建完整包的临时目录(`npm ci` 等),逐个文件(大小 + sha256)和每个旧版本比较:和任何一个旧版本不同、或旧版本没有的文件都打进去,所以一个更新包能覆盖在列出的每个版本上;旧版本有、新版本没有的文件记进 `UPDATE.json` 的 `removed`(连同各旧版本里的 sha256,玩家那边只删内容一致的文件;只差大小写的同名文件和 `.env` 之类的本机文件名不删,摘要里会列出)。旧版本必须比当前版本旧、带素材(不能是精简包或更新包)、每个版本只给一次,`--dry-run` 只读取并检查旧版本。更新包里还有新版本的 `MANIFEST.json` 和 `UPDATE.json`(适用的旧版本、文件数、字节数、文件列表和 `removed`),同样经过拒绝名单和个人信息检查;摘要列出和每个旧版本相比改动 / 新增 / 删除的文件数和更新包里各类文件的数量。发布时在 Releases 说明里写明更新包适用的版本(即 `--from` 列出的版本)。`--keep-stage` 保留更新包目录和完整包的临时目录(`<目录>/Stronghold-Protocol-v<版本>-update/` 里的 `Stronghold-Protocol/` 和 `.full/`)。 - **需要**:已下载素材的仓库(完整包)——打包前先联网运行一次 `node tools/fetch-assets.mjs`,补齐清单计划但本机还没有的素材(清单只列出磁盘上有的文件,打包工具看不出缺了哪些;`data/assets.json` 有变化就先提交);能访问 npm 的网络(`npm ci`);`zip`(或 bsdtar 的 `tar`,Windows 10 起自带);更新包还要之前各版本的完整包(工具自己读 zip,不需要 `unzip`;Releases 上可以重新下载)。`--out` 默认是系统临时目录下的 `stronghold-protocol-release`,不能在仓库里面;`--force` 覆盖已有的 zip,`--keep-stage` 保留打包用的目录供检查。