diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs index ae94224..c7ddc96 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/'; @@ -1804,7 +1804,7 @@ try { if ( progressTabState?.activeTab !== 'progress' || progressTabState.campaignProgress?.completedKnown !== 17 || - progressTabState.campaignProgress?.totalKnown !== 63 || + progressTabState.campaignProgress?.totalKnown !== 64 || progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' || progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' || progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전' @@ -1952,7 +1952,7 @@ try { if ( postBowangProgressState?.activeTab !== 'progress' || postBowangProgressState.campaignProgress?.completedKnown !== 18 || - postBowangProgressState.campaignProgress?.totalKnown !== 63 || + postBowangProgressState.campaignProgress?.totalKnown !== 64 || postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' || postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로' @@ -2087,7 +2087,7 @@ try { if ( postChangbanProgressState?.activeTab !== 'progress' || postChangbanProgressState.campaignProgress?.completedKnown !== 19 || - postChangbanProgressState.campaignProgress?.totalKnown !== 63 || + postChangbanProgressState.campaignProgress?.totalKnown !== 64 || postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' || postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로' @@ -2222,7 +2222,7 @@ try { if ( postJiangdongProgressState?.activeTab !== 'progress' || postJiangdongProgressState.campaignProgress?.completedKnown !== 20 || - postJiangdongProgressState.campaignProgress?.totalKnown !== 63 || + postJiangdongProgressState.campaignProgress?.totalKnown !== 64 || postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' || postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전' @@ -2359,7 +2359,7 @@ try { if ( postRedCliffsProgressState?.activeTab !== 'progress' || postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 || - postRedCliffsProgressState.campaignProgress?.totalKnown !== 63 || + postRedCliffsProgressState.campaignProgress?.totalKnown !== 64 || postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' || postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전' @@ -2496,7 +2496,7 @@ try { if ( postFireAttackProgressState?.activeTab !== 'progress' || postFireAttackProgressState.campaignProgress?.completedKnown !== 22 || - postFireAttackProgressState.campaignProgress?.totalKnown !== 63 || + postFireAttackProgressState.campaignProgress?.totalKnown !== 64 || postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' || postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전' @@ -2636,7 +2636,7 @@ try { if ( postJingzhouProgressState?.activeTab !== 'progress' || postJingzhouProgressState.campaignProgress?.completedKnown !== 23 || - postJingzhouProgressState.campaignProgress?.totalKnown !== 63 || + postJingzhouProgressState.campaignProgress?.totalKnown !== 64 || postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' || postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전' @@ -2775,7 +2775,7 @@ try { if ( postGuiyangProgressState?.activeTab !== 'progress' || postGuiyangProgressState.campaignProgress?.completedKnown !== 24 || - postGuiyangProgressState.campaignProgress?.totalKnown !== 63 || + postGuiyangProgressState.campaignProgress?.totalKnown !== 64 || postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' || postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전' @@ -2915,7 +2915,7 @@ try { if ( postWulingProgressState?.activeTab !== 'progress' || postWulingProgressState.campaignProgress?.completedKnown !== 25 || - postWulingProgressState.campaignProgress?.totalKnown !== 63 || + postWulingProgressState.campaignProgress?.totalKnown !== 64 || postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' || postWulingProgressState.campaignProgress?.nextBattleTitle !== '장사 노장 대면전' @@ -3061,7 +3061,7 @@ try { if ( postChangshaProgressState?.activeTab !== 'progress' || postChangshaProgressState.campaignProgress?.completedKnown !== 26 || - postChangshaProgressState.campaignProgress?.totalKnown !== 63 || + postChangshaProgressState.campaignProgress?.totalKnown !== 64 || postChangshaProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postChangshaProgressState.campaignProgress?.latestBattleTitle !== '장사 노장 대면전' || postChangshaProgressState.campaignProgress?.nextBattleTitle !== '익주 원군로' @@ -3203,7 +3203,7 @@ try { if ( postYizhouProgressState?.activeTab !== 'progress' || postYizhouProgressState.campaignProgress?.completedKnown !== 27 || - postYizhouProgressState.campaignProgress?.totalKnown !== 63 || + postYizhouProgressState.campaignProgress?.totalKnown !== 64 || postYizhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postYizhouProgressState.campaignProgress?.latestBattleTitle !== '익주 원군로' || postYizhouProgressState.campaignProgress?.nextBattleTitle !== '부수관 진입전' @@ -3347,7 +3347,7 @@ try { if ( postFuPassProgressState?.activeTab !== 'progress' || postFuPassProgressState.campaignProgress?.completedKnown !== 28 || - postFuPassProgressState.campaignProgress?.totalKnown !== 63 || + postFuPassProgressState.campaignProgress?.totalKnown !== 64 || postFuPassProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postFuPassProgressState.campaignProgress?.latestBattleTitle !== '부수관 진입전' || postFuPassProgressState.campaignProgress?.nextBattleTitle !== '낙성 외곽전' @@ -3493,7 +3493,7 @@ try { if ( postLuoProgressState?.activeTab !== 'progress' || postLuoProgressState.campaignProgress?.completedKnown !== 29 || - postLuoProgressState.campaignProgress?.totalKnown !== 63 || + postLuoProgressState.campaignProgress?.totalKnown !== 64 || postLuoProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuoProgressState.campaignProgress?.latestBattleTitle !== '낙성 외곽전' || postLuoProgressState.campaignProgress?.nextBattleTitle !== '낙봉파 매복전' @@ -3636,7 +3636,7 @@ try { if ( postLuofengProgressState?.activeTab !== 'progress' || postLuofengProgressState.campaignProgress?.completedKnown !== 30 || - postLuofengProgressState.campaignProgress?.totalKnown !== 63 || + postLuofengProgressState.campaignProgress?.totalKnown !== 64 || postLuofengProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuofengProgressState.campaignProgress?.latestBattleTitle !== '낙봉파 매복전' || postLuofengProgressState.campaignProgress?.nextBattleTitle !== '낙성 본성 공략' @@ -3785,7 +3785,7 @@ try { if ( postLuoMainProgressState?.activeTab !== 'progress' || postLuoMainProgressState.campaignProgress?.completedKnown !== 31 || - postLuoMainProgressState.campaignProgress?.totalKnown !== 63 || + postLuoMainProgressState.campaignProgress?.totalKnown !== 64 || postLuoMainProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuoMainProgressState.campaignProgress?.latestBattleTitle !== '낙성 본성 공략' || postLuoMainProgressState.campaignProgress?.nextBattleTitle !== '면죽관 압박전' @@ -3935,7 +3935,7 @@ try { if ( postMianzhuProgressState?.activeTab !== 'progress' || postMianzhuProgressState.campaignProgress?.completedKnown !== 32 || - postMianzhuProgressState.campaignProgress?.totalKnown !== 63 || + postMianzhuProgressState.campaignProgress?.totalKnown !== 64 || postMianzhuProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postMianzhuProgressState.campaignProgress?.latestBattleTitle !== '면죽관 압박전' || postMianzhuProgressState.campaignProgress?.nextBattleTitle !== '성도 항복 권고전' @@ -4086,7 +4086,7 @@ try { if ( postChengduProgressState?.activeTab !== 'progress' || postChengduProgressState.campaignProgress?.completedKnown !== 33 || - postChengduProgressState.campaignProgress?.totalKnown !== 63 || + postChengduProgressState.campaignProgress?.totalKnown !== 64 || postChengduProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postChengduProgressState.campaignProgress?.latestBattleTitle !== '성도 항복 권고전' || postChengduProgressState.campaignProgress?.nextBattleTitle !== '가맹관 마초 대면전' @@ -4238,7 +4238,7 @@ try { if ( postJiamengProgressState?.activeTab !== 'progress' || postJiamengProgressState.campaignProgress?.completedKnown !== 34 || - postJiamengProgressState.campaignProgress?.totalKnown !== 63 || + postJiamengProgressState.campaignProgress?.totalKnown !== 64 || postJiamengProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postJiamengProgressState.campaignProgress?.latestBattleTitle !== '가맹관 마초 대면전' || postJiamengProgressState.campaignProgress?.nextBattleTitle !== '양평관 정찰전' @@ -4391,7 +4391,7 @@ try { if ( postYangpingProgressState?.activeTab !== 'progress' || postYangpingProgressState.campaignProgress?.completedKnown !== 35 || - postYangpingProgressState.campaignProgress?.totalKnown !== 63 || + postYangpingProgressState.campaignProgress?.totalKnown !== 64 || postYangpingProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postYangpingProgressState.campaignProgress?.latestBattleTitle !== '양평관 정찰전' || postYangpingProgressState.campaignProgress?.nextBattleTitle !== '정군산 전초전' @@ -4545,7 +4545,7 @@ try { if ( postDingjunProgressState?.activeTab !== 'progress' || postDingjunProgressState.campaignProgress?.completedKnown !== 36 || - postDingjunProgressState.campaignProgress?.totalKnown !== 63 || + postDingjunProgressState.campaignProgress?.totalKnown !== 64 || postDingjunProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postDingjunProgressState.campaignProgress?.latestBattleTitle !== '정군산 전초전' || postDingjunProgressState.campaignProgress?.nextBattleTitle !== '한중 결전' @@ -4695,7 +4695,7 @@ try { if ( postHanzhongProgressState?.activeTab !== 'progress' || postHanzhongProgressState.campaignProgress?.completedKnown !== 37 || - postHanzhongProgressState.campaignProgress?.totalKnown !== 63 || + postHanzhongProgressState.campaignProgress?.totalKnown !== 64 || postHanzhongProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postHanzhongProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postHanzhongProgressState.campaignProgress?.nextBattleTitle !== '한중왕 즉위 준비' @@ -4753,7 +4753,7 @@ try { if ( postKingCouncilProgressState?.activeTab !== 'progress' || postKingCouncilProgressState.campaignProgress?.completedKnown !== 37 || - postKingCouncilProgressState.campaignProgress?.totalKnown !== 63 || + postKingCouncilProgressState.campaignProgress?.totalKnown !== 64 || postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포' ) { @@ -4810,7 +4810,7 @@ try { if ( postFoundationProgressState?.activeTab !== 'progress' || postFoundationProgressState.campaignProgress?.completedKnown !== 37 || - postFoundationProgressState.campaignProgress?.totalKnown !== 63 || + postFoundationProgressState.campaignProgress?.totalKnown !== 64 || postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전' ) { @@ -4958,7 +4958,7 @@ try { if ( postJingDefenseProgressState?.activeTab !== 'progress' || postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 || - postJingDefenseProgressState.campaignProgress?.totalKnown !== 63 || + postJingDefenseProgressState.campaignProgress?.totalKnown !== 64 || postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' || postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전' ) { @@ -5108,7 +5108,7 @@ try { if ( postFanCastleProgressState?.activeTab !== 'progress' || postFanCastleProgressState.campaignProgress?.completedKnown !== 39 || - postFanCastleProgressState.campaignProgress?.totalKnown !== 63 || + postFanCastleProgressState.campaignProgress?.totalKnown !== 64 || postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' || postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공' ) { @@ -5259,7 +5259,7 @@ try { if ( postHanRiverProgressState?.activeTab !== 'progress' || postHanRiverProgressState.campaignProgress?.completedKnown !== 40 || - postHanRiverProgressState.campaignProgress?.totalKnown !== 63 || + postHanRiverProgressState.campaignProgress?.totalKnown !== 64 || postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' || postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전' ) { @@ -5410,7 +5410,7 @@ try { if ( postFanSiegeProgressState?.activeTab !== 'progress' || postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 || - postFanSiegeProgressState.campaignProgress?.totalKnown !== 63 || + postFanSiegeProgressState.campaignProgress?.totalKnown !== 64 || postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' || postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전' ) { @@ -5569,7 +5569,7 @@ try { if ( postJingRearProgressState?.activeTab !== 'progress' || postJingRearProgressState.campaignProgress?.completedKnown !== 42 || - postJingRearProgressState.campaignProgress?.totalKnown !== 63 || + postJingRearProgressState.campaignProgress?.totalKnown !== 64 || postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' || postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고' ) { @@ -5721,7 +5721,7 @@ try { if ( postGonganProgressState?.activeTab !== 'progress' || postGonganProgressState.campaignProgress?.completedKnown !== 43 || - postGonganProgressState.campaignProgress?.totalKnown !== 63 || + postGonganProgressState.campaignProgress?.totalKnown !== 64 || postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' || postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전' ) { @@ -5876,7 +5876,7 @@ try { if ( postMaichengProgressState?.activeTab !== 'progress' || postMaichengProgressState.campaignProgress?.completedKnown !== 44 || - postMaichengProgressState.campaignProgress?.totalKnown !== 63 || + postMaichengProgressState.campaignProgress?.totalKnown !== 64 || postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' || postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로' ) { @@ -6030,7 +6030,7 @@ try { if ( postYilingProgressState?.activeTab !== 'progress' || postYilingProgressState.campaignProgress?.completedKnown !== 45 || - postYilingProgressState.campaignProgress?.totalKnown !== 63 || + postYilingProgressState.campaignProgress?.totalKnown !== 64 || postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' || postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전' ) { @@ -6185,7 +6185,7 @@ try { if ( postYilingFireProgressState?.activeTab !== 'progress' || postYilingFireProgressState.campaignProgress?.completedKnown !== 46 || - postYilingFireProgressState.campaignProgress?.totalKnown !== 63 || + postYilingFireProgressState.campaignProgress?.totalKnown !== 64 || postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁' ) { @@ -6248,7 +6248,7 @@ try { if ( postBaidiProgressState?.activeTab !== 'progress' || postBaidiProgressState.campaignProgress?.completedKnown !== 46 || - postBaidiProgressState.campaignProgress?.totalKnown !== 63 || + postBaidiProgressState.campaignProgress?.totalKnown !== 64 || postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로' ) { @@ -6406,7 +6406,7 @@ try { if ( postNanzhongProgressState?.activeTab !== 'progress' || postNanzhongProgressState.campaignProgress?.completedKnown !== 47 || - postNanzhongProgressState.campaignProgress?.totalKnown !== 63 || + postNanzhongProgressState.campaignProgress?.totalKnown !== 64 || postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' || postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전' ) { @@ -6565,7 +6565,7 @@ try { if ( postMengHuoProgressState?.activeTab !== 'progress' || postMengHuoProgressState.campaignProgress?.completedKnown !== 48 || - postMengHuoProgressState.campaignProgress?.totalKnown !== 63 || + postMengHuoProgressState.campaignProgress?.totalKnown !== 64 || postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' || postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전' ) { @@ -6724,7 +6724,7 @@ try { if ( postSecondCaptureProgressState?.activeTab !== 'progress' || postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 || - postSecondCaptureProgressState.campaignProgress?.totalKnown !== 63 || + postSecondCaptureProgressState.campaignProgress?.totalKnown !== 64 || postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' || postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전' ) { @@ -6886,7 +6886,7 @@ try { if ( postThirdCaptureProgressState?.activeTab !== 'progress' || postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 || - postThirdCaptureProgressState.campaignProgress?.totalKnown !== 63 || + postThirdCaptureProgressState.campaignProgress?.totalKnown !== 64 || postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' || postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전' ) { @@ -7049,7 +7049,7 @@ try { if ( postFourthCaptureProgressState?.activeTab !== 'progress' || postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 || - postFourthCaptureProgressState.campaignProgress?.totalKnown !== 63 || + postFourthCaptureProgressState.campaignProgress?.totalKnown !== 64 || postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' || postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전' ) { @@ -7212,7 +7212,7 @@ try { if ( postFifthCaptureProgressState?.activeTab !== 'progress' || postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 || - postFifthCaptureProgressState.campaignProgress?.totalKnown !== 63 || + postFifthCaptureProgressState.campaignProgress?.totalKnown !== 64 || postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' || postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 6차전' ) { @@ -7374,7 +7374,7 @@ try { if ( postSixthCaptureProgressState?.activeTab !== 'progress' || postSixthCaptureProgressState.campaignProgress?.completedKnown !== 53 || - postSixthCaptureProgressState.campaignProgress?.totalKnown !== 63 || + postSixthCaptureProgressState.campaignProgress?.totalKnown !== 64 || postSixthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 6차전' || postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 7차전' ) { @@ -7537,7 +7537,7 @@ try { if ( postFinalCaptureProgressState?.activeTab !== 'progress' || postFinalCaptureProgressState.campaignProgress?.completedKnown !== 54 || - postFinalCaptureProgressState.campaignProgress?.totalKnown !== 63 || + postFinalCaptureProgressState.campaignProgress?.totalKnown !== 64 || postFinalCaptureProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFinalCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' || !String(postFinalCaptureProgressState.campaignProgress?.nextBattleTitle ?? '').includes('북벌 준비') @@ -7607,7 +7607,7 @@ try { if ( postNorthernPrepProgressState?.activeTab !== 'progress' || postNorthernPrepProgressState.campaignProgress?.completedKnown !== 54 || - postNorthernPrepProgressState.campaignProgress?.totalKnown !== 63 || + postNorthernPrepProgressState.campaignProgress?.totalKnown !== 64 || postNorthernPrepProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postNorthernPrepProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' || postNorthernPrepProgressState.campaignProgress?.nextBattleTitle !== '제1차 북벌 출진로' @@ -7754,7 +7754,7 @@ try { if ( postFiftyFifthProgressState?.activeTab !== 'progress' || postFiftyFifthProgressState.campaignProgress?.completedKnown !== 55 || - postFiftyFifthProgressState.campaignProgress?.totalKnown !== 63 || + postFiftyFifthProgressState.campaignProgress?.totalKnown !== 64 || postFiftyFifthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFiftyFifthProgressState.campaignProgress?.latestBattleTitle !== '제1차 북벌 출진로' || postFiftyFifthProgressState.campaignProgress?.nextBattleTitle !== '천수 진군로' @@ -7901,7 +7901,7 @@ try { if ( postFiftySixthProgressState?.activeTab !== 'progress' || postFiftySixthProgressState.campaignProgress?.completedKnown !== 56 || - postFiftySixthProgressState.campaignProgress?.totalKnown !== 63 || + postFiftySixthProgressState.campaignProgress?.totalKnown !== 64 || postFiftySixthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFiftySixthProgressState.campaignProgress?.latestBattleTitle !== '천수 진군로' || postFiftySixthProgressState.campaignProgress?.nextBattleTitle !== '가정 배치 위기' @@ -8053,7 +8053,7 @@ try { if ( postFiftySeventhProgressState?.activeTab !== 'progress' || postFiftySeventhProgressState.campaignProgress?.completedKnown !== 57 || - postFiftySeventhProgressState.campaignProgress?.totalKnown !== 63 || + postFiftySeventhProgressState.campaignProgress?.totalKnown !== 64 || postFiftySeventhProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFiftySeventhProgressState.campaignProgress?.latestBattleTitle !== '가정 배치 위기' || postFiftySeventhProgressState.campaignProgress?.nextBattleTitle !== '기산 후퇴로' @@ -8201,7 +8201,7 @@ try { if ( postFiftyEighthProgressState?.activeTab !== 'progress' || postFiftyEighthProgressState.campaignProgress?.completedKnown !== 58 || - postFiftyEighthProgressState.campaignProgress?.totalKnown !== 63 || + postFiftyEighthProgressState.campaignProgress?.totalKnown !== 64 || postFiftyEighthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFiftyEighthProgressState.campaignProgress?.latestBattleTitle !== '기산 후퇴로' || postFiftyEighthProgressState.campaignProgress?.nextBattleTitle !== '진창 공성전' @@ -8349,7 +8349,7 @@ try { if ( postFiftyNinthProgressState?.activeTab !== 'progress' || postFiftyNinthProgressState.campaignProgress?.completedKnown !== 59 || - postFiftyNinthProgressState.campaignProgress?.totalKnown !== 63 || + postFiftyNinthProgressState.campaignProgress?.totalKnown !== 64 || postFiftyNinthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFiftyNinthProgressState.campaignProgress?.latestBattleTitle !== '진창 공성전' || postFiftyNinthProgressState.campaignProgress?.nextBattleTitle !== '무도·음평 확보전' @@ -8503,7 +8503,7 @@ try { if ( postSixtiethProgressState?.activeTab !== 'progress' || postSixtiethProgressState.campaignProgress?.completedKnown !== 60 || - postSixtiethProgressState.campaignProgress?.totalKnown !== 63 || + postSixtiethProgressState.campaignProgress?.totalKnown !== 64 || postSixtiethProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postSixtiethProgressState.campaignProgress?.latestBattleTitle !== '무도·음평 확보전' || postSixtiethProgressState.campaignProgress?.nextBattleTitle !== '한중 우로 방어전' @@ -8660,7 +8660,7 @@ try { if ( postSixtyFirstProgressState?.activeTab !== 'progress' || postSixtyFirstProgressState.campaignProgress?.completedKnown !== 61 || - postSixtyFirstProgressState.campaignProgress?.totalKnown !== 63 || + postSixtyFirstProgressState.campaignProgress?.totalKnown !== 64 || postSixtyFirstProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postSixtyFirstProgressState.campaignProgress?.latestBattleTitle !== '한중 우로 방어전' || postSixtyFirstProgressState.campaignProgress?.nextBattleTitle !== '기산 재출정전' @@ -8820,7 +8820,7 @@ try { if ( postSixtySecondProgressState?.activeTab !== 'progress' || postSixtySecondProgressState.campaignProgress?.completedKnown !== 62 || - postSixtySecondProgressState.campaignProgress?.totalKnown !== 63 || + postSixtySecondProgressState.campaignProgress?.totalKnown !== 64 || postSixtySecondProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postSixtySecondProgressState.campaignProgress?.latestBattleTitle !== '기산 재출정전' || postSixtySecondProgressState.campaignProgress?.nextBattleTitle !== '노성 추격전' @@ -8999,15 +8999,194 @@ try { if ( postSixtyThirdProgressState?.activeTab !== 'progress' || postSixtyThirdProgressState.campaignProgress?.completedKnown !== 63 || - postSixtyThirdProgressState.campaignProgress?.totalKnown !== 63 || + postSixtyThirdProgressState.campaignProgress?.totalKnown !== 64 || postSixtyThirdProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postSixtyThirdProgressState.campaignProgress?.latestBattleTitle !== '노성 추격전' || - postSixtyThirdProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + postSixtyThirdProgressState.campaignProgress?.nextBattleTitle !== '위수 진영전' ) { - throw new Error(`Expected post-sixty-third progress tab to complete Lucheng pursuit and leave the next northern step pending: ${JSON.stringify(postSixtyThirdProgressState?.campaignProgress)}`); + throw new Error(`Expected post-sixty-third progress tab to complete Lucheng pursuit and point toward Weishui camps: ${JSON.stringify(postSixtyThirdProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-sixty-third-progress.png', fullPage: true }); + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const weishuiCampsSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !weishuiCampsSortieState?.sortieVisible || + weishuiCampsSortieState.campaign?.step !== 'sixty-third-camp' || + weishuiCampsSortieState.campTitle !== '노성 추격 후 군영' || + !String(weishuiCampsSortieState.sortiePlan?.objectiveLine ?? '').includes('위수 진영전') || + !String(weishuiCampsSortieState.sortiePlan?.objectiveLine ?? '').includes('봉화 차단') || + weishuiCampsSortieState.sortiePlan?.maxCount !== 7 || + weishuiCampsSortieState.sortiePlan?.recommendedTotal !== 9 || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'jiang-wei' && unit.recommended) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'wang-ping' && unit.recommended) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'li-yan' && unit.recommended) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'wei-yan' && unit.recommended) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'ma-dai' && unit.recommended) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recommended) || + !weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) || + weishuiCampsSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected Weishui camps sortie to require Zhuge Liang, exclude Liu Bei, and recommend nine river/camp/supply roles: ${JSON.stringify(weishuiCampsSortieState)}`); + } + assertSortieTacticalRoster(weishuiCampsSortieState, [ + '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', 'jiang-wei' + ]); + + const weishuiCampsPriorityUnits = ['jiang-wei', 'wang-ping', 'huang-quan', 'li-yan', 'wei-yan', 'ma-dai']; + for (const unitId of weishuiCampsPriorityUnits) { + 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 && !weishuiCampsPriorityUnits.includes(unit.id) + ); + if (removable) { + await clickSortieRosterUnit(page, removable.id); + } + await clickSortieRosterUnit(page, unitId); + } + } + + const weishuiCampsSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !weishuiCampsPriorityUnits.every((unitId) => + weishuiCampsSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected) + ) || + !weishuiCampsSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + weishuiCampsSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') || + weishuiCampsSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.selected) || + weishuiCampsSortieReadyState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.selected) || + weishuiCampsSortieReadyState.sortiePlan?.selectedCount !== 7 || + weishuiCampsSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 || + weishuiCampsSortieReadyState.sortiePlan?.recommendedTotal !== 9 + ) { + throw new Error(`Expected Weishui camps sortie to deploy Zhuge Liang plus six river/camp/supply officers while benching Zhao Yun and Ma Liang: ${JSON.stringify(weishuiCampsSortieReadyState)}`); + } + await page.screenshot({ path: 'dist/verification-sixty-fourth-sortie.png', fullPage: true }); + + await page.mouse.click(1068, 646); + await waitForStoryReady(page); + for (let i = 0; i < 12; i += 1) { + const storyState = await page.evaluate(() => + window.__HEROS_GAME__?.scene.getScene('StoryScene')?.getDebugState?.() + ); + if (storyState?.background === 'story-weishui-camps') { + break; + } + await page.keyboard.press('Space'); + await page.waitForTimeout(320); + } + const weishuiCampsStoryState = await page.evaluate(() => + window.__HEROS_GAME__?.scene.getScene('StoryScene')?.getDebugState?.() + ); + if ( + weishuiCampsStoryState?.background !== 'story-weishui-camps' || + weishuiCampsStoryState?.backgroundReady !== true + ) { + throw new Error(`Expected Weishui camps story to lazy-load its new generated background: ${JSON.stringify(weishuiCampsStoryState)}`); + } + await page.screenshot({ path: 'dist/verification-sixty-fourth-story.png', fullPage: true }); + + for (let i = 0; i < 50; i += 1) { + const enteredSixtyFourthBattle = await page.evaluate(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'sixty-fourth-battle-weishui-camps'; + }); + if (enteredSixtyFourthBattle) { + 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 === 'sixty-fourth-battle-weishui-camps' && state?.battleOutcome === null && state?.phase === 'idle'; + }); + await page.screenshot({ path: 'dist/verification-sixty-fourth-battle.png', fullPage: true }); + + const sixtyFourthBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + const sixtyFourthEnemies = sixtyFourthBattleState.units.filter((unit) => unit.faction === 'enemy'); + const sixtyFourthAllies = sixtyFourthBattleState.units.filter((unit) => unit.faction === 'ally'); + const sixtyFourthEnemyBehaviors = new Set(sixtyFourthEnemies.map((unit) => unit.ai)); + if ( + sixtyFourthBattleState.camera?.mapWidth !== 128 || + sixtyFourthBattleState.camera?.mapHeight !== 110 || + sixtyFourthBattleState.mapTextureKey !== 'battle-map-sixty-fourth' || + sixtyFourthBattleState.mapTextureReady !== true || + sixtyFourthBattleState.mapBackgroundReady !== true || + sixtyFourthBattleState.victoryConditionLabel !== '위수 남안 진영 확보와 봉화 차단' || + !sixtyFourthBattleState.objectives?.some((objective) => objective.id === 'break-sima-yi-weishui-camps' && objective.label === '사마의 위수 본영 압박') || + !sixtyFourthBattleState.objectives?.some((objective) => objective.id === 'stop-zhang-he-crossing-raid' && objective.label === '장합 물목 추격 저지') || + !sixtyFourthBattleState.objectives?.some((objective) => objective.id === 'silence-guo-huai-weishui-beacons' && objective.label === '곽회 위수 봉화 차단') || + !sixtyFourthBattleState.objectives?.some((objective) => objective.id === 'hold-weishui-supply-camps' && objective.label === '위수 보급 진영 확보') || + !sixtyFourthBattleState.objectives?.some((objective) => objective.id === 'reassure-river-villages' && objective.label === '강가 마을 안심') || + sixtyFourthEnemies.length < 42 || + !sixtyFourthEnemyBehaviors.has('aggressive') || + !sixtyFourthEnemyBehaviors.has('guard') || + !sixtyFourthEnemyBehaviors.has('hold') || + !sixtyFourthEnemies.some((unit) => unit.id === 'northern-tenth-leader-sima-yi') || + !sixtyFourthEnemies.some((unit) => unit.id === 'northern-tenth-officer-zhang-he') || + !sixtyFourthEnemies.some((unit) => unit.id === 'northern-tenth-officer-guo-huai') || + !sixtyFourthAllies.some((unit) => unit.id === 'zhuge-liang') || + !sixtyFourthAllies.some((unit) => unit.id === 'jiang-wei') || + !sixtyFourthAllies.some((unit) => unit.id === 'wang-ping') || + !sixtyFourthAllies.some((unit) => unit.id === 'huang-quan') || + !sixtyFourthAllies.some((unit) => unit.id === 'li-yan') || + !sixtyFourthAllies.some((unit) => unit.id === 'wei-yan') || + !sixtyFourthAllies.some((unit) => unit.id === 'ma-dai') || + sixtyFourthAllies.some((unit) => unit.id === 'zhao-yun') || + sixtyFourthAllies.some((unit) => unit.id === 'ma-liang') || + sixtyFourthAllies.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected sixty-fourth battle to use the Weishui camps map, Sima Yi/Zhang He/Guo Huai objectives, selected river/camp/supply officers, and mixed Wei AI: ${JSON.stringify(sixtyFourthBattleState)}`); + } + + 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 sixtyFourthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + sixtyFourthCampState?.campBattleId !== 'sixty-fourth-battle-weishui-camps' || + sixtyFourthCampState.campaign?.step !== 'sixty-fourth-camp' || + sixtyFourthCampState.campTitle !== '위수 진영전 후 군영' || + sixtyFourthCampState.availableDialogueIds?.length !== 2 || + !sixtyFourthCampState.availableDialogueIds.every((id) => id.startsWith('northern-tenth-')) || + sixtyFourthCampState.availableVisitIds?.length !== 1 || + !sixtyFourthCampState.availableVisitIds.every((id) => id.startsWith('northern-tenth-')) || + sixtyFourthCampState.rosterCollection?.total < 22 || + !sixtyFourthCampState.report?.bonds?.some((bond) => bond.id === 'zhuge-liang__huang-quan_northern-tenth') + ) { + throw new Error(`Expected Weishui camps aftermath camp to expose tenth northern campaign events and bonds: ${JSON.stringify(sixtyFourthCampState)}`); + } + await page.screenshot({ path: 'dist/verification-sixty-fourth-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postSixtyFourthProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postSixtyFourthProgressState?.activeTab !== 'progress' || + postSixtyFourthProgressState.campaignProgress?.completedKnown !== 64 || + postSixtyFourthProgressState.campaignProgress?.totalKnown !== 64 || + postSixtyFourthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || + postSixtyFourthProgressState.campaignProgress?.latestBattleTitle !== '위수 진영전' || + postSixtyFourthProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + ) { + throw new Error(`Expected post-sixty-fourth progress tab to complete Weishui camps and leave the next northern step pending: ${JSON.stringify(postSixtyFourthProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-sixty-fourth-progress.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); @@ -9036,12 +9215,12 @@ try { } } for (let attempt = 0; attempt < 24; attempt += 1) { - const continuedToLuchengPursuitCamp = await page.evaluate(() => { + const continuedToWeishuiCampsCamp = await page.evaluate(() => { const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; const camp = window.__HEROS_DEBUG__?.camp?.() ?? null; - return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-third-camp'; + return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-fourth-camp'; }); - if (continuedToLuchengPursuitCamp) { + if (continuedToWeishuiCampsCamp) { break; } await page.keyboard.press('Space'); @@ -9050,17 +9229,17 @@ try { await page.waitForFunction(() => { const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; const camp = window.__HEROS_DEBUG__?.camp?.() ?? null; - return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-third-camp'; + return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-fourth-camp'; }); const titleContinueState = await page.evaluate(() => ({ activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], camp: window.__HEROS_DEBUG__?.camp?.() ?? null })); - if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'sixty-third-camp') { - throw new Error(`Expected title continue to reopen the Lucheng pursuit aftermath camp: ${JSON.stringify(titleContinueState)}`); + if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'sixty-fourth-camp') { + throw new Error(`Expected title continue to reopen the Weishui camps aftermath camp: ${JSON.stringify(titleContinueState)}`); } - console.log(`Verified title-to-Lucheng-pursuit 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, sixth capture, final capture, northern campaign preparation, Qishan road first northern expedition, Tianshui advance, Jieting crisis, Jiang Wei joining, Qishan retreat, Chencang siege, Wudu/Yinping commanderies, Hanzhong rainy-pass defense, Qishan renewed offensive, Lucheng pursuit, and pending next northern step state at ${targetUrl}`); + console.log(`Verified title-to-Weishui-camps 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, sixth capture, final capture, northern campaign preparation, Qishan road first northern expedition, Tianshui advance, Jieting crisis, Jiang Wei joining, Qishan retreat, Chencang siege, Wudu/Yinping commanderies, Hanzhong rainy-pass defense, Qishan renewed offensive, Lucheng pursuit, Weishui camps, and pending next northern step state at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/assets/images/battle/sixty-fourth-battle-map.svg b/src/assets/images/battle/sixty-fourth-battle-map.svg new file mode 100644 index 0000000..72760a1 --- /dev/null +++ b/src/assets/images/battle/sixty-fourth-battle-map.svg @@ -0,0 +1,96 @@ + + Weishui camps map + Original tactical battlefield map with Wei River crossings, Shu supply camps, Wei fortified palisades, signal towers, villages, cliffs, roads, and winter ridges. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/story/23-weishui-camps.png b/src/assets/images/story/23-weishui-camps.png new file mode 100644 index 0000000..690d8aa Binary files /dev/null and b/src/assets/images/story/23-weishui-camps.png differ diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts index edc9d08..5d513b0 100644 --- a/src/game/data/battleMapAssets.ts +++ b/src/game/data/battleMapAssets.ts @@ -13,6 +13,7 @@ import sixtiethBattleMapUrl from '../../assets/images/battle/sixtieth-battle-map import sixtyFirstBattleMapUrl from '../../assets/images/battle/sixty-first-battle-map.svg'; import sixtySecondBattleMapUrl from '../../assets/images/battle/sixty-second-battle-map.svg'; import sixtyThirdBattleMapUrl from '../../assets/images/battle/sixty-third-battle-map.svg'; +import sixtyFourthBattleMapUrl from '../../assets/images/battle/sixty-fourth-battle-map.svg'; import fiftySeventhBattleMapUrl from '../../assets/images/battle/fifty-seventh-battle-map.svg'; import fiftySixthBattleMapUrl from '../../assets/images/battle/fifty-sixth-battle-map.svg'; import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg'; @@ -124,6 +125,7 @@ export const battleMapAssets: Record = { 'battle-map-sixty-first': sixtyFirstBattleMapUrl, 'battle-map-sixty-second': sixtySecondBattleMapUrl, 'battle-map-sixty-third': sixtyThirdBattleMapUrl, + 'battle-map-sixty-fourth': sixtyFourthBattleMapUrl, 'battle-map-fifty-seventh': fiftySeventhBattleMapUrl, 'battle-map-fifty-sixth': fiftySixthBattleMapUrl }; diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index d61241d..0a06e4a 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -51,6 +51,10 @@ import { sixtyThirdBattleMap, sixtyThirdBattleUnits, sixtyThirdBattleVictoryPages, + sixtyFourthBattleBonds, + sixtyFourthBattleMap, + sixtyFourthBattleUnits, + sixtyFourthBattleVictoryPages, fiftySeventhBattleBonds, fiftySeventhBattleMap, fiftySeventhBattleUnits, @@ -320,7 +324,8 @@ export type BattleScenarioId = | 'sixtieth-battle-wudu-yinping' | 'sixty-first-battle-hanzhong-rain-defense' | 'sixty-second-battle-qishan-renewed-offensive' - | 'sixty-third-battle-lucheng-pursuit'; + | 'sixty-third-battle-lucheng-pursuit' + | 'sixty-fourth-battle-weishui-camps'; export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory'; @@ -4263,6 +4268,73 @@ export const sixtyThirdBattleScenario: BattleScenarioDefinition = { nextCampScene: 'CampScene' }; +export const sixtyFourthBattleScenario: BattleScenarioDefinition = { + id: 'sixty-fourth-battle-weishui-camps', + title: '위수 진영전', + victoryConditionLabel: '위수 남안 진영 확보와 봉화 차단', + defeatConditionLabel: '제갈량 퇴각 또는 보급 수레 붕괴', + openingObjectiveLines: [ + '노성 추격 뒤에도 사마의는 위수 북안에 진영을 겹겹이 세우고 시간을 벌고 있습니다. 촉한군은 강을 성급히 넘기보다 남안의 진영과 물목, 봉화선을 먼저 끊어야 합니다.', + '강유와 왕평은 낮은 길과 회수 표식을 맡고, 황권과 이엄은 긴 수레길을 전장 기준으로 맞춥니다. 위연과 마대는 장합의 기병이 뒤를 물기 전에 진영 사이를 흔들어야 합니다.', + '사마의의 위수 본영, 장합의 물목 추격대, 곽회의 봉화선을 동시에 늦추십시오. 강가 마을과 보급 진영을 지키면 다음 북방 공세가 더 깊게 이어집니다.' + ], + map: sixtyFourthBattleMap, + units: sixtyFourthBattleUnits, + bonds: sixtyFourthBattleBonds, + mapTextureKey: 'battle-map-sixty-fourth', + leaderUnitId: 'northern-tenth-leader-sima-yi', + quickVictoryTurnLimit: 128, + baseVictoryGold: 20560, + objectives: [ + { + id: 'break-sima-yi-weishui-camps', + kind: 'defeat-leader', + label: '사마의 위수 본영 압박', + rewardGold: 14600, + unitId: 'northern-tenth-leader-sima-yi' + }, + { + id: 'stop-zhang-he-crossing-raid', + kind: 'defeat-leader', + label: '장합 물목 추격 저지', + rewardGold: 8840, + unitId: 'northern-tenth-officer-zhang-he' + }, + { + id: 'silence-guo-huai-weishui-beacons', + kind: 'defeat-leader', + label: '곽회 위수 봉화 차단', + rewardGold: 8220, + unitId: 'northern-tenth-officer-guo-huai' + }, + { + id: 'hold-weishui-supply-camps', + kind: 'secure-terrain', + label: '위수 보급 진영 확보', + rewardGold: 6880, + terrain: 'camp' + }, + { + id: 'reassure-river-villages', + kind: 'secure-terrain', + label: '강가 마을 안심', + rewardGold: 6660, + terrain: 'village' + }, + { + id: 'quick', + kind: 'quick-victory', + label: '128턴 이내 위수 진영 장악', + rewardGold: 4540, + maxTurn: 128 + } + ], + defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }], + itemRewards: ['금 138', '상처약 106', '군주 52', '위수 진영 장부 1', '강안 봉화 기록 1'], + victoryPages: sixtyFourthBattleVictoryPages, + nextCampScene: 'CampScene' +}; + export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id; export const battleScenarios: Record = { @@ -4328,7 +4400,8 @@ export const battleScenarios: Record 'sixtieth-battle-wudu-yinping': sixtiethBattleScenario, 'sixty-first-battle-hanzhong-rain-defense': sixtyFirstBattleScenario, 'sixty-second-battle-qishan-renewed-offensive': sixtySecondBattleScenario, - 'sixty-third-battle-lucheng-pursuit': sixtyThirdBattleScenario + 'sixty-third-battle-lucheng-pursuit': sixtyThirdBattleScenario, + 'sixty-fourth-battle-weishui-camps': sixtyFourthBattleScenario }; export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index 0fe4792..682f95b 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -18,6 +18,7 @@ import { sixtyFirstBattleScenario, sixtySecondBattleScenario, sixtyThirdBattleScenario, + sixtyFourthBattleScenario, fortiethBattleScenario, fortyFirstBattleScenario, fortySecondBattleScenario, @@ -87,6 +88,8 @@ import { sixtySecondBattleVictoryPages, sixtyThirdBattleIntroPages, sixtyThirdBattleVictoryPages, + sixtyFourthBattleIntroPages, + sixtyFourthBattleVictoryPages, fiftyFirstBattleIntroPages, fiftyFirstBattleVictoryPages, fiftySecondBattleIntroPages, @@ -925,13 +928,24 @@ const sortieFlows: Record = { }, [sixtyThirdBattleScenario.id]: { afterBattleId: sixtyThirdBattleScenario.id, - eyebrow: '다음 장 준비', - title: '노성 추격 이후', + eyebrow: '다음 전장', + title: sixtyFourthBattleScenario.title, description: - '노성 추격로와 회수 표식은 지켜 냈지만 북방 전선은 여전히 길고 무겁습니다. 다음 계산에서는 얻은 길을 지키는 장부와 더 깊은 북벌 공세를 다시 맞춰야 합니다.', - rewardHint: '다음 목표: 북방 전선 재정비 / 다음 북벌 공격로 검토', - pages: sixtyThirdBattleVictoryPages, - unavailableNotice: '노성 추격 이후의 다음 북벌 전투는 이어지는 작업에서 확장합니다. 현재는 회수 표식, 장합 추격 저지, 노성 주변 보급과 민심 정비를 확인할 수 있습니다.' + '노성 추격로와 회수 표식은 지켰지만 사마의는 위수 북안에 진영을 겹겹이 세웠습니다. 이번에는 강을 무리하게 넘기보다 남안 진영, 물목, 봉화선을 먼저 끊어 다음 북방 공세의 발판을 만들어야 합니다.', + rewardHint: `예상 보상: ${sixtyFourthBattleScenario.title} 개방 / 위수 진영 장부`, + nextBattleId: sixtyFourthBattleScenario.id, + campaignStep: 'sixty-fourth-battle', + pages: [...sixtyThirdBattleVictoryPages, ...sixtyFourthBattleIntroPages] + }, + [sixtyFourthBattleScenario.id]: { + afterBattleId: sixtyFourthBattleScenario.id, + eyebrow: '다음 장 준비', + title: '위수 진영 이후', + description: + '위수 남안 진영과 봉화선은 흔들었지만, 북방의 길은 아직 끝나지 않았습니다. 다음 계산에서는 강가 장기전의 보급, 사마의 본영 압박, 장합의 재추격을 다시 나누어야 합니다.', + rewardHint: '다음 목표: 위수 전선 재정비 / 다음 북방 공세 검토', + pages: sixtyFourthBattleVictoryPages, + unavailableNotice: '위수 진영 이후의 다음 북방 전투는 이어지는 작업에서 확장합니다. 현재는 위수 진영 확보, 장합 물목 추격 저지, 곽회 봉화 차단과 강가 보급 정비를 확인할 수 있습니다.' }, 'northern-campaign-prep-camp-legacy': { afterBattleId: fiftyFourthBattleScenario.id, diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index cc17953..7705a7a 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -4013,6 +4013,59 @@ export const sixtyThirdBattleVictoryPages: StoryPage[] = [ } ]; +export const sixtyFourthBattleIntroPages: StoryPage[] = [ + { + id: 'sixty-fourth-weishui-camp-council', + bgm: 'story-dark', + chapter: '위수 진영', + background: 'story-weishui-camps', + speaker: '제갈량', + text: '노성 추격으로 돌아올 길은 지켰으나, 사마의는 위수 북안에 여러 진영을 겹겹이 세웠습니다. 강을 건너기보다 먼저 적의 진영 사이를 흔들어 보급 표식과 물목을 빼앗아야 합니다.' + }, + { + id: 'sixty-fourth-river-camps-ledger', + bgm: 'battle-prep', + chapter: '강가의 장부', + background: 'story-weishui-camps', + speaker: '황권', + text: '위수까지 나아가면 길은 넓어지지만 수레는 더 느려집니다. 진영 하나를 깨는 것보다, 얻은 진영에 군량과 표식을 남겨 다음 부대가 길을 잃지 않게 하는 일이 먼저입니다.' + }, + { + id: 'sixty-fourth-zhanghe-crossing-threat', + bgm: 'story-dark', + chapter: '장합의 물목', + background: 'story-weishui-camps', + speaker: '강유', + text: '장합은 정면에서 기다리지 않을 것입니다. 강안의 낮은 길과 물목을 이용해 뒤를 물고, 곽회는 봉화로 사마의의 본영에 시간을 벌어 줄 것입니다. 이번 출전은 돌파와 회수, 진영 확보가 한꺼번에 얽힙니다.' + } +]; + +export const sixtyFourthBattleVictoryPages: StoryPage[] = [ + { + id: 'sixty-fourth-victory-river-camps-held', + bgm: 'militia-theme', + chapter: '위수 진영 확보', + background: 'story-weishui-camps', + text: '촉한군은 위수 남안의 진영을 빼앗고 장합의 물목 추격을 늦췄습니다. 사마의의 본영은 여전히 멀지만, 강가의 표식과 군량 장부가 처음으로 북방 깊숙한 곳에 남았습니다.' + }, + { + id: 'sixty-fourth-victory-signal-lines', + bgm: 'story-dark', + chapter: '꺼진 봉화', + background: 'story-weishui-camps', + speaker: '마량', + text: '곽회의 봉화는 늦어졌고, 강가 마을에는 촉한군이 지나간 까닭을 적은 고시문이 붙었습니다. 병사들은 이긴 길보다 돌아올 표식을 먼저 확인하며 다음 명령을 기다렸습니다.' + }, + { + id: 'sixty-fourth-next-northern-plan', + bgm: 'story-dark', + chapter: '더 먼 북방', + background: 'story-weishui-camps', + speaker: '제갈량', + text: '위수 진영을 얻었다 해도 북방의 끝은 아직 보이지 않습니다. 강을 끼고 오래 싸울 사람, 수레를 붙들 사람, 적의 다음 계책을 먼저 읽을 사람을 다시 나누어야 합니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, @@ -4536,6 +4589,12 @@ export const sixtyThirdBattleMap: BattleMap = { terrain: createSixtyThirdBattleTerrain() }; +export const sixtyFourthBattleMap: BattleMap = { + width: 128, + height: 110, + terrain: createSixtyFourthBattleTerrain() +}; + export const firstBattleUnits: UnitData[] = [ { id: 'liu-bei', @@ -18032,6 +18091,103 @@ export const sixtyThirdBattleUnits: UnitData[] = [ ...sixtyThirdEnemySpecs.map((spec) => createThirtyFirstEnemyUnit(...spec)) ]; +const sixtyFourthBattleAllyPositions: Record = { + 'guan-yu': { x: 17, y: 98 }, + 'zhang-fei': { x: 25, y: 101 }, + 'jian-yong': { x: 8, y: 106 }, + 'mi-zhu': { x: 14, y: 107 }, + 'sun-qian': { x: 20, y: 107 }, + 'zhao-yun': { x: 32, y: 96 }, + 'zhuge-liang': { x: 19, y: 93 }, + 'ma-liang': { x: 26, y: 90 }, + 'yi-ji': { x: 8, y: 108 }, + 'gong-zhi': { x: 30, y: 105 }, + 'huang-zhong': { x: 40, y: 94 }, + 'wei-yan': { x: 46, y: 95 }, + 'pang-tong': { x: 16, y: 89 }, + 'fa-zheng': { x: 30, y: 88 }, + 'wu-yi': { x: 49, y: 103 }, + 'yan-yan': { x: 53, y: 96 }, + 'li-yan': { x: 35, y: 87 }, + 'huang-quan': { x: 13, y: 92 }, + 'ma-chao': { x: 56, y: 92 }, + 'ma-dai': { x: 61, y: 99 }, + 'wang-ping': { x: 42, y: 87 }, + 'jiang-wei': { x: 36, y: 90 } +}; + +const sixtyFourthEnemySpecs: ReadonlyArray< + readonly [string, string, string, UnitClassKey, number, number, number, number, number] +> = [ + ['northern-tenth-scout-a', '위수 서안 척후병', '서쪽 강안 정찰', 'bandit', 232, 1120, 306, 50, 90], + ['northern-tenth-scout-b', '수레길 척후병', '수레길 정찰', 'bandit', 232, 1120, 306, 63, 86], + ['northern-tenth-scout-c', '물목 척후병', '중앙 물목 정찰', 'bandit', 233, 1132, 308, 77, 80], + ['northern-tenth-scout-d', '봉화 척후병', '동쪽 봉화 정찰', 'bandit', 233, 1132, 308, 91, 72], + ['northern-tenth-scout-e', '곽회 전초 척후병', '차단로 정찰', 'bandit', 234, 1144, 310, 106, 60], + ['northern-tenth-scout-f', '위수 뒤 척후병', '북쪽 진영 정찰', 'bandit', 234, 1144, 310, 124, 38], + ['northern-tenth-infantry-a', '위군 강안 보병', '서쪽 강안 보병', 'yellowTurban', 233, 1700, 404, 57, 85], + ['northern-tenth-infantry-b', '위군 수레길 보병', '수레길 보병', 'yellowTurban', 233, 1700, 404, 70, 82], + ['northern-tenth-infantry-c', '위군 물목 보병', '중앙 물목 보병', 'yellowTurban', 234, 1720, 408, 84, 75], + ['northern-tenth-infantry-d', '위군 봉화 보병', '봉화대 보병', 'yellowTurban', 234, 1720, 408, 98, 68], + ['northern-tenth-infantry-e', '곽회 차단 보병', '차단로 보병', 'yellowTurban', 235, 1740, 412, 110, 55], + ['northern-tenth-infantry-f', '위수 본영 보병', '본영 보병', 'yellowTurban', 235, 1740, 412, 121, 45], + ['northern-tenth-infantry-g', '강가 수레 차단병', '수레 차단 보병', 'yellowTurban', 236, 1760, 416, 94, 91], + ['northern-tenth-infantry-h', '북쪽 진문 보병', '진문 보병', 'yellowTurban', 236, 1760, 416, 126, 27], + ['northern-tenth-archer-a', '위수 서안 궁병', '서쪽 능선 궁병', 'archer', 233, 1230, 334, 66, 77], + ['northern-tenth-archer-b', '수레길 궁병', '수레길 궁병', 'archer', 233, 1230, 334, 79, 71], + ['northern-tenth-archer-c', '물목 궁병', '중앙 물목 궁병', 'archer', 234, 1242, 336, 93, 63], + ['northern-tenth-archer-d', '봉화대 궁병', '봉화 궁병', 'archer', 235, 1254, 338, 106, 52], + ['northern-tenth-archer-e', '본영 궁병', '위수 본영 궁병', 'archer', 235, 1254, 338, 118, 35], + ['northern-tenth-archer-f', '북쪽 절벽 궁병', '절벽 궁병', 'archer', 236, 1266, 340, 126, 19], + ['northern-tenth-cavalry-a', '장합 물목 기병', '서쪽 물목 기병', 'cavalry', 234, 1780, 428, 52, 78], + ['northern-tenth-cavalry-b', '장합 물목 기병', '수레길 추격 기병', 'cavalry', 234, 1780, 428, 65, 75], + ['northern-tenth-cavalry-c', '장합 물목 기병', '중앙 추격 기병', 'cavalry', 235, 1802, 432, 82, 67], + ['northern-tenth-cavalry-d', '곽회 차단 기병', '동쪽 차단 기병', 'cavalry', 236, 1824, 436, 98, 59], + ['northern-tenth-cavalry-e', '사마의 예비 기병', '본영 예비 기병', 'cavalry', 236, 1824, 436, 114, 45], + ['northern-tenth-cavalry-f', '북쪽 절벽 기병', '절벽 차단 기병', 'cavalry', 237, 1846, 440, 126, 30], + ['northern-tenth-camp-guard-a', '위수 서안 수비대', '서쪽 진영 수비', 'yellowTurban', 234, 1720, 410, 44, 64], + ['northern-tenth-camp-guard-b', '수레 표식 수비대', '표식 수비', 'yellowTurban', 234, 1720, 410, 61, 60], + ['northern-tenth-camp-guard-c', '물목 수비대', '중앙 물목 수비', 'yellowTurban', 235, 1740, 414, 79, 53], + ['northern-tenth-camp-guard-d', '봉화 길 수비대', '봉화 길 수비', 'yellowTurban', 235, 1740, 414, 97, 44], + ['northern-tenth-camp-guard-e', '사마의 본영 수비대', '본영 수비', 'yellowTurban', 236, 1760, 418, 113, 36], + ['northern-tenth-camp-guard-f', '북쪽 절벽 수비대', '절벽 수비', 'yellowTurban', 236, 1760, 418, 126, 23], + ['northern-tenth-strategist-a', '위군 위수 군리', '강안 장부 군리', 'strategist', 234, 1320, 354, 82, 60], + ['northern-tenth-strategist-b', '위군 보급 군리', '수레 차단 군리', 'strategist', 235, 1332, 356, 98, 48], + ['northern-tenth-strategist-c', '사마의 본영 군리', '본영 장부 군리', 'strategist', 235, 1332, 356, 114, 38], + ['northern-tenth-strategist-d', '곽회 봉화 군리', '봉화 장부 군리', 'strategist', 236, 1344, 358, 121, 68], + ['northern-tenth-officer-river', '위수 진영장', '위수 진영 장수', 'rebelLeader', 238, 1840, 440, 88, 82], + ['northern-tenth-officer-supply', '강가 수레 차단장', '수레 차단 장수', 'rebelLeader', 238, 1840, 440, 108, 74], + ['northern-tenth-officer-guo-huai', '곽회', '봉화 차단 장수', 'rebelLeader', 237, 1820, 436, 118, 72], + ['northern-tenth-officer-relay', '봉화 연락장', '위수 뒤 봉화 장수', 'rebelLeader', 237, 1820, 436, 121, 54], + ['northern-tenth-officer-zhang-he', '장합', '위수 물목 추격대장', 'rebelLeader', 239, 1880, 446, 124, 33], + ['northern-tenth-leader-sima-yi', '사마의', '위수 진영 총지휘', 'rebelLeader', 240, 1920, 452, 120, 45] +]; + +export const sixtyFourthBattleUnits: 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, + ...jiangWeiRecruitUnits + ].map((unit) => placeScenarioUnit(unit, sixtyFourthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })), + ...sixtyFourthEnemySpecs.map((spec) => createThirtyFirstEnemyUnit(...spec)) +]; + export const firstBattleBonds: BattleBond[] = [ { id: 'liu-bei__guan-yu', @@ -19469,6 +19625,46 @@ export const sixtyThirdBattleBonds: BattleBond[] = [ ...northernNinthBattleBonds ].map(cloneBattleBondForScenario); +export const northernTenthBattleBonds: BattleBond[] = [ + { + id: 'jiang-wei__wang-ping_northern-tenth', + unitIds: ['jiang-wei', 'wang-ping'], + title: '물목과 정지선', + level: 58, + exp: 0, + description: '강유의 낮은 길 판단과 왕평의 정지선이 맞물리면, 위수 물목을 넘볼 때도 돌아올 표식을 잃지 않습니다.' + }, + { + id: 'zhuge-liang__huang-quan_northern-tenth', + unitIds: ['zhuge-liang', 'huang-quan'], + title: '강가의 장부', + level: 56, + exp: 0, + description: '제갈량의 전선 계산과 황권의 군량 장부가 함께 움직이면, 강을 낀 장기전에서도 수레가 먼저 무너지지 않습니다.' + }, + { + id: 'wei-yan__ma-dai_northern-tenth', + unitIds: ['wei-yan', 'ma-dai'], + title: '돌파와 측면', + level: 52, + exp: 0, + description: '위연의 돌파와 마대의 측면 기동은 위수 진영 사이를 흔들어 장합의 추격 기병을 분산시킵니다.' + }, + { + id: 'zhao-yun__li-yan_northern-tenth', + unitIds: ['zhao-yun', 'li-yan'], + title: '예비대와 수레', + level: 50, + exp: 0, + description: '조운의 예비대 회수와 이엄의 운송 기준이 맞으면, 깊어진 진영전에서도 보급 수레를 버리지 않고 물릴 수 있습니다.' + } +]; + +export const sixtyFourthBattleBonds: BattleBond[] = [ + ...sixtyThirdBattleBonds, + ...northernTenthBattleBonds +].map(cloneBattleBondForScenario); + function createEighthBattleTerrain(): TerrainType[][] { return Array.from({ length: 22 }, (_, y) => Array.from({ length: 26 }, (_, x): TerrainType => { @@ -23027,6 +23223,87 @@ function createSixtyThirdBattleTerrain(): TerrainType[][] { ); } +function createSixtyFourthBattleTerrain(): TerrainType[][] { + return Array.from({ length: 110 }, (_, y) => + Array.from({ length: 128 }, (_, x): TerrainType => { + if ( + (x >= 2 && x <= 126 && y >= 21 && y <= 32) || + (x >= 2 && x <= 126 && y >= 36 && y <= 45) + ) { + return 'river'; + } + if ( + (x >= 7 && x <= 90 && y >= 95 && y <= 101) || + (x >= 24 && x <= 108 && y >= 83 && y <= 91) || + (x >= 48 && x <= 116 && y >= 71 && y <= 81) || + (x >= 78 && x <= 124 && y >= 58 && y <= 70) || + (x >= 99 && x <= 127 && y >= 44 && y <= 56) || + (x >= 33 && x <= 43 && y >= 75 && y <= 105) || + (x >= 59 && x <= 69 && y >= 61 && y <= 96) || + (x >= 88 && x <= 99 && y >= 47 && y <= 86) || + (x >= 102 && x <= 112 && y >= 27 && y <= 72) || + (x >= 43 && x <= 88 && y >= 87 && y <= 93) + ) { + return 'road'; + } + if ( + (x >= 0 && x <= 40 && y >= 65 && y <= 98) || + (x >= 44 && x <= 86 && y >= 80 && y <= 101) || + (x >= 92 && x <= 127 && y >= 74 && y <= 100) || + (x >= 16 && x <= 45 && y >= 48 && y <= 69) || + (x >= 102 && x <= 127 && y >= 57 && y <= 78) || + (x >= 118 && x <= 127 && y >= 30 && y <= 53) + ) { + return 'forest'; + } + if ( + (x >= 0 && x <= 55 && y <= 19) || + (x >= 47 && x <= 98 && y <= 22) || + (x >= 90 && x <= 127 && y <= 27) || + (x >= 100 && x <= 127 && y >= 50 && y <= 78) + ) { + return 'hill'; + } + if ( + (x <= 2 && y <= 101) || + (x >= 125 && y <= 94) || + (x >= 41 && x <= 48 && y >= 0 && y <= 70) || + (x >= 80 && x <= 87 && y >= 0 && y <= 53) || + (x >= 64 && x <= 72 && y >= 90 && y <= 109) + ) { + return 'cliff'; + } + if ( + (x >= 13 && x <= 33 && y >= 91 && y <= 101) || + (x >= 45 && x <= 68 && y >= 78 && y <= 90) || + (x >= 84 && x <= 105 && y >= 64 && y <= 76) || + (x >= 106 && x <= 125 && y >= 48 && y <= 62) || + (x >= 113 && x <= 127 && y >= 31 && y <= 41) + ) { + return 'village'; + } + if ( + (x >= 22 && x <= 52 && y >= 85 && y <= 99) || + (x >= 57 && x <= 86 && y >= 72 && y <= 85) || + (x >= 92 && x <= 118 && y >= 58 && y <= 71) || + (x >= 101 && x <= 122 && y >= 28 && y <= 44) + ) { + return 'camp'; + } + if ( + (x >= 51 && x <= 74 && y >= 52 && y <= 68) || + (x >= 75 && x <= 104 && y >= 42 && y <= 58) || + (x >= 97 && x <= 124 && y >= 51 && y <= 67) || + (x >= 106 && x <= 127 && y >= 16 && y <= 40) || + (x >= 113 && x <= 127 && y >= 70 && y <= 88) + ) { + return 'fort'; + } + return 'plain'; + }) + ); +} + function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData { return { ...cloneUnitForScenario(unit), diff --git a/src/game/data/storyAssets.ts b/src/game/data/storyAssets.ts index c0090b1..666bd3e 100644 --- a/src/game/data/storyAssets.ts +++ b/src/game/data/storyAssets.ts @@ -20,6 +20,7 @@ import storyChencangSiegeUrl from '../../assets/images/story/19-chencang-siege.p import storyWuduYinpingUrl from '../../assets/images/story/20-wudu-yinping.png'; import storyHanzhongRainDefenseUrl from '../../assets/images/story/21-hanzhong-rain-defense.png'; import storyLuchengPursuitUrl from '../../assets/images/story/22-lucheng-pursuit.png'; +import storyWeishuiCampsUrl from '../../assets/images/story/23-weishui-camps.png'; export const storyBackgroundAssets: Record = { 'story-chaos': storyChaosUrl, @@ -44,5 +45,6 @@ export const storyBackgroundAssets: Record = { 'story-wudu-yinping': storyWuduYinpingUrl, 'story-hanzhong-rain': storyHanzhongRainDefenseUrl, 'story-lucheng-pursuit': storyLuchengPursuitUrl, + 'story-weishui-camps': storyWeishuiCampsUrl, 'story-resolve': storyLiuBeiUrl }; diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 8120929..aa6560a 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -6572,14 +6572,16 @@ export class BattleScene extends Phaser.Scene { 'northern-sixth', 'northern-seventh', 'northern-eighth', - 'northern-ninth' + 'northern-ninth', + 'northern-tenth' ]; if (lateNorthernPrefixes.some((prefix) => enemy.id.startsWith(prefix))) { if ( enemy.id.includes('-scout') || enemy.id.includes('-cavalry') || enemy.id === 'northern-eighth-officer-zhang-he' || - enemy.id === 'northern-ninth-officer-zhang-he' + enemy.id === 'northern-ninth-officer-zhang-he' || + enemy.id === 'northern-tenth-officer-zhang-he' ) { return 'aggressive'; } diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index b75fa37..9a4637b 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -59,6 +59,7 @@ import { northernSecondBattleBonds, northernSeventhBattleBonds, northernSixthBattleBonds, + northernTenthBattleBonds, northernThirdBattleBonds, wulingRecruitBonds, wulingRecruitUnits, @@ -451,9 +452,10 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [ 'sixtieth-battle-wudu-yinping', 'sixty-first-battle-hanzhong-rain-defense', 'sixty-second-battle-qishan-renewed-offensive', - 'sixty-third-battle-lucheng-pursuit' + 'sixty-third-battle-lucheng-pursuit', + 'sixty-fourth-battle-weishui-camps' ], - nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면', '강유 합류', '진창 공성 재계산', '무도·음평 확보', '한중 우로 방어', '기산 재출정', '노성 추격'] + nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면', '강유 합류', '진창 공성 재계산', '무도·음평 확보', '한중 우로 방어', '기산 재출정', '노성 추격', '위수 진영'] } ]; @@ -520,7 +522,8 @@ const campBattleIds = { sixtieth: 'sixtieth-battle-wudu-yinping', sixtyFirst: 'sixty-first-battle-hanzhong-rain-defense', sixtySecond: 'sixty-second-battle-qishan-renewed-offensive', - sixtyThird: 'sixty-third-battle-lucheng-pursuit' + sixtyThird: 'sixty-third-battle-lucheng-pursuit', + sixtyFourth: 'sixty-fourth-battle-weishui-camps' } as const; const defaultRequiredSortieUnitIds = ['liu-bei']; @@ -1321,6 +1324,23 @@ const sortieRulesByBattleId: Partial