From 189766491f6a4874caa9c273d128be02045cc794 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Tue, 23 Jun 2026 23:38:57 +0900 Subject: [PATCH] Add fifth Meng Huo capture battle --- docs/roadmap.md | 5 +- scripts/verify-flow.mjs | 254 +++++++++++++++--- .../images/battle/fifty-second-battle-map.svg | 77 ++++++ src/game/data/battleMapAssets.ts | 4 +- src/game/data/battles.ts | 77 +++++- src/game/data/campaignFlow.ts | 26 +- src/game/data/scenario.ts | 249 +++++++++++++++++ src/game/scenes/BattleScene.ts | 54 +++- src/game/scenes/CampScene.ts | 172 +++++++++++- src/game/scenes/TitleScene.ts | 7 + src/game/state/campaignState.ts | 5 +- 11 files changed, 867 insertions(+), 63 deletions(-) create mode 100644 src/assets/images/battle/fifty-second-battle-map.svg diff --git a/docs/roadmap.md b/docs/roadmap.md index 3e2f8a1..0fedf4f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -76,15 +76,16 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of - Forty-ninth battle Meng Huo second-capture route, turning the first release into a repeated Seven Captures arc with a 98x80 marsh-jungle battlefield, renewed pacify-leader objective, second-release camp dialogues, and village/captive reassurance visits - Fiftieth battle Meng Huo third-capture route, making the growing recruited roster matter through a Zhuge Liang-led seven-officer sortie choice, a 100x82 clan-and-marsh battlefield, named southern clan officers, and post-battle clan-persuasion camp events - Fifty-first battle Meng Huo fourth-capture route, where prior clan-persuasion camp choices become battlefield goals through an amnesty-route objective, fire-fort suppression, a 102x84 poison-river battlefield, and Ma Chao/Ma Dai pursuit-control sortie recommendations +- Fifty-second battle Meng Huo fifth-capture route, where hardline clans split from wavering clans across a 104x86 bamboo-ravine battlefield, with hardliner-separation, village-protection, and fort-suppression objectives plus post-battle ledger/banner camp events - Camp progress timeline tab that summarizes Liu Bei's long campaign arc, completed battles, current chapter, latest battle, and next major chapter - Tactical sortie preparation panel with battle-specific sortie limits, recommended officers with reasons, class role, named equipment, core stats, bond partner, next-map terrain suitability, focused officer analysis, deployment preview, formation roles, active bond count, recruited-officer count, reserve roster summary, and readiness advice for each deployable officer - Officer collection support in camp, including full roster status, selected/reserve/recommended markers, selectable reserve drill focus, and post-battle reserve training growth for benched officers - Treasure equipment effects wired into battle previews and resolution, including named weapon damage bonuses, defensive treasure mitigation, support recovery bonuses, turn-start charm recovery, and equipment-specific growth bonuses -- Flow verification script from title through the fourth Meng Huo capture camp, King of Hanzhong council milestone, Shu-Han foundation milestone, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling/Changsha/Yi Province/Fu Pass/Luo Castle/Luofeng/Luo main gate/Mianzhu/Chengdu/Jiameng/Yangping/Dingjun/Hanzhong/Jing defense/Fan Castle/Han River/Fan siege/Jing rear/Gongan collapse/Maicheng isolation/Yiling vanguard/Yiling fire/Baidi entrustment/Nanzhong stabilization/Meng Huo pacification camp states, campaign timeline state, and camp save state +- Flow verification script from title through the fifth Meng Huo capture camp, King of Hanzhong council milestone, Shu-Han foundation milestone, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling/Changsha/Yi Province/Fu Pass/Luo Castle/Luofeng/Luo main gate/Mianzhu/Chengdu/Jiameng/Yangping/Dingjun/Hanzhong/Jing defense/Fan Castle/Han River/Fan siege/Jing rear/Gongan collapse/Maicheng isolation/Yiling vanguard/Yiling fire/Baidi entrustment/Nanzhong stabilization/Meng Huo pacification camp states, campaign timeline state, and camp save state ## Next Steps -1. Build the fifth Seven Captures Nanman battle so hardline clan separation and repeated amnesty routes begin changing enemy morale, optional objectives, and post-battle persuasion outcomes +1. Build the sixth Seven Captures Nanman battle so Meng Huo's remaining pride and the now-visible clan split create sharper capture, restraint, and village-trust tradeoffs 2. Expand post-Baidi and post-Nanzhong camp consequences with grief checks, recovery policy choices, village reassurance, captive handling, and regency decisions that affect sortie recommendations 3. Expand reserve planning into individual officer assignments, including targeted class practice, paired bond training, and camp task slots 4. Add a dedicated treasure/equipment management view so players can compare special effects and growth diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs index 34c3851..310e86e 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -1,4 +1,4 @@ -import { spawn } from 'node:child_process'; +import { spawn } from 'node:child_process'; import { chromium } from 'playwright'; const targetUrl = process.env.VERIFY_URL ?? 'http://localhost:5173/'; @@ -1739,7 +1739,7 @@ try { if ( progressTabState?.activeTab !== 'progress' || progressTabState.campaignProgress?.completedKnown !== 17 || - progressTabState.campaignProgress?.totalKnown !== 51 || + progressTabState.campaignProgress?.totalKnown !== 52 || progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' || progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' || progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전' @@ -1891,7 +1891,7 @@ try { if ( postBowangProgressState?.activeTab !== 'progress' || postBowangProgressState.campaignProgress?.completedKnown !== 18 || - postBowangProgressState.campaignProgress?.totalKnown !== 51 || + postBowangProgressState.campaignProgress?.totalKnown !== 52 || postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' || postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로' @@ -2029,7 +2029,7 @@ try { if ( postChangbanProgressState?.activeTab !== 'progress' || postChangbanProgressState.campaignProgress?.completedKnown !== 19 || - postChangbanProgressState.campaignProgress?.totalKnown !== 51 || + postChangbanProgressState.campaignProgress?.totalKnown !== 52 || postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' || postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로' @@ -2167,7 +2167,7 @@ try { if ( postJiangdongProgressState?.activeTab !== 'progress' || postJiangdongProgressState.campaignProgress?.completedKnown !== 20 || - postJiangdongProgressState.campaignProgress?.totalKnown !== 51 || + postJiangdongProgressState.campaignProgress?.totalKnown !== 52 || postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' || postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전' @@ -2307,7 +2307,7 @@ try { if ( postRedCliffsProgressState?.activeTab !== 'progress' || postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 || - postRedCliffsProgressState.campaignProgress?.totalKnown !== 51 || + postRedCliffsProgressState.campaignProgress?.totalKnown !== 52 || postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' || postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전' @@ -2447,7 +2447,7 @@ try { if ( postFireAttackProgressState?.activeTab !== 'progress' || postFireAttackProgressState.campaignProgress?.completedKnown !== 22 || - postFireAttackProgressState.campaignProgress?.totalKnown !== 51 || + postFireAttackProgressState.campaignProgress?.totalKnown !== 52 || postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' || postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전' @@ -2590,7 +2590,7 @@ try { if ( postJingzhouProgressState?.activeTab !== 'progress' || postJingzhouProgressState.campaignProgress?.completedKnown !== 23 || - postJingzhouProgressState.campaignProgress?.totalKnown !== 51 || + postJingzhouProgressState.campaignProgress?.totalKnown !== 52 || postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' || postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전' @@ -2732,7 +2732,7 @@ try { if ( postGuiyangProgressState?.activeTab !== 'progress' || postGuiyangProgressState.campaignProgress?.completedKnown !== 24 || - postGuiyangProgressState.campaignProgress?.totalKnown !== 51 || + postGuiyangProgressState.campaignProgress?.totalKnown !== 52 || postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' || postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전' @@ -2875,7 +2875,7 @@ try { if ( postWulingProgressState?.activeTab !== 'progress' || postWulingProgressState.campaignProgress?.completedKnown !== 25 || - postWulingProgressState.campaignProgress?.totalKnown !== 51 || + postWulingProgressState.campaignProgress?.totalKnown !== 52 || postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' || postWulingProgressState.campaignProgress?.nextBattleTitle !== '장사 노장 대면전' @@ -3024,7 +3024,7 @@ try { if ( postChangshaProgressState?.activeTab !== 'progress' || postChangshaProgressState.campaignProgress?.completedKnown !== 26 || - postChangshaProgressState.campaignProgress?.totalKnown !== 51 || + postChangshaProgressState.campaignProgress?.totalKnown !== 52 || postChangshaProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postChangshaProgressState.campaignProgress?.latestBattleTitle !== '장사 노장 대면전' || postChangshaProgressState.campaignProgress?.nextBattleTitle !== '익주 원군로' @@ -3169,7 +3169,7 @@ try { if ( postYizhouProgressState?.activeTab !== 'progress' || postYizhouProgressState.campaignProgress?.completedKnown !== 27 || - postYizhouProgressState.campaignProgress?.totalKnown !== 51 || + postYizhouProgressState.campaignProgress?.totalKnown !== 52 || postYizhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postYizhouProgressState.campaignProgress?.latestBattleTitle !== '익주 원군로' || postYizhouProgressState.campaignProgress?.nextBattleTitle !== '부수관 진입전' @@ -3316,7 +3316,7 @@ try { if ( postFuPassProgressState?.activeTab !== 'progress' || postFuPassProgressState.campaignProgress?.completedKnown !== 28 || - postFuPassProgressState.campaignProgress?.totalKnown !== 51 || + postFuPassProgressState.campaignProgress?.totalKnown !== 52 || postFuPassProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postFuPassProgressState.campaignProgress?.latestBattleTitle !== '부수관 진입전' || postFuPassProgressState.campaignProgress?.nextBattleTitle !== '낙성 외곽전' @@ -3465,7 +3465,7 @@ try { if ( postLuoProgressState?.activeTab !== 'progress' || postLuoProgressState.campaignProgress?.completedKnown !== 29 || - postLuoProgressState.campaignProgress?.totalKnown !== 51 || + postLuoProgressState.campaignProgress?.totalKnown !== 52 || postLuoProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuoProgressState.campaignProgress?.latestBattleTitle !== '낙성 외곽전' || postLuoProgressState.campaignProgress?.nextBattleTitle !== '낙봉파 매복전' @@ -3611,7 +3611,7 @@ try { if ( postLuofengProgressState?.activeTab !== 'progress' || postLuofengProgressState.campaignProgress?.completedKnown !== 30 || - postLuofengProgressState.campaignProgress?.totalKnown !== 51 || + postLuofengProgressState.campaignProgress?.totalKnown !== 52 || postLuofengProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuofengProgressState.campaignProgress?.latestBattleTitle !== '낙봉파 매복전' || postLuofengProgressState.campaignProgress?.nextBattleTitle !== '낙성 본성 공략' @@ -3763,7 +3763,7 @@ try { if ( postLuoMainProgressState?.activeTab !== 'progress' || postLuoMainProgressState.campaignProgress?.completedKnown !== 31 || - postLuoMainProgressState.campaignProgress?.totalKnown !== 51 || + postLuoMainProgressState.campaignProgress?.totalKnown !== 52 || postLuoMainProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuoMainProgressState.campaignProgress?.latestBattleTitle !== '낙성 본성 공략' || postLuoMainProgressState.campaignProgress?.nextBattleTitle !== '면죽관 압박전' @@ -3916,7 +3916,7 @@ try { if ( postMianzhuProgressState?.activeTab !== 'progress' || postMianzhuProgressState.campaignProgress?.completedKnown !== 32 || - postMianzhuProgressState.campaignProgress?.totalKnown !== 51 || + postMianzhuProgressState.campaignProgress?.totalKnown !== 52 || postMianzhuProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postMianzhuProgressState.campaignProgress?.latestBattleTitle !== '면죽관 압박전' || postMianzhuProgressState.campaignProgress?.nextBattleTitle !== '성도 항복 권고전' @@ -4070,7 +4070,7 @@ try { if ( postChengduProgressState?.activeTab !== 'progress' || postChengduProgressState.campaignProgress?.completedKnown !== 33 || - postChengduProgressState.campaignProgress?.totalKnown !== 51 || + postChengduProgressState.campaignProgress?.totalKnown !== 52 || postChengduProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postChengduProgressState.campaignProgress?.latestBattleTitle !== '성도 항복 권고전' || postChengduProgressState.campaignProgress?.nextBattleTitle !== '가맹관 마초 대면전' @@ -4225,7 +4225,7 @@ try { if ( postJiamengProgressState?.activeTab !== 'progress' || postJiamengProgressState.campaignProgress?.completedKnown !== 34 || - postJiamengProgressState.campaignProgress?.totalKnown !== 51 || + postJiamengProgressState.campaignProgress?.totalKnown !== 52 || postJiamengProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postJiamengProgressState.campaignProgress?.latestBattleTitle !== '가맹관 마초 대면전' || postJiamengProgressState.campaignProgress?.nextBattleTitle !== '양평관 정찰전' @@ -4381,7 +4381,7 @@ try { if ( postYangpingProgressState?.activeTab !== 'progress' || postYangpingProgressState.campaignProgress?.completedKnown !== 35 || - postYangpingProgressState.campaignProgress?.totalKnown !== 51 || + postYangpingProgressState.campaignProgress?.totalKnown !== 52 || postYangpingProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postYangpingProgressState.campaignProgress?.latestBattleTitle !== '양평관 정찰전' || postYangpingProgressState.campaignProgress?.nextBattleTitle !== '정군산 전초전' @@ -4538,7 +4538,7 @@ try { if ( postDingjunProgressState?.activeTab !== 'progress' || postDingjunProgressState.campaignProgress?.completedKnown !== 36 || - postDingjunProgressState.campaignProgress?.totalKnown !== 51 || + postDingjunProgressState.campaignProgress?.totalKnown !== 52 || postDingjunProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postDingjunProgressState.campaignProgress?.latestBattleTitle !== '정군산 전초전' || postDingjunProgressState.campaignProgress?.nextBattleTitle !== '한중 결전' @@ -4691,7 +4691,7 @@ try { if ( postHanzhongProgressState?.activeTab !== 'progress' || postHanzhongProgressState.campaignProgress?.completedKnown !== 37 || - postHanzhongProgressState.campaignProgress?.totalKnown !== 51 || + postHanzhongProgressState.campaignProgress?.totalKnown !== 52 || postHanzhongProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postHanzhongProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postHanzhongProgressState.campaignProgress?.nextBattleTitle !== '한중왕 즉위 준비' @@ -4752,7 +4752,7 @@ try { if ( postKingCouncilProgressState?.activeTab !== 'progress' || postKingCouncilProgressState.campaignProgress?.completedKnown !== 37 || - postKingCouncilProgressState.campaignProgress?.totalKnown !== 51 || + postKingCouncilProgressState.campaignProgress?.totalKnown !== 52 || postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포' ) { @@ -4812,7 +4812,7 @@ try { if ( postFoundationProgressState?.activeTab !== 'progress' || postFoundationProgressState.campaignProgress?.completedKnown !== 37 || - postFoundationProgressState.campaignProgress?.totalKnown !== 51 || + postFoundationProgressState.campaignProgress?.totalKnown !== 52 || postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전' ) { @@ -4963,7 +4963,7 @@ try { if ( postJingDefenseProgressState?.activeTab !== 'progress' || postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 || - postJingDefenseProgressState.campaignProgress?.totalKnown !== 51 || + postJingDefenseProgressState.campaignProgress?.totalKnown !== 52 || postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' || postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전' ) { @@ -5116,7 +5116,7 @@ try { if ( postFanCastleProgressState?.activeTab !== 'progress' || postFanCastleProgressState.campaignProgress?.completedKnown !== 39 || - postFanCastleProgressState.campaignProgress?.totalKnown !== 51 || + postFanCastleProgressState.campaignProgress?.totalKnown !== 52 || postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' || postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공' ) { @@ -5270,7 +5270,7 @@ try { if ( postHanRiverProgressState?.activeTab !== 'progress' || postHanRiverProgressState.campaignProgress?.completedKnown !== 40 || - postHanRiverProgressState.campaignProgress?.totalKnown !== 51 || + postHanRiverProgressState.campaignProgress?.totalKnown !== 52 || postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' || postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전' ) { @@ -5424,7 +5424,7 @@ try { if ( postFanSiegeProgressState?.activeTab !== 'progress' || postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 || - postFanSiegeProgressState.campaignProgress?.totalKnown !== 51 || + postFanSiegeProgressState.campaignProgress?.totalKnown !== 52 || postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' || postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전' ) { @@ -5586,7 +5586,7 @@ try { if ( postJingRearProgressState?.activeTab !== 'progress' || postJingRearProgressState.campaignProgress?.completedKnown !== 42 || - postJingRearProgressState.campaignProgress?.totalKnown !== 51 || + postJingRearProgressState.campaignProgress?.totalKnown !== 52 || postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' || postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고' ) { @@ -5741,7 +5741,7 @@ try { if ( postGonganProgressState?.activeTab !== 'progress' || postGonganProgressState.campaignProgress?.completedKnown !== 43 || - postGonganProgressState.campaignProgress?.totalKnown !== 51 || + postGonganProgressState.campaignProgress?.totalKnown !== 52 || postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' || postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전' ) { @@ -5899,7 +5899,7 @@ try { if ( postMaichengProgressState?.activeTab !== 'progress' || postMaichengProgressState.campaignProgress?.completedKnown !== 44 || - postMaichengProgressState.campaignProgress?.totalKnown !== 51 || + postMaichengProgressState.campaignProgress?.totalKnown !== 52 || postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' || postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로' ) { @@ -6056,7 +6056,7 @@ try { if ( postYilingProgressState?.activeTab !== 'progress' || postYilingProgressState.campaignProgress?.completedKnown !== 45 || - postYilingProgressState.campaignProgress?.totalKnown !== 51 || + postYilingProgressState.campaignProgress?.totalKnown !== 52 || postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' || postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전' ) { @@ -6214,7 +6214,7 @@ try { if ( postYilingFireProgressState?.activeTab !== 'progress' || postYilingFireProgressState.campaignProgress?.completedKnown !== 46 || - postYilingFireProgressState.campaignProgress?.totalKnown !== 51 || + postYilingFireProgressState.campaignProgress?.totalKnown !== 52 || postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁' ) { @@ -6280,7 +6280,7 @@ try { if ( postBaidiProgressState?.activeTab !== 'progress' || postBaidiProgressState.campaignProgress?.completedKnown !== 46 || - postBaidiProgressState.campaignProgress?.totalKnown !== 51 || + postBaidiProgressState.campaignProgress?.totalKnown !== 52 || postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로' ) { @@ -6441,7 +6441,7 @@ try { if ( postNanzhongProgressState?.activeTab !== 'progress' || postNanzhongProgressState.campaignProgress?.completedKnown !== 47 || - postNanzhongProgressState.campaignProgress?.totalKnown !== 51 || + postNanzhongProgressState.campaignProgress?.totalKnown !== 52 || postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' || postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전' ) { @@ -6603,7 +6603,7 @@ try { if ( postMengHuoProgressState?.activeTab !== 'progress' || postMengHuoProgressState.campaignProgress?.completedKnown !== 48 || - postMengHuoProgressState.campaignProgress?.totalKnown !== 51 || + postMengHuoProgressState.campaignProgress?.totalKnown !== 52 || postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' || postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전' ) { @@ -6765,7 +6765,7 @@ try { if ( postSecondCaptureProgressState?.activeTab !== 'progress' || postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 || - postSecondCaptureProgressState.campaignProgress?.totalKnown !== 51 || + postSecondCaptureProgressState.campaignProgress?.totalKnown !== 52 || postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' || postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전' ) { @@ -6930,7 +6930,7 @@ try { if ( postThirdCaptureProgressState?.activeTab !== 'progress' || postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 || - postThirdCaptureProgressState.campaignProgress?.totalKnown !== 51 || + postThirdCaptureProgressState.campaignProgress?.totalKnown !== 52 || postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' || postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전' ) { @@ -7096,14 +7096,180 @@ try { if ( postFourthCaptureProgressState?.activeTab !== 'progress' || postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 || - postFourthCaptureProgressState.campaignProgress?.totalKnown !== 51 || + postFourthCaptureProgressState.campaignProgress?.totalKnown !== 52 || postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' || - postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전' ) { - throw new Error(`Expected post-fourth-capture progress tab to complete the fifty-first battle and pause before the fifth capture arc: ${JSON.stringify(postFourthCaptureProgressState?.campaignProgress)}`); + throw new Error(`Expected post-fourth-capture progress tab to complete the fifty-first battle and point to the fifth capture arc: ${JSON.stringify(postFourthCaptureProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-fourth-capture-progress.png', fullPage: true }); + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const fifthCaptureSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !fifthCaptureSortieState?.sortieVisible || + fifthCaptureSortieState.campaign?.step !== 'fifty-first-camp' || + fifthCaptureSortieState.campTitle !== '칠종칠금 4차전 후 군영' || + fifthCaptureSortieState.sortiePlan?.objectiveLine !== '칠종칠금 5차전 · 맹획 다섯 번째 생포와 강경파 분리' || + fifthCaptureSortieState.sortiePlan?.maxCount !== 7 || + fifthCaptureSortieState.sortiePlan?.recommendedTotal !== 7 || + !fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + !fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) || + !fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) || + !fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'huang-zhong' && unit.recommended) || + !fifthCaptureSortieState.sortiePlan?.recruitedLine?.includes('합류 무장') || + fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected fifth Meng Huo sortie prep to expose hardliner-separation recommendations, Zhuge Liang lock, and the expanded recruited roster: ${JSON.stringify(fifthCaptureSortieState)}`); + } + assertSortieTacticalRoster(fifthCaptureSortieState, [ + 'guan-yu', + 'zhang-fei', + 'jian-yong', + 'mi-zhu', + 'sun-qian', + 'zhao-yun', + 'zhuge-liang', + 'ma-liang', + 'yi-ji', + 'gong-zhi', + 'huang-zhong', + 'wei-yan', + 'pang-tong', + 'fa-zheng', + 'wu-yi', + 'yan-yan', + 'li-yan', + 'huang-quan', + 'ma-chao', + 'ma-dai', + 'wang-ping' + ]); + + const fifthCapturePriorityUnits = ['huang-quan', 'ma-liang', 'wang-ping', 'zhao-yun', 'huang-zhong', 'ma-dai']; + for (const unitId of fifthCapturePriorityUnits) { + const currentSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if (!currentSortieState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)) { + const removable = currentSortieState.sortieRoster?.find( + (unit) => unit.selected && !unit.required && !fifthCapturePriorityUnits.includes(unit.id) + ); + if (removable) { + await clickSortieRosterUnit(page, removable.id); + } + await clickSortieRosterUnit(page, unitId); + } + } + + const fifthCaptureSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !fifthCapturePriorityUnits.every((unitId) => + fifthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected) + ) || + !fifthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + fifthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') || + fifthCaptureSortieReadyState.sortiePlan?.selectedCount !== 7 || + fifthCaptureSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 + ) { + throw new Error(`Expected fifth Meng Huo sortie to deploy Zhuge Liang, Huang Quan, Ma Liang, Wang Ping, Zhao Yun, Huang Zhong, and Ma Dai: ${JSON.stringify(fifthCaptureSortieReadyState)}`); + } + await page.screenshot({ path: 'dist/verification-fifth-capture-sortie.png', fullPage: true }); + + await page.mouse.click(1068, 646); + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('StoryScene'); + }); + await page.screenshot({ path: 'dist/verification-fifth-capture-story.png', fullPage: true }); + + for (let i = 0; i < 70; i += 1) { + const enteredFiftySecondBattle = await page.evaluate(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'fifty-second-battle-meng-huo-fifth-capture'; + }); + if (enteredFiftySecondBattle) { + break; + } + await page.keyboard.press('Space'); + await page.waitForTimeout(320); + } + await page.waitForFunction(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'fifty-second-battle-meng-huo-fifth-capture' && state?.battleOutcome === null && state?.phase === 'idle'; + }); + await page.screenshot({ path: 'dist/verification-fifty-second-battle.png', fullPage: true }); + + const fiftySecondBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + const fiftySecondEnemies = fiftySecondBattleState.units.filter((unit) => unit.faction === 'enemy'); + const fiftySecondAllies = fiftySecondBattleState.units.filter((unit) => unit.faction === 'ally'); + const fiftySecondEnemyBehaviors = new Set(fiftySecondEnemies.map((unit) => unit.ai)); + if ( + fiftySecondBattleState.camera?.mapWidth !== 104 || + fiftySecondBattleState.camera?.mapHeight !== 86 || + fiftySecondBattleState.victoryConditionLabel !== '맹획 다섯 번째 생포와 강경파 분리' || + !fiftySecondBattleState.objectives?.some((objective) => objective.id === 'pacify-meng-huo-fifth' && objective.label === '맹획 다섯 번째 생포') || + !fiftySecondBattleState.objectives?.some((objective) => objective.id === 'hardliner-separation' && objective.label === '강경파 분리로 확보') || + fiftySecondEnemies.length < 40 || + !fiftySecondEnemyBehaviors.has('aggressive') || + !fiftySecondEnemyBehaviors.has('guard') || + !fiftySecondEnemyBehaviors.has('hold') || + !fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-leader') || + !fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-zhurong') || + !fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-mulu') || + !fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-duosi') || + !fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-mengyou') || + !fiftySecondAllies.some((unit) => unit.id === 'zhuge-liang') || + !fiftySecondAllies.some((unit) => unit.id === 'huang-quan') || + !fiftySecondAllies.some((unit) => unit.id === 'ma-liang') || + !fiftySecondAllies.some((unit) => unit.id === 'wang-ping') || + !fiftySecondAllies.some((unit) => unit.id === 'zhao-yun') || + !fiftySecondAllies.some((unit) => unit.id === 'huang-zhong') || + !fiftySecondAllies.some((unit) => unit.id === 'ma-dai') || + fiftySecondAllies.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected fifty-second battle to use the fifth Meng Huo map, hardliner-separation objective, selected recruited officers, and mixed clan AI: ${JSON.stringify(fiftySecondBattleState)}`); + } + + await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory')); + await page.waitForFunction(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.battleOutcome === 'victory' && state?.phase === 'resolved' && state?.resultVisible === true; + }); + + await page.mouse.click(738, 642); + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('CampScene'); + }); + + const fiftySecondCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + fiftySecondCampState?.campBattleId !== 'fifty-second-battle-meng-huo-fifth-capture' || + fiftySecondCampState.campTitle !== '칠종칠금 5차전 후 군영' || + fiftySecondCampState.availableDialogueIds?.length !== 3 || + !fiftySecondCampState.availableDialogueIds.every((id) => id.includes('fifth-capture')) || + fiftySecondCampState.availableVisitIds?.length !== 2 || + !fiftySecondCampState.availableVisitIds.every((id) => id.includes('fifth-capture')) || + fiftySecondCampState.rosterCollection?.total < 21 + ) { + throw new Error(`Expected fifty-second camp to expose fifth capture dialogue/visit sets and preserve the deployable officer roster: ${JSON.stringify(fiftySecondCampState)}`); + } + await page.screenshot({ path: 'dist/verification-fifth-capture-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postFifthCaptureProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postFifthCaptureProgressState?.activeTab !== 'progress' || + postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 || + postFifthCaptureProgressState.campaignProgress?.totalKnown !== 52 || + postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' || + postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + ) { + throw new Error(`Expected post-fifth-capture progress tab to complete the fifty-second battle and pause before the sixth capture arc: ${JSON.stringify(postFifthCaptureProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-fifth-capture-progress.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); @@ -7135,11 +7301,11 @@ try { activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], camp: window.__HEROS_DEBUG__?.camp?.() ?? null })); - if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-first-camp') { - throw new Error(`Expected title continue to reopen the latest fourth Meng Huo capture camp: ${JSON.stringify(titleContinueState)}`); + if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-second-camp') { + throw new Error(`Expected title continue to reopen the latest fifth Meng Huo capture camp: ${JSON.stringify(titleContinueState)}`); } - console.log(`Verified title-to-fourth-Meng-Huo-capture flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan Castle siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, and fourth capture state at ${targetUrl}`); + console.log(`Verified title-to-fifth-Meng-Huo-capture flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, and fifth capture state at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/assets/images/battle/fifty-second-battle-map.svg b/src/assets/images/battle/fifty-second-battle-map.svg new file mode 100644 index 0000000..f50c81f --- /dev/null +++ b/src/assets/images/battle/fifty-second-battle-map.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts index a1e0e8f..199439f 100644 --- a/src/game/data/battleMapAssets.ts +++ b/src/game/data/battleMapAssets.ts @@ -3,6 +3,7 @@ import eighteenthBattleMapUrl from '../../assets/images/battle/eighteenth-battle import eleventhBattleMapUrl from '../../assets/images/battle/eleventh-battle-map.svg'; import fiftiethBattleMapUrl from '../../assets/images/battle/fiftieth-battle-map.svg'; import fiftyFirstBattleMapUrl from '../../assets/images/battle/fifty-first-battle-map.svg'; +import fiftySecondBattleMapUrl from '../../assets/images/battle/fifty-second-battle-map.svg'; import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg'; import fifteenthBattleMapUrl from '../../assets/images/battle/fifteenth-battle-map.svg'; import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png'; @@ -101,5 +102,6 @@ export const battleMapAssets: Record = { 'battle-map-forty-eighth': fortyEighthBattleMapUrl, 'battle-map-forty-ninth': fortyNinthBattleMapUrl, 'battle-map-fiftieth': fiftiethBattleMapUrl, - 'battle-map-fifty-first': fiftyFirstBattleMapUrl + 'battle-map-fifty-first': fiftyFirstBattleMapUrl, + 'battle-map-fifty-second': fiftySecondBattleMapUrl }; diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index 2450572..fcfb04f 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -7,6 +7,10 @@ import { fiftyFirstBattleMap, fiftyFirstBattleUnits, fiftyFirstBattleVictoryPages, + fiftySecondBattleBonds, + fiftySecondBattleMap, + fiftySecondBattleUnits, + fiftySecondBattleVictoryPages, firstBattleBonds, fifteenthBattleBonds, fifteenthBattleMap, @@ -260,7 +264,8 @@ export type BattleScenarioId = | 'forty-eighth-battle-meng-huo-main-force' | 'forty-ninth-battle-meng-huo-second-capture' | 'fiftieth-battle-meng-huo-third-capture' - | 'fifty-first-battle-meng-huo-fourth-capture'; + | 'fifty-first-battle-meng-huo-fourth-capture' + | 'fifty-second-battle-meng-huo-fifth-capture'; export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory'; @@ -3319,6 +3324,73 @@ export const fiftyFirstBattleScenario: BattleScenarioDefinition = { nextCampScene: 'CampScene' }; +export const fiftySecondBattleScenario: BattleScenarioDefinition = { + id: 'fifty-second-battle-meng-huo-fifth-capture', + title: '칠종칠금 5차전', + victoryConditionLabel: '맹획 다섯 번째 생포와 강경파 분리', + defeatConditionLabel: '제갈량 퇴각', + openingObjectiveLines: [ + '강경파 호족이 회유 퇴로를 배신이라 부르며 길목을 막고 있습니다. 맹획을 다시 생포하되, 물러설 뜻이 있는 호족이 본대와 갈라질 길을 지켜야 합니다.', + '마을 보증과 포로장 장부가 흔들리면 지난 네 번의 회유도 약해집니다. 강경파 요새를 누르고, 마을과 포로장을 지켜 남중 안의 의심을 끊으십시오.', + '제갈량은 반드시 생존해야 합니다. 마량과 황권으로 말을 남기고, 왕평과 마대로 퇴로를 열며, 조운과 황충으로 추격선을 억제하십시오.' + ], + map: fiftySecondBattleMap, + units: fiftySecondBattleUnits, + bonds: fiftySecondBattleBonds, + mapTextureKey: 'battle-map-fifty-second', + leaderUnitId: 'menghuo-fifth-leader', + quickVictoryTurnLimit: 98, + baseVictoryGold: 12940, + objectives: [ + { + id: 'pacify-meng-huo-fifth', + kind: 'pacify-leader', + label: '맹획 다섯 번째 생포', + rewardGold: 8780, + unitId: 'menghuo-fifth-leader' + }, + { + id: 'zhuge-liang', + kind: 'keep-unit-alive', + label: '제갈량 생존', + rewardGold: 2220, + unitId: 'zhuge-liang' + }, + { + id: 'hardliner-separation', + kind: 'secure-terrain', + label: '강경파 분리로 확보', + rewardGold: 4380, + terrain: 'road' + }, + { + id: 'clan-villages', + kind: 'secure-terrain', + label: '회유 마을 보호', + rewardGold: 4040, + terrain: 'village' + }, + { + id: 'hardliner-forts', + kind: 'secure-terrain', + label: '강경파 요새 제압', + rewardGold: 3860, + terrain: 'fort' + }, + { + id: 'quick', + kind: 'quick-victory', + label: '98턴 이내 다섯 번째 생포', + rewardGold: 2920, + maxTurn: 98 + } + ], + defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }], + itemRewards: ['콩 70', '상처약 54', '탁주 24', '강경파 분리 장부 1', '남중 회유 깃발 1'], + victoryPages: fiftySecondBattleVictoryPages, + nextCampScene: 'CampScene' +}; + export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id; export const battleScenarios: Record = { @@ -3372,7 +3444,8 @@ export const battleScenarios: Record 'forty-eighth-battle-meng-huo-main-force': fortyEighthBattleScenario, 'forty-ninth-battle-meng-huo-second-capture': fortyNinthBattleScenario, 'fiftieth-battle-meng-huo-third-capture': fiftiethBattleScenario, - 'fifty-first-battle-meng-huo-fourth-capture': fiftyFirstBattleScenario + 'fifty-first-battle-meng-huo-fourth-capture': fiftyFirstBattleScenario, + 'fifty-second-battle-meng-huo-fifth-capture': fiftySecondBattleScenario }; export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index b11098f..3dbc6ec 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -6,6 +6,7 @@ import { eleventhBattleScenario, fiftiethBattleScenario, fiftyFirstBattleScenario, + fiftySecondBattleScenario, fortiethBattleScenario, fortyFirstBattleScenario, fortySecondBattleScenario, @@ -65,6 +66,8 @@ import { fiftiethBattleVictoryPages, fiftyFirstBattleIntroPages, fiftyFirstBattleVictoryPages, + fiftySecondBattleIntroPages, + fiftySecondBattleVictoryPages, fortiethBattleIntroPages, fortiethBattleVictoryPages, fortyFirstBattleIntroPages, @@ -745,13 +748,24 @@ const sortieFlows: Record = { }, [fiftyFirstBattleScenario.id]: { afterBattleId: fiftyFirstBattleScenario.id, - eyebrow: '다음 장 준비', - title: '더 깊은 남중 회유', + eyebrow: '다음 전장', + title: fiftySecondBattleScenario.title, description: - '맹획은 네 번째로 붙잡혔고, 회유 퇴로와 보증 장부는 남중 호족들의 말 속에 남았습니다. 다음 장에서는 더 깊은 산채와 강경파 호족을 상대로 같은 약속을 지킬 수 있는지 다룹니다.', - rewardHint: '다음 장: 칠종칠금 5차전과 남중 강경파 회유 준비 중', - pages: fiftyFirstBattleVictoryPages, - unavailableNotice: '칠종칠금 5차전은 다음 작업에서 이어집니다. 지금은 네 번째 생포 뒤의 보증 장부, 퇴로 표식, 강경파 분리 흐름을 정비할 수 있습니다.' + '맹획은 네 번째로 붙잡혔고, 회유 퇴로와 보증 장부는 남중 호족들의 말 속에 남았습니다. 이제 강경파 호족이 그 길을 끊으려 하니, 다섯 번째 생포에서는 회유를 받아들일 자와 끝까지 싸울 자를 전장 안에서 분리해야 합니다.', + rewardHint: `예상 보상: ${fiftySecondBattleScenario.title} 개방 / 강경파 분리 장부`, + nextBattleId: fiftySecondBattleScenario.id, + campaignStep: 'fifty-second-battle', + pages: [...fiftyFirstBattleVictoryPages, ...fiftySecondBattleIntroPages] + }, + [fiftySecondBattleScenario.id]: { + afterBattleId: fiftySecondBattleScenario.id, + eyebrow: '다음 장 준비', + title: '여섯 번째 회유의 길', + description: + '맹획은 다섯 번째로 붙잡혔고, 강경파 호족과 물러선 호족의 말이 갈라졌습니다. 다음 장에서는 남중의 자존심을 더 직접적으로 마주하면서도, 지난 회유의 약속을 무너뜨리지 않는 길을 준비합니다.', + rewardHint: '다음 장: 칠종칠금 6차전과 마지막 설득 준비 중', + pages: fiftySecondBattleVictoryPages, + unavailableNotice: '칠종칠금 6차전은 다음 작업에서 이어집니다. 지금은 다섯 번째 생포 뒤의 강경파 분리 장부와 회유 깃발을 정비할 수 있습니다.' } }; diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 48260f6..2355806 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -3350,6 +3350,59 @@ export const fiftyFirstBattleVictoryPages: StoryPage[] = [ } ]; +export const fiftySecondBattleIntroPages: StoryPage[] = [ + { + id: 'fifty-second-hardliner-split', + bgm: 'story-dark', + chapter: '강경파의 갈라짐', + background: 'story-militia', + speaker: '마량', + text: '네 번째 회유 뒤 일부 호족은 맹획의 깃발 아래 남아 있으면서도 마을을 지키는 길을 찾고 있습니다. 반대로 강경파는 그 길을 배신이라 부르며 퇴로를 끊으려 합니다.' + }, + { + id: 'fifty-second-fifth-capture-plan', + bgm: 'battle-prep', + chapter: '다섯 번째 생포', + background: 'story-three-heroes', + speaker: '제갈량', + text: '이번 전투는 맹획을 다시 잡는 것만으로는 부족합니다. 강경파 호족을 본대와 분리하고, 회유 길목과 마을 보증을 동시에 지키면 남중 안에서 싸움을 그만둘 명분이 더 커질 것입니다.' + }, + { + id: 'fifty-second-sortie', + bgm: 'battle-prep', + chapter: '칠종칠금 5차전', + background: 'story-sortie', + speaker: '왕평', + text: '깊은 죽림과 협곡이 이어져 있습니다. 장수마다 맡을 길이 다릅니다. 마량과 황권은 말을 남기고, 왕평과 마대는 물러날 길을 열며, 조운과 황충은 추격을 막아야 합니다.' + } +]; + +export const fiftySecondBattleVictoryPages: StoryPage[] = [ + { + id: 'fifty-second-victory-fifth-capture', + bgm: 'militia-theme', + chapter: '다섯 번째 생포', + background: 'story-sortie', + text: '맹획은 다섯 번째로 포위되었고, 강경파 호족은 본대와 갈라졌습니다. 촉한군은 다시 마을과 퇴로를 지켰고, 돌아간 자들은 이제 맹획의 말만 듣지 않게 되었습니다.' + }, + { + id: 'fifty-second-victory-hardliner-ledger', + bgm: 'story-dark', + chapter: '갈라진 장부', + background: 'story-militia', + speaker: '황권', + text: '끝까지 싸운 자와 물러난 자의 이름이 더 분명해졌습니다. 처벌의 장부가 아니라 약속의 장부로 남겨야 합니다. 그래야 다음 석방도 군율을 잃지 않습니다.' + }, + { + id: 'fifty-second-next-sixth-capture', + bgm: 'story-dark', + chapter: '여섯 번째 길', + background: 'story-three-heroes', + speaker: '제갈량', + text: '남중의 마음은 점점 흔들리고 있습니다. 그러나 맹획의 자존심은 아직 마지막 벽처럼 남아 있습니다. 다음 싸움에서는 그 자존심을 꺾되, 남중 전체를 꺾지는 말아야 합니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, @@ -3801,6 +3854,12 @@ export const fiftyFirstBattleMap: BattleMap = { terrain: createFiftyFirstBattleTerrain() }; +export const fiftySecondBattleMap: BattleMap = { + width: 104, + height: 86, + terrain: createFiftySecondBattleTerrain() +}; + export const firstBattleUnits: UnitData[] = [ { id: 'liu-bei', @@ -16187,6 +16246,94 @@ export const fiftyFirstBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('menghuo-fourth-leader', '맹획', '남만왕', 'rebelLeader', 156, 1048, 258, 96, 43) ]; +const fiftySecondBattleAllyPositions: Record = { + 'guan-yu': { x: 15, y: 65 }, + 'zhang-fei': { x: 20, y: 67 }, + 'jian-yong': { x: 11, y: 72 }, + 'mi-zhu': { x: 13, y: 70 }, + 'sun-qian': { x: 17, y: 74 }, + 'zhao-yun': { x: 25, y: 62 }, + 'zhuge-liang': { x: 14, y: 62 }, + 'ma-liang': { x: 18, y: 59 }, + 'yi-ji': { x: 11, y: 77 }, + 'gong-zhi': { x: 21, y: 75 }, + 'huang-zhong': { x: 28, y: 63 }, + 'wei-yan': { x: 33, y: 67 }, + 'pang-tong': { x: 16, y: 56 }, + 'fa-zheng': { x: 22, y: 57 }, + 'wu-yi': { x: 35, y: 72 }, + 'yan-yan': { x: 38, y: 65 }, + 'li-yan': { x: 27, y: 55 }, + 'huang-quan': { x: 11, y: 60 }, + 'ma-chao': { x: 40, y: 62 }, + 'ma-dai': { x: 43, y: 67 }, + 'wang-ping': { x: 30, y: 58 } +}; + +export const fiftySecondBattleUnits: UnitData[] = [ + ...[ + ...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'), + ...xuzhouRecruitUnits, + ...caoBreakRecruitUnits, + ...liuBiaoRecruitUnits, + ...zhugeRecruitUnits, + ...jingzhouRecruitUnits, + ...guiyangRecruitUnits, + ...wulingRecruitUnits, + ...changshaRecruitUnits, + ...yizhouRecruitUnits, + ...fuPassRecruitUnits, + ...luoCastleRecruitUnits, + ...luoCastleProperRecruitUnits, + ...chengduPressureRecruitUnits, + ...chengduSurrenderRecruitUnits, + ...hanzhongOpeningRecruitUnits, + ...hanzhongScoutRecruitUnits, + ...hanzhongMainRecruitUnits + ].map((unit) => placeScenarioUnit(unit, fiftySecondBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })), + createThirtyFirstEnemyUnit('menghuo-fifth-scout-a', '강경파 정찰병', '서쪽 죽림 정찰', 'bandit', 154, 684, 206, 47, 38), + createThirtyFirstEnemyUnit('menghuo-fifth-scout-b', '강경파 정찰병', '늪지 정찰', 'bandit', 154, 684, 206, 63, 36), + createThirtyFirstEnemyUnit('menghuo-fifth-scout-c', '강경파 정찰병', '마을 감시', 'bandit', 155, 692, 208, 78, 53), + createThirtyFirstEnemyUnit('menghuo-fifth-scout-d', '강경파 정찰병', '동쪽 협곡 감시', 'bandit', 155, 692, 208, 94, 68), + createThirtyFirstEnemyUnit('menghuo-fifth-scout-e', '강경파 정찰병', '북쪽 산길 감시', 'bandit', 156, 700, 210, 55, 26), + createThirtyFirstEnemyUnit('menghuo-fifth-scout-f', '강경파 정찰병', '분리로 감시', 'bandit', 156, 700, 210, 70, 74), + createThirtyFirstEnemyUnit('menghuo-fifth-vine-a', '강경파 등갑병', '죽림 등갑병', 'yellowTurban', 155, 960, 244, 52, 47), + createThirtyFirstEnemyUnit('menghuo-fifth-vine-b', '강경파 등갑병', '불씨 등갑병', 'yellowTurban', 156, 972, 246, 65, 50), + createThirtyFirstEnemyUnit('menghuo-fifth-vine-c', '강경파 등갑병', '마을 등갑병', 'yellowTurban', 156, 972, 246, 78, 60), + createThirtyFirstEnemyUnit('menghuo-fifth-vine-d', '강경파 등갑병', '본영 등갑병', 'yellowTurban', 157, 984, 248, 90, 52), + createThirtyFirstEnemyUnit('menghuo-fifth-vine-e', '강경파 등갑병', '후위 등갑병', 'yellowTurban', 157, 984, 248, 98, 44), + createThirtyFirstEnemyUnit('menghuo-fifth-vine-f', '강경파 등갑병', '협곡 등갑병', 'yellowTurban', 158, 996, 250, 86, 33), + createThirtyFirstEnemyUnit('menghuo-fifth-archer-a', '남만 죽림 궁병', '독천 궁병', 'archer', 154, 736, 218, 48, 30), + createThirtyFirstEnemyUnit('menghuo-fifth-archer-b', '남만 죽림 궁병', '절벽 궁병', 'archer', 155, 744, 220, 64, 28), + createThirtyFirstEnemyUnit('menghuo-fifth-archer-c', '남만 죽림 궁병', '마을 궁병', 'archer', 155, 744, 220, 76, 46), + createThirtyFirstEnemyUnit('menghuo-fifth-archer-d', '남만 죽림 궁병', '늪지 궁병', 'archer', 156, 752, 222, 88, 64), + createThirtyFirstEnemyUnit('menghuo-fifth-archer-e', '남만 죽림 궁병', '본영 궁병', 'archer', 156, 752, 222, 97, 57), + createThirtyFirstEnemyUnit('menghuo-fifth-beast-a', '남만 맹수 추격대', '강가 맹수대', 'cavalry', 156, 976, 248, 58, 40), + createThirtyFirstEnemyUnit('menghuo-fifth-beast-b', '남만 맹수 추격대', '늪지 맹수대', 'cavalry', 156, 976, 248, 71, 42), + createThirtyFirstEnemyUnit('menghuo-fifth-beast-c', '남만 맹수 추격대', '산채 맹수대', 'cavalry', 157, 988, 250, 83, 55), + createThirtyFirstEnemyUnit('menghuo-fifth-beast-d', '남만 맹수 추격대', '추격 맹수대', 'cavalry', 157, 988, 250, 95, 61), + createThirtyFirstEnemyUnit('menghuo-fifth-beast-e', '남만 맹수 추격대', '분리로 차단대', 'cavalry', 158, 1000, 252, 69, 70), + createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-a', '강경파 매복병', '죽림 매복병', 'bandit', 155, 704, 212, 39, 53), + createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-b', '강경파 매복병', '숲속 매복병', 'bandit', 156, 712, 214, 55, 56), + createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-c', '강경파 매복병', '강가 매복병', 'bandit', 156, 712, 214, 68, 69), + createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-d', '강경파 매복병', '후방 차단대', 'bandit', 157, 720, 216, 88, 76), + createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-e', '강경파 매복병', '서쪽 차단대', 'bandit', 157, 720, 216, 34, 66), + createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-f', '강경파 매복병', '분리로 감시대', 'bandit', 158, 728, 218, 75, 74), + createThirtyFirstEnemyUnit('menghuo-fifth-guard-a', '맹획 오차 친위', '포로장 친위대', 'yellowTurban', 158, 1012, 254, 72, 44), + createThirtyFirstEnemyUnit('menghuo-fifth-guard-b', '맹획 오차 친위', '산채 친위대', 'yellowTurban', 158, 1012, 254, 85, 50), + createThirtyFirstEnemyUnit('menghuo-fifth-guard-c', '맹획 오차 친위', '본영 친위대', 'yellowTurban', 159, 1024, 256, 96, 47), + createThirtyFirstEnemyUnit('menghuo-fifth-guard-d', '맹획 오차 친위', '죽림 친위대', 'yellowTurban', 159, 1024, 256, 91, 37), + createThirtyFirstEnemyUnit('menghuo-fifth-shaman-a', '독천 제사장', '늪지 제사장', 'strategist', 157, 784, 230, 64, 38), + createThirtyFirstEnemyUnit('menghuo-fifth-shaman-b', '독천 제사장', '강가 제사장', 'strategist', 158, 792, 232, 79, 51), + createThirtyFirstEnemyUnit('menghuo-fifth-shaman-c', '독천 제사장', '산채 제사장', 'strategist', 158, 792, 232, 90, 40), + createThirtyFirstEnemyUnit('menghuo-fifth-shaman-d', '독천 제사장', '죽림 제사장', 'strategist', 159, 800, 234, 84, 59), + createThirtyFirstEnemyUnit('menghuo-fifth-officer-zhurong', '축융 부인', '남중 화전장', 'rebelLeader', 160, 1058, 264, 82, 67), + createThirtyFirstEnemyUnit('menghuo-fifth-officer-mulu', '목록대왕', '맹수대왕', 'rebelLeader', 160, 1058, 264, 88, 39), + createThirtyFirstEnemyUnit('menghuo-fifth-officer-duosi', '타사대왕', '독천 호족장', 'rebelLeader', 159, 1038, 258, 72, 62), + createThirtyFirstEnemyUnit('menghuo-fifth-officer-mengyou', '맹우', '맹획의 아우', 'rebelLeader', 159, 1038, 258, 68, 54), + createThirtyFirstEnemyUnit('menghuo-fifth-leader', '맹획', '남만왕', 'rebelLeader', 162, 1100, 270, 98, 45) +]; + export const firstBattleBonds: BattleBond[] = [ { id: 'liu-bei__guan-yu', @@ -17151,6 +17298,35 @@ export const mengHuoFourthCaptureBonds: BattleBond[] = [ export const fiftyFirstBattleBonds: BattleBond[] = [...fiftiethBattleBonds, ...mengHuoFourthCaptureBonds].map( cloneBattleBondForScenario ); +export const mengHuoFifthCaptureBonds: BattleBond[] = [ + { + id: 'zhuge-liang__huang-quan_fifth-capture', + unitIds: ['zhuge-liang', 'huang-quan'], + title: '강경파 분리 장부', + level: 53, + exp: 0, + description: '제갈량의 회유 방침과 황권의 장부는 끝까지 싸운 자와 물러난 자를 갈라, 불필요한 원한을 줄인다.' + }, + { + id: 'ma-liang__wang-ping_fifth-capture', + unitIds: ['ma-liang', 'wang-ping'], + title: '말과 길의 분리', + level: 49, + exp: 0, + description: '마량이 호족에게 남길 말을 고르고 왕평이 열린 길을 지켜, 전장 안에서도 항복의 길을 보존한다.' + }, + { + id: 'zhao-yun__huang-zhong_fifth-capture', + unitIds: ['zhao-yun', 'huang-zhong'], + title: '추격선 억제', + level: 45, + exp: 0, + description: '조운과 황충은 맹수대와 고지 궁병을 눌러, 강경파가 회유 길목을 짓밟지 못하게 한다.' + } +]; +export const fiftySecondBattleBonds: BattleBond[] = [...fiftyFirstBattleBonds, ...mengHuoFifthCaptureBonds].map( + cloneBattleBondForScenario +); function createEighthBattleTerrain(): TerrainType[][] { return Array.from({ length: 22 }, (_, y) => @@ -19792,6 +19968,79 @@ function createFiftyFirstBattleTerrain(): TerrainType[][] { ); } +function createFiftySecondBattleTerrain(): TerrainType[][] { + return Array.from({ length: 86 }, (_, y) => + Array.from({ length: 104 }, (_, x): TerrainType => { + if ( + (x >= 0 && x <= 103 && y >= 78) || + (x >= 65 && x <= 71 && y >= 0 && y <= 80) || + (x >= 5 && x <= 31 && y >= 69 && y <= 85) || + (x >= 78 && x <= 95 && y >= 60 && y <= 78) + ) { + return 'river'; + } + if ( + (x >= 10 && x <= 98 && y >= 65 && y <= 71) || + (x >= 28 && x <= 76 && y >= 44 && y <= 51) || + (x >= 72 && x <= 98 && y >= 36 && y <= 42) || + (x >= 39 && x <= 47 && y >= 41 && y <= 74) || + (x >= 78 && x <= 84 && y >= 50 && y <= 82) || + (x >= 18 && x <= 52 && y >= 57 && y <= 61) + ) { + return 'road'; + } + if ( + (x >= 18 && x <= 74 && y >= 21 && y <= 76) || + (x >= 56 && x <= 98 && y >= 29 && y <= 72) || + (x >= 2 && x <= 39 && y >= 51 && y <= 76) || + (x >= 80 && x <= 103 && y >= 43 && y <= 75) + ) { + return 'forest'; + } + if ( + (x >= 0 && x <= 32 && y <= 40) || + (x >= 34 && x <= 70 && y <= 30) || + (x >= 73 && x <= 103 && y <= 39) || + (x >= 88 && x <= 103 && y >= 35 && y <= 57) + ) { + return 'hill'; + } + if ( + (x <= 2 && y <= 76) || + (x >= 101 && y <= 72) || + (x >= 31 && x <= 37 && y >= 0 && y <= 42) || + (x >= 70 && x <= 76 && y >= 0 && y <= 36) || + (x >= 44 && x <= 50 && y >= 66 && y <= 85) + ) { + return 'cliff'; + } + if ( + (x >= 11 && x <= 26 && y >= 61 && y <= 73) || + (x >= 40 && x <= 56 && y >= 45 && y <= 58) || + (x >= 77 && x <= 93 && y >= 51 && y <= 63) || + (x >= 82 && x <= 96 && y >= 34 && y <= 46) + ) { + return 'village'; + } + if ( + (x >= 24 && x <= 38 && y >= 53 && y <= 64) || + (x >= 65 && x <= 80 && y >= 58 && y <= 70) || + (x >= 83 && x <= 97 && y >= 41 && y <= 53) + ) { + return 'camp'; + } + if ( + (x >= 48 && x <= 65 && y >= 33 && y <= 44) || + (x >= 74 && x <= 94 && y >= 33 && y <= 47) || + (x >= 86 && x <= 98 && y >= 45 && y <= 56) + ) { + return 'fort'; + } + return 'plain'; + }) + ); +} + function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData { return { ...cloneUnitForScenario(unit), diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index f649b55..44a7103 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -883,7 +883,16 @@ const unitTexture: Record = { 'menghuo-fourth-officer-mangyachang': 'unit-rebel-leader', 'menghuo-fourth-officer-dongtuna': 'unit-rebel-leader', 'menghuo-fourth-officer-ahuinan': 'unit-rebel-leader', - 'menghuo-fourth-leader': 'unit-rebel-leader' + 'menghuo-fourth-leader': 'unit-rebel-leader', + 'menghuo-fifth-shaman-a': 'unit-rebel-archer', + 'menghuo-fifth-shaman-b': 'unit-rebel-archer', + 'menghuo-fifth-shaman-c': 'unit-rebel-archer', + 'menghuo-fifth-shaman-d': 'unit-rebel-archer', + 'menghuo-fifth-officer-zhurong': 'unit-rebel-leader', + 'menghuo-fifth-officer-mulu': 'unit-rebel-leader', + 'menghuo-fifth-officer-duosi': 'unit-rebel-leader', + 'menghuo-fifth-officer-mengyou': 'unit-rebel-leader', + 'menghuo-fifth-leader': 'unit-rebel-leader' }; const unitTextureByClass: Partial> = { @@ -2422,7 +2431,48 @@ const enemyAiByUnitId: Record = { 'menghuo-fourth-officer-mangyachang': 'aggressive', 'menghuo-fourth-officer-dongtuna': 'guard', 'menghuo-fourth-officer-ahuinan': 'guard', - 'menghuo-fourth-leader': 'hold' + 'menghuo-fourth-leader': 'hold', + 'menghuo-fifth-scout-a': 'aggressive', + 'menghuo-fifth-scout-b': 'aggressive', + 'menghuo-fifth-scout-c': 'aggressive', + 'menghuo-fifth-scout-d': 'aggressive', + 'menghuo-fifth-scout-e': 'aggressive', + 'menghuo-fifth-scout-f': 'aggressive', + 'menghuo-fifth-vine-a': 'guard', + 'menghuo-fifth-vine-b': 'guard', + 'menghuo-fifth-vine-c': 'guard', + 'menghuo-fifth-vine-d': 'guard', + 'menghuo-fifth-vine-e': 'guard', + 'menghuo-fifth-vine-f': 'guard', + 'menghuo-fifth-archer-a': 'hold', + 'menghuo-fifth-archer-b': 'hold', + 'menghuo-fifth-archer-c': 'hold', + 'menghuo-fifth-archer-d': 'hold', + 'menghuo-fifth-archer-e': 'hold', + 'menghuo-fifth-beast-a': 'aggressive', + 'menghuo-fifth-beast-b': 'aggressive', + 'menghuo-fifth-beast-c': 'aggressive', + 'menghuo-fifth-beast-d': 'aggressive', + 'menghuo-fifth-beast-e': 'aggressive', + 'menghuo-fifth-ambusher-a': 'aggressive', + 'menghuo-fifth-ambusher-b': 'aggressive', + 'menghuo-fifth-ambusher-c': 'aggressive', + 'menghuo-fifth-ambusher-d': 'aggressive', + 'menghuo-fifth-ambusher-e': 'aggressive', + 'menghuo-fifth-ambusher-f': 'aggressive', + 'menghuo-fifth-guard-a': 'guard', + 'menghuo-fifth-guard-b': 'guard', + 'menghuo-fifth-guard-c': 'guard', + 'menghuo-fifth-guard-d': 'guard', + 'menghuo-fifth-shaman-a': 'hold', + 'menghuo-fifth-shaman-b': 'hold', + 'menghuo-fifth-shaman-c': 'hold', + 'menghuo-fifth-shaman-d': 'hold', + 'menghuo-fifth-officer-zhurong': 'aggressive', + 'menghuo-fifth-officer-mulu': 'aggressive', + 'menghuo-fifth-officer-duosi': 'guard', + 'menghuo-fifth-officer-mengyou': 'guard', + 'menghuo-fifth-leader': 'hold' }; const defaultEnemyAiByClass: Partial> = { diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 4f85ddb..bd7a33e 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -27,8 +27,9 @@ import { hanzhongMainRecruitUnits, hanzhongScoutRecruitBonds, hanzhongScoutRecruitUnits, - mengHuoThirdCaptureBonds, mengHuoFourthCaptureBonds, + mengHuoFifthCaptureBonds, + mengHuoThirdCaptureBonds, fanCastleBonds, fanCastleSiegeBonds, jingCollapseBonds, @@ -415,7 +416,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [ 'forty-eighth-battle-meng-huo-main-force', 'forty-ninth-battle-meng-huo-second-capture', 'fiftieth-battle-meng-huo-third-capture', - 'fifty-first-battle-meng-huo-fourth-capture' + 'fifty-first-battle-meng-huo-fourth-capture', + 'fifty-second-battle-meng-huo-fifth-capture' ], nextHints: ['왕업 선언', '번성 포위', '형주 위기', '이릉 화공', '남중 안정', '맹획 회유', '칠종칠금'] } @@ -472,7 +474,8 @@ const campBattleIds = { fortyEighth: 'forty-eighth-battle-meng-huo-main-force', fortyNinth: 'forty-ninth-battle-meng-huo-second-capture', fiftieth: 'fiftieth-battle-meng-huo-third-capture', - fiftyFirst: 'fifty-first-battle-meng-huo-fourth-capture' + fiftyFirst: 'fifty-first-battle-meng-huo-fourth-capture', + fiftySecond: 'fifty-second-battle-meng-huo-fifth-capture' } as const; const defaultRequiredSortieUnitIds = ['liu-bei']; @@ -1089,6 +1092,20 @@ const sortieRulesByBattleId: Partial