From 43347fb33b8358d752c2cb2765ad8146a0106179 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Tue, 23 Jun 2026 14:21:47 +0900 Subject: [PATCH] Add King of Hanzhong council milestone --- docs/roadmap.md | 5 ++- scripts/verify-flow.mjs | 66 +++++++++++++++++++++++++++++++-- src/game/data/campaignFlow.ts | 15 ++++---- src/game/data/scenario.ts | 37 ++++++++++++++++++ src/game/scenes/CampScene.ts | 27 +++++++++++++- src/game/scenes/TitleScene.ts | 3 +- src/game/state/campaignState.ts | 3 +- 7 files changed, 140 insertions(+), 16 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 091b885..3bccf0c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -59,15 +59,16 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of - Thirty-fifth battle Yangping scout route, using Ma Chao's first allied sortie across a 70x52 Hanzhong gate battlefield and recruiting Ma Dai after the pass is surveyed - Thirty-sixth battle Dingjun Mountain vanguard route, using Huang Zhong and Fa Zheng's high-ground plan across a 72x54 Hanzhong battlefield and recruiting Wang Ping after the ridge is secured - Thirty-seventh battle Hanzhong decisive route, using Wang Ping's terrain knowledge and a seven-officer sortie choice across a 74x56 Hanzhong battlefield to push Cao Cao out and prepare Liu Bei's King of Hanzhong step +- Post-Hanzhong King of Hanzhong council flow, where the sortie button can advance a story-only political camp milestone and persist a dedicated campaign step before Shu-Han foundation - 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, 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, 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 thirty-seventh battle victory, 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 camp states, campaign timeline state, and camp save state +- Flow verification script from title through the thirty-seventh battle victory and King of Hanzhong council 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 camp states, campaign timeline state, and camp save state ## Next Steps -1. Continue from Hanzhong victory into the King of Hanzhong and Shu-Han foundation setup, making the political transition playable in camp rather than a single text page +1. Continue from the King of Hanzhong council into the formal Shu-Han foundation scene, making Liu Bei's political transition feel like a payoff for the campaign arc 2. Add the next tension around Guan Yu's Jing Province defense so the Shu-Han foundation has an immediate strategic cost 3. Expand sortie selection into richer pre-battle officer planning, including explicit reserve drills, class practice, and bond-focused camp assignments 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 d162be8..7acf61e 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -4691,12 +4691,72 @@ try { postHanzhongProgressState.campaignProgress?.totalKnown !== 37 || postHanzhongProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postHanzhongProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || - postHanzhongProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + postHanzhongProgressState.campaignProgress?.nextBattleTitle !== '한중왕 즉위 준비' ) { - throw new Error(`Expected post-Hanzhong progress tab to complete the decisive battle and pause on Shu-Han foundation prep: ${JSON.stringify(postHanzhongProgressState?.campaignProgress)}`); + throw new Error(`Expected post-Hanzhong progress tab to complete the decisive battle and expose the King of Hanzhong council: ${JSON.stringify(postHanzhongProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-hanzhong-progress.png', fullPage: true }); + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const hanzhongKingSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !hanzhongKingSortieState?.sortieVisible || + hanzhongKingSortieState.campaign?.step !== 'thirty-seventh-camp' || + hanzhongKingSortieState.campBattleId !== 'thirty-seventh-battle-hanzhong-decisive' + ) { + throw new Error(`Expected King of Hanzhong council to be available from the post-Hanzhong sortie panel: ${JSON.stringify(hanzhongKingSortieState)}`); + } + + 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-hanzhong-king-council-story.png', fullPage: true }); + + for (let i = 0; i < 16; i += 1) { + const returnedToCamp = await page.evaluate(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('CampScene') && !activeScenes.includes('StoryScene'); + }); + if (returnedToCamp) { + break; + } + await page.keyboard.press('Space'); + await page.waitForTimeout(320); + } + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('CampScene') && !activeScenes.includes('StoryScene'); + }); + + const hanzhongKingCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + hanzhongKingCampState?.campaign?.step !== 'hanzhong-king-camp' || + hanzhongKingCampState.campTitle !== '한중왕 즉위 준비 군영' || + hanzhongKingCampState.campBattleId !== 'thirty-seventh-battle-hanzhong-decisive' || + hanzhongKingCampState.availableDialogueIds?.length !== 3 || + hanzhongKingCampState.availableVisitIds?.length !== 2 + ) { + throw new Error(`Expected King of Hanzhong council story to persist a dedicated camp step: ${JSON.stringify(hanzhongKingCampState)}`); + } + await page.screenshot({ path: 'dist/verification-hanzhong-king-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postKingCouncilProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postKingCouncilProgressState?.activeTab !== 'progress' || + postKingCouncilProgressState.campaignProgress?.completedKnown !== 37 || + postKingCouncilProgressState.campaignProgress?.totalKnown !== 37 || + postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || + postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 준비' + ) { + throw new Error(`Expected King of Hanzhong council to move the progress tab toward Shu-Han foundation prep: ${JSON.stringify(postKingCouncilProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-king-council-progress.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); @@ -4715,7 +4775,7 @@ try { return activeScenes.includes('CampScene'); }); - console.log(`Verified title-to-thirty-seventh-battle 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, and Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, and Hanzhong decisive battles, result states, and debug API at ${targetUrl}`); + console.log(`Verified title-to-thirty-seventh-battle 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, and Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, and King of Hanzhong council state at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index 7eb190b..1f9f0d2 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -55,6 +55,7 @@ import { firstBattleVictoryPages, fourthBattleIntroPages, fourthBattleVictoryPages, + hanzhongKingCouncilPages, ninthBattleIntroPages, ninthBattleVictoryPages, nineteenthBattleIntroPages, @@ -86,7 +87,6 @@ import { thirtySixthBattleIntroPages, thirtySixthBattleVictoryPages, thirtySeventhBattleIntroPages, - thirtySeventhBattleVictoryPages, thirtiethBattleIntroPages, thirtiethBattleVictoryPages, twentyEighthBattleIntroPages, @@ -515,13 +515,14 @@ const sortieFlows: Record = { }, [thirtySeventhBattleScenario.id]: { afterBattleId: thirtySeventhBattleScenario.id, - eyebrow: '다음 장 준비', - title: '촉한 건국 준비', + eyebrow: '군영 의정', + title: '한중왕 즉위 준비', description: - '한중 결전에서 조조군을 물리치고 북문을 얻었습니다. 다음은 한중왕 즉위와 촉한의 이름을 세우는 정치적 전환을 군영 이벤트와 다음 전장으로 준비해야 합니다.', - rewardHint: '다음 장: 한중왕 즉위와 촉한 건국 준비 중', - pages: thirtySeventhBattleVictoryPages, - unavailableNotice: '한중왕 즉위와 촉한 건국 선포는 다음 작업에서 이어집니다. 유비의 명분, 제갈량의 국정 구상, 관우가 지키는 형주의 긴장을 함께 준비하십시오.' + '한중 결전에서 조조군을 물리치고 북문을 얻었습니다. 출진 준비를 통해 새 전투가 아니라 한중왕 즉위 논의를 진행하고, 촉한 건국의 명분을 군영 안에서 정리합니다.', + rewardHint: '군영 목표: 한중왕 즉위 준비 / 촉한 건국 명분 정리', + campaignStep: 'hanzhong-king-camp', + pages: hanzhongKingCouncilPages, + unavailableNotice: '한중왕 즉위 준비가 완료되었습니다. 다음 작업에서는 촉한 건국 선포와 형주 방면의 긴장을 이어 준비합니다.' } }; diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index abb1b0d..1c016fb 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -2387,6 +2387,43 @@ export const thirtySeventhBattleVictoryPages: StoryPage[] = [ } ]; +export const hanzhongKingCouncilPages: StoryPage[] = [ + { + id: 'hanzhong-king-council-open', + bgm: 'story-dark', + chapter: '한중왕 즉위 논의', + background: 'story-three-heroes', + speaker: '제갈량', + text: '한중을 얻은 뒤 군영의 분위기는 전장의 승리에서 나라의 이름으로 옮겨 갔습니다. 이제 유비군은 어디를 지키는 군대인지, 누구의 뜻을 잇는 세력인지 밝혀야 합니다.' + }, + { + id: 'hanzhong-king-liu-bei-doubt', + bgm: 'battle-prep', + chapter: '이름의 무게', + background: 'story-liu-bei', + speaker: '유비', + portrait: 'liuBei', + text: '내가 왕의 이름을 받는 것이 백성을 위한 길이라면 마다하지 않겠소. 다만 그 이름이 욕심으로 보인다면 지금까지 따른 이들의 뜻을 해칠까 두렵소.' + }, + { + id: 'hanzhong-king-officers', + bgm: 'battle-prep', + chapter: '장수들의 서명', + background: 'story-militia', + speaker: '관우', + portrait: 'guanYu', + text: '형님이 세우는 이름은 한실을 잇기 위한 깃발입니다. 전장마다 모인 장수들이 그 깃발 아래 서명한다면, 병사들도 무엇을 위해 싸우는지 알게 될 것입니다.' + }, + { + id: 'hanzhong-king-next-foundation', + bgm: 'militia-theme', + chapter: '촉한의 문 앞에서', + background: 'story-sortie', + speaker: '제갈량', + text: '한중왕의 격문이 준비되면 다음은 촉한의 이름을 세울 차례입니다. 그러나 나라의 이름은 새 전장을 부릅니다. 형주의 긴장과 북방의 그림자도 함께 보아야 합니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 43ef3c4..ea68483 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -5854,6 +5854,9 @@ export class CampScene extends Phaser.Scene { private currentCampTitle() { const battleId = this.currentCampBattleId(); + if (this.campaign?.step === 'hanzhong-king-camp') { + return '한중왕 즉위 준비 군영'; + } if (battleId === campBattleIds.thirtySeventh) { return '한중 결전 후 군영'; } @@ -6673,6 +6676,19 @@ export class CampScene extends Phaser.Scene { if (!flow.nextBattleId || !flow.campaignStep || flow.pages.length === 0) { this.hideSortiePrep(); + if (!flow.nextBattleId && flow.campaignStep && flow.pages.length > 0) { + if (this.campaign?.step === flow.campaignStep) { + this.showCampNotice(flow.unavailableNotice ?? '다음 장은 아직 준비 중입니다. 군영에서 성장 상태를 정비하세요.'); + return; + } + markCampaignStep(flow.campaignStep); + this.campaign = getCampaignState(); + this.scene.start('StoryScene', { + pages: flow.pages, + nextScene: 'CampScene' + }); + return; + } if (flow.pages.length > 0) { this.scene.start('StoryScene', { pages: flow.pages, @@ -6950,14 +6966,21 @@ export class CampScene extends Phaser.Scene { const activeChapterIndex = this.activeTimelineChapterIndex(completedIds); const activeChapter = campaignTimelineChapters[activeChapterIndex]; const latestBattleId = this.campaign?.latestBattleId ?? this.report?.battleId; - const nextBattleId = getSortieFlow(this.campaign?.latestBattleId).nextBattleId; + const flow = getSortieFlow(this.campaign?.latestBattleId); + const nextBattleId = flow.nextBattleId; return { completedKnown, totalKnown, activeChapterIndex, activeChapter, latestBattleTitle: latestBattleId ? getBattleScenario(latestBattleId).title : '없음', - nextBattleTitle: nextBattleId ? getBattleScenario(nextBattleId).title : '준비 중' + nextBattleTitle: this.campaign?.step === 'hanzhong-king-camp' + ? '촉한 건국 준비' + : nextBattleId + ? getBattleScenario(nextBattleId).title + : flow.campaignStep + ? flow.title + : '준비 중' }; } diff --git a/src/game/scenes/TitleScene.ts b/src/game/scenes/TitleScene.ts index c4e78fc..b739fe4 100644 --- a/src/game/scenes/TitleScene.ts +++ b/src/game/scenes/TitleScene.ts @@ -373,7 +373,8 @@ export class TitleScene extends Phaser.Scene { campaign.step === 'thirty-fourth-camp' || campaign.step === 'thirty-fifth-camp' || campaign.step === 'thirty-sixth-camp' || - campaign.step === 'thirty-seventh-camp' + campaign.step === 'thirty-seventh-camp' || + campaign.step === 'hanzhong-king-camp' ) { this.scene.start('CampScene'); return; diff --git a/src/game/state/campaignState.ts b/src/game/state/campaignState.ts index a0dd0ae..cbe7e5a 100644 --- a/src/game/state/campaignState.ts +++ b/src/game/state/campaignState.ts @@ -115,7 +115,8 @@ export type CampaignStep = | 'thirty-sixth-battle' | 'thirty-sixth-camp' | 'thirty-seventh-battle' - | 'thirty-seventh-camp'; + | 'thirty-seventh-camp' + | 'hanzhong-king-camp'; export type CampaignUnitProgressSnapshot = { unitId: string;