diff --git a/docs/roadmap.md b/docs/roadmap.md index 0fedf4f..1c1e738 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -77,15 +77,16 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of - 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 +- Fifty-third battle Meng Huo sixth-capture route, where Meng Huo's remaining pride meets a 106x88 deep-ravine battlefield, with village-trust, trust-road, and hardliner-remnant objectives plus post-battle trust-ledger/final-persuasion 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 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 +- Flow verification script from title through the sixth 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 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 +1. Build the seventh Seven Captures Nanman battle so Meng Huo's final surrender can close the Nanzhong pacification arc without treating it as the campaign ending 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 310e86e..c4e2c4b 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -1739,7 +1739,7 @@ try { if ( progressTabState?.activeTab !== 'progress' || progressTabState.campaignProgress?.completedKnown !== 17 || - progressTabState.campaignProgress?.totalKnown !== 52 || + progressTabState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postBowangProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postChangbanProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postJiangdongProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postRedCliffsProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postFireAttackProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postJingzhouProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postGuiyangProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postWulingProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postChangshaProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postYizhouProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postFuPassProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postLuoProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postLuofengProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postLuoMainProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postMianzhuProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postChengduProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postJiamengProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postYangpingProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postDingjunProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postHanzhongProgressState.campaignProgress?.totalKnown !== 53 || 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 !== 52 || + postKingCouncilProgressState.campaignProgress?.totalKnown !== 53 || postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포' ) { @@ -4812,7 +4812,7 @@ try { if ( postFoundationProgressState?.activeTab !== 'progress' || postFoundationProgressState.campaignProgress?.completedKnown !== 37 || - postFoundationProgressState.campaignProgress?.totalKnown !== 52 || + postFoundationProgressState.campaignProgress?.totalKnown !== 53 || postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전' ) { @@ -4963,7 +4963,7 @@ try { if ( postJingDefenseProgressState?.activeTab !== 'progress' || postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 || - postJingDefenseProgressState.campaignProgress?.totalKnown !== 52 || + postJingDefenseProgressState.campaignProgress?.totalKnown !== 53 || postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' || postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전' ) { @@ -5116,7 +5116,7 @@ try { if ( postFanCastleProgressState?.activeTab !== 'progress' || postFanCastleProgressState.campaignProgress?.completedKnown !== 39 || - postFanCastleProgressState.campaignProgress?.totalKnown !== 52 || + postFanCastleProgressState.campaignProgress?.totalKnown !== 53 || postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' || postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공' ) { @@ -5270,7 +5270,7 @@ try { if ( postHanRiverProgressState?.activeTab !== 'progress' || postHanRiverProgressState.campaignProgress?.completedKnown !== 40 || - postHanRiverProgressState.campaignProgress?.totalKnown !== 52 || + postHanRiverProgressState.campaignProgress?.totalKnown !== 53 || postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' || postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전' ) { @@ -5424,7 +5424,7 @@ try { if ( postFanSiegeProgressState?.activeTab !== 'progress' || postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 || - postFanSiegeProgressState.campaignProgress?.totalKnown !== 52 || + postFanSiegeProgressState.campaignProgress?.totalKnown !== 53 || postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' || postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전' ) { @@ -5586,7 +5586,7 @@ try { if ( postJingRearProgressState?.activeTab !== 'progress' || postJingRearProgressState.campaignProgress?.completedKnown !== 42 || - postJingRearProgressState.campaignProgress?.totalKnown !== 52 || + postJingRearProgressState.campaignProgress?.totalKnown !== 53 || postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' || postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고' ) { @@ -5741,7 +5741,7 @@ try { if ( postGonganProgressState?.activeTab !== 'progress' || postGonganProgressState.campaignProgress?.completedKnown !== 43 || - postGonganProgressState.campaignProgress?.totalKnown !== 52 || + postGonganProgressState.campaignProgress?.totalKnown !== 53 || postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' || postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전' ) { @@ -5899,7 +5899,7 @@ try { if ( postMaichengProgressState?.activeTab !== 'progress' || postMaichengProgressState.campaignProgress?.completedKnown !== 44 || - postMaichengProgressState.campaignProgress?.totalKnown !== 52 || + postMaichengProgressState.campaignProgress?.totalKnown !== 53 || postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' || postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로' ) { @@ -6056,7 +6056,7 @@ try { if ( postYilingProgressState?.activeTab !== 'progress' || postYilingProgressState.campaignProgress?.completedKnown !== 45 || - postYilingProgressState.campaignProgress?.totalKnown !== 52 || + postYilingProgressState.campaignProgress?.totalKnown !== 53 || postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' || postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전' ) { @@ -6214,7 +6214,7 @@ try { if ( postYilingFireProgressState?.activeTab !== 'progress' || postYilingFireProgressState.campaignProgress?.completedKnown !== 46 || - postYilingFireProgressState.campaignProgress?.totalKnown !== 52 || + postYilingFireProgressState.campaignProgress?.totalKnown !== 53 || postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁' ) { @@ -6280,7 +6280,7 @@ try { if ( postBaidiProgressState?.activeTab !== 'progress' || postBaidiProgressState.campaignProgress?.completedKnown !== 46 || - postBaidiProgressState.campaignProgress?.totalKnown !== 52 || + postBaidiProgressState.campaignProgress?.totalKnown !== 53 || postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로' ) { @@ -6441,7 +6441,7 @@ try { if ( postNanzhongProgressState?.activeTab !== 'progress' || postNanzhongProgressState.campaignProgress?.completedKnown !== 47 || - postNanzhongProgressState.campaignProgress?.totalKnown !== 52 || + postNanzhongProgressState.campaignProgress?.totalKnown !== 53 || postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' || postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전' ) { @@ -6603,7 +6603,7 @@ try { if ( postMengHuoProgressState?.activeTab !== 'progress' || postMengHuoProgressState.campaignProgress?.completedKnown !== 48 || - postMengHuoProgressState.campaignProgress?.totalKnown !== 52 || + postMengHuoProgressState.campaignProgress?.totalKnown !== 53 || postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' || postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전' ) { @@ -6765,7 +6765,7 @@ try { if ( postSecondCaptureProgressState?.activeTab !== 'progress' || postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 || - postSecondCaptureProgressState.campaignProgress?.totalKnown !== 52 || + postSecondCaptureProgressState.campaignProgress?.totalKnown !== 53 || postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' || postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전' ) { @@ -6930,7 +6930,7 @@ try { if ( postThirdCaptureProgressState?.activeTab !== 'progress' || postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 || - postThirdCaptureProgressState.campaignProgress?.totalKnown !== 52 || + postThirdCaptureProgressState.campaignProgress?.totalKnown !== 53 || postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' || postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전' ) { @@ -7096,7 +7096,7 @@ try { if ( postFourthCaptureProgressState?.activeTab !== 'progress' || postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 || - postFourthCaptureProgressState.campaignProgress?.totalKnown !== 52 || + postFourthCaptureProgressState.campaignProgress?.totalKnown !== 53 || postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' || postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전' ) { @@ -7262,14 +7262,179 @@ try { if ( postFifthCaptureProgressState?.activeTab !== 'progress' || postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 || - postFifthCaptureProgressState.campaignProgress?.totalKnown !== 52 || + postFifthCaptureProgressState.campaignProgress?.totalKnown !== 53 || postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' || - postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 6차전' ) { - 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)}`); + throw new Error(`Expected post-fifth-capture progress tab to complete the fifty-second battle and point to the sixth capture arc: ${JSON.stringify(postFifthCaptureProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-fifth-capture-progress.png', fullPage: true }); + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const sixthCaptureSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !sixthCaptureSortieState?.sortieVisible || + sixthCaptureSortieState.campaign?.step !== 'fifty-second-camp' || + sixthCaptureSortieState.campTitle !== '칠종칠금 5차전 후 군영' || + sixthCaptureSortieState.sortiePlan?.objectiveLine !== '칠종칠금 6차전 · 맹획 여섯 번째 생포와 마을 신뢰 유지' || + sixthCaptureSortieState.sortiePlan?.maxCount !== 7 || + sixthCaptureSortieState.sortiePlan?.recommendedTotal !== 7 || + !sixthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + !sixthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) || + !sixthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) || + !sixthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'wei-yan' && unit.recommended) || + sixthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected sixth Meng Huo sortie prep to expose village-trust recommendations, Zhuge Liang lock, and the expanded recruited roster: ${JSON.stringify(sixthCaptureSortieState)}`); + } + assertSortieTacticalRoster(sixthCaptureSortieState, [ + '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 sixthCapturePriorityUnits = ['ma-liang', 'huang-quan', 'wang-ping', 'zhao-yun', 'wei-yan', 'ma-dai']; + for (const unitId of sixthCapturePriorityUnits) { + 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 && !sixthCapturePriorityUnits.includes(unit.id) + ); + if (removable) { + await clickSortieRosterUnit(page, removable.id); + } + await clickSortieRosterUnit(page, unitId); + } + } + + const sixthCaptureSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !sixthCapturePriorityUnits.every((unitId) => + sixthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected) + ) || + !sixthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + sixthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') || + sixthCaptureSortieReadyState.sortiePlan?.selectedCount !== 7 || + sixthCaptureSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 + ) { + throw new Error(`Expected sixth Meng Huo sortie to deploy Zhuge Liang, Ma Liang, Huang Quan, Wang Ping, Zhao Yun, Wei Yan, and Ma Dai: ${JSON.stringify(sixthCaptureSortieReadyState)}`); + } + await page.screenshot({ path: 'dist/verification-sixth-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-sixth-capture-story.png', fullPage: true }); + + for (let i = 0; i < 70; i += 1) { + const enteredFiftyThirdBattle = await page.evaluate(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'fifty-third-battle-meng-huo-sixth-capture'; + }); + if (enteredFiftyThirdBattle) { + 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-third-battle-meng-huo-sixth-capture' && state?.battleOutcome === null && state?.phase === 'idle'; + }); + await page.screenshot({ path: 'dist/verification-fifty-third-battle.png', fullPage: true }); + + const fiftyThirdBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + const fiftyThirdEnemies = fiftyThirdBattleState.units.filter((unit) => unit.faction === 'enemy'); + const fiftyThirdAllies = fiftyThirdBattleState.units.filter((unit) => unit.faction === 'ally'); + const fiftyThirdEnemyBehaviors = new Set(fiftyThirdEnemies.map((unit) => unit.ai)); + if ( + fiftyThirdBattleState.camera?.mapWidth !== 106 || + fiftyThirdBattleState.camera?.mapHeight !== 88 || + fiftyThirdBattleState.victoryConditionLabel !== '맹획 여섯 번째 생포와 마을 신뢰 유지' || + !fiftyThirdBattleState.objectives?.some((objective) => objective.id === 'pacify-meng-huo-sixth' && objective.label === '맹획 여섯 번째 생포') || + !fiftyThirdBattleState.objectives?.some((objective) => objective.id === 'village-trust' && objective.label === '마을 신뢰 유지') || + fiftyThirdEnemies.length < 41 || + !fiftyThirdEnemyBehaviors.has('aggressive') || + !fiftyThirdEnemyBehaviors.has('guard') || + !fiftyThirdEnemyBehaviors.has('hold') || + !fiftyThirdEnemies.some((unit) => unit.id === 'menghuo-sixth-leader') || + !fiftyThirdEnemies.some((unit) => unit.id === 'menghuo-sixth-officer-dailai') || + !fiftyThirdEnemies.some((unit) => unit.id === 'menghuo-sixth-officer-zhurong') || + !fiftyThirdEnemies.some((unit) => unit.id === 'menghuo-sixth-officer-mulu') || + !fiftyThirdEnemies.some((unit) => unit.id === 'menghuo-sixth-officer-duosi') || + !fiftyThirdAllies.some((unit) => unit.id === 'zhuge-liang') || + !fiftyThirdAllies.some((unit) => unit.id === 'ma-liang') || + !fiftyThirdAllies.some((unit) => unit.id === 'huang-quan') || + !fiftyThirdAllies.some((unit) => unit.id === 'wang-ping') || + !fiftyThirdAllies.some((unit) => unit.id === 'zhao-yun') || + !fiftyThirdAllies.some((unit) => unit.id === 'wei-yan') || + !fiftyThirdAllies.some((unit) => unit.id === 'ma-dai') || + fiftyThirdAllies.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected fifty-third battle to use the sixth Meng Huo map, village-trust objective, selected recruited officers, and mixed clan AI: ${JSON.stringify(fiftyThirdBattleState)}`); + } + + 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 fiftyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + fiftyThirdCampState?.campBattleId !== 'fifty-third-battle-meng-huo-sixth-capture' || + fiftyThirdCampState.campTitle !== '칠종칠금 6차전 후 군영' || + fiftyThirdCampState.availableDialogueIds?.length !== 3 || + !fiftyThirdCampState.availableDialogueIds.every((id) => id.includes('sixth-capture')) || + fiftyThirdCampState.availableVisitIds?.length !== 2 || + !fiftyThirdCampState.availableVisitIds.every((id) => id.includes('sixth-capture')) || + fiftyThirdCampState.rosterCollection?.total < 21 + ) { + throw new Error(`Expected fifty-third camp to expose sixth capture dialogue/visit sets and preserve the deployable officer roster: ${JSON.stringify(fiftyThirdCampState)}`); + } + await page.screenshot({ path: 'dist/verification-sixth-capture-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postSixthCaptureProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postSixthCaptureProgressState?.activeTab !== 'progress' || + postSixthCaptureProgressState.campaignProgress?.completedKnown !== 53 || + postSixthCaptureProgressState.campaignProgress?.totalKnown !== 53 || + postSixthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 6차전' || + postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + ) { + throw new Error(`Expected post-sixth-capture progress tab to complete the fifty-third battle and pause before the final capture arc: ${JSON.stringify(postSixthCaptureProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-sixth-capture-progress.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); @@ -7301,11 +7466,11 @@ try { activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], camp: window.__HEROS_DEBUG__?.camp?.() ?? null })); - 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)}`); + if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-third-camp') { + throw new Error(`Expected title continue to reopen the latest sixth Meng Huo capture camp: ${JSON.stringify(titleContinueState)}`); } - 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}`); + console.log(`Verified title-to-sixth-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, fifth capture, and sixth capture state at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/assets/images/battle/fifty-third-battle-map.svg b/src/assets/images/battle/fifty-third-battle-map.svg new file mode 100644 index 0000000..d7d2583 --- /dev/null +++ b/src/assets/images/battle/fifty-third-battle-map.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts index 199439f..4324152 100644 --- a/src/game/data/battleMapAssets.ts +++ b/src/game/data/battleMapAssets.ts @@ -4,6 +4,7 @@ import eleventhBattleMapUrl from '../../assets/images/battle/eleventh-battle-map 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 fiftyThirdBattleMapUrl from '../../assets/images/battle/fifty-third-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'; @@ -103,5 +104,6 @@ export const battleMapAssets: Record = { 'battle-map-forty-ninth': fortyNinthBattleMapUrl, 'battle-map-fiftieth': fiftiethBattleMapUrl, 'battle-map-fifty-first': fiftyFirstBattleMapUrl, - 'battle-map-fifty-second': fiftySecondBattleMapUrl + 'battle-map-fifty-second': fiftySecondBattleMapUrl, + 'battle-map-fifty-third': fiftyThirdBattleMapUrl }; diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index fcfb04f..447e1ab 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -11,6 +11,10 @@ import { fiftySecondBattleMap, fiftySecondBattleUnits, fiftySecondBattleVictoryPages, + fiftyThirdBattleBonds, + fiftyThirdBattleMap, + fiftyThirdBattleUnits, + fiftyThirdBattleVictoryPages, firstBattleBonds, fifteenthBattleBonds, fifteenthBattleMap, @@ -265,7 +269,8 @@ export type BattleScenarioId = | 'forty-ninth-battle-meng-huo-second-capture' | 'fiftieth-battle-meng-huo-third-capture' | 'fifty-first-battle-meng-huo-fourth-capture' - | 'fifty-second-battle-meng-huo-fifth-capture'; + | 'fifty-second-battle-meng-huo-fifth-capture' + | 'fifty-third-battle-meng-huo-sixth-capture'; export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory'; @@ -3391,6 +3396,73 @@ export const fiftySecondBattleScenario: BattleScenarioDefinition = { nextCampScene: 'CampScene' }; +export const fiftyThirdBattleScenario: BattleScenarioDefinition = { + id: 'fifty-third-battle-meng-huo-sixth-capture', + title: '칠종칠금 6차전', + victoryConditionLabel: '맹획 여섯 번째 생포와 마을 신뢰 유지', + defeatConditionLabel: '제갈량 퇴각', + openingObjectiveLines: [ + '맹획은 남은 자존심을 내세워 깊은 계곡의 부족들을 다시 불러 모았습니다. 이번 전투는 적 본영을 누르면서도 마을 신뢰를 깨뜨리지 않는 것이 핵심입니다.', + '강경파 잔당은 신뢰 장부와 계곡 퇴로를 동시에 흔들려 합니다. 마을, 길목, 요새를 함께 지키면 마지막 설득으로 이어질 근거가 생깁니다.', + '제갈량은 반드시 생존해야 합니다. 마량과 황권으로 말과 장부를 남기고, 왕평과 마대로 계곡 길을 지키며, 조운과 위연으로 잔당 추격을 끊으십시오.' + ], + map: fiftyThirdBattleMap, + units: fiftyThirdBattleUnits, + bonds: fiftyThirdBattleBonds, + mapTextureKey: 'battle-map-fifty-third', + leaderUnitId: 'menghuo-sixth-leader', + quickVictoryTurnLimit: 102, + baseVictoryGold: 13620, + objectives: [ + { + id: 'pacify-meng-huo-sixth', + kind: 'pacify-leader', + label: '맹획 여섯 번째 생포', + rewardGold: 9180, + unitId: 'menghuo-sixth-leader' + }, + { + id: 'zhuge-liang', + kind: 'keep-unit-alive', + label: '제갈량 생존', + rewardGold: 2320, + unitId: 'zhuge-liang' + }, + { + id: 'trust-road', + kind: 'secure-terrain', + label: '신뢰 퇴로 확보', + rewardGold: 4560, + terrain: 'road' + }, + { + id: 'village-trust', + kind: 'secure-terrain', + label: '마을 신뢰 유지', + rewardGold: 4240, + terrain: 'village' + }, + { + id: 'hardliner-remnants', + kind: 'secure-terrain', + label: '강경파 잔당 요새 제압', + rewardGold: 4040, + terrain: 'fort' + }, + { + id: 'quick', + kind: 'quick-victory', + label: '102턴 이내 여섯 번째 생포', + rewardGold: 3060, + maxTurn: 102 + } + ], + defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }], + itemRewards: ['콩 74', '상처약 58', '탁주 26', '남중 신뢰 장부 1', '계곡 회유 표식 1'], + victoryPages: fiftyThirdBattleVictoryPages, + nextCampScene: 'CampScene' +}; + export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id; export const battleScenarios: Record = { @@ -3445,7 +3517,8 @@ export const battleScenarios: Record 'forty-ninth-battle-meng-huo-second-capture': fortyNinthBattleScenario, 'fiftieth-battle-meng-huo-third-capture': fiftiethBattleScenario, 'fifty-first-battle-meng-huo-fourth-capture': fiftyFirstBattleScenario, - 'fifty-second-battle-meng-huo-fifth-capture': fiftySecondBattleScenario + 'fifty-second-battle-meng-huo-fifth-capture': fiftySecondBattleScenario, + 'fifty-third-battle-meng-huo-sixth-capture': fiftyThirdBattleScenario }; export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index 3dbc6ec..10c9632 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -7,6 +7,7 @@ import { fiftiethBattleScenario, fiftyFirstBattleScenario, fiftySecondBattleScenario, + fiftyThirdBattleScenario, fortiethBattleScenario, fortyFirstBattleScenario, fortySecondBattleScenario, @@ -68,6 +69,8 @@ import { fiftyFirstBattleVictoryPages, fiftySecondBattleIntroPages, fiftySecondBattleVictoryPages, + fiftyThirdBattleIntroPages, + fiftyThirdBattleVictoryPages, fortiethBattleIntroPages, fortiethBattleVictoryPages, fortyFirstBattleIntroPages, @@ -759,13 +762,24 @@ const sortieFlows: Record = { }, [fiftySecondBattleScenario.id]: { afterBattleId: fiftySecondBattleScenario.id, - eyebrow: '다음 장 준비', - title: '여섯 번째 회유의 길', + eyebrow: '다음 전장', + title: fiftyThirdBattleScenario.title, description: - '맹획은 다섯 번째로 붙잡혔고, 강경파 호족과 물러선 호족의 말이 갈라졌습니다. 다음 장에서는 남중의 자존심을 더 직접적으로 마주하면서도, 지난 회유의 약속을 무너뜨리지 않는 길을 준비합니다.', - rewardHint: '다음 장: 칠종칠금 6차전과 마지막 설득 준비 중', - pages: fiftySecondBattleVictoryPages, - unavailableNotice: '칠종칠금 6차전은 다음 작업에서 이어집니다. 지금은 다섯 번째 생포 뒤의 강경파 분리 장부와 회유 깃발을 정비할 수 있습니다.' + '맹획은 다섯 번째로 붙잡혔고, 강경파 호족과 물러선 호족의 말이 갈라졌습니다. 이제 남중의 자존심을 더 직접적으로 마주하면서도, 지난 회유의 약속을 무너뜨리지 않는 여섯 번째 길을 엽니다.', + rewardHint: `예상 보상: ${fiftyThirdBattleScenario.title} 개방 / 남중 신뢰 장부`, + nextBattleId: fiftyThirdBattleScenario.id, + campaignStep: 'fifty-third-battle', + pages: [...fiftySecondBattleVictoryPages, ...fiftyThirdBattleIntroPages] + }, + [fiftyThirdBattleScenario.id]: { + afterBattleId: fiftyThirdBattleScenario.id, + eyebrow: '다음 장 준비', + title: '칠종칠금의 마지막 문턱', + description: + '맹획은 여섯 번째로 붙잡혔고, 남중의 마을들은 촉한군의 약속을 직접 보았습니다. 다음 장에서는 칠종칠금의 마지막 설득과 남중 안정의 결말을 준비합니다.', + rewardHint: '다음 장: 칠종칠금 7차전과 남중 완전 회유 준비 중', + pages: fiftyThirdBattleVictoryPages, + unavailableNotice: '칠종칠금 7차전은 다음 작업에서 이어집니다. 지금은 여섯 번째 생포 뒤의 신뢰 장부와 계곡 회유 표식을 정비할 수 있습니다.' } }; diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 2355806..201b767 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -3403,6 +3403,59 @@ export const fiftySecondBattleVictoryPages: StoryPage[] = [ } ]; +export const fiftyThirdBattleIntroPages: StoryPage[] = [ + { + id: 'fifty-third-pride-remains', + bgm: 'story-dark', + chapter: '남은 자존심', + background: 'story-militia', + speaker: '마량', + text: '다섯 번째 생포 뒤 강경파와 물러설 호족의 말이 갈라졌습니다. 그러나 맹획은 아직 남중의 자존심을 내세우며, 마지막으로 깊은 계곡의 부족들을 불러 모았습니다.' + }, + { + id: 'fifty-third-sixth-capture-plan', + bgm: 'battle-prep', + chapter: '여섯 번째 생포', + background: 'story-three-heroes', + speaker: '제갈량', + text: '이번에는 적을 밀어붙이는 힘보다 멈춰 설 줄 아는 군율이 중요합니다. 마을 신뢰를 지키고 강경파 잔당의 길을 끊되, 맹획에게 남중 전체가 더는 같은 마음이 아님을 보이십시오.' + }, + { + id: 'fifty-third-sortie', + bgm: 'battle-prep', + chapter: '칠종칠금 6차전', + background: 'story-sortie', + speaker: '황권', + text: '계곡 길은 넓어 보이나 물길과 절벽이 칼처럼 갈라져 있습니다. 장부를 들 장수, 산길을 지킬 장수, 추격을 멈출 장수를 따로 세워야 여섯 번째 생포가 설득으로 남습니다.' + } +]; + +export const fiftyThirdBattleVictoryPages: StoryPage[] = [ + { + id: 'fifty-third-victory-sixth-capture', + bgm: 'militia-theme', + chapter: '여섯 번째 생포', + background: 'story-sortie', + text: '맹획은 여섯 번째로 붙잡혔고, 깊은 계곡의 부족들은 촉한군이 강경파 잔당을 누르면서도 마을을 함부로 밟지 않는 모습을 보았습니다.' + }, + { + id: 'fifty-third-victory-trust-ledger', + bgm: 'story-dark', + chapter: '신뢰의 장부', + background: 'story-militia', + speaker: '마량', + text: '처음에는 두려워 숨던 촌장들이 이제는 직접 이름을 말했습니다. 두려움의 장부가 아니라 신뢰의 장부가 만들어졌으니, 마지막 설득의 말도 조금은 닿을 수 있습니다.' + }, + { + id: 'fifty-third-next-final-capture', + bgm: 'story-dark', + chapter: '마지막 설득 앞', + background: 'story-three-heroes', + speaker: '제갈량', + text: '여섯 번 잡고도 맹획은 쉽게 항복하지 않을 것입니다. 그러나 이제 남중의 눈은 맹획의 고집뿐 아니라 촉한군의 약속도 함께 보고 있습니다. 다음은 칠종칠금의 마지막 문턱입니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, @@ -3860,6 +3913,12 @@ export const fiftySecondBattleMap: BattleMap = { terrain: createFiftySecondBattleTerrain() }; +export const fiftyThirdBattleMap: BattleMap = { + width: 106, + height: 88, + terrain: createFiftyThirdBattleTerrain() +}; + export const firstBattleUnits: UnitData[] = [ { id: 'liu-bei', @@ -16334,6 +16393,95 @@ export const fiftySecondBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('menghuo-fifth-leader', '맹획', '남만왕', 'rebelLeader', 162, 1100, 270, 98, 45) ]; +const fiftyThirdBattleAllyPositions: Record = { + 'guan-yu': { x: 16, y: 67 }, + 'zhang-fei': { x: 21, y: 69 }, + 'jian-yong': { x: 12, y: 74 }, + 'mi-zhu': { x: 14, y: 72 }, + 'sun-qian': { x: 18, y: 76 }, + 'zhao-yun': { x: 26, y: 64 }, + 'zhuge-liang': { x: 15, y: 64 }, + 'ma-liang': { x: 19, y: 61 }, + 'yi-ji': { x: 12, y: 79 }, + 'gong-zhi': { x: 22, y: 77 }, + 'huang-zhong': { x: 29, y: 65 }, + 'wei-yan': { x: 34, y: 69 }, + 'pang-tong': { x: 17, y: 58 }, + 'fa-zheng': { x: 23, y: 59 }, + 'wu-yi': { x: 36, y: 74 }, + 'yan-yan': { x: 39, y: 67 }, + 'li-yan': { x: 28, y: 57 }, + 'huang-quan': { x: 12, y: 62 }, + 'ma-chao': { x: 41, y: 64 }, + 'ma-dai': { x: 44, y: 69 }, + 'wang-ping': { x: 31, y: 60 } +}; + +export const fiftyThirdBattleUnits: 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, fiftyThirdBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })), + createThirtyFirstEnemyUnit('menghuo-sixth-scout-a', '남중 잔심 척후', '서쪽 계곡 정찰', 'bandit', 160, 716, 214, 48, 40), + createThirtyFirstEnemyUnit('menghuo-sixth-scout-b', '남중 잔심 척후', '물길 정찰', 'bandit', 160, 716, 214, 64, 38), + createThirtyFirstEnemyUnit('menghuo-sixth-scout-c', '남중 잔심 척후', '마을 감시', 'bandit', 161, 724, 216, 80, 55), + createThirtyFirstEnemyUnit('menghuo-sixth-scout-d', '남중 잔심 척후', '동쪽 절벽 감시', 'bandit', 161, 724, 216, 96, 70), + createThirtyFirstEnemyUnit('menghuo-sixth-scout-e', '남중 잔심 척후', '북쪽 산문 감시', 'bandit', 162, 732, 218, 57, 28), + createThirtyFirstEnemyUnit('menghuo-sixth-scout-f', '남중 잔심 척후', '신뢰로 감시', 'bandit', 162, 732, 218, 72, 76), + createThirtyFirstEnemyUnit('menghuo-sixth-vine-a', '잔당 등갑병', '계곡 등갑병', 'yellowTurban', 161, 1002, 252, 54, 49), + createThirtyFirstEnemyUnit('menghuo-sixth-vine-b', '잔당 등갑병', '불씨 등갑병', 'yellowTurban', 162, 1014, 254, 67, 52), + createThirtyFirstEnemyUnit('menghuo-sixth-vine-c', '잔당 등갑병', '마을 등갑병', 'yellowTurban', 162, 1014, 254, 80, 62), + createThirtyFirstEnemyUnit('menghuo-sixth-vine-d', '잔당 등갑병', '본영 등갑병', 'yellowTurban', 163, 1026, 256, 92, 54), + createThirtyFirstEnemyUnit('menghuo-sixth-vine-e', '잔당 등갑병', '후위 등갑병', 'yellowTurban', 163, 1026, 256, 100, 46), + createThirtyFirstEnemyUnit('menghuo-sixth-vine-f', '잔당 등갑병', '산문 등갑병', 'yellowTurban', 164, 1038, 258, 88, 35), + createThirtyFirstEnemyUnit('menghuo-sixth-archer-a', '남만 절벽 궁병', '독천 궁병', 'archer', 160, 768, 226, 50, 32), + createThirtyFirstEnemyUnit('menghuo-sixth-archer-b', '남만 절벽 궁병', '절벽 궁병', 'archer', 161, 776, 228, 66, 30), + createThirtyFirstEnemyUnit('menghuo-sixth-archer-c', '남만 절벽 궁병', '마을 궁병', 'archer', 161, 776, 228, 78, 48), + createThirtyFirstEnemyUnit('menghuo-sixth-archer-d', '남만 절벽 궁병', '물길 궁병', 'archer', 162, 784, 230, 90, 66), + createThirtyFirstEnemyUnit('menghuo-sixth-archer-e', '남만 절벽 궁병', '본영 궁병', 'archer', 162, 784, 230, 99, 59), + createThirtyFirstEnemyUnit('menghuo-sixth-beast-a', '남만 맹수 결사대', '강가 맹수대', 'cavalry', 162, 1018, 256, 60, 42), + createThirtyFirstEnemyUnit('menghuo-sixth-beast-b', '남만 맹수 결사대', '물길 맹수대', 'cavalry', 162, 1018, 256, 73, 44), + createThirtyFirstEnemyUnit('menghuo-sixth-beast-c', '남만 맹수 결사대', '산채 맹수대', 'cavalry', 163, 1030, 258, 85, 57), + createThirtyFirstEnemyUnit('menghuo-sixth-beast-d', '남만 맹수 결사대', '추격 맹수대', 'cavalry', 163, 1030, 258, 97, 63), + createThirtyFirstEnemyUnit('menghuo-sixth-beast-e', '남만 맹수 결사대', '신뢰로 차단대', 'cavalry', 164, 1042, 260, 71, 72), + createThirtyFirstEnemyUnit('menghuo-sixth-ambusher-a', '잔당 매복병', '계곡 매복병', 'bandit', 161, 736, 220, 41, 55), + createThirtyFirstEnemyUnit('menghuo-sixth-ambusher-b', '잔당 매복병', '숲속 매복병', 'bandit', 162, 744, 222, 57, 58), + createThirtyFirstEnemyUnit('menghuo-sixth-ambusher-c', '잔당 매복병', '강가 매복병', 'bandit', 162, 744, 222, 70, 71), + createThirtyFirstEnemyUnit('menghuo-sixth-ambusher-d', '잔당 매복병', '후방 차단대', 'bandit', 163, 752, 224, 90, 78), + createThirtyFirstEnemyUnit('menghuo-sixth-ambusher-e', '잔당 매복병', '서쪽 차단대', 'bandit', 163, 752, 224, 36, 68), + createThirtyFirstEnemyUnit('menghuo-sixth-ambusher-f', '잔당 매복병', '신뢰로 감시대', 'bandit', 164, 760, 226, 77, 76), + createThirtyFirstEnemyUnit('menghuo-sixth-guard-a', '맹획 육차 친위', '포로장 친위대', 'yellowTurban', 164, 1054, 262, 74, 46), + createThirtyFirstEnemyUnit('menghuo-sixth-guard-b', '맹획 육차 친위', '산채 친위대', 'yellowTurban', 164, 1054, 262, 87, 52), + createThirtyFirstEnemyUnit('menghuo-sixth-guard-c', '맹획 육차 친위', '본영 친위대', 'yellowTurban', 165, 1066, 264, 98, 49), + createThirtyFirstEnemyUnit('menghuo-sixth-guard-d', '맹획 육차 친위', '계곡 친위대', 'yellowTurban', 165, 1066, 264, 93, 39), + createThirtyFirstEnemyUnit('menghuo-sixth-shaman-a', '깊은 독천 제사장', '물길 제사장', 'strategist', 163, 816, 238, 66, 40), + createThirtyFirstEnemyUnit('menghuo-sixth-shaman-b', '깊은 독천 제사장', '강가 제사장', 'strategist', 164, 824, 240, 81, 53), + createThirtyFirstEnemyUnit('menghuo-sixth-shaman-c', '깊은 독천 제사장', '산채 제사장', 'strategist', 164, 824, 240, 92, 42), + createThirtyFirstEnemyUnit('menghuo-sixth-shaman-d', '깊은 독천 제사장', '계곡 제사장', 'strategist', 165, 832, 242, 86, 61), + createThirtyFirstEnemyUnit('menghuo-sixth-officer-zhurong', '축융 부인', '남중 화전장', 'rebelLeader', 166, 1098, 272, 84, 69), + createThirtyFirstEnemyUnit('menghuo-sixth-officer-mulu', '목록대왕', '맹수대왕', 'rebelLeader', 166, 1098, 272, 90, 41), + createThirtyFirstEnemyUnit('menghuo-sixth-officer-duosi', '타사대왕', '독천 호족장', 'rebelLeader', 165, 1078, 266, 74, 64), + createThirtyFirstEnemyUnit('menghuo-sixth-officer-mengyou', '맹우', '맹획의 아우', 'rebelLeader', 165, 1078, 266, 70, 56), + createThirtyFirstEnemyUnit('menghuo-sixth-officer-dailai', '대래동주', '깊은 산채 호족', 'rebelLeader', 165, 1078, 266, 83, 71), + createThirtyFirstEnemyUnit('menghuo-sixth-leader', '맹획', '남만왕', 'rebelLeader', 168, 1140, 280, 100, 47) +]; + export const firstBattleBonds: BattleBond[] = [ { id: 'liu-bei__guan-yu', @@ -17327,6 +17475,35 @@ export const mengHuoFifthCaptureBonds: BattleBond[] = [ export const fiftySecondBattleBonds: BattleBond[] = [...fiftyFirstBattleBonds, ...mengHuoFifthCaptureBonds].map( cloneBattleBondForScenario ); +export const mengHuoSixthCaptureBonds: BattleBond[] = [ + { + id: 'zhuge-liang__ma-liang_sixth-capture', + unitIds: ['zhuge-liang', 'ma-liang'], + title: '자존심을 남기는 말', + level: 55, + exp: 0, + description: '제갈량의 큰 회유와 마량의 부드러운 말은 맹획의 체면을 완전히 짓밟지 않으면서도 남중의 마음을 돌린다.' + }, + { + id: 'huang-quan__wang-ping_sixth-capture', + unitIds: ['huang-quan', 'wang-ping'], + title: '신뢰 장부와 계곡 길', + level: 51, + exp: 0, + description: '황권이 신뢰 장부를 정리하고 왕평이 계곡 퇴로를 지키면, 여섯 번째 생포도 보복이 아니라 약속으로 남는다.' + }, + { + id: 'zhao-yun__wei-yan_sixth-capture', + unitIds: ['zhao-yun', 'wei-yan'], + title: '빠른 차단과 절제', + level: 47, + exp: 0, + description: '조운의 깨끗한 기동과 위연의 거친 돌파가 균형을 맞추면, 잔당 추격을 막으면서도 마을 신뢰를 잃지 않는다.' + } +]; +export const fiftyThirdBattleBonds: BattleBond[] = [...fiftySecondBattleBonds, ...mengHuoSixthCaptureBonds].map( + cloneBattleBondForScenario +); function createEighthBattleTerrain(): TerrainType[][] { return Array.from({ length: 22 }, (_, y) => @@ -20041,6 +20218,80 @@ function createFiftySecondBattleTerrain(): TerrainType[][] { ); } +function createFiftyThirdBattleTerrain(): TerrainType[][] { + return Array.from({ length: 88 }, (_, y) => + Array.from({ length: 106 }, (_, x): TerrainType => { + if ( + (x >= 0 && x <= 105 && y >= 80) || + (x >= 67 && x <= 73 && y >= 0 && y <= 82) || + (x >= 6 && x <= 34 && y >= 71 && y <= 87) || + (x >= 80 && x <= 98 && y >= 62 && y <= 80) + ) { + return 'river'; + } + if ( + (x >= 10 && x <= 101 && y >= 67 && y <= 73) || + (x >= 29 && x <= 78 && y >= 45 && y <= 52) || + (x >= 74 && x <= 101 && y >= 37 && y <= 43) || + (x >= 40 && x <= 49 && y >= 42 && y <= 76) || + (x >= 80 && x <= 86 && y >= 51 && y <= 84) || + (x >= 18 && x <= 54 && y >= 59 && y <= 63) || + (x >= 53 && x <= 67 && y >= 54 && y <= 59) + ) { + return 'road'; + } + if ( + (x >= 18 && x <= 76 && y >= 22 && y <= 78) || + (x >= 57 && x <= 100 && y >= 30 && y <= 74) || + (x >= 2 && x <= 41 && y >= 52 && y <= 78) || + (x >= 82 && x <= 105 && y >= 44 && y <= 77) + ) { + return 'forest'; + } + if ( + (x >= 0 && x <= 33 && y <= 41) || + (x >= 35 && x <= 72 && y <= 31) || + (x >= 75 && x <= 105 && y <= 40) || + (x >= 90 && x <= 105 && y >= 36 && y <= 59) + ) { + return 'hill'; + } + if ( + (x <= 2 && y <= 78) || + (x >= 103 && y <= 74) || + (x >= 32 && x <= 38 && y >= 0 && y <= 43) || + (x >= 72 && x <= 78 && y >= 0 && y <= 37) || + (x >= 46 && x <= 52 && y >= 68 && y <= 87) + ) { + return 'cliff'; + } + if ( + (x >= 12 && x <= 28 && y >= 63 && y <= 75) || + (x >= 41 && x <= 58 && y >= 46 && y <= 60) || + (x >= 79 && x <= 95 && y >= 52 && y <= 65) || + (x >= 84 && x <= 98 && y >= 35 && y <= 47) + ) { + return 'village'; + } + if ( + (x >= 25 && x <= 40 && y >= 55 && y <= 66) || + (x >= 67 && x <= 82 && y >= 60 && y <= 72) || + (x >= 85 && x <= 100 && y >= 42 && y <= 55) + ) { + return 'camp'; + } + if ( + (x >= 49 && x <= 67 && y >= 34 && y <= 45) || + (x >= 76 && x <= 97 && y >= 34 && y <= 48) || + (x >= 88 && x <= 101 && y >= 46 && y <= 58) + ) { + 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 44a7103..98ec4e6 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -892,7 +892,17 @@ const unitTexture: Record = { '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' + 'menghuo-fifth-leader': 'unit-rebel-leader', + 'menghuo-sixth-shaman-a': 'unit-rebel-archer', + 'menghuo-sixth-shaman-b': 'unit-rebel-archer', + 'menghuo-sixth-shaman-c': 'unit-rebel-archer', + 'menghuo-sixth-shaman-d': 'unit-rebel-archer', + 'menghuo-sixth-officer-zhurong': 'unit-rebel-leader', + 'menghuo-sixth-officer-mulu': 'unit-rebel-leader', + 'menghuo-sixth-officer-duosi': 'unit-rebel-leader', + 'menghuo-sixth-officer-mengyou': 'unit-rebel-leader', + 'menghuo-sixth-officer-dailai': 'unit-rebel-leader', + 'menghuo-sixth-leader': 'unit-rebel-leader' }; const unitTextureByClass: Partial> = { @@ -2472,7 +2482,49 @@ const enemyAiByUnitId: Record = { 'menghuo-fifth-officer-mulu': 'aggressive', 'menghuo-fifth-officer-duosi': 'guard', 'menghuo-fifth-officer-mengyou': 'guard', - 'menghuo-fifth-leader': 'hold' + 'menghuo-fifth-leader': 'hold', + 'menghuo-sixth-scout-a': 'aggressive', + 'menghuo-sixth-scout-b': 'aggressive', + 'menghuo-sixth-scout-c': 'aggressive', + 'menghuo-sixth-scout-d': 'aggressive', + 'menghuo-sixth-scout-e': 'aggressive', + 'menghuo-sixth-scout-f': 'aggressive', + 'menghuo-sixth-vine-a': 'guard', + 'menghuo-sixth-vine-b': 'guard', + 'menghuo-sixth-vine-c': 'guard', + 'menghuo-sixth-vine-d': 'guard', + 'menghuo-sixth-vine-e': 'guard', + 'menghuo-sixth-vine-f': 'guard', + 'menghuo-sixth-archer-a': 'hold', + 'menghuo-sixth-archer-b': 'hold', + 'menghuo-sixth-archer-c': 'hold', + 'menghuo-sixth-archer-d': 'hold', + 'menghuo-sixth-archer-e': 'hold', + 'menghuo-sixth-beast-a': 'aggressive', + 'menghuo-sixth-beast-b': 'aggressive', + 'menghuo-sixth-beast-c': 'aggressive', + 'menghuo-sixth-beast-d': 'aggressive', + 'menghuo-sixth-beast-e': 'aggressive', + 'menghuo-sixth-ambusher-a': 'aggressive', + 'menghuo-sixth-ambusher-b': 'aggressive', + 'menghuo-sixth-ambusher-c': 'aggressive', + 'menghuo-sixth-ambusher-d': 'aggressive', + 'menghuo-sixth-ambusher-e': 'aggressive', + 'menghuo-sixth-ambusher-f': 'aggressive', + 'menghuo-sixth-guard-a': 'guard', + 'menghuo-sixth-guard-b': 'guard', + 'menghuo-sixth-guard-c': 'guard', + 'menghuo-sixth-guard-d': 'guard', + 'menghuo-sixth-shaman-a': 'hold', + 'menghuo-sixth-shaman-b': 'hold', + 'menghuo-sixth-shaman-c': 'hold', + 'menghuo-sixth-shaman-d': 'hold', + 'menghuo-sixth-officer-zhurong': 'aggressive', + 'menghuo-sixth-officer-mulu': 'aggressive', + 'menghuo-sixth-officer-duosi': 'guard', + 'menghuo-sixth-officer-mengyou': 'guard', + 'menghuo-sixth-officer-dailai': 'aggressive', + 'menghuo-sixth-leader': 'hold' }; const defaultEnemyAiByClass: Partial> = { diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index bd7a33e..51010f4 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -29,6 +29,7 @@ import { hanzhongScoutRecruitUnits, mengHuoFourthCaptureBonds, mengHuoFifthCaptureBonds, + mengHuoSixthCaptureBonds, mengHuoThirdCaptureBonds, fanCastleBonds, fanCastleSiegeBonds, @@ -417,7 +418,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [ 'forty-ninth-battle-meng-huo-second-capture', 'fiftieth-battle-meng-huo-third-capture', 'fifty-first-battle-meng-huo-fourth-capture', - 'fifty-second-battle-meng-huo-fifth-capture' + 'fifty-second-battle-meng-huo-fifth-capture', + 'fifty-third-battle-meng-huo-sixth-capture' ], nextHints: ['왕업 선언', '번성 포위', '형주 위기', '이릉 화공', '남중 안정', '맹획 회유', '칠종칠금'] } @@ -475,7 +477,8 @@ const campBattleIds = { fortyNinth: 'forty-ninth-battle-meng-huo-second-capture', fiftieth: 'fiftieth-battle-meng-huo-third-capture', fiftyFirst: 'fifty-first-battle-meng-huo-fourth-capture', - fiftySecond: 'fifty-second-battle-meng-huo-fifth-capture' + fiftySecond: 'fifty-second-battle-meng-huo-fifth-capture', + fiftyThird: 'fifty-third-battle-meng-huo-sixth-capture' } as const; const defaultRequiredSortieUnitIds = ['liu-bei']; @@ -1106,6 +1109,20 @@ const sortieRulesByBattleId: Partial