feedback2: merge WE — setup downloads the battle emotes and 玩法说明 pages from the public mirror, accurate deployment docs (GitHub #42); English title Stronghold Protocol: Alliance; doctor comment after PR #27

This commit is contained in:
sganggs committed 2026-10-03 23:51:56 +08:00
commit 7ab852ef57
27 files changed
+441 -88

No files matched your search

+26 -4
View File
@@ -15,12 +15,14 @@
// act2autochess match (07 enemy list ∪ act1autochess wave/boss levels used by
// act2 modes ∪ bosses ∪ their summons ∪ enemy units spawned by operator kits),
// the 23 bonds, 59 shop items, 40 bands,
// default-skill icons, profession icons, autochess UI sprites, BGM and SFX.
// default-skill icons, profession icons, autochess UI sprites (the 36 battle
// emotes and the 19 玩法说明 pages included: UI_EXTRAS), BGM and SFX.
import { RAW, joinUrl, safeName, urlBase, urlDir } from './sources.mjs';
import { kindOf } from './formats.mjs';
import { pickUnitSfx, UI_SFX, BATTLE_SFX, resolveSpec } from './audio.mjs';
import { literal } from './manifest.mjs';
import { EMOTE_CATALOG } from '../../shared/constants.js';
/**
* Enemies whose Spine no community dump carries: the web model is another enemy's (research 07 §5.6). Their official
@@ -38,8 +40,26 @@ const LOADING_USED = new Set(['loading_ac_core', 'loading_ac_prototype', 'loadin
const PROFESSIONS = ['caster', 'medic', 'pioneer', 'sniper', 'special', 'support', 'tank', 'warrior'];
/** Extra UI sprites (not in 07-assets.json groups): [group, key, path under ArknightsAssets2 cn assets/dyn]. */
const UI_EXTRAS = (() => {
/**
* The 19 official 玩法说明 (tutorial) pages, in the reading order of public/js/ui/guide.js GUIDE_CHAPTERS (a test keeps
* the two identical): 基础规则 home 1–9, 调度手册 shop 1–6, 进阶图鉴 handbook 1–4.
*/
export const GUIDE_PAGES = Object.freeze([
...[1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => `autochess_home_${i}`),
...[1, 2, 3, 4, 5, 6].map((i) => `autochess_shop_${i}`),
...[1, 2, 3, 4].map((i) => `autochess_handbook_${i}`),
]);
/**
* Extra UI sprites (not in 07-assets.json groups): [group, key, path under ArknightsAssets2 cn assets/dyn].
* The last two blocks are art that used to come from the local client only (tools/local-extract, DESIGN §13) and that
* the mirror carries too (GitHub issue #42: a server without the client showed default emote icons): the 36 battle
* emotes (shared/constants.js EMOTE_CATALOG; the bundle ui/emoticon/theme/[uc]<themeId>.ab → its icon/<picId>.png) and
* the 19 玩法说明 pages (arts/guidebookpages/[pack]autochess.ab, 1024² like the local copies: displayed at 16:9). Their
* manifest keys are the data/local-assets.json group and name — ui['emoticon/<dir>/<picId>'], ui['guide/<key>'] — so
* the client looks both up by the same names (public/js/data.js artUrls: the local file first, then this copy).
*/
export const UI_EXTRAS = (() => {
const L = [];
const mc = 'ui/autochess/[uc]autochessouter/modechoice/auto_chess_mode_choice_state/';
for (const m of ['normal', 'hard', 'abyss', 'funny']) L.push(['modeChoice', `${m}_rhodes_island`, `${mc}${m}_rhodes_island.png`]);
@@ -67,7 +87,9 @@ const UI_EXTRAS = (() => {
L.push(['prepReady', 'countdown_arrow', `${pr}countdown_arrow.png`]);
const si = 'ui/autochess/[uc]autochessouter/stageinfo/auto_chess_stage_info_state/';
for (const k of ['img_title_mode_abyss', 'img_title_mode_funny', 'img_title_mode_hard', 'img_title_mode_normal', 'btn_confirm', 'btn_confirmed']) L.push(['stageInfo', k, `${si}${k}.png`]);
return L;
for (const e of EMOTE_CATALOG) L.push([`emoticon/${e.dir}`, e.picId, `ui/emoticon/theme/[uc]${e.themeId}/icon/${e.picId}.png`]);
for (const k of GUIDE_PAGES) L.push(['guide', k, `arts/guidebookpages/[pack]autochess/${k}.png`]);
return Object.freeze(L.map((x) => Object.freeze(x)));
})();
/** Renames of research 07 `arts` groups to manifest UI groups. */
+2 -1
View File
@@ -14,7 +14,8 @@
// themes: [{ themeId, dir, sortId, isBasic, name, emotes: [id…] }], pages, enabled-list order
// emotes: [{ id, themeId, sortId, picId, art, label }] } 36, page order then sortId
// `art` = /assets/local/emoticon/<dir>/<picId>.png (tools/local-extract; key by picId — fooldoctor_03…06 use pics
// 04/05/06/08). Official emotes carry no text (desc is null); `label` is ours and only ever an aria-label.
// 04/05/06/08); the mirror copy that tools/fetch-assets.mjs downloads is data/assets.json ui['emoticon/<dir>/<picId>'].
// Official emotes carry no text (desc is null); `label` is ours and only ever an aria-label.
// shared/constants.js EMOTE_THEMES mirrors this file (test/ui/emotes.test.js keeps them identical).
//
// Usage: node tools/build-emotes.mjs [--offline] [--check] [--out data/emotes.json] [--cache .cache/gamedata]
+4 -3
View File
@@ -18,6 +18,7 @@ import { fileURLToPath } from 'node:url';
import {
ROOT, MIN_NODE, IS_WIN, IS_MAC, c, mark, capture, padDisplay, displayWidth,
checkNode, checkDeps, checkVendor, checkData, checkAssets, checkLocal, findClient, findPython,
LOCAL_ART_FALLBACK, LOCAL_ART_COPY_HINT,
} from './setup.mjs';
// ---------------------------------------------------------------------------------------------------
@@ -25,8 +26,8 @@ import {
// ---------------------------------------------------------------------------------------------------
// 名字里带这些的网卡不对局域网开放:虚拟机 / 容器 / WSL / 代理软件的 TUN 适配器(Mihomo、Clash)。
// 这里不再列 `^tun\d`:下面 VPN_IF 的 `tun\d` 先判定,任何能被 `^tun\d` 命中的名字也一定被它命中,
// 于是 tun0/tap0 一律归为 vpn(那正是同组好友互连用的地址)。写在两处只会让人以为顺序无关。
// tun0 / tap0 不归这一类:classifyAddresses 先判下面的 VPN_IF,它的 `tun\d` / `tap` 是子串匹配,
// tun0 / tap0 先被它命中,归为 vpn(那正是同组好友互连用的地址)。
const VIRTUAL_IF = /(vethernet|virtualbox|vmware|vmnet|docker|^br-|^veth|wsl|hyper-v|vboxnet|bridge\d|utun|awdl|llw|parallels|loopback|mihomo|clash|sing-?box)/i;
// 点对点 VPN:这些地址就是同组好友互相访问用的(Tailscale / ZeroTier / WireGuard / Radmin VPN / Hamachi)。
const VPN_IF = /(tailscale|zerotier|^zt|wireguard|^wg\d|tun\d|tap|radmin|hamachi)/i;
@@ -192,7 +193,7 @@ async function main() {
const client = findClient(null);
row(local.manifest ? 'ok' : 'skip', '本地客户端美术(可选)', local.manifest
? `${local.count} 项${local.board3d ? ',3D 棋盘可用' : ',无棋盘贴图(2D 棋盘)'}${local.board3d && !local.tiles ? ';缺 tiles.json → node tools/setup.mjs' : ''}`
: client ? `检测到 ${client.kind} 客户端 → node tools/setup.mjs --local` : '未提取(不影响游戏)');
: client ? `检测到 ${client.kind} 客户端 → node tools/setup.mjs --local` : `未提取:${LOCAL_ART_FALLBACK}(${LOCAL_ART_COPY_HINT})`);
if (client || local.manifest) {
const py = findPython();
row(py ? 'ok' : 'skip', 'Python(仅提取用)', py ? `${py.cmd} ${py.version}` : '未找到 Python 3.8+');
+2
View File
@@ -8,6 +8,8 @@ aklz4.py registers a decoder for it. This script pulls the art the web sources l
- the 6 in-match emoticon themes of 盟约 (activity_table autoChessData.enabledEmoticonThemeIdList; only the
*_battle sprites, keyed by display_meta_table picId: emoticon/<dir>/<picId>.png, see data/emotes.json)
- the autochess guidebook pages, battle projectile sprites and a few token/skin Spine models missing upstream
(the emotes and the guidebook pages are on the public mirror too: tools/fetch-assets.mjs downloads them, and the
client uses these local copies first — GitHub issue #42)
- the enemy battle Spine models no community dump carries (ENEMY_SPINES: 灼热源石虫 / 炽焰源石虫), from the enemy art
bundles (refs/arts/enm_art_*.ab) → spine/enemy/<enemyId>/<stem>.skel|.atlas + page PNGs with the [alpha] texture
merged in (premultiplied RGB + A, the Ark-Models format; the atlas gets `size:` / `pma: true` like the fetched
+16 -5
View File
@@ -8,7 +8,7 @@
// 2. Dependencies: `npm ci` (falls back to `npm install`) when node_modules is missing or incomplete.
// 3. Client libraries in public/vendor (tools/vendor.mjs) when any is missing.
// 4. Game data (data/*.json, committed) present and parseable.
// 5. Art/audio (tools/fetch-assets.mjs, ~250 MB into public/assets, resumable, mirror fallback) when public/assets
// 5. Art/audio (tools/fetch-assets.mjs, ~270 MB into public/assets, resumable, mirror fallback) when public/assets
// is missing or data/assets.json lists files that are not on disk. A failure is a warning: the game still runs
// with fallback visuals and the next run resumes.
// 6. Optional: official board/UI art from a locally installed Arknights client (Windows native install, CrossOver
@@ -189,6 +189,16 @@ export function checkAssets() {
return { ok: present && missing.length === 0 && urls.length > 0, present, manifest: true, total: urls.length, missing: missing.length, sample: missing.slice(0, 5), bytes: Number(m.stats?.bytes) || 0 };
}
/**
* What the game draws instead when the local-client art is absent (DESIGN §13, docs/DEPLOY.md §6). The battle emotes and
* the 玩法说明 pages are not in the list: step 5 downloads them from the public mirror with the other assets (GitHub
* issue #42). Shown by setup and doctor.
*/
export const LOCAL_ART_FALLBACK = '3D 棋盘改用 2D,部分官方界面图标和灼热/炽焰源石虫模型用替代样式';
/** Where a machine without the client gets the local art (docs/DEPLOY.md §6「本地客户端素材」); shown by doctor (setup's row,
* printed on every start by scripts/launch.mjs, only points to that section). */
export const LOCAL_ART_COPY_HINT = '没有客户端的服务器可以从同一版本的整合包复制 public/assets/local 和 data/local-assets.json';
/**
* Local-client art (optional): manifest entry count, whether the 3D board atlas is on disk and whether the extraction
* has the enemy models only the client has (`spine/enemy/*` groups, extract.py ENEMY_SPINES — added after 0.1.0).
@@ -400,7 +410,7 @@ async function main() {
let assets = checkAssets();
if (!opts.assets) add(assets.ok ? 'ok' : 'skip', '美术/音频 public/assets', assets.ok ? `${assets.total} 个文件` : '已跳过(--no-assets)');
else if (!assets.ok && deps.ok && !opts.check) {
const what = !assets.present ? `首次下载约 ${assets.bytes ? mb(assets.bytes) : '250 MB'},可随时中断,重新运行会续传`
const what = !assets.present ? `首次下载约 ${assets.bytes ? mb(assets.bytes) : '270 MB'},可随时中断,重新运行会续传`
: `补全缺失的 ${assets.missing} 个文件`;
log(`\n${c.cyan('▶')} 下载美术与音频素材(${what})…`);
const r = run(process.execPath, [path.join(ROOT, 'tools', 'fetch-assets.mjs')]);
@@ -422,7 +432,8 @@ async function main() {
const already = local.manifest && local.dirPresent;
if (!client) {
if (already && local.board3d && !local.tiles && !opts.check) cropBoardTiles(log);
add(already ? 'ok' : 'skip', '本地客户端美术(可选)', already ? `已提取 ${local.count} 项` : (opts.game ? `找不到 ${opts.game}` : '未检测到本机明日方舟客户端(不影响游戏)'));
add(already ? 'ok' : 'skip', '本地客户端美术(可选)', already ? `已提取 ${local.count} 项`
: `${opts.game ? `找不到 ${opts.game}` : '未检测到本机明日方舟客户端'}:${LOCAL_ART_FALLBACK}(见 docs/DEPLOY.md 第 6 节)`);
} else if (!client.autochess) {
add(already ? 'ok' : 'warn', '本地客户端美术(可选)', `${client.kind} 客户端缺少卫戍协议资源(请在游戏内下载全部资源):${client.path}`);
} else if (already && opts.local !== 'force') {
@@ -438,7 +449,7 @@ async function main() {
let go = opts.local === 'force' || opts.yes;
if (go || !state.localDeclined) {
log(`\n${c.cyan('▶')} 检测到本机明日方舟客户端(${client.kind}):\n ${c.dim(client.path)}`);
log(' 可以从中提取官方棋盘贴图、UI 图标、表情等(仅本机使用;通常 1–5 分钟,Python 依赖约 40 MB,装在项目内的 .venv-extract)。');
log(' 可以从中提取官方 3D 棋盘贴图、界面图标等(仅本机使用;通常 1–5 分钟,Python 依赖约 40 MB,装在项目内的 .venv-extract)。');
}
let unattended = false;
if (!go && !state.localDeclined) {
@@ -457,7 +468,7 @@ async function main() {
if (r.ok) cropBoardTiles(log);
const after = checkLocal();
if (r.ok && after.manifest) { add('ok', '本地客户端美术(可选)', `提取 ${after.count} 项${after.board3d ? ',3D 棋盘可用' : ''}`); saveState({ localDeclined: false, localExtractedFrom: client.path }); }
else add('warn', '本地客户端美术(可选)', `提取未成功(退出码 ${r.code}),游戏不受影响;可稍后重试 node tools/setup.mjs --local`);
else add('warn', '本地客户端美术(可选)', `提取未成功(退出码 ${r.code}):游戏照常运行,${LOCAL_ART_FALLBACK};可稍后重试 node tools/setup.mjs --local`);
}
}
}