From dd562ae62736f9b781cff191497a75d0fda1314f Mon Sep 17 00:00:00 2001 From: Art_Sakura <1754798088@qq.com> Date: Tue, 29 Sep 2026 17:44:21 +0800 Subject: [PATCH] =?UTF-8?q?fixed=20#=20=E4=BF=AE=E5=A4=8D=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E7=AB=AF=E5=9C=B0=E5=9B=BE=E6=BB=91=E5=8A=A8=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E7=9A=84=E9=97=AE=E9=A2=98=20fixed=20#=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=9B=B4=E6=96=B0=E6=A1=86=E6=9E=B6=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=80=89=E6=8B=A9=E6=98=AF=E5=90=A6=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=A2=A6=E4=B8=89=E6=A8=A1=E5=BC=8F=20fixed=20#=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E9=83=A8=E5=88=86=E6=84=8F=E5=9B=BE=E7=9A=84=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E5=9B=BE=E7=89=87=E4=B8=8E=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/release.yml | 22 +- .github/workflows/release.yml | 20 +- .gitignore | 7 + extension.js | 19 +- mengsan/assets/intent/Intent_buff.png | Bin 0 -> 5505 bytes mengsan/assets/intent/Intent_card_debuff.png | Bin 0 -> 8179 bytes mengsan/assets/intent/Intent_death_blow.png | Bin 0 -> 7712 bytes mengsan/assets/intent/Intent_defend.png | Bin 0 -> 9518 bytes mengsan/assets/intent/Intent_escape.png | Bin 0 -> 7440 bytes mengsan/assets/intent/Intent_heal.png | Bin 0 -> 5046 bytes mengsan/assets/intent/Intent_sleep.png | Bin 0 -> 8969 bytes mengsan/assets/intent/Intent_status_card.png | Bin 0 -> 10048 bytes mengsan/assets/intent/Intent_stun.png | Bin 0 -> 13334 bytes mengsan/assets/intent/Intent_summon.png | Bin 0 -> 10668 bytes mengsan/assets/intent/Intent_unknown.png | Bin 0 -> 6401 bytes mengsan/config.js | 3 +- mengsan/content/acts/act1/map.js | 12 +- mengsan/content/acts/act1/rewards.js | 5 + mengsan/map/renderer.js | 8 + mengsan/mode.js | 157 ++++++- mengsan/runtime/death-blow.js | 38 ++ mengsan/runtime/intent-damage.js | 28 ++ mengsan/runtime/intent-display.js | 109 +++-- mengsan/runtime/stun-intent.js | 45 ++ mengsan/state.js | 3 + mengsan/style.css | 12 +- menu.js | 13 + tools/UPDATE.md | 39 ++ tools/generate_manifest.py | 249 ++++++++--- tools/legacy/mengsan-files.json | 61 +++ tools/manifest_config.json | 14 +- update.js | 427 ++++++++++++++++++- 32 files changed, 1156 insertions(+), 135 deletions(-) create mode 100644 mengsan/assets/intent/Intent_buff.png create mode 100644 mengsan/assets/intent/Intent_card_debuff.png create mode 100644 mengsan/assets/intent/Intent_death_blow.png create mode 100644 mengsan/assets/intent/Intent_defend.png create mode 100644 mengsan/assets/intent/Intent_escape.png create mode 100644 mengsan/assets/intent/Intent_heal.png create mode 100644 mengsan/assets/intent/Intent_sleep.png create mode 100644 mengsan/assets/intent/Intent_status_card.png create mode 100644 mengsan/assets/intent/Intent_stun.png create mode 100644 mengsan/assets/intent/Intent_summon.png create mode 100644 mengsan/assets/intent/Intent_unknown.png create mode 100644 mengsan/runtime/death-blow.js create mode 100644 mengsan/runtime/intent-damage.js create mode 100644 mengsan/runtime/stun-intent.js create mode 100644 tools/UPDATE.md create mode 100644 tools/legacy/mengsan-files.json diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 8e8f3a9..ca03790 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -92,10 +92,13 @@ jobs: ) as archive: for name in manifest["files"]: archive.write(root / name, folder + name) - archive.write( - root / "dist/manifest.json", - folder + "manifest.json" - ) + for descriptor in manifest.get("modules", {}).values(): + component = json.loads( + (root / descriptor["manifest"]).read_text(encoding="utf-8") + ) + for name in component["files"]: + archive.write(root / name, folder + name) + archive.write(root / "dist/manifest.json", folder + "manifest.json") '@ | python - $stage $package if ($LASTEXITCODE -ne 0) { throw '打包失败' } @@ -135,7 +138,14 @@ jobs: -Body $body $assetApi = "$api/$($release.id)/assets" - foreach ($file in @($manifestPath, $package)) { + $moduleManifests = @( + Get-ChildItem (Join-Path $stage 'dist/modules') ` + -Filter '*.json' -ErrorAction SilentlyContinue + ) + $releaseFiles = @($manifestPath) + $releaseFiles += $moduleManifests + $releaseFiles += $package + foreach ($file in $releaseFiles) { curl.exe --fail --silent --show-error ` -H "Authorization: token $env:GITEA_TOKEN" ` -F "attachment=@$file" ` @@ -151,4 +161,4 @@ jobs: -ContentType 'application/json' ` -Body '{"draft":false}' | Out-Null - Write-Host "发布完成:$tag" \ No newline at end of file + Write-Host "发布完成:$tag" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0dcab3a..4896f9c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,10 +90,19 @@ jobs: Copy-Item -LiteralPath $manifestPath ` -Destination (Join-Path $env:GITHUB_WORKSPACE ` 'dist/manifest.json') -Force + $moduleManifestDir = Join-Path $source 'dist/modules' + if (Test-Path $moduleManifestDir) { + New-Item -ItemType Directory -Force ` + (Join-Path $env:GITHUB_WORKSPACE 'dist/modules') | Out-Null + Copy-Item -Path (Join-Path $moduleManifestDir '*.json') ` + -Destination (Join-Path $env:GITHUB_WORKSPACE 'dist/modules') ` + -Force + } git config user.name 'github-actions[bot]' git config user.email ` '41898282+github-actions[bot]@users.noreply.github.com' git add dist/manifest.json + git add dist/modules if ($LASTEXITCODE -ne 0) { throw '暂存清单失败' } git commit -m "Prepare manifest for $tag" if ($LASTEXITCODE -ne 0) { throw '提交清单失败' } @@ -143,10 +152,13 @@ jobs: ) as archive: for name in manifest["files"]: archive.write(root / name, folder + name) - archive.write( - root / "dist/manifest.json", - folder + "manifest.json" - ) + for descriptor in manifest.get("modules", {}).values(): + component = json.loads( + (root / descriptor["manifest"]).read_text(encoding="utf-8") + ) + for name in component["files"]: + archive.write(root / name, folder + name) + archive.write(root / "dist/manifest.json", folder + "manifest.json") '@ | python - $stage $package if ($LASTEXITCODE -ne 0) { throw '打包失败' } diff --git a/.gitignore b/.gitignore index 839bf71..eadf95c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,13 @@ log.txt +findings.md +progress.md +task_plan.md + dist/manifest.json + tools/__pycache__/ tools/test_update.mjs +tools/test_intent.mjs + /mengsan/.planning/ /mengsan/verification \ No newline at end of file diff --git a/extension.js b/extension.js index 5fe1e15..0cca5ce 100644 --- a/extension.js +++ b/extension.js @@ -250,9 +250,22 @@ shuYing.precontent = async () => { shuYing.initCSS(); shuYing.initFunction(); shuYing.initConfig(); - const registerMengsan = await shuYing.loadModule("./mengsan/register.js", "梦三模式", true); - if (typeof registerMengsan == "function") { - registerMengsan(); + const mengsanState = lib.config.shuYing_mengsan_installed_version; + let hasMengsan = !!mengsanState && mengsanState != "disabled"; + if (!mengsanState && typeof game.checkFile != "function") { + hasMengsan = true; + } + else if (!mengsanState && typeof game.checkFile == "function") { + hasMengsan = await new Promise(resolve => { + game.checkFile("extension/术樱包/mengsan/register.js", + result => resolve(result == 1), () => resolve(false)); + }); + } + if (hasMengsan) { + const registerMengsan = await shuYing.loadModule( + "./mengsan/register.js", "梦三模式", true + ); + if (typeof registerMengsan == "function") registerMengsan(); } initShuYingMenu({ lib, game, ui, shuYing, updater }); shuYing.getVersion(); diff --git a/mengsan/assets/intent/Intent_buff.png b/mengsan/assets/intent/Intent_buff.png new file mode 100644 index 0000000000000000000000000000000000000000..921ff3791a4f1348f8da779c5feb2a17586064ae GIT binary patch literal 5505 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 literal 0 HcmV?d00001 diff --git a/mengsan/assets/intent/Intent_card_debuff.png b/mengsan/assets/intent/Intent_card_debuff.png new file mode 100644 index 0000000000000000000000000000000000000000..c0b21f79bad7cd296d9e57cdb57f68271ad3a041 GIT binary patch literal 8179 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 literal 0 HcmV?d00001 diff --git a/mengsan/assets/intent/Intent_death_blow.png b/mengsan/assets/intent/Intent_death_blow.png new file mode 100644 index 0000000000000000000000000000000000000000..b1b67365955b73f452ddcb4865669f73b4cfbe41 GIT binary patch literal 7712 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πx1amxa_1 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 I%G*T!4~w?482|tP literal 0 HcmV?d00001 diff --git a/mengsan/assets/intent/Intent_defend.png b/mengsan/assets/intent/Intent_defend.png new file mode 100644 index 0000000000000000000000000000000000000000..e4ee37ce5e2f53a206902dd8e4431fc17ce57532 GIT binary patch literal 9518 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 literal 0 HcmV?d00001 diff --git a/mengsan/assets/intent/Intent_escape.png b/mengsan/assets/intent/Intent_escape.png new file mode 100644 index 0000000000000000000000000000000000000000..18650755042b398bd3292d12abf7e97647dbfe13 GIT binary patch literal 7440 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 O0000!{K*(b7P9FAmUmLf7TFFPbV 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 EA2UAxp8x;= literal 0 HcmV?d00001 diff --git a/mengsan/assets/intent/Intent_status_card.png b/mengsan/assets/intent/Intent_status_card.png new file mode 100644 index 0000000000000000000000000000000000000000..24fdaebc90b082e5b696978c12e45d2b5805a7d8 GIT binary patch literal 10048 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 H^aK7M`X-GH literal 0 HcmV?d00001 diff --git a/mengsan/assets/intent/Intent_summon.png b/mengsan/assets/intent/Intent_summon.png new file mode 100644 index 0000000000000000000000000000000000000000..d77d780aee2e69e1f36c57a091b43498fe5e0c3b GIT binary patch literal 10668 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 literal 0 HcmV?d00001 diff --git a/mengsan/config.js b/mengsan/config.js index 45ae2e1..8c2bdb8 100644 --- a/mengsan/config.js +++ b/mengsan/config.js @@ -6,7 +6,7 @@ const mengsanConfig = { saveKey: "mengsan_run_shuying", profileKey: "mengsan_profile_shuying", schemaVersion: 1, - mapLayoutVersion: 7, + mapLayoutVersion: 8, routeCount: 4, characters: ["mengsan_liubei_shuying"], acts: contentRegistry.acts, @@ -16,6 +16,7 @@ const mengsanConfig = { elite: "精英", event: "事件", story: "剧情", + chest: "宝箱", rest: "休息", shop: "商店", boss: "Boss", diff --git a/mengsan/content/acts/act1/map.js b/mengsan/content/acts/act1/map.js index 9650b20..3382917 100644 --- a/mengsan/content/acts/act1/map.js +++ b/mengsan/content/acts/act1/map.js @@ -1,9 +1,17 @@ const act1Map = { id: "act1", name: "第一关·初梦", - floorNodes: [1, 4, 4, 4, 1], - minFloorNodes: [1, 3, 3, 3, 1], + floorNodes: [ + 1, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 1, + ], + minFloorNodes: [ + 1, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 1, + ], nodeWeights: { battle: 65, event: 15, rest: 10, shop: 10 }, + requiredFloors: { 2: "battle", 10: "chest", 16: "rest" }, + chestRewardPool: "act1.pool.chest", fixedNodes: [ { floor: 0, diff --git a/mengsan/content/acts/act1/rewards.js b/mengsan/content/acts/act1/rewards.js index 0374d05..d16bdaf 100644 --- a/mengsan/content/acts/act1/rewards.js +++ b/mengsan/content/acts/act1/rewards.js @@ -1,4 +1,9 @@ export const rewardPools = { + "act1.pool.chest": [ + "shared.reward.item.handCharm", + "shared.reward.maxHp", + "shared.reward.support.scout", + ], "act1.pool.story.scoutGift": [ "shared.reward.support.scout", "shared.reward.card.sha", diff --git a/mengsan/map/renderer.js b/mengsan/map/renderer.js index afcfcf7..3c61682 100644 --- a/mengsan/map/renderer.js +++ b/mengsan/map/renderer.js @@ -10,6 +10,8 @@ const iconPaths = { elite: "M16 3l10 5v9c0 6-10 12-10 12S6 23 6 17V8zM10 13l6 4 6-4M16 17v7M11 5l5-3 5 3", event: "M10 10c0-9 15-8 15 0 0 5-9 5-9 10M16 25v1", story: "M8 5h18v20H10c-5 0-5-6 0-6h16M8 5c-5 0-5 6 0 6h3V5M12 10h10M12 14h8", + chest: "M4 13h24v15H4zM2 8h28v6H2zM16 8v20" + + "M9 8c-4-3-1-7 3-5l4 5M23 8c4-3 1-7-3-5l-4 5", rest: "M3 26L16 5l13 21zM10 26l6-11 6 11M16 5V2M7 28h19", shop: "M4 12l3-7h18l3 7M4 12v4h24v-4M7 16v12h18V16M12 28v-8h8v8M3 28h26M11 6v6M21 6v6", boss: "M6 28V5M6 6c8-8 12 6 22-1v16c-10 7-14-7-22 1M11 10l11 7M22 10l-11 7M3 28h9", @@ -60,6 +62,7 @@ const nodeColor = { elite: "#7e3f79", event: "#477aa6", story: "#8c6a34", + chest: "#a27b35", rest: "#3f8560", shop: "#a27b35", boss: "#a62727", @@ -353,6 +356,7 @@ export const showMap = run => new Promise(resolve => { viewport.addEventListener("pointerdown", event => { if (!event.isPrimary || event.button !== 0) return; suppressClick = false; + if (event.pointerType !== "mouse") return; drag = { id: event.pointerId, y: event.clientY, top: viewport.scrollTop, moved: false }; }); viewport.addEventListener("pointermove", event => { @@ -386,6 +390,10 @@ export const showMap = run => new Promise(resolve => { } }, true); const map = ui.create.div(".mengsan-map-shuying", viewport); + const floorCount = Math.max(...run.map.nodes.map(node => + Math.floor(node.floor))) + 1; + map.style.setProperty("--mengsan-map-min-height", + `${floorCount * 100}px`); const landscape = document.createElementNS("http://www.w3.org/2000/svg", "svg"); landscape.classList.add("mengsan-ink-landscape-shuying"); landscape.setAttribute("viewBox", "0 0 800 1200"); diff --git a/mengsan/mode.js b/mengsan/mode.js index e62e2af..7aad497 100644 --- a/mengsan/mode.js +++ b/mengsan/mode.js @@ -4,6 +4,21 @@ import { buildBattlePlan, consumeSupports, createBattleDirector, grantSupport } import { installPersonalPiles } from "./runtime/personal-piles.js"; import { createMonster } from "./runtime/monster.js"; import { mountEnemyIntent } from "./runtime/intent-display.js"; +import { attackHitCount, outgoingAttackDamage } from "./runtime/intent-damage.js"; +import { + beginDeathBlow, + finishDeathBlow, + hasPendingDeathBlow, + isDeathBlowIntent, +} from "./runtime/death-blow.js"; +import { + STUN_INTENT, + applyStun, + clearStun, + finishStunnedTurn, + isStunned, + skipStunnedAction, +} from "./runtime/stun-intent.js"; import { selectFlyconidMove, recordFlyconidAction } from "./runtime/flyconid-intent.js"; import { PLAYER_ENERGY, PLAYER_HAND_LIMIT, canPayCard, payCard, isActiveCardUse } from "./runtime/combat-rules.js"; import { mountBattlePiles } from "./runtime/card-library.js"; @@ -172,6 +187,13 @@ const finishUtilityNode = async (run, node) => { ]); applyReward(run, choice); } + else if (node.type == "chest") { + await chooseReward(run, { + rewardPool: config.acts[run.actIndex].chestRewardPool || + "shared.pool.boss.premium", + rewardTitle: "宝箱奖励(三选一)", + }); + } else if (node.type == "shop") { const price = 20; const choice = await chooseButtons("商店 Demo", [ @@ -186,12 +208,18 @@ const finishUtilityNode = async (run, node) => { } } else { - const choice = await chooseButtons("随机事件", [ - { id: "branch", name: "调查异象", description: "在当前路线后插入一个额外剧情战斗节点" }, + const choices = [ { id: "gold", name: "收下钱袋", description: "获得 15 金币" }, - ]); + ]; + if (run.actIndex !== 0) { + choices.unshift({ + id: "branch", name: "调查异象", + description: "在当前路线后插入一个额外剧情战斗节点", + }); + } + const choice = await chooseButtons("随机事件", choices); if (choice == "branch") insertStoryNode(run, node.id); - else { + else if (choice == "gold") { run.player.gold += 15; run.statistics.goldEarned += 15; } @@ -286,10 +314,17 @@ const executeFlyconidIntent = async player => { game.log(player, "消耗1费用发动", move.name); const target = game.me; if (!target?.isAlive() || !player.isAlive()) return; + if (isDeathBlowIntent(move)) { + await beginDeathBlow(player, target, move); + return; + } if (move.damage != null) { - const hit = target.damage(move.damage, player); - hit.mengsanAttack_shuying = true; - await hit; + for (let index = 0; index < attackHitCount(move); index++) { + if (!target.isAlive() || !player.isAlive()) break; + const hit = target.damage(outgoingAttackDamage(move, player), player); + hit.mengsanAttack_shuying = true; + await hit; + } } if (target.isAlive() && player.isAlive()) applyFlyconidDebuff(target, move); }; @@ -316,7 +351,8 @@ const createScenario = (plan, current) => { installMonsterPile(player, monster, current); if (monster) { current.energyUI.set(player, mountEnergy(player, current.session)); - current.intentUI.set(player, mountEnemyIntent(player, current.session, STYLE_PATH, document)); + current.intentUI.set(player, mountEnemyIntent(player, + current.session, STYLE_PATH, document, () => game.me)); } for (const participant of [...game.players, ...game.dead]) participant.setSeatNum(Number(participant.dataset.position) + 1); await equipBattleUnit(player, spec, current.resources); @@ -398,7 +434,9 @@ const prepareBattle = async (run, node, encounter, session, resources) => { const monsters = plan.units.map((spec, index) => initBattleUnit(participants[index], spec)); plan.units.forEach((spec, index) => installMonsterPile(participants[index], monsters[index], currentBattle)); participants.forEach((player, index) => { - if (monsters[index]) currentBattle.intentUI.set(player, mountEnemyIntent(player, session, STYLE_PATH, document)); + if (monsters[index]) currentBattle.intentUI.set(player, + mountEnemyIntent(player, session, STYLE_PATH, document, + () => game.me)); }); activeBattle.director = createScenario(plan, currentBattle); activeBattle.director.bind("player", me); @@ -418,7 +456,15 @@ const prepareBattle = async (run, node, encounter, session, resources) => { run.player.permanentSkills.forEach(skill => { if (lib.skill[skill] && !me.hasSkill(skill)) me.addSkill(skill); }); - for (const skill of ["mengsan_draw_shuying", "mengsan_card_use_shuying", "mengsan_card_affixes_shuying", "mengsan_scenario_shuying", "mengsan_card_payment_shuying", "mengsan_monster_draw_shuying", "mengsan_flyconid_action_shuying"]) { + for (const skill of [ + "mengsan_draw_shuying", "mengsan_card_use_shuying", + "mengsan_card_affixes_shuying", "mengsan_scenario_shuying", + "mengsan_card_payment_shuying", "mengsan_monster_draw_shuying", + "mengsan_flyconid_action_shuying", + "mengsan_death_blow_finish_shuying", + "mengsan_stun_skip_shuying", "mengsan_stun_recover_shuying", + "mengsan_stun_clear_shuying", + ]) { if (!lib.skill.global.includes(skill)) { game.addGlobalSkill(skill); session.ownResource({}, () => game.removeGlobalSkill(skill)); @@ -552,7 +598,10 @@ const routeRun = async (run, selected = null, supplied = false) => { const node = supplied ? selected : await showMap(run); supplied = false; if (!node) { await openModeSelection(); return; } - if (["event", "rest", "shop"].includes(node.type)) { await finishUtilityNode(run, node); continue; } + if (["event", "rest", "shop", "chest"].includes(node.type)) { + await finishUtilityNode(run, node); + continue; + } if (node.type === "story" && config.nodeContents?.[node.contentId]?.kind === "dialogue") { if (await finishStoryNode(run, node)) continue; return; @@ -650,6 +699,42 @@ const createMode = identityMode => { update(intent); return true; }, + mengsanStunEnemy_shuying(player, options = {}) { + const update = activeBattle?.intentUI.get(player); + if (!activeBattle?.session.active || !update || + !player?.isAlive() || + player.storage?.mengsanCamp_shuying !== "enemy") { + return false; + } + if (isStunned(player)) return false; + const { resume = "advance", recover } = options; + const flyconid = isFlyconid(player); + const intent = options.intent ?? (flyconid ? + player.storage.mengsanFlyconidIntent_shuying : null); + const restore = recover || (flyconid ? + (choice, original) => { + const state = + player.storage.mengsanFlyconidState_shuying || {}; + player.storage.mengsanFlyconidState_shuying = + recordFlyconidAction(state, null, false); + const next = choice === "retry" ? original : null; + player.storage.mengsanFlyconidIntent_shuying = next; + return next; + } : null); + const onRecover = async (choice, original) => { + const next = await restore(choice, original); + update(next === undefined ? + (choice === "retry" ? original : null) : next); + }; + if (typeof restore !== "function") { + throw new TypeError("此怪物需要击晕恢复回调"); + } + if (!applyStun(player, intent, { resume, recover: onRecover })) { + return false; + } + update(STUN_INTENT); + return true; + }, async mengsanPersistRun_shuying(run) { await saveRun(run); }, @@ -720,14 +805,60 @@ const createMode = identityMode => { trigger: { player: "phaseDrawBegin2" }, forced: true, silent: true, popup: false, filter(event, player) { return Boolean(player.storage?.mengsanMonster_shuying); }, - async content(event, trigger, player) { trigger.num = player.storage.mengsanMonster_shuying.draw; }, + async content(event, trigger, player) { + trigger.num = isStunned(player) ? 0 : + player.storage.mengsanMonster_shuying.draw; + }, }, mengsan_flyconid_action_shuying: { trigger: { global: "phaseUseBefore" }, forced: true, silent: true, popup: false, priority: 100, - filter(event) { return Boolean(activeBattle?.session.active && isFlyconid(event.player) && event.player.storage.mengsanFlyconidIntent_shuying); }, + filter(event) { return Boolean(activeBattle?.session.active && + !isStunned(event.player) && isFlyconid(event.player) && + event.player.storage.mengsanFlyconidIntent_shuying); }, async content(event, trigger) { await executeFlyconidIntent(trigger.player); }, }, + mengsan_stun_skip_shuying: { + trigger: { global: "phaseUseBefore" }, + forced: true, silent: true, popup: false, priority: 200, + filter(event) { + return Boolean(activeBattle?.session.active && + isStunned(event.player)); + }, + async content(event, trigger) { + skipStunnedAction(trigger.player, trigger); + }, + }, + mengsan_stun_recover_shuying: { + trigger: { global: "phaseAfter" }, + forced: true, silent: true, popup: false, priority: 200, + filter(event) { + return Boolean(activeBattle?.session.active && + isStunned(event.player)); + }, + async content(event, trigger) { + await finishStunnedTurn(trigger.player); + }, + }, + mengsan_stun_clear_shuying: { + trigger: { global: "dieAfter" }, + forced: true, silent: true, popup: false, + filter(event) { return isStunned(event.player); }, + async content(event, trigger) { + clearStun(trigger.player); + }, + }, + mengsan_death_blow_finish_shuying: { + trigger: { global: "phaseUseAfter" }, + forced: true, silent: true, popup: false, + filter(event) { + return Boolean(activeBattle?.session.active && + hasPendingDeathBlow(event.player)); + }, + async content(event, trigger) { + await finishDeathBlow(trigger.player); + }, + }, mengsan_frail_shuying: { mark: true, marktext: "脆", onremove(player) { delete player.storage.mengsanFrail_shuying; }, diff --git a/mengsan/runtime/death-blow.js b/mengsan/runtime/death-blow.js new file mode 100644 index 0000000..dc96331 --- /dev/null +++ b/mengsan/runtime/death-blow.js @@ -0,0 +1,38 @@ +import { + attackHitCount, + outgoingAttackDamage, +} from "./intent-damage.js"; + +const pendingDeathBlows = new WeakSet(); + +export function isDeathBlowIntent(intent) { + return intent?.intentType === "death_blow"; +} + +export function hasPendingDeathBlow(source) { + return pendingDeathBlows.has(source); +} + +export async function beginDeathBlow(source, target, intent) { + if (!isDeathBlowIntent(intent)) { + throw new TypeError("濒死一击需要 death_blow 意图类型"); + } + if (attackHitCount(intent) !== 1) { + throw new RangeError("濒死一击只能造成一次伤害"); + } + const damage = outgoingAttackDamage(intent, source); + if (!source?.isAlive() || !target?.isAlive()) return false; + + const hit = target.damage(damage, source); + hit.mengsanAttack_shuying = true; + await hit; + if (source.isAlive()) pendingDeathBlows.add(source); + return true; +} + +export async function finishDeathBlow(source) { + if (!hasPendingDeathBlow(source)) return false; + pendingDeathBlows.delete(source); + if (source.isAlive()) await source.die(); + return true; +} diff --git a/mengsan/runtime/intent-damage.js b/mengsan/runtime/intent-damage.js new file mode 100644 index 0000000..039cd6b --- /dev/null +++ b/mengsan/runtime/intent-damage.js @@ -0,0 +1,28 @@ +export function attackHitCount(intent) { + const hits = intent.hits ?? 1; + if (!Number.isInteger(hits) || hits < 1) { + throw new RangeError("攻击段数必须是正整数"); + } + return hits; +} + +export function outgoingAttackDamage(intent, source) { + if (!Number.isInteger(intent.damage) || intent.damage < 0) { + throw new RangeError("预测伤害必须是非负整数"); + } + const storedStrength = source?.storage?.mengsanStrength_shuying; + const strength = Number.isInteger(storedStrength) ? storedStrength : 0; + let damage = Math.max(0, intent.damage + strength); + if (source?.storage?.mengsanWeak_shuying > 0) { + damage = Math.floor(damage * 0.75); + } + return damage; +} + +export function previewAttackDamage(intent, source, target) { + let damage = outgoingAttackDamage(intent, source); + if (target?.storage?.mengsanVulnerable_shuying > 0) { + damage = Math.ceil(damage * 1.5); + } + return damage; +} diff --git a/mengsan/runtime/intent-display.js b/mengsan/runtime/intent-display.js index 403bd8f..9006752 100644 --- a/mengsan/runtime/intent-display.js +++ b/mengsan/runtime/intent-display.js @@ -1,6 +1,12 @@ // 只呈现已经选定的预测技能;不从手牌或原生技能猜测意图。 +import { attackHitCount, previewAttackDamage } from "./intent-damage.js"; +import { isDeathBlowIntent } from "./death-blow.js"; +import { isStunIntent } from "./stun-intent.js"; + export function attackIconForDamage(damage) { - if (!Number.isInteger(damage) || damage < 0) throw new RangeError("预测伤害必须是非负整数"); + if (!Number.isInteger(damage) || damage < 0) { + throw new RangeError("预测伤害必须是非负整数"); + } if (damage < 5) return 1; if (damage < 10) return 2; if (damage < 20) return 3; @@ -8,56 +14,111 @@ export function attackIconForDamage(damage) { return 5; } -export function mountEnemyIntent(player, session, assetBase, document) { +export function mountEnemyIntent(player, session, assetBase, document, + getTarget = () => null) { const badge = document.createElement("div"); badge.className = "mengsan-intent-shuying"; badge.hidden = true; badge.setAttribute("aria-live", "polite"); - const name = document.createElement("span"); - name.className = "mengsan-intent-name-shuying"; + badge.setAttribute("aria-atomic", "true"); const damageLine = document.createElement("span"); damageLine.className = "mengsan-intent-damage-shuying"; const amount = document.createElement("span"); amount.className = "mengsan-intent-amount-shuying"; const icon = document.createElement("img"); icon.className = "mengsan-intent-icon-shuying"; - icon.alt = "预计伤害"; + icon.alt = "Attack"; icon.decoding = "async"; - damageLine.append(amount, icon); + damageLine.append(icon, amount); const debuffLine = document.createElement("span"); debuffLine.className = "mengsan-intent-debuff-shuying"; const debuffIcon = document.createElement("img"); debuffIcon.className = "mengsan-intent-icon-shuying"; - debuffIcon.alt = "预计施加负面状态"; + debuffIcon.alt = "Debuff"; debuffIcon.decoding = "async"; debuffIcon.src = `${assetBase}/assets/intent/Intent_debuff.webp`; const debuffAmount = document.createElement("span"); debuffLine.append(debuffIcon, debuffAmount); - badge.append(name, damageLine, debuffLine); + badge.append(damageLine, debuffLine); player.appendChild(badge); - session.ownResource(badge, () => badge.remove()); - return intent => { + let currentIntent = null; + let timer; + session.ownResource(badge, () => { + clearInterval(timer); + badge.remove(); + }); + const refresh = () => { + const intent = currentIntent; if (!intent) { badge.hidden = true; - badge.removeAttribute("title"); return; } - if (typeof intent.name !== "string" || !intent.name.trim()) throw new TypeError("预测技能必须有名称"); - name.textContent = intent.name; - const hasDamage = intent.damage != null; - damageLine.hidden = !hasDamage; - const hasDebuff = typeof intent.debuff === "string" && Number.isInteger(intent.stacks) && intent.stacks > 0; - debuffLine.hidden = !hasDebuff; - if (hasDebuff) debuffAmount.textContent = `${intent.debuff}×${intent.stacks}`; - if (hasDamage) { - const tier = attackIconForDamage(intent.damage); - amount.textContent = String(intent.damage); - const src = `${assetBase}/assets/intent/Intent_attack_${tier}.webp`; + if (isStunIntent(intent)) { + damageLine.hidden = false; + debuffLine.hidden = true; + amount.textContent = ""; + const src = `${assetBase}/assets/intent/Intent_stun.png`; if (icon.getAttribute("src") !== src) icon.src = src; - badge.title = `${intent.name}:预计造成 ${intent.damage} 点伤害(非最终伤害)${hasDebuff ? `,施加${intent.stacks}层${intent.debuff}` : ""}`; + icon.alt = "Stunned"; + if (badge.getAttribute("aria-label") !== "Stunned") { + badge.setAttribute("aria-label", "Stunned"); + } + badge.hidden = false; + return; + } + const hasDamage = intent.damage != null; + const deathBlow = isDeathBlowIntent(intent); + if (deathBlow && !hasDamage) { + throw new TypeError("濒死一击必须有伤害数值"); + } + damageLine.hidden = !hasDamage; + const hasDebuff = typeof intent.debuff === "string" && + Number.isInteger(intent.stacks) && intent.stacks > 0; + debuffLine.hidden = !hasDebuff; + if (hasDebuff) { + const label = intent.id === "frail" ? "Frail" : + intent.id === "vulnerable" ? "Vulnerable" : "Debuff"; + const debuffText = `${label} ×${intent.stacks}`; + if (debuffAmount.textContent !== debuffText) { + debuffAmount.textContent = debuffText; + } + } + if (hasDamage) { + const hits = attackHitCount(intent); + if (deathBlow && hits !== 1) { + throw new RangeError("濒死一击只能造成一次伤害"); + } + const damage = previewAttackDamage(intent, player, getTarget()); + const tier = deathBlow ? null : + attackIconForDamage(damage * hits); + const damageText = hits > 1 ? `${damage}×${hits}` : + String(damage); + if (amount.textContent !== damageText) { + amount.textContent = damageText; + } + const src = deathBlow ? + `${assetBase}/assets/intent/Intent_death_blow.png` : + `${assetBase}/assets/intent/Intent_attack_${tier}.webp`; + if (icon.getAttribute("src") !== src) icon.src = src; + icon.alt = deathBlow ? "Death Blow" : "Attack"; + const label = `${deathBlow ? "Death Blow" : "Attack"}: ` + + `${damage} damage${hits > 1 ? + ` × ${hits} hits` : ""}${hasDebuff ? + `, ${debuffAmount.textContent}` : ""}`; + if (badge.getAttribute("aria-label") !== label) { + badge.setAttribute("aria-label", label); + } } else { - badge.title = hasDebuff ? `${intent.name}:施加${intent.stacks}层${intent.debuff}` : intent.name; + const label = debuffAmount.textContent; + if (badge.getAttribute("aria-label") !== label) { + badge.setAttribute("aria-label", label); + } } badge.hidden = false; }; + timer = setInterval(refresh, 100); + return intent => { + currentIntent = intent; + refresh(); + }; } diff --git a/mengsan/runtime/stun-intent.js b/mengsan/runtime/stun-intent.js new file mode 100644 index 0000000..9e11fbf --- /dev/null +++ b/mengsan/runtime/stun-intent.js @@ -0,0 +1,45 @@ +const stunned = new WeakMap(); + +export const STUN_INTENT = Object.freeze({ intentType: "stun" }); + +export function isStunIntent(intent) { + return intent?.intentType === "stun"; +} + +export function isStunned(player) { + return stunned.has(player); +} + +export function applyStun(player, intent, options = {}) { + if (stunned.has(player)) return false; + const { resume = "advance", recover } = options; + if (resume !== "advance" && resume !== "retry") { + throw new RangeError("击晕恢复方式无效"); + } + if (typeof recover !== "function") { + throw new TypeError("击晕需要恢复回调"); + } + if (!player?.isAlive()) return false; + stunned.set(player, { intent, resume, recover, skipped: false }); + return true; +} + +export function skipStunnedAction(player, phaseUse) { + const state = stunned.get(player); + if (!state || state.skipped) return false; + phaseUse.cancel(); + state.skipped = true; + return true; +} + +export async function finishStunnedTurn(player) { + const state = stunned.get(player); + if (!state?.skipped) return false; + stunned.delete(player); + if (player.isAlive()) await state.recover(state.resume, state.intent); + return true; +} + +export function clearStun(player) { + return stunned.delete(player); +} diff --git a/mengsan/state.js b/mengsan/state.js index 13199c1..aae143c 100644 --- a/mengsan/state.js +++ b/mengsan/state.js @@ -110,6 +110,9 @@ export const generateActMap = (run, actIndex) => { let type; if (floor == lastFloor) type = "boss"; else if (fixedNode?.type) type = fixedNode.type; + else if (act.requiredFloors?.[floor + 1]) { + type = act.requiredFloors[floor + 1]; + } else if (floor == 0) type = "battle"; else type = weightedType(run, act.nodeWeights); const id = `${act.id}_f${floor}_n${index}_${Math.floor(nextRandom(run) * 1e6)}`; diff --git a/mengsan/style.css b/mengsan/style.css index df2fb27..03ec96d 100644 --- a/mengsan/style.css +++ b/mengsan/style.css @@ -188,7 +188,7 @@ overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; - touch-action: none; + touch-action: pan-y; cursor: grab; user-select: none; scrollbar-width: none; @@ -198,7 +198,8 @@ position: relative; display: block !important; width: min(100%, 860px); - height: max(1080px, 160vh) !important; + height: max(1080px, 160vh, + var(--mengsan-map-min-height, 1080px)) !important; margin: 0 auto; overflow: hidden; isolation: isolate; @@ -520,7 +521,6 @@ .mengsan-map-viewport-shuying::-webkit-scrollbar { display: none; width: 0; height: 0; } .mengsan-map-viewport-shuying.dragging, .mengsan-map-viewport-shuying.dragging * { cursor: grabbing !important; } -.mengsan-map-shuying .mengsan-node-shuying { touch-action: none; } /* 梦三行军图视觉系统:深墨舞台、宣纸路线、朱砂交互、铜金层级 */ .mengsan-map-overlay-shuying { --ms-ink-950: #111713; @@ -1301,7 +1301,7 @@ dialog.mengsan-deck-dialog-shuying::backdrop { background: #080706b8; } text-align: center; pointer-events: none; } -/* The selected skill appears below an enemy's frame; number precedes the weapon icon. */ +/* The selected intent appears below an enemy's frame. */ .player > .mengsan-intent-shuying { position: absolute !important; bottom: -31px !important; @@ -1327,10 +1327,6 @@ dialog.mengsan-deck-dialog-shuying::backdrop { background: #080706b8; } .player > .mengsan-intent-shuying[hidden], .mengsan-intent-damage-shuying[hidden], .mengsan-intent-debuff-shuying[hidden] { display: none !important; } -.mengsan-intent-name-shuying { - overflow: hidden; - text-overflow: ellipsis; -} .mengsan-intent-damage-shuying { display: inline-flex; flex: none; diff --git a/menu.js b/menu.js index 2669a7a..d8fe896 100644 --- a/menu.js +++ b/menu.js @@ -339,6 +339,19 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) { }, }; + menu.mengsanModule = { + name: "梦三模式(按需安装/卸载)", + clear: true, + onclick() { + if (shuYing.m_bIsDownload) { + shuYing.m_bIsDownload = false; + updater.manageMengsanModule(shuYing); + } else { + alert("有其他文件正在下载,请稍后再试吧。"); + } + }, + }; + menu.repairBug = { name: "本地资源修复", clear: true, diff --git a/tools/UPDATE.md b/tools/UPDATE.md new file mode 100644 index 0000000..a363c00 --- /dev/null +++ b/tools/UPDATE.md @@ -0,0 +1,39 @@ +# 更新清单与可选模块 + +## 梦三模式 + +梦三仍与主包保存在同一仓库,但作为独立下载模块发布。基础清单 +`dist/manifest.json` 不包含 `mengsan/` 文件;模块清单 +`dist/modules/mengsan.json` 单独记录梦三文件、大小和 SHA-256。 + +用户通过扩展菜单的“梦三模式(按需安装/卸载)”下载或卸载。主包版本 +更新只检查主包文件,以及本地已安装模块的文件;“查漏补缺”会检查主包 +资源和已安装模块,不会自动下载未安装的梦三模式。 + +## 发布流程 + +1. 在 `tools/manifest_config.json` 的 `core` 中维护主包必须文件。 +2. 在 `modules` 中维护可选模块的目录、入口和显示名。 +3. 发布工作流为每个模块生成独立清单,并将清单提交到发布标签。 +4. 完整 ZIP 安装包仍包含所有模块,供离线或整包安装使用。 + +客户端从标签的 raw 文件路径读取清单。因此 Gitea 发布必须使用已经包含 +`dist/manifest.json` 和 `dist/modules/*.json` 的同一标签;当前 Gitea 工作流 +会生成并上传附件,但不会改写触发它的标签。 + +发布时生成器会把 `tools/legacy/mengsan-files.json`、上一次完整清单和当前模块 +文件合并,随新标签发布迁移路径清单。它用于接管旧安装中的梦三文件并清理 +不再属于新版本的文件。该文件由迁移时仓库内的梦三路径生成,作为冻结的旧版 +所有权记录,兼容旧更新器已经推进主包版本号、但尚未更新梦三模块的用户。 + +## 旧文件与目录 + +升级时比较旧版和新版的文件路径清单,删除旧清单有、新清单没有的模块文件; +新增或损坏文件会按 SHA-256 下载和修复。模块卸载只删除模块清单声明的文件。 + +不根据“目录不在新清单中”递归删除目录,避免误删用户自行放入目录的文件。 +确需删除整个旧目录时,先确认其不包含用户数据,再通过主包清单的 +`removeDirectories` 明确声明。 + +新增模块时,清单生成器可通过配置建立独立清单;客户端的安装菜单、启动 +加载和卸载入口仍需为该模块添加对应管理逻辑。 diff --git a/tools/generate_manifest.py b/tools/generate_manifest.py index 8bcc58c..26a4525 100644 --- a/tools/generate_manifest.py +++ b/tools/generate_manifest.py @@ -68,15 +68,36 @@ def load_config(root: Path, config_path: str) -> dict: "core": [], "remove": [], "removeDirectories": [], + "modules": {}, } with path.open("r", encoding="utf-8") as file: config = json.load(file) + modules = {} + for name, item in config.get("modules", {}).items(): + legacy_files = item.get("legacy_files", []) + legacy_source = item.get("legacy_files_from") + if legacy_source: + legacy_path = Path(legacy_source) + if not legacy_path.is_absolute(): + legacy_path = root / legacy_path + with legacy_path.open("r", encoding="utf-8") as file: + legacy_files = json.load(file) + if not isinstance(legacy_files, list): + raise ValueError(f"legacy file list must be an array: {name}") + modules[name] = { + "root": normalize_path(item["root"]), + "entry": normalize_path(item["entry"]), + "title": item.get("title", name), + "legacy_files": [normalize_path(path) for path in legacy_files], + } + return { "core": [normalize_path(item) for item in config.get("core", [])], "remove": [normalize_path(item) for item in config.get("remove", [])], "removeDirectories": [normalize_path(item) for item in config.get("removeDirectories", [])], + "modules": modules, } @@ -117,67 +138,168 @@ def set_nested_file(tree: dict, relative_path: str, info: dict) -> None: } -def build_manifest(root: Path, version: str, config: dict) -> dict: - flat_files = {} - tree = { - "type": "directory", - "name": root.name, - "children": {}, - } - core_files = config["core"] - core_file_set = set(core_files) - remove_files = config["remove"] - remove_directories = config["removeDirectories"] - validate_config_files(root, core_files, "core") - validate_relative_paths(remove_files, "remove") - validate_relative_paths(remove_directories, "removeDirectories") +def build_manifest(root: Path, version: str, config: dict, + previous_manifest=None) -> tuple[dict, dict, dict]: + module_configs = config.get("modules", {}) + module_roots = {name: item["root"] for name, item in module_configs.items()} + validate_relative_paths(list(module_roots.values()), "module roots") + if len(set(module_roots.values())) != len(module_roots): + raise ValueError("module roots must be unique") + for name, item in module_configs.items(): + for other_name, other_root in module_roots.items(): + if name != other_name and ( + item["root"] == other_root + or item["root"].startswith(other_root + "/") + or other_root.startswith(item["root"] + "/") + ): + raise ValueError("module roots cannot overlap") + def owned_by_module(path: str) -> bool: + return any(path == module_root or path.startswith(module_root + "/") + for module_root in module_roots.values()) + + config["remove"] = [file for file in config["remove"] + if not owned_by_module(file)] + config["removeDirectories"] = [ + directory for directory in config["removeDirectories"] + if not any(directory == module_root + or directory.startswith(module_root + "/") + or module_root.startswith(directory + "/") + for module_root in module_roots.values()) + ] + + all_files = {} for path in scan_files(root): relative_path = str(path.relative_to(root)).replace("\\", "/") - info = { + all_files[relative_path] = { "size": path.stat().st_size, "sha256": sha256_file(path), } - flat_files[relative_path] = info - set_nested_file(tree["children"], relative_path, info) + def make_manifest(file_map: dict, core_files: list[str], removals: list[str], + remove_directories: list[str], module_id: str = "") -> dict: + tree = { + "type": "directory", + "name": root.name, + "children": {}, + } + for relative_path, info in file_map.items(): + set_nested_file(tree["children"], relative_path, info) - remove_file_conflicts = [file for file in remove_files if file in flat_files] - if remove_file_conflicts: - raise ValueError(f"remove still contains current files: {', '.join(remove_file_conflicts)}") + core_file_set = set(core_files) + validate_config_files(root, core_files, "core") + missing_core = [file for file in core_files if file not in file_map] + if missing_core: + raise ValueError( + "core contains files outside this manifest: " + + ", ".join(missing_core) + ) + validate_relative_paths(removals, "remove") + validate_relative_paths(remove_directories, "removeDirectories") + conflicts = [file for file in removals if file in file_map] + if conflicts: + raise ValueError(f"remove still contains current files: {', '.join(conflicts)}") + directory_conflicts = [ + directory for directory in remove_directories + if any(file == directory or file.startswith(directory + "/") + for file in file_map) + ] + if directory_conflicts: + raise ValueError( + "removeDirectories still contain current files: " + + ", ".join(directory_conflicts) + ) - remove_directory_conflicts = [ - directory for directory in remove_directories - if any(file == directory or file.startswith(f"{directory}/") for file in flat_files) - ] - if remove_directory_conflicts: - raise ValueError( - "removeDirectories still contain current files: " - + ", ".join(remove_directory_conflicts) - ) + folder_digest = hashlib.sha256() + for relative_path in sorted(file_map): + info = file_map[relative_path] + folder_digest.update(relative_path.encode("utf-8")) + folder_digest.update(str(info["size"]).encode("ascii")) + folder_digest.update(info["sha256"].encode("ascii")) - folder_digest = hashlib.sha256() - for relative_path in sorted(flat_files): - info = flat_files[relative_path] - folder_digest.update(relative_path.encode("utf-8")) - folder_digest.update(str(info["size"]).encode("ascii")) - folder_digest.update(info["sha256"].encode("ascii")) + result = { + "version": version, + "generated_at": datetime.now(timezone.utc).isoformat(), + "algorithm": "sha256", + "update_channels": True, + "file_count": len(file_map), + "size_bytes": sum(info["size"] for info in file_map.values()), + "folder_sha256": folder_digest.hexdigest(), + "core": core_files, + "assets": [file for file in sorted(file_map) if file not in core_file_set], + "remove": removals, + "removeDirectories": remove_directories, + "files": file_map, + "tree": tree, + } + if module_id: + result["module_id"] = module_id + return result - return { - "version": version, - "generated_at": datetime.now(timezone.utc).isoformat(), - "algorithm": "sha256", - "update_channels": True, - "file_count": len(flat_files), - "folder_sha256": folder_digest.hexdigest(), - "core": core_files, - "assets": [file for file in sorted(flat_files) if file not in core_file_set], - "remove": remove_files, - "removeDirectories": remove_directories, - "files": flat_files, - "tree": tree, + module_files = {name: {} for name in module_configs} + base_files = {} + for path, info in all_files.items(): + owner = next((name for name, module_root in module_roots.items() + if path == module_root or path.startswith(module_root + "/")), None) + if owner: + module_files[owner][path] = info + else: + base_files[path] = info + + base_manifest = make_manifest( + base_files, config["core"], config["remove"], config["removeDirectories"] + ) + base_manifest["modules"] = { + name: { + "title": item["title"], + "entry": item["entry"], + "manifest": f"dist/modules/{name}.json", + "file_count": len(module_files[name]), + "size_bytes": sum(info["size"] for info in module_files[name].values()), + } + for name, item in module_configs.items() } + manifests = {} + legacy_manifests = {} + for name, item in module_configs.items(): + files = module_files[name] + if item["entry"] not in files: + raise FileNotFoundError(f"module entry is missing: {item['entry']}") + manifests[name] = make_manifest( + files, [item["entry"]], [], [], module_id=name + ) + manifests[name]["assets"] = [ + file for file in sorted(files) if file != item["entry"] + ] + if not item["entry"].startswith(item["root"] + "/"): + raise ValueError(f"module entry is outside its root: {item['entry']}") + previous_files = previous_manifest.get("files", {}) \ + if isinstance(previous_manifest, dict) else {} + legacy_files = set(item["legacy_files"]) + legacy_files.update(module_files[name]) + legacy_files.update( + file for file in previous_files + if isinstance(file, str) and file.startswith(item["root"] + "/") + ) + legacy_files = sorted(legacy_files) + if legacy_files: + validate_relative_paths(list(legacy_files), "legacy module files") + if any(not file.startswith(item["root"] + "/") + for file in legacy_files): + raise ValueError(f"legacy files escape module root: {name}") + legacy_manifests[name] = { + "version": version, + "module_id": name, + "snapshot": "legacy", + "files": legacy_files, + } + base_manifest["modules"][name]["legacy_manifest"] = ( + f"dist/modules/{name}.legacy.json" + ) + + return base_manifest, manifests, legacy_manifests + def main() -> None: parser = argparse.ArgumentParser(description="Generate shuying manifest.json") @@ -194,10 +316,37 @@ def main() -> None: raise ValueError("Version cannot be empty") output = (root / args.out).resolve() + previous_manifest = None + if output.is_file(): + with output.open("r", encoding="utf-8") as file: + previous_manifest = json.load(file) config = preserve_removals(load_config(root, args.config), output) - manifest = build_manifest(root, version, config) + manifest, modules, legacy_modules = build_manifest( + root, version, config, previous_manifest + ) output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8") + module_output = output.parent / "modules" + for name, module_manifest in modules.items(): + module_path = module_output / f"{name}.json" + module_path.parent.mkdir(parents=True, exist_ok=True) + module_path.write_text( + json.dumps(module_manifest, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + for name, module_manifest in legacy_modules.items(): + module_path = module_output / f"{name}.legacy.json" + module_path.parent.mkdir(parents=True, exist_ok=True) + module_path.write_text( + json.dumps(module_manifest, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + if module_output.is_dir(): + expected = {f"{name}.json" for name in modules} + expected.update(f"{name}.legacy.json" for name in legacy_modules) + for stale in module_output.glob("*.json"): + if stale.name not in expected: + stale.unlink() print(f"version: {version}") print(f"file_count: {manifest['file_count']}") @@ -207,6 +356,8 @@ def main() -> None: print(f"remove_directory_count: {len(manifest['removeDirectories'])}") print(f"folder_sha256: {manifest['folder_sha256']}") print(f"generated: {output}") + for name, module_manifest in modules.items(): + print(f"module_{name}_file_count: {module_manifest['file_count']}") if __name__ == "__main__": diff --git a/tools/legacy/mengsan-files.json b/tools/legacy/mengsan-files.json new file mode 100644 index 0000000..1e06fa1 --- /dev/null +++ b/tools/legacy/mengsan-files.json @@ -0,0 +1,61 @@ +[ + "mengsan/assets/flyconid-portrait.png", + "mengsan/assets/intent/Intent_attack_1.webp", + "mengsan/assets/intent/Intent_attack_2.webp", + "mengsan/assets/intent/Intent_attack_3.webp", + "mengsan/assets/intent/Intent_attack_4.webp", + "mengsan/assets/intent/Intent_attack_5.webp", + "mengsan/assets/intent/Intent_debuff.webp", + "mengsan/assets/sushuang-zhangshiping.png", + "mengsan/config.js", + "mengsan/content/acts/act1/index.js", + "mengsan/content/acts/act1/map.js", + "mengsan/content/acts/act1/rewards.js", + "mengsan/content/acts/act1/stories/forest-ambush.js", + "mengsan/content/acts/act1/stories/liubei-opening.js", + "mengsan/content/acts/act2/index.js", + "mengsan/content/acts/act2/map.js", + "mengsan/content/acts/act3/index.js", + "mengsan/content/acts/act3/map.js", + "mengsan/content/affixes.js", + "mengsan/content/card-definitions.js", + "mengsan/content/card-library.js", + "mengsan/content/mode-cards.js", + "mengsan/content/registry.js", + "mengsan/content/scenario-characters.js", + "mengsan/content/shared/index.js", + "mengsan/content/shared/rewards.js", + "mengsan/docs/battle-config.md", + "mengsan/docs/combat-rules.md", + "mengsan/docs/liubei-opening.md", + "mengsan/map/index.js", + "mengsan/map/renderer.js", + "mengsan/mode.js", + "mengsan/register.js", + "mengsan/runtime/battle-director.js", + "mengsan/runtime/battle-flow.js", + "mengsan/runtime/battle-loop.js", + "mengsan/runtime/battle-resources.js", + "mengsan/runtime/battle-session.js", + "mengsan/runtime/battle-settlement.js", + "mengsan/runtime/card-data.js", + "mengsan/runtime/card-library.js", + "mengsan/runtime/combat-rules.js", + "mengsan/runtime/dialogue-choice.js", + "mengsan/runtime/dialogue.js", + "mengsan/runtime/engine-session.js", + "mengsan/runtime/flow-ui.js", + "mengsan/runtime/flyconid-intent.js", + "mengsan/runtime/gm-manager.js", + "mengsan/runtime/intent-display.js", + "mengsan/runtime/mode-storage.js", + "mengsan/runtime/monster.js", + "mengsan/runtime/navigation.js", + "mengsan/runtime/personal-piles.js", + "mengsan/runtime/reward-ui.js", + "mengsan/runtime/reward.js", + "mengsan/runtime/skill-teardown.js", + "mengsan/runtime/story.js", + "mengsan/state.js", + "mengsan/style.css" +] \ No newline at end of file diff --git a/tools/manifest_config.json b/tools/manifest_config.json index fa39bb8..98a3b22 100644 --- a/tools/manifest_config.json +++ b/tools/manifest_config.json @@ -4,10 +4,6 @@ "menu.js", "function.js", "update.js", - "mengsan/mode.js", - "mengsan/config.js", - "mengsan/state.js", - "mengsan/style.css", "tianshu/extension.js", "tianshu/config.js", "pve/boss/index.js", @@ -79,5 +75,13 @@ "pve/boss/sets/tianshu/voices.js" ], "remove": [], - "removeDirectories": [] + "removeDirectories": [], + "modules": { + "mengsan": { + "root": "mengsan", + "entry": "mengsan/register.js", + "title": "梦三模式", + "legacy_files_from": "tools/legacy/mengsan-files.json" + } + } } diff --git a/update.js b/update.js index 818d703..f871df9 100644 --- a/update.js +++ b/update.js @@ -165,6 +165,12 @@ function escapeHTML(text) { }[char])); } +function formatBytes(bytes) { + const value = Math.max(0, Number(bytes) || 0); + if (value < 1024 * 1024) return `${Math.ceil(value / 1024)} KB`; + return `${(value / 1024 / 1024).toFixed(1)} MB`; +} + function setProgress(shuYing, options = {}) { if (!shuYing?.text) return; @@ -238,14 +244,26 @@ async function getManifestFromTag(tag, requireChannels = true, if (requireChannels && manifest?.update_channels !== true) { throw new Error("目标标签尚未包含新版更新器,请发布支持通道切换的版本"); } + validateManifest(manifest, tag.version); + + manifest.tag_name = tag.name; + manifest.source = source; + return manifest; +} + +function validateManifest(manifest, version, moduleId = "") { if (!manifest || manifest.algorithm != "sha256" || !manifest.files || Array.isArray(manifest.files) || typeof manifest.files != "object" - || manifest.version != tag.version) { + || manifest.version != version + || (moduleId && manifest.module_id != moduleId)) { throw new Error("校验清单格式错误"); } const files = getManifestFileKeys(manifest); + if (moduleId && files.some(file => !file.startsWith(`${moduleId}/`))) { + throw new Error(`校验清单包含越界的 ${moduleId} 模块文件`); + } if (!files.length || files.some(file => { const info = manifest.files[file]; return !info || !Number.isSafeInteger(info.size) || info.size < 0 @@ -257,7 +275,27 @@ async function getManifestFromTag(tag, requireChannels = true, getManifestAssetFiles(manifest, coreFiles); getManifestRemoveFiles(manifest); getManifestRemoveDirectories(manifest); + return manifest; +} +async function getModuleManifest(tag, rootManifest, moduleId, + source = rootManifest?.source || getUpdateSource()) { + const descriptor = rootManifest?.modules?.[moduleId]; + if (!descriptor || !isSafeManifestPath(descriptor.manifest)) { + throw new Error(`更新清单未提供 ${moduleId} 模块`); + } + if (typeof descriptor.entry != "string" + || !isSafeManifestPath(descriptor.entry) + || !descriptor.entry.startsWith(`${moduleId}/`)) { + throw new Error(`${moduleId} 模块入口配置无效`); + } + const response = await fetch(getRemoteUrl(tag.name, descriptor.manifest, source)); + if (!response.ok) throw new Error(`${moduleId} 模块清单请求失败:${response.status}`); + const manifest = await response.json(); + validateManifest(manifest, tag.version, moduleId); + if (!manifest.files[descriptor.entry]) { + throw new Error(`${moduleId} 模块清单缺少入口文件`); + } manifest.tag_name = tag.name; manifest.source = source; return manifest; @@ -609,6 +647,89 @@ async function getChangedFiles(manifest) { return changed; } +function makeLegacyModuleManifest(manifest, moduleId) { + const prefix = `${moduleId}/`; + const files = Object.fromEntries(Object.entries(manifest.files || {}) + .filter(([file]) => file.startsWith(prefix))); + if (!Object.keys(files).length) return null; + const entry = `${moduleId}/register.js`; + return { + ...manifest, + module_id: moduleId, + files, + core: files[entry] ? [entry] : [], + assets: Object.keys(files).filter(file => file != entry), + remove: [], + removeDirectories: [], + }; +} + +async function getInstalledModuleManifest(moduleId, version, source) { + const tag = { name: `v${version}`, version }; + const oldRoot = await getManifestFromTag(tag, false, source); + if (oldRoot.modules?.[moduleId]) { + return await getModuleManifest(tag, oldRoot, moduleId, source); + } + return makeLegacyModuleManifest(oldRoot, moduleId); +} + +async function getLegacyModuleManifest(moduleId, rootManifest) { + const path = rootManifest?.modules?.[moduleId]?.legacy_manifest; + if (!path) return null; + if (!isSafeManifestPath(path)) { + throw new Error(`${moduleId} 迁移清单路径无效`); + } + const response = await fetch(getRemoteUrl( + rootManifest.tag_name, path, rootManifest.source + )); + if (!response.ok) throw new Error(`${moduleId} 迁移清单请求失败:${response.status}`); + const snapshot = await response.json(); + if (snapshot.version != rootManifest.version + || snapshot.module_id != moduleId + || snapshot.snapshot != "legacy" + || !Array.isArray(snapshot.files)) { + throw new Error(`${moduleId} 迁移清单格式错误`); + } + const files = uniqueManifestList(snapshot.files); + if (files.some(file => !file.startsWith(`${moduleId}/`))) { + throw new Error(`${moduleId} 迁移清单包含越界文件`); + } + return { files: Object.fromEntries(files.map(file => [file, {}])) }; +} + +async function isMengsanInstalled(localVersion, previousManifest, + currentManifest) { + const saved = lib.config.shuYing_mengsan_installed_version; + if (saved == "disabled") return false; + if (saved) return true; + const entryPath = "extension/术樱包/mengsan/register.js"; + if (typeof game.checkFile == "function" + && await safeCheckFile(entryPath) == 1) { + return true; + } + if (typeof game.checkFile != "function" && typeof game.readFile == "function") { + try { + await readFile(entryPath); + return true; + } + catch (error) { } + } + const oldManifest = previousManifest || (parseVersion(localVersion) + ? await getManifestFromTag({ + name: `v${localVersion}`, version: localVersion, + }, false, currentManifest.source).catch(() => null) + : null); + return !!oldManifest?.files?.["mengsan/register.js"]; +} + +function mergeRemovedFiles(manifest, oldFiles, retainedFiles = []) { + const current = new Set(getManifestFileKeys(manifest)); + const retained = new Set(retainedFiles); + manifest.remove = [...new Set([...(manifest.remove || []), + ...oldFiles.filter(file => !current.has(file) && !retained.has(file))])]; + getManifestRemoveFiles(manifest); +} + async function getObsoleteFiles(manifest) { const existing = []; for (const file of getManifestRemoveFiles(manifest)) { @@ -635,13 +756,15 @@ async function updateAllFiles(shuYing, manifest, files) { const oldRoot = `${coreTempDir}/old`; const backedUp = new Set(); const installed = []; + const obsoleteBackups = []; + const obsoleteFiles = getManifestRemoveFiles(manifest); let filesInstalled = false; try { ensureProgressNode(shuYing); await createDir(`${newRoot}/`); await createDir(`${oldRoot}/`); await createFolders(`${newRoot}/`, files); - await createFolders(`${oldRoot}/`, files); + await createFolders(`${oldRoot}/`, [...files, ...obsoleteFiles]); for (let i = 0; i < files.length; i++) { const file = files[i]; @@ -672,11 +795,25 @@ async function updateAllFiles(shuYing, manifest, files) { installed.push(file); await writeTargetFile(await readFile(`${newRoot}/${file}`), target); } - filesInstalled = true; + for (const file of obsoleteFiles) { + const target = `extension/术樱包/${file}`; + try { + const original = await readFile(target); + await writeTargetFile(original, `${oldRoot}/${file}`); + backedUp.add(file); + obsoleteBackups.push(file); + } + catch (error) { + if (await safeCheckFile(target) == 1) throw error; + } + } await removeManifestOldPaths(shuYing, manifest); + filesInstalled = true; } catch (error) { - for (const file of filesInstalled ? [] : installed.reverse()) { + const rollbackFiles = filesInstalled ? [] + : [...installed, ...obsoleteBackups].reverse(); + for (const file of rollbackFiles) { const target = `extension/术樱包/${file}`; try { if (backedUp.has(file)) { @@ -693,7 +830,7 @@ async function updateAllFiles(shuYing, manifest, files) { throw error; } finally { - for (const file of files) { + for (const file of new Set([...files, ...backedUp])) { await removeFile(`${newRoot}/${file}`); if (backedUp.has(file)) await removeFile(`${oldRoot}/${file}`); } @@ -758,41 +895,124 @@ async function checkVersion(shuYing, options = {}) { && compareVersions(onlineVersion.online_version, localVersion) < 0) { throw new Error("目标更新源的版本落后于本地版本,请稍后重试"); } - if (options.switchChannel && parseVersion(localVersion) - && localVersion != manifest.version) { - const previous = await getManifestFromTag({ - name: `v${localVersion}`, - version: localVersion, - }, false, manifest.source); - const targetFiles = new Set(getManifestFileKeys(manifest)); - const removed = getManifestFileKeys(previous) - .filter(file => !targetFiles.has(file)); - manifest.remove = [...new Set([...(manifest.remove || []), ...removed])]; - getManifestRemoveFiles(manifest); + let previousManifest = null; + if (parseVersion(localVersion) && localVersion != manifest.version) { + previousManifest = await getManifestFromTag({ + name: `v${localVersion}`, version: localVersion, + }, false, manifest.source).catch(error => { + console.warn("读取本地旧版清单失败,跳过旧文件差集清理:", error); + return null; + }); } + + const installedMengsan = await isMengsanInstalled( + localVersion, previousManifest, manifest + ); + if (installedMengsan + && !lib.config.shuYing_mengsan_installed_version + && parseVersion(localVersion)) { + game.saveConfig("shuYing_mengsan_installed_version", + localVersion == manifest.version ? "legacy" : localVersion); + } + if (previousManifest) { + const retainedModuleFiles = manifest.modules?.mengsan + ? getManifestFileKeys(previousManifest) + .filter(file => file.startsWith("mengsan/")) + : []; + mergeRemovedFiles( + manifest, + getManifestFileKeys(previousManifest), + retainedModuleFiles + ); + } + const changed = await getChangedFiles(manifest); const obsolete = await getObsoleteFiles(manifest); - await addLog(shuYing, `更新源:${manifest.source};标签:${manifest.tag_name};待更新文件:${changed.length};待删除文件:${obsolete.length}`); + let mengsanManifest = null; + let mengsanChanged = []; + let mengsanObsolete = []; + let mengsanUpdateError = null; + let previousMengsan = null; + if (installedMengsan && manifest.modules?.mengsan) { + try { + mengsanManifest = await getModuleManifest({ + name: manifest.tag_name, version: manifest.version, + }, manifest, "mengsan"); + const installedModuleVersion = + lib.config.shuYing_mengsan_installed_version; + if (installedModuleVersion + && installedModuleVersion != "disabled" + && parseVersion(installedModuleVersion) + && installedModuleVersion != manifest.version) { + previousMengsan = await getInstalledModuleManifest( + "mengsan", installedModuleVersion, manifest.source + ); + } + else if (previousManifest && previousManifest.modules?.mengsan) { + previousMengsan = await getModuleManifest({ + name: `v${localVersion}`, version: localVersion, + }, previousManifest, "mengsan", manifest.source); + } + else if (previousManifest) { + previousMengsan = makeLegacyModuleManifest( + previousManifest, "mengsan" + ); + } + if (!previousMengsan + && (!installedModuleVersion || installedModuleVersion == "legacy")) { + previousMengsan = await getLegacyModuleManifest( + "mengsan", manifest + ); + } + if (previousMengsan) { + mergeRemovedFiles( + mengsanManifest, + getManifestFileKeys(previousMengsan) + ); + } + mengsanChanged = await getChangedFiles(mengsanManifest); + mengsanObsolete = await getObsoleteFiles(mengsanManifest); + } + catch (error) { + mengsanUpdateError = error; + await addLog(shuYing, `梦三模块更新暂不可用:${getErrorMessage(error)}`); + } + } + const allChangedCount = changed.length + mengsanChanged.length; + const allObsoleteCount = obsolete.length + mengsanObsolete.length; + await addLog(shuYing, `更新源:${manifest.source};标签:${manifest.tag_name};待更新文件:${allChangedCount};待删除文件:${allObsoleteCount}`); if (localVersion == onlineVersion.online_version - && !changed.length && !obsolete.length) { + && !allChangedCount && !allObsoleteCount && !mengsanUpdateError) { game.saveConfig("shuYing_online_version", onlineVersion.online_version); alert("本地版本为最新版"); return onlineVersion.online_version; } const message = localVersion == onlineVersion.online_version - ? `当前版本有 ${changed.length} 个文件需修复、${obsolete.length} 个旧文件需删除,是否继续?` + ? `当前版本有 ${allChangedCount} 个文件需修复、${allObsoleteCount} 个旧文件需删除,是否继续?` : `检测到最新版本为:${onlineVersion.online_version}, 本地版本为:${localVersion},是否更新?`; if (!options.switchChannel && !confirm(message)) { return null; } await updateAllFiles(shuYing, manifest, changed); + if (mengsanManifest && !mengsanUpdateError) { + try { + await updateAllFiles(shuYing, mengsanManifest, mengsanChanged); + game.saveConfig("shuYing_mengsan_installed_version", manifest.version); + } + catch (error) { + mengsanUpdateError = error; + await addLog(shuYing, `梦三模块更新失败:${getErrorMessage(error)}`); + } + } game.saveConfig("shuYing_local_version", onlineVersion.online_version); game.saveConfig("shuYing_online_version", onlineVersion.online_version); await addLog(shuYing, "版本更新完成"); - alert("下载完成,重启生效"); + alert(mengsanUpdateError + ? "主包更新完成;梦三模块未完成更新,可稍后重试。" + : "下载完成,重启生效"); return onlineVersion.online_version; } catch (error) { @@ -821,6 +1041,69 @@ async function repairMissingFiles(shuYing) { setProgress(shuYing, { title: "查漏补缺", status: "获取校验清单", current: 0, total: 1 }); const manifest = await getManifest(); const files = getManifestDownloadFiles(manifest); + const localVersion = lib.config.shuYing_local_version || "0.0.0.0"; + let previousManifest = null; + if (parseVersion(localVersion) && localVersion != manifest.version) { + previousManifest = await getManifestFromTag({ + name: `v${localVersion}`, version: localVersion, + }, false, manifest.source).catch(() => null); + if (previousManifest) { + const retainedModuleFiles = manifest.modules?.mengsan + ? getManifestFileKeys(previousManifest) + .filter(file => file.startsWith("mengsan/")) + : []; + mergeRemovedFiles( + manifest, + getManifestFileKeys(previousManifest), + retainedModuleFiles + ); + } + } + const installedMengsan = await isMengsanInstalled( + localVersion, previousManifest, manifest + ); + let mengsanManifest = null; + let previousMengsan = null; + if (installedMengsan && manifest.modules?.mengsan) { + mengsanManifest = await getModuleManifest({ + name: manifest.tag_name, version: manifest.version, + }, manifest, "mengsan"); + const installedModuleVersion = + lib.config.shuYing_mengsan_installed_version; + if (installedModuleVersion == "legacy") { + previousMengsan = await getLegacyModuleManifest( + "mengsan", manifest + ); + } + else if (installedModuleVersion + && parseVersion(installedModuleVersion) + && installedModuleVersion != manifest.version) { + previousMengsan = await getInstalledModuleManifest( + "mengsan", installedModuleVersion, manifest.source + ); + } + else if (previousManifest?.modules?.mengsan) { + previousMengsan = await getModuleManifest({ + name: `v${localVersion}`, version: localVersion, + }, previousManifest, "mengsan", manifest.source); + } + else if (previousManifest) { + previousMengsan = makeLegacyModuleManifest( + previousManifest, "mengsan" + ); + } + else if (!installedModuleVersion) { + previousMengsan = await getLegacyModuleManifest( + "mengsan", manifest + ); + } + if (previousMengsan) { + mergeRemovedFiles( + mengsanManifest, + getManifestFileKeys(previousMengsan) + ); + } + } await addLog(shuYing, `待检查文件数:${files.length}`); await addLog(shuYing, `校验清单版本:${manifest.version || "未知"};清单文件数:${Object.keys(manifest.files || {}).length}`); @@ -830,6 +1113,11 @@ async function repairMissingFiles(shuYing) { await downloadList(shuYing, files, manifest); await removeManifestOldPaths(shuYing, manifest); + if (mengsanManifest) { + const moduleChanged = await getChangedFiles(mengsanManifest); + await updateAllFiles(shuYing, mengsanManifest, moduleChanged); + game.saveConfig("shuYing_mengsan_installed_version", manifest.version); + } setProgress(shuYing, { title: "查漏补缺", status: "完成", current: files.length, total: files.length }); await addLog(shuYing, "查漏补缺完成"); @@ -846,6 +1134,104 @@ async function repairMissingFiles(shuYing) { } } +async function manageMengsanModule(shuYing) { + setBusy(shuYing, true); + try { + shuYing._updateLogs = []; + const localVersion = lib.config.shuYing_local_version || "0.0.0.0"; + const rootManifest = await getManifest(); + const installed = await isMengsanInstalled( + localVersion, null, rootManifest + ); + if (installed) { + if (!confirm("确认卸载梦三模式?只删除梦三模块清单拥有的文件。")) return; + let moduleManifest; + const moduleVersion = lib.config.shuYing_mengsan_installed_version; + if (moduleVersion == "legacy" || !moduleVersion) { + const legacyManifest = await getLegacyModuleManifest( + "mengsan", rootManifest + ).catch(() => null); + const currentManifest = await getModuleManifest({ + name: rootManifest.tag_name, version: rootManifest.version, + }, rootManifest, "mengsan"); + let localManifest = null; + if (parseVersion(localVersion) + && localVersion != rootManifest.version) { + localManifest = await getInstalledModuleManifest( + "mengsan", localVersion, rootManifest.source + ).catch(() => null); + } + moduleManifest = { + files: Object.fromEntries([...new Set([ + ...getManifestFileKeys(legacyManifest || { files: {} }), + ...getManifestFileKeys(localManifest || { files: {} }), + ...getManifestFileKeys(currentManifest), + ])].map(file => [file, {}])), + }; + } + else if (parseVersion(moduleVersion || "")) { + moduleManifest = await getInstalledModuleManifest( + "mengsan", moduleVersion, rootManifest.source + ).catch(async error => { + await addLog(shuYing, `读取已安装模块清单失败:${getErrorMessage(error)}`); + return null; + }); + } + if (!moduleManifest && parseVersion(localVersion)) { + moduleManifest = await getInstalledModuleManifest( + "mengsan", localVersion, rootManifest.source + ).catch(() => null); + } + if (!moduleManifest) { + moduleManifest = await getModuleManifest({ + name: rootManifest.tag_name, version: rootManifest.version, + }, rootManifest, "mengsan"); + } + const files = getManifestFileKeys(moduleManifest); + if (typeof game.removeFile != "function") { + throw new Error("当前环境不支持安全卸载文件"); + } + for (const file of files) { + await addLog(shuYing, `卸载梦三文件:${file}`); + await removeFile(`extension/术樱包/${file}`); + if (typeof game.checkFile == "function" + && await safeCheckFile(`extension/术樱包/${file}`) == 1) { + throw new Error(`梦三文件删除失败:${file}`); + } + } + game.saveConfig("shuYing_mengsan_installed_version", "disabled"); + alert("梦三模式文件已卸载,请重启游戏生效。"); + return; + } + + const moduleInfo = rootManifest.modules?.mengsan; + const sizeText = moduleInfo?.size_bytes + ? `,约 ${formatBytes(moduleInfo.size_bytes)}` : ""; + if (!confirm(`梦三模式尚未安装,是否下载并校验全部梦三资源${sizeText}?`)) return; + const moduleManifest = await getModuleManifest({ + name: rootManifest.tag_name, version: rootManifest.version, + }, rootManifest, "mengsan"); + const files = getManifestFileKeys(moduleManifest); + ensureProgressNode(shuYing); + setProgress(shuYing, { + title: "安装梦三模式", status: "准备下载", current: 0, + total: files.length * 2, + }); + await updateAllFiles(shuYing, moduleManifest, files); + game.saveConfig("shuYing_mengsan_installed_version", rootManifest.version); + alert("梦三模式下载完成,请重启游戏生效。"); + } + catch (error) { + console.error(error); + await addLog(shuYing, `梦三模块操作失败:${getErrorMessage(error)}`); + alert("梦三模式操作失败,请检查网络或 extension/术樱包/log.txt。"); + } + finally { + removeProgressNode(shuYing); + setBusy(shuYing, false); + } +} + async function repairCoreFiles(shuYing) { let shouldDownload = false; let manifest; @@ -904,4 +1290,5 @@ export default { checkVersion, repairMissingFiles, repairCoreFiles, + manageMengsanModule, };