Add Changsha veteran chapter
This commit is contained in:
@@ -47,16 +47,17 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of
|
|||||||
- Twenty-third battle Jing Province south entry route, opening the post-Red-Cliffs land campaign with Xing Daorong, village stabilization, a larger 46x32 battlefield, and Ma Liang recruitment after victory
|
- Twenty-third battle Jing Province south entry route, opening the post-Red-Cliffs land campaign with Xing Daorong, village stabilization, a larger 46x32 battlefield, and Ma Liang recruitment after victory
|
||||||
- Twenty-fourth battle Guiyang persuasion route, using Ma Liang's local knowledge in sortie selection, expanding the southern-commandery map to 48x32, and recruiting Yi Ji after Zhao Fan yields
|
- Twenty-fourth battle Guiyang persuasion route, using Ma Liang's local knowledge in sortie selection, expanding the southern-commandery map to 48x32, and recruiting Yi Ji after Zhao Fan yields
|
||||||
- Twenty-fifth battle Wuling mountain road route, pushing ten recruited officers into a six-officer sortie choice across a 50x34 mountain battlefield and recruiting Gong Zhi after Jin Xuan withdraws
|
- Twenty-fifth battle Wuling mountain road route, pushing ten recruited officers into a six-officer sortie choice across a 50x34 mountain battlefield and recruiting Gong Zhi after Jin Xuan withdraws
|
||||||
|
- Twenty-sixth battle Changsha veteran audience route, turning the southern Jing Province finale into a 52x34 castle approach battle and recruiting Huang Zhong plus Wei Yan after Han Xuan withdraws
|
||||||
- Camp progress timeline tab that summarizes Liu Bei's long campaign arc, completed battles, current chapter, latest battle, and next major chapter
|
- 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
|
- 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
|
- 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
|
- 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 twenty-fifth battle victory, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling camp states, campaign timeline state, and camp save state
|
- Flow verification script from title through the twenty-sixth battle victory, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling/Changsha camp states, campaign timeline state, and camp save state
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
1. Continue Jing Province south from Wuling into Changsha acquisition, including Huang Zhong and Wei Yan recruitment pressure
|
1. Start the Yi Province entry arc from the stabilized Jing Province base, with larger roster pressure and political camp events
|
||||||
2. Add more recruitable officers as the campaign moves through Changsha, Yi Province, and Shu Han
|
2. Add more recruitable officers as the campaign moves through Yi Province and toward Shu Han
|
||||||
3. Expand reserve training into explicit drill choices, class practice, and bond-focused camp assignments
|
3. Expand reserve training into explicit drill choices, class practice, and bond-focused camp assignments
|
||||||
4. Add a dedicated treasure/equipment management view so players can compare special effects and growth
|
4. Add a dedicated treasure/equipment management view so players can compare special effects and growth
|
||||||
5. Keep expanding scenarios through the long campaign instead of treating the current slice as an ending
|
5. Keep expanding scenarios through the long campaign instead of treating the current slice as an ending
|
||||||
|
|||||||
@@ -1736,7 +1736,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
progressTabState?.activeTab !== 'progress' ||
|
progressTabState?.activeTab !== 'progress' ||
|
||||||
progressTabState.campaignProgress?.completedKnown !== 17 ||
|
progressTabState.campaignProgress?.completedKnown !== 17 ||
|
||||||
progressTabState.campaignProgress?.totalKnown !== 25 ||
|
progressTabState.campaignProgress?.totalKnown !== 26 ||
|
||||||
progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
||||||
progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' ||
|
progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' ||
|
||||||
progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전'
|
progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전'
|
||||||
@@ -1888,7 +1888,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
postBowangProgressState?.activeTab !== 'progress' ||
|
postBowangProgressState?.activeTab !== 'progress' ||
|
||||||
postBowangProgressState.campaignProgress?.completedKnown !== 18 ||
|
postBowangProgressState.campaignProgress?.completedKnown !== 18 ||
|
||||||
postBowangProgressState.campaignProgress?.totalKnown !== 25 ||
|
postBowangProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
||||||
postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' ||
|
postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' ||
|
||||||
postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로'
|
postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로'
|
||||||
@@ -2026,7 +2026,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
postChangbanProgressState?.activeTab !== 'progress' ||
|
postChangbanProgressState?.activeTab !== 'progress' ||
|
||||||
postChangbanProgressState.campaignProgress?.completedKnown !== 19 ||
|
postChangbanProgressState.campaignProgress?.completedKnown !== 19 ||
|
||||||
postChangbanProgressState.campaignProgress?.totalKnown !== 25 ||
|
postChangbanProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
||||||
postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' ||
|
postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' ||
|
||||||
postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로'
|
postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로'
|
||||||
@@ -2164,7 +2164,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
postJiangdongProgressState?.activeTab !== 'progress' ||
|
postJiangdongProgressState?.activeTab !== 'progress' ||
|
||||||
postJiangdongProgressState.campaignProgress?.completedKnown !== 20 ||
|
postJiangdongProgressState.campaignProgress?.completedKnown !== 20 ||
|
||||||
postJiangdongProgressState.campaignProgress?.totalKnown !== 25 ||
|
postJiangdongProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
||||||
postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' ||
|
postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' ||
|
||||||
postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전'
|
postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전'
|
||||||
@@ -2304,7 +2304,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
postRedCliffsProgressState?.activeTab !== 'progress' ||
|
postRedCliffsProgressState?.activeTab !== 'progress' ||
|
||||||
postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 ||
|
postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 ||
|
||||||
postRedCliffsProgressState.campaignProgress?.totalKnown !== 25 ||
|
postRedCliffsProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
|
||||||
postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' ||
|
postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' ||
|
||||||
postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전'
|
postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전'
|
||||||
@@ -2444,7 +2444,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
postFireAttackProgressState?.activeTab !== 'progress' ||
|
postFireAttackProgressState?.activeTab !== 'progress' ||
|
||||||
postFireAttackProgressState.campaignProgress?.completedKnown !== 22 ||
|
postFireAttackProgressState.campaignProgress?.completedKnown !== 22 ||
|
||||||
postFireAttackProgressState.campaignProgress?.totalKnown !== 25 ||
|
postFireAttackProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
||||||
postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' ||
|
postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' ||
|
||||||
postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전'
|
postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전'
|
||||||
@@ -2587,7 +2587,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
postJingzhouProgressState?.activeTab !== 'progress' ||
|
postJingzhouProgressState?.activeTab !== 'progress' ||
|
||||||
postJingzhouProgressState.campaignProgress?.completedKnown !== 23 ||
|
postJingzhouProgressState.campaignProgress?.completedKnown !== 23 ||
|
||||||
postJingzhouProgressState.campaignProgress?.totalKnown !== 25 ||
|
postJingzhouProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
||||||
postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' ||
|
postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' ||
|
||||||
postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전'
|
postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전'
|
||||||
@@ -2729,7 +2729,7 @@ try {
|
|||||||
if (
|
if (
|
||||||
postGuiyangProgressState?.activeTab !== 'progress' ||
|
postGuiyangProgressState?.activeTab !== 'progress' ||
|
||||||
postGuiyangProgressState.campaignProgress?.completedKnown !== 24 ||
|
postGuiyangProgressState.campaignProgress?.completedKnown !== 24 ||
|
||||||
postGuiyangProgressState.campaignProgress?.totalKnown !== 25 ||
|
postGuiyangProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
||||||
postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' ||
|
postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' ||
|
||||||
postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전'
|
postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전'
|
||||||
@@ -2872,15 +2872,164 @@ try {
|
|||||||
if (
|
if (
|
||||||
postWulingProgressState?.activeTab !== 'progress' ||
|
postWulingProgressState?.activeTab !== 'progress' ||
|
||||||
postWulingProgressState.campaignProgress?.completedKnown !== 25 ||
|
postWulingProgressState.campaignProgress?.completedKnown !== 25 ||
|
||||||
postWulingProgressState.campaignProgress?.totalKnown !== 25 ||
|
postWulingProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
||||||
postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' ||
|
postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' ||
|
||||||
postWulingProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
|
postWulingProgressState.campaignProgress?.nextBattleTitle !== '장사 노장 대면전'
|
||||||
) {
|
) {
|
||||||
throw new Error(`Expected post-Wuling progress tab to complete the mountain-road battle and pause on Changsha preparation: ${JSON.stringify(postWulingProgressState?.campaignProgress)}`);
|
throw new Error(`Expected post-Wuling progress tab to complete the mountain-road battle and pause on Changsha preparation: ${JSON.stringify(postWulingProgressState?.campaignProgress)}`);
|
||||||
}
|
}
|
||||||
await page.screenshot({ path: 'dist/verification-post-wuling-progress.png', fullPage: true });
|
await page.screenshot({ path: 'dist/verification-post-wuling-progress.png', fullPage: true });
|
||||||
|
|
||||||
|
await page.mouse.click(1120, 38);
|
||||||
|
await page.waitForTimeout(180);
|
||||||
|
const changshaSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||||
|
if (
|
||||||
|
!changshaSortieState?.sortieVisible ||
|
||||||
|
!changshaSortieState.sortiePlan?.objectiveLine?.includes('장사 노장 대면전') ||
|
||||||
|
!changshaSortieState.sortieRoster?.some((unit) => unit.id === 'gong-zhi' && unit.recruited && unit.recommended) ||
|
||||||
|
!changshaSortieState.sortieRoster?.some((unit) => unit.id === 'yi-ji' && unit.recruited && unit.recommended) ||
|
||||||
|
!changshaSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.recruited && unit.recommended) ||
|
||||||
|
!changshaSortieState.sortieRoster?.some((unit) => unit.id === 'guan-yu' && unit.recommended) ||
|
||||||
|
!changshaSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recruited && unit.recommended) ||
|
||||||
|
changshaSortieState.sortieRoster?.some((unit) => unit.id === 'huang-zhong') ||
|
||||||
|
changshaSortieState.sortieRoster?.some((unit) => unit.id === 'wei-yan') ||
|
||||||
|
changshaSortieState.sortieRoster?.length < 11 ||
|
||||||
|
changshaSortieState.sortiePlan?.maxCount !== 6
|
||||||
|
) {
|
||||||
|
throw new Error(`Expected twenty-fifth camp sortie prep to target Changsha with Gong Zhi recommended and Huang Zhong/Wei Yan absent before victory: ${JSON.stringify(changshaSortieState)}`);
|
||||||
|
}
|
||||||
|
assertSortieTacticalRoster(changshaSortieState, [
|
||||||
|
'liu-bei',
|
||||||
|
'guan-yu',
|
||||||
|
'zhang-fei',
|
||||||
|
'jian-yong',
|
||||||
|
'mi-zhu',
|
||||||
|
'sun-qian',
|
||||||
|
'zhao-yun',
|
||||||
|
'zhuge-liang',
|
||||||
|
'ma-liang',
|
||||||
|
'yi-ji',
|
||||||
|
'gong-zhi'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const changshaPriorityUnits = ['gong-zhi', 'yi-ji', 'zhuge-liang', 'guan-yu', 'zhao-yun'];
|
||||||
|
for (const unitId of changshaPriorityUnits) {
|
||||||
|
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.id !== 'liu-bei' && !changshaPriorityUnits.includes(unit.id)
|
||||||
|
);
|
||||||
|
if (removable) {
|
||||||
|
await clickSortieRosterUnit(page, removable.id);
|
||||||
|
}
|
||||||
|
await clickSortieRosterUnit(page, unitId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changshaSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||||
|
if (
|
||||||
|
!changshaPriorityUnits.every((unitId) =>
|
||||||
|
changshaSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)
|
||||||
|
) ||
|
||||||
|
changshaSortieReadyState.sortiePlan?.selectedCount !== 6 ||
|
||||||
|
changshaSortieReadyState.sortiePlan?.recommendedSelectedCount < 6
|
||||||
|
) {
|
||||||
|
throw new Error(`Expected Changsha sortie to deploy Gong Zhi and the veteran-audience priority officers while preserving six-officer pressure: ${JSON.stringify(changshaSortieReadyState)}`);
|
||||||
|
}
|
||||||
|
await page.screenshot({ path: 'dist/verification-changsha-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-changsha-story.png', fullPage: true });
|
||||||
|
|
||||||
|
for (let i = 0; i < 50; i += 1) {
|
||||||
|
const enteredTwentySixthBattle = await page.evaluate(() => {
|
||||||
|
const state = window.__HEROS_DEBUG__?.battle();
|
||||||
|
return state?.scene === 'BattleScene' && state?.battleId === 'twenty-sixth-battle-changsha-veteran';
|
||||||
|
});
|
||||||
|
if (enteredTwentySixthBattle) {
|
||||||
|
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 === 'twenty-sixth-battle-changsha-veteran' && state?.battleOutcome === null && state?.phase === 'idle';
|
||||||
|
});
|
||||||
|
await page.screenshot({ path: 'dist/verification-twenty-sixth-battle.png', fullPage: true });
|
||||||
|
|
||||||
|
const twentySixthBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
|
||||||
|
const twentySixthEnemies = twentySixthBattleState.units.filter((unit) => unit.faction === 'enemy');
|
||||||
|
const twentySixthAllies = twentySixthBattleState.units.filter((unit) => unit.faction === 'ally');
|
||||||
|
const twentySixthEnemyBehaviors = new Set(twentySixthEnemies.map((unit) => unit.ai));
|
||||||
|
if (
|
||||||
|
twentySixthBattleState.camera?.mapWidth !== 52 ||
|
||||||
|
twentySixthBattleState.camera?.mapHeight !== 34 ||
|
||||||
|
twentySixthBattleState.victoryConditionLabel !== '한현 퇴각' ||
|
||||||
|
twentySixthEnemies.length < 18 ||
|
||||||
|
!twentySixthEnemyBehaviors.has('aggressive') ||
|
||||||
|
!twentySixthEnemyBehaviors.has('guard') ||
|
||||||
|
!twentySixthEnemyBehaviors.has('hold') ||
|
||||||
|
!twentySixthEnemies.some((unit) => unit.id === 'changsha-leader-han-xuan') ||
|
||||||
|
!twentySixthEnemies.some((unit) => unit.id === 'changsha-veteran-huang-zhong') ||
|
||||||
|
!twentySixthEnemies.some((unit) => unit.id === 'changsha-officer-wei-yan') ||
|
||||||
|
!twentySixthAllies.some((unit) => unit.id === 'gong-zhi') ||
|
||||||
|
!twentySixthAllies.some((unit) => unit.id === 'yi-ji') ||
|
||||||
|
twentySixthAllies.some((unit) => unit.id === 'huang-zhong') ||
|
||||||
|
twentySixthAllies.some((unit) => unit.id === 'wei-yan')
|
||||||
|
) {
|
||||||
|
throw new Error(`Expected twenty-sixth battle to use Changsha map, Han Xuan objective, Huang Zhong/Wei Yan as enemy officers, mixed AI, and no Changsha recruits before victory: ${JSON.stringify(twentySixthBattleState)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 twentySixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||||
|
if (
|
||||||
|
twentySixthCampState?.campBattleId !== 'twenty-sixth-battle-changsha-veteran' ||
|
||||||
|
twentySixthCampState.campTitle !== '장사 노장 대면 후 군영' ||
|
||||||
|
twentySixthCampState.availableDialogueIds?.length !== 4 ||
|
||||||
|
!twentySixthCampState.availableDialogueIds.every((id) => id.endsWith('changsha')) ||
|
||||||
|
twentySixthCampState.availableVisitIds?.length !== 2 ||
|
||||||
|
!twentySixthCampState.campaign?.roster?.some((unit) => unit.id === 'huang-zhong') ||
|
||||||
|
!twentySixthCampState.campaign?.roster?.some((unit) => unit.id === 'wei-yan') ||
|
||||||
|
!twentySixthCampState.sortieRoster?.some((unit) => unit.id === 'huang-zhong' && unit.recruited) ||
|
||||||
|
!twentySixthCampState.sortieRoster?.some((unit) => unit.id === 'wei-yan' && unit.recruited) ||
|
||||||
|
twentySixthCampState.rosterCollection?.total < 13
|
||||||
|
) {
|
||||||
|
throw new Error(`Expected twenty-sixth camp to recruit Huang Zhong and Wei Yan and expose Changsha dialogue/visit sets: ${JSON.stringify(twentySixthCampState)}`);
|
||||||
|
}
|
||||||
|
await page.screenshot({ path: 'dist/verification-changsha-camp.png', fullPage: true });
|
||||||
|
|
||||||
|
await page.mouse.click(966, 38);
|
||||||
|
await page.waitForTimeout(180);
|
||||||
|
const postChangshaProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||||
|
if (
|
||||||
|
postChangshaProgressState?.activeTab !== 'progress' ||
|
||||||
|
postChangshaProgressState.campaignProgress?.completedKnown !== 26 ||
|
||||||
|
postChangshaProgressState.campaignProgress?.totalKnown !== 26 ||
|
||||||
|
postChangshaProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
|
||||||
|
postChangshaProgressState.campaignProgress?.latestBattleTitle !== '장사 노장 대면전' ||
|
||||||
|
postChangshaProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
|
||||||
|
) {
|
||||||
|
throw new Error(`Expected post-Changsha progress tab to complete the veteran-audience battle and pause on Yi Province preparation: ${JSON.stringify(postChangshaProgressState?.campaignProgress)}`);
|
||||||
|
}
|
||||||
|
await page.screenshot({ path: 'dist/verification-post-changsha-progress.png', fullPage: true });
|
||||||
|
|
||||||
await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene'));
|
await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene'));
|
||||||
await page.waitForFunction(() => {
|
await page.waitForFunction(() => {
|
||||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||||
@@ -2892,7 +3041,7 @@ try {
|
|||||||
return activeScenes.includes('CampScene');
|
return activeScenes.includes('CampScene');
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Verified title-to-twenty-fifth-battle flow, recruited officer sortie selection, Ma Liang, Yi Ji, and Gong Zhi joins, result states, and debug API at ${targetUrl}`);
|
console.log(`Verified title-to-twenty-sixth-battle flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, and Wei Yan joins, result states, and debug API at ${targetUrl}`);
|
||||||
} finally {
|
} finally {
|
||||||
await browser?.close();
|
await browser?.close();
|
||||||
if (serverProcess && !serverProcess.killed) {
|
if (serverProcess && !serverProcess.killed) {
|
||||||
|
|||||||
74
src/assets/images/battle/twenty-sixth-battle-map.svg
Normal file
74
src/assets/images/battle/twenty-sixth-battle-map.svg
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5200 3400" role="img" aria-label="Changsha veteran audience tactical battlefield">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="field" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0" stop-color="#8d7a45"/>
|
||||||
|
<stop offset="0.42" stop-color="#aa955c"/>
|
||||||
|
<stop offset="1" stop-color="#736c43"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="wall" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0" stop-color="#8e7b55"/>
|
||||||
|
<stop offset="1" stop-color="#3d3829"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="river" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0" stop-color="#9bd7e0"/>
|
||||||
|
<stop offset="0.55" stop-color="#407f8e"/>
|
||||||
|
<stop offset="1" stop-color="#1f4658"/>
|
||||||
|
</linearGradient>
|
||||||
|
<pattern id="dust" width="96" height="96" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M12 22h22M58 31h26M28 68h36M8 84h18" stroke="#40331f" stroke-width="5" opacity=".18"/>
|
||||||
|
<path d="M74 11h14M44 43h16M78 75h10" stroke="#dec582" stroke-width="4" opacity=".22"/>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="grid" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M100 0H0v100" fill="none" stroke="#14100b" stroke-width="6" opacity=".2"/>
|
||||||
|
</pattern>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="18" dy="26" stdDeviation="13" flood-color="#12100b" flood-opacity=".34"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width="5200" height="3400" fill="url(#field)"/>
|
||||||
|
<rect width="5200" height="3400" fill="url(#dust)" opacity=".86"/>
|
||||||
|
|
||||||
|
<path d="M0 0 C360 210 520 470 470 820 C420 1180 220 1510 280 1940 C330 2320 190 2790 0 3190 Z" fill="#34422d" opacity=".78"/>
|
||||||
|
<path d="M2020 80 C2450 -20 2960 80 3240 370 C3040 720 2720 900 2280 880 C1940 860 1730 620 1710 340 C1710 210 1830 120 2020 80 Z" fill="#415033" opacity=".76"/>
|
||||||
|
<path d="M3560 2380 C4020 2250 4660 2320 5200 2690 L5200 3400 L3600 3400 C3440 3040 3380 2600 3560 2380 Z" fill="#38472e" opacity=".78"/>
|
||||||
|
|
||||||
|
<path d="M1120 -60 C1260 420 1160 820 1080 1230 C1000 1640 1100 1940 1240 2360" fill="none" stroke="url(#river)" stroke-width="130" opacity=".9"/>
|
||||||
|
<path d="M3360 3400 C3330 3100 3410 2880 3560 2660 C3700 2440 3760 2220 3700 2000" fill="none" stroke="url(#river)" stroke-width="118" opacity=".88"/>
|
||||||
|
<path d="M1120 -60 C1260 420 1160 820 1080 1230 C1000 1640 1100 1940 1240 2360" fill="none" stroke="#d9f1ef" stroke-width="18" opacity=".35"/>
|
||||||
|
|
||||||
|
<path d="M260 2840 C820 2800 1280 2740 1710 2520 C2130 2300 2490 2420 2910 2290 C3330 2160 3520 1870 3860 1720 C4170 1580 4490 1600 4890 1510" fill="none" stroke="#b28b4a" stroke-width="150" opacity=".78" stroke-linecap="round" filter="url(#shadow)"/>
|
||||||
|
<path d="M260 2840 C820 2800 1280 2740 1710 2520 C2130 2300 2490 2420 2910 2290 C3330 2160 3520 1870 3860 1720 C4170 1580 4490 1600 4890 1510" fill="none" stroke="#dbc27a" stroke-width="56" opacity=".48" stroke-linecap="round"/>
|
||||||
|
<path d="M1540 2300 C1850 2050 2040 1780 2210 1430 C2390 1070 2680 750 3030 520" fill="none" stroke="#a68043" stroke-width="92" opacity=".48" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<g opacity=".92">
|
||||||
|
<ellipse cx="1620" cy="2240" rx="190" ry="112" fill="#6f7e3a"/>
|
||||||
|
<rect x="1545" y="2165" width="126" height="104" fill="#8d5d2d"/>
|
||||||
|
<path d="M1522 2165 L1608 2092 L1698 2165 Z" fill="#d99235"/>
|
||||||
|
<rect x="1742" y="2190" width="98" height="82" fill="#8b6130"/>
|
||||||
|
<path d="M1728 2190 L1792 2132 L1858 2190 Z" fill="#e1a249"/>
|
||||||
|
</g>
|
||||||
|
<g opacity=".9">
|
||||||
|
<ellipse cx="3060" cy="2050" rx="180" ry="106" fill="#738441"/>
|
||||||
|
<rect x="2990" y="1978" width="120" height="96" fill="#865a2c"/>
|
||||||
|
<path d="M2968 1978 L3050 1908 L3132 1978 Z" fill="#d99634"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<path d="M4060 910 L4950 960 L5010 1960 L4040 2030 Z" fill="url(#wall)" opacity=".96"/>
|
||||||
|
<path d="M4200 1080 L4760 1115 L4810 1780 L4200 1830 Z" fill="#7b623b"/>
|
||||||
|
<path d="M4210 1080 L4480 875 L4770 1115 Z" fill="#b97935"/>
|
||||||
|
<rect x="4290" y="1220" width="96" height="270" fill="#28281f"/>
|
||||||
|
<rect x="4580" y="1230" width="92" height="260" fill="#28281f"/>
|
||||||
|
<path d="M3960 1430 C4180 1370 4410 1400 4620 1490 C4750 1545 4900 1565 5060 1520" fill="none" stroke="#c9ad68" stroke-width="36" opacity=".7"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g fill="#2f4b2a" opacity=".78">
|
||||||
|
<circle cx="530" cy="610" r="88"/><circle cx="740" cy="940" r="76"/><circle cx="540" cy="1450" r="92"/>
|
||||||
|
<circle cx="1940" cy="940" r="82"/><circle cx="2260" cy="1160" r="90"/><circle cx="2760" cy="1420" r="84"/>
|
||||||
|
<circle cx="3920" cy="2640" r="106"/><circle cx="4380" cy="2860" r="96"/><circle cx="4780" cy="3080" r="84"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<rect width="5200" height="3400" fill="url(#grid)"/>
|
||||||
|
<rect x="18" y="18" width="5164" height="3364" fill="none" stroke="#2b2012" stroke-width="18" opacity=".58"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -59,6 +59,10 @@ import {
|
|||||||
twentyFifthBattleMap,
|
twentyFifthBattleMap,
|
||||||
twentyFifthBattleUnits,
|
twentyFifthBattleUnits,
|
||||||
twentyFifthBattleVictoryPages,
|
twentyFifthBattleVictoryPages,
|
||||||
|
twentySixthBattleBonds,
|
||||||
|
twentySixthBattleMap,
|
||||||
|
twentySixthBattleUnits,
|
||||||
|
twentySixthBattleVictoryPages,
|
||||||
twentiethBattleBonds,
|
twentiethBattleBonds,
|
||||||
twentiethBattleMap,
|
twentiethBattleMap,
|
||||||
twentiethBattleUnits,
|
twentiethBattleUnits,
|
||||||
@@ -130,7 +134,8 @@ export type BattleScenarioId =
|
|||||||
| 'twenty-second-battle-red-cliffs-fire'
|
| 'twenty-second-battle-red-cliffs-fire'
|
||||||
| 'twenty-third-battle-jingzhou-south-entry'
|
| 'twenty-third-battle-jingzhou-south-entry'
|
||||||
| 'twenty-fourth-battle-guiyang-persuasion'
|
| 'twenty-fourth-battle-guiyang-persuasion'
|
||||||
| 'twenty-fifth-battle-wuling-mountain-road';
|
| 'twenty-fifth-battle-wuling-mountain-road'
|
||||||
|
| 'twenty-sixth-battle-changsha-veteran';
|
||||||
|
|
||||||
export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
|
export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
|
||||||
|
|
||||||
@@ -1565,6 +1570,69 @@ export const twentyFifthBattleScenario: BattleScenarioDefinition = {
|
|||||||
nextCampScene: 'CampScene'
|
nextCampScene: 'CampScene'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const twentySixthBattleScenario: BattleScenarioDefinition = {
|
||||||
|
id: 'twenty-sixth-battle-changsha-veteran',
|
||||||
|
title: '장사 노장 대면전',
|
||||||
|
victoryConditionLabel: '한현 퇴각',
|
||||||
|
defeatConditionLabel: '유비 또는 제갈량 퇴각',
|
||||||
|
openingObjectiveLines: [
|
||||||
|
'무릉 산길을 연 유비군은 장사로 향합니다. 장사에는 노장 황충과 의심 많은 태수 한현이 버티고 있습니다.',
|
||||||
|
'이번 전투는 황충을 욕보이지 않고 한현의 의심을 꺾어야 합니다. 마을을 안정시키고 성문 전열을 흔들어 장사 내부의 길을 여십시오.',
|
||||||
|
'유비와 제갈량을 지키며 한현을 물러나게 하면 황충과 위연이 군영에 합류합니다.'
|
||||||
|
],
|
||||||
|
map: twentySixthBattleMap,
|
||||||
|
units: twentySixthBattleUnits,
|
||||||
|
bonds: twentySixthBattleBonds,
|
||||||
|
mapTextureKey: 'battle-map-twenty-sixth',
|
||||||
|
leaderUnitId: 'changsha-leader-han-xuan',
|
||||||
|
quickVictoryTurnLimit: 35,
|
||||||
|
baseVictoryGold: 3560,
|
||||||
|
objectives: [
|
||||||
|
{
|
||||||
|
id: 'leader',
|
||||||
|
kind: 'defeat-leader',
|
||||||
|
label: '한현 퇴각',
|
||||||
|
rewardGold: 2320,
|
||||||
|
unitId: 'changsha-leader-han-xuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'liu-bei',
|
||||||
|
kind: 'keep-unit-alive',
|
||||||
|
label: '유비 생존',
|
||||||
|
rewardGold: 660,
|
||||||
|
unitId: 'liu-bei'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zhuge-liang',
|
||||||
|
kind: 'keep-unit-alive',
|
||||||
|
label: '제갈량 생존',
|
||||||
|
rewardGold: 940,
|
||||||
|
unitId: 'zhuge-liang'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-village',
|
||||||
|
kind: 'secure-terrain',
|
||||||
|
label: '장사 마을 안정',
|
||||||
|
rewardGold: 1120,
|
||||||
|
terrain: 'village'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quick',
|
||||||
|
kind: 'quick-victory',
|
||||||
|
label: '35턴 이내 승리',
|
||||||
|
rewardGold: 880,
|
||||||
|
maxTurn: 35
|
||||||
|
}
|
||||||
|
],
|
||||||
|
defeatConditions: [
|
||||||
|
{ kind: 'unit-defeated', unitId: 'liu-bei' },
|
||||||
|
{ kind: 'unit-defeated', unitId: 'zhuge-liang' }
|
||||||
|
],
|
||||||
|
itemRewards: ['콩 12', '상처약 8', '탁주 5', '장사 사절 문서 +1', '황충 합류', '위연 합류'],
|
||||||
|
victoryPages: twentySixthBattleVictoryPages,
|
||||||
|
nextCampScene: 'CampScene'
|
||||||
|
};
|
||||||
|
|
||||||
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
|
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
|
||||||
|
|
||||||
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
|
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
|
||||||
@@ -1592,7 +1660,8 @@ export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition>
|
|||||||
'twenty-second-battle-red-cliffs-fire': twentySecondBattleScenario,
|
'twenty-second-battle-red-cliffs-fire': twentySecondBattleScenario,
|
||||||
'twenty-third-battle-jingzhou-south-entry': twentyThirdBattleScenario,
|
'twenty-third-battle-jingzhou-south-entry': twentyThirdBattleScenario,
|
||||||
'twenty-fourth-battle-guiyang-persuasion': twentyFourthBattleScenario,
|
'twenty-fourth-battle-guiyang-persuasion': twentyFourthBattleScenario,
|
||||||
'twenty-fifth-battle-wuling-mountain-road': twentyFifthBattleScenario
|
'twenty-fifth-battle-wuling-mountain-road': twentyFifthBattleScenario,
|
||||||
|
'twenty-sixth-battle-changsha-veteran': twentySixthBattleScenario
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];
|
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
twentyThirdBattleScenario,
|
twentyThirdBattleScenario,
|
||||||
twentyFifthBattleScenario,
|
twentyFifthBattleScenario,
|
||||||
twentyFourthBattleScenario,
|
twentyFourthBattleScenario,
|
||||||
|
twentySixthBattleScenario,
|
||||||
twentiethBattleScenario,
|
twentiethBattleScenario,
|
||||||
twelfthBattleScenario,
|
twelfthBattleScenario,
|
||||||
thirdBattleScenario,
|
thirdBattleScenario,
|
||||||
@@ -71,6 +72,8 @@ import {
|
|||||||
twentyFifthBattleVictoryPages,
|
twentyFifthBattleVictoryPages,
|
||||||
twentyFourthBattleIntroPages,
|
twentyFourthBattleIntroPages,
|
||||||
twentyFourthBattleVictoryPages,
|
twentyFourthBattleVictoryPages,
|
||||||
|
twentySixthBattleIntroPages,
|
||||||
|
twentySixthBattleVictoryPages,
|
||||||
twentiethBattleIntroPages,
|
twentiethBattleIntroPages,
|
||||||
twentiethBattleVictoryPages,
|
twentiethBattleVictoryPages,
|
||||||
twelfthBattleIntroPages,
|
twelfthBattleIntroPages,
|
||||||
@@ -347,13 +350,24 @@ const sortieFlows: Record<string, SortieFlow> = {
|
|||||||
},
|
},
|
||||||
[twentyFifthBattleScenario.id]: {
|
[twentyFifthBattleScenario.id]: {
|
||||||
afterBattleId: twentyFifthBattleScenario.id,
|
afterBattleId: twentyFifthBattleScenario.id,
|
||||||
eyebrow: '다음 장 준비',
|
eyebrow: '다음 전장',
|
||||||
title: '장사 노장 대면 준비',
|
title: twentySixthBattleScenario.title,
|
||||||
description:
|
description:
|
||||||
'무릉 산길이 열리고 공지가 합류했습니다. 다음은 장사의 노장 황충과 태수 한현을 마주하며 형주 남부 확보의 큰 고비를 넘겨야 합니다.',
|
'무릉 산길이 열리고 공지가 합류했습니다. 이제 장사의 노장 황충과 의심 많은 한현을 마주하며 형주 남부 확보의 큰 고비를 넘어야 합니다.',
|
||||||
rewardHint: '다음 장: 장사 노장 대면 준비 중',
|
rewardHint: `예상 보상: ${twentySixthBattleScenario.title} 개방 / 황충·위연 합류`,
|
||||||
pages: twentyFifthBattleVictoryPages,
|
nextBattleId: twentySixthBattleScenario.id,
|
||||||
unavailableNotice: '장사 노장 대면 장은 다음 작업에서 이어집니다. 군영에서 공지와 대화하고 황충을 맞을 준비를 하십시오.'
|
campaignStep: 'twenty-sixth-battle',
|
||||||
|
pages: [...twentyFifthBattleVictoryPages, ...twentySixthBattleIntroPages]
|
||||||
|
},
|
||||||
|
[twentySixthBattleScenario.id]: {
|
||||||
|
afterBattleId: twentySixthBattleScenario.id,
|
||||||
|
eyebrow: '다음 장 준비',
|
||||||
|
title: '익주 진입 준비',
|
||||||
|
description:
|
||||||
|
'장사까지 열리며 형주 남부의 뿌리가 단단해졌습니다. 다음은 익주로 향하는 더 긴 정치와 전장의 길을 준비해야 합니다.',
|
||||||
|
rewardHint: '다음 장: 익주 진입 준비 중',
|
||||||
|
pages: twentySixthBattleVictoryPages,
|
||||||
|
unavailableNotice: '익주 진입 장은 다음 작업에서 이어집니다. 군영에서 황충과 위연을 살피고 다음 대장정의 출전 후보를 정비하십시오.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1634,6 +1634,76 @@ export const twentyFifthBattleVictoryPages: StoryPage[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const twentySixthBattleIntroPages: StoryPage[] = [
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-changsha-old-general',
|
||||||
|
bgm: 'story-dark',
|
||||||
|
chapter: '장사의 노장',
|
||||||
|
background: 'story-three-heroes',
|
||||||
|
speaker: '공지',
|
||||||
|
text: '장사에는 황충이라는 노장이 있습니다. 나이는 들었으나 활은 여전히 매섭고, 성 안 병사들은 그 이름을 믿고 버팁니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-han-xuan-suspicion',
|
||||||
|
bgm: 'battle-prep',
|
||||||
|
chapter: '의심 많은 태수',
|
||||||
|
background: 'story-militia',
|
||||||
|
speaker: '이적',
|
||||||
|
text: '한현은 황충의 명망도 의심한다고 합니다. 성을 무너뜨리기보다 황충의 체면을 지키면, 장사 사람들의 마음도 흔들릴 것입니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-wei-yan-rumor',
|
||||||
|
bgm: 'battle-prep',
|
||||||
|
chapter: '성문 안의 불씨',
|
||||||
|
background: 'story-liu-bei',
|
||||||
|
speaker: '마량',
|
||||||
|
text: '위연이라는 장수는 성 안의 부당함을 참지 못한다고 들었습니다. 백성을 해치지 않고 한현의 의심을 꺾으면, 성 안에서 길이 열릴 수 있습니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-changsha-sortie',
|
||||||
|
bgm: 'battle-prep',
|
||||||
|
chapter: '장사 노장 대면전',
|
||||||
|
background: 'story-sortie',
|
||||||
|
speaker: '유비',
|
||||||
|
portrait: 'liuBei',
|
||||||
|
text: '오늘은 노장의 이름을 꺾으러 가는 것이 아니오. 예를 갖추고 한현의 의심을 물리쳐, 장사의 사람을 얻어야 하오.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const twentySixthBattleVictoryPages: StoryPage[] = [
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-victory-changsha-open',
|
||||||
|
bgm: 'militia-theme',
|
||||||
|
chapter: '장사 성문이 열리다',
|
||||||
|
background: 'story-sortie',
|
||||||
|
text: '한현의 의심이 전장 위에서 무너지자, 장사 성문은 크게 흔들렸습니다. 유비군은 황충의 명예를 욕보이지 않고 병사들에게 길을 열었습니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-huang-zhong-joins',
|
||||||
|
bgm: 'battle-prep',
|
||||||
|
chapter: '황충의 합류',
|
||||||
|
background: 'story-militia',
|
||||||
|
speaker: '황충',
|
||||||
|
text: '늙은 몸이라 하나, 아직 활은 뜻을 향해 날아갈 수 있습니다. 장사를 예로 대하신 주공의 군에 제 활을 맡기겠습니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-wei-yan-joins',
|
||||||
|
bgm: 'battle-prep',
|
||||||
|
chapter: '위연의 결심',
|
||||||
|
background: 'story-liu-bei',
|
||||||
|
speaker: '위연',
|
||||||
|
text: '의심과 두려움으로 닫힌 성문을 더는 따를 수 없습니다. 제 창은 닫힌 문을 여는 데 쓰겠습니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'twenty-sixth-next-yi-province',
|
||||||
|
bgm: 'story-dark',
|
||||||
|
chapter: '형주 남부의 끝',
|
||||||
|
background: 'story-three-heroes',
|
||||||
|
speaker: '제갈량',
|
||||||
|
text: '장사까지 열리면 형주 남부의 뿌리가 굳어집니다. 이제 익주로 이어질 더 큰 길을 바라볼 때가 다가옵니다.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export const firstBattleMap: BattleMap = {
|
export const firstBattleMap: BattleMap = {
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 18,
|
height: 18,
|
||||||
@@ -1929,6 +1999,12 @@ export const twentyFifthBattleMap: BattleMap = {
|
|||||||
terrain: createTwentyFifthBattleTerrain()
|
terrain: createTwentyFifthBattleTerrain()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const twentySixthBattleMap: BattleMap = {
|
||||||
|
width: 52,
|
||||||
|
height: 34,
|
||||||
|
terrain: createTwentySixthBattleTerrain()
|
||||||
|
};
|
||||||
|
|
||||||
export const firstBattleUnits: UnitData[] = [
|
export const firstBattleUnits: UnitData[] = [
|
||||||
{
|
{
|
||||||
id: 'liu-bei',
|
id: 'liu-bei',
|
||||||
@@ -3957,6 +4033,51 @@ export const wulingRecruitUnits: UnitData[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const changshaRecruitUnits: UnitData[] = [
|
||||||
|
{
|
||||||
|
id: 'huang-zhong',
|
||||||
|
name: '황충',
|
||||||
|
faction: 'ally',
|
||||||
|
className: '노장 궁기병',
|
||||||
|
classKey: 'archer',
|
||||||
|
level: 15,
|
||||||
|
exp: 0,
|
||||||
|
hp: 44,
|
||||||
|
maxHp: 44,
|
||||||
|
attack: 15,
|
||||||
|
move: 4,
|
||||||
|
stats: { might: 88, intelligence: 72, leadership: 86, agility: 78, luck: 76 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'short-bow', level: 3, exp: 12 },
|
||||||
|
armor: { itemId: 'lamellar-armor', level: 2, exp: 14 },
|
||||||
|
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 8,
|
||||||
|
y: 23
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'wei-yan',
|
||||||
|
name: '위연',
|
||||||
|
faction: 'ally',
|
||||||
|
className: '장사 돌격장',
|
||||||
|
classKey: 'infantry',
|
||||||
|
level: 15,
|
||||||
|
exp: 0,
|
||||||
|
hp: 48,
|
||||||
|
maxHp: 48,
|
||||||
|
attack: 16,
|
||||||
|
move: 4,
|
||||||
|
stats: { might: 90, intelligence: 64, leadership: 82, agility: 82, luck: 70 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 3, exp: 10 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 2, exp: 10 },
|
||||||
|
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 9,
|
||||||
|
y: 24
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const eighthBattleAllyPositions: Record<string, { x: number; y: number }> = {
|
const eighthBattleAllyPositions: Record<string, { x: number; y: number }> = {
|
||||||
'liu-bei': { x: 2, y: 17 },
|
'liu-bei': { x: 2, y: 17 },
|
||||||
'guan-yu': { x: 3, y: 17 },
|
'guan-yu': { x: 3, y: 17 },
|
||||||
@@ -10058,6 +10179,411 @@ export const twentyFifthBattleUnits: UnitData[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const twentySixthBattleAllyPositions: Record<string, { x: number; y: number }> = {
|
||||||
|
'liu-bei': { x: 4, y: 27 },
|
||||||
|
'guan-yu': { x: 6, y: 25 },
|
||||||
|
'zhang-fei': { x: 6, y: 29 },
|
||||||
|
'jian-yong': { x: 3, y: 30 },
|
||||||
|
'mi-zhu': { x: 4, y: 31 },
|
||||||
|
'sun-qian': { x: 8, y: 28 },
|
||||||
|
'zhao-yun': { x: 8, y: 25 },
|
||||||
|
'zhuge-liang': { x: 5, y: 27 },
|
||||||
|
'ma-liang': { x: 7, y: 30 },
|
||||||
|
'yi-ji': { x: 9, y: 27 },
|
||||||
|
'gong-zhi': { x: 9, y: 30 }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const twentySixthBattleUnits: UnitData[] = [
|
||||||
|
...[
|
||||||
|
...firstBattleUnits.filter((unit) => unit.faction === 'ally'),
|
||||||
|
...xuzhouRecruitUnits,
|
||||||
|
...caoBreakRecruitUnits,
|
||||||
|
...liuBiaoRecruitUnits,
|
||||||
|
...zhugeRecruitUnits,
|
||||||
|
...jingzhouRecruitUnits,
|
||||||
|
...guiyangRecruitUnits,
|
||||||
|
...wulingRecruitUnits
|
||||||
|
].map((unit) => placeScenarioUnit(unit, twentySixthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
|
||||||
|
{
|
||||||
|
id: 'changsha-scout-a',
|
||||||
|
name: '장사 척후',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 척후',
|
||||||
|
classKey: 'bandit',
|
||||||
|
level: 37,
|
||||||
|
exp: 56,
|
||||||
|
hp: 100,
|
||||||
|
maxHp: 100,
|
||||||
|
attack: 38,
|
||||||
|
move: 4,
|
||||||
|
stats: { might: 124, intelligence: 90, leadership: 100, agility: 118, luck: 82 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'yellow-turban-saber', level: 4, exp: 36 },
|
||||||
|
armor: { itemId: 'rebel-vest', level: 4, exp: 22 },
|
||||||
|
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 14,
|
||||||
|
y: 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-scout-b',
|
||||||
|
name: '장사 척후',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '서문 척후',
|
||||||
|
classKey: 'bandit',
|
||||||
|
level: 37,
|
||||||
|
exp: 56,
|
||||||
|
hp: 100,
|
||||||
|
maxHp: 100,
|
||||||
|
attack: 38,
|
||||||
|
move: 4,
|
||||||
|
stats: { might: 124, intelligence: 90, leadership: 100, agility: 118, luck: 82 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'yellow-turban-saber', level: 4, exp: 36 },
|
||||||
|
armor: { itemId: 'rebel-vest', level: 4, exp: 22 },
|
||||||
|
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 17,
|
||||||
|
y: 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-infantry-a',
|
||||||
|
name: '장사 보병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 보병',
|
||||||
|
classKey: 'yellowTurban',
|
||||||
|
level: 38,
|
||||||
|
exp: 58,
|
||||||
|
hp: 130,
|
||||||
|
maxHp: 130,
|
||||||
|
attack: 42,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 138, intelligence: 90, leadership: 116, agility: 98, luck: 82 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 50 },
|
||||||
|
armor: { itemId: 'lamellar-armor', level: 4, exp: 34 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 23,
|
||||||
|
y: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-infantry-b',
|
||||||
|
name: '장사 보병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '마을 보병',
|
||||||
|
classKey: 'yellowTurban',
|
||||||
|
level: 38,
|
||||||
|
exp: 58,
|
||||||
|
hp: 130,
|
||||||
|
maxHp: 130,
|
||||||
|
attack: 42,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 138, intelligence: 90, leadership: 116, agility: 98, luck: 82 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 50 },
|
||||||
|
armor: { itemId: 'lamellar-armor', level: 4, exp: 34 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 26,
|
||||||
|
y: 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-infantry-c',
|
||||||
|
name: '장사 보병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '성문 보병',
|
||||||
|
classKey: 'yellowTurban',
|
||||||
|
level: 39,
|
||||||
|
exp: 60,
|
||||||
|
hp: 136,
|
||||||
|
maxHp: 136,
|
||||||
|
attack: 43,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 140, intelligence: 90, leadership: 118, agility: 98, luck: 82 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 54 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 38 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 34,
|
||||||
|
y: 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-infantry-d',
|
||||||
|
name: '장사 보병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '성내 보병',
|
||||||
|
classKey: 'yellowTurban',
|
||||||
|
level: 39,
|
||||||
|
exp: 60,
|
||||||
|
hp: 136,
|
||||||
|
maxHp: 136,
|
||||||
|
attack: 43,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 140, intelligence: 90, leadership: 118, agility: 98, luck: 82 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 54 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 38 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 39,
|
||||||
|
y: 17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-archer-a',
|
||||||
|
name: '장사 궁병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 궁병',
|
||||||
|
classKey: 'archer',
|
||||||
|
level: 38,
|
||||||
|
exp: 58,
|
||||||
|
hp: 98,
|
||||||
|
maxHp: 98,
|
||||||
|
attack: 39,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 96, intelligence: 98, leadership: 104, agility: 112, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'short-bow', level: 4, exp: 46 },
|
||||||
|
armor: { itemId: 'cloth-armor', level: 4, exp: 28 },
|
||||||
|
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 28,
|
||||||
|
y: 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-archer-b',
|
||||||
|
name: '장사 궁병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '성루 궁병',
|
||||||
|
classKey: 'archer',
|
||||||
|
level: 38,
|
||||||
|
exp: 58,
|
||||||
|
hp: 98,
|
||||||
|
maxHp: 98,
|
||||||
|
attack: 39,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 96, intelligence: 98, leadership: 104, agility: 112, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'short-bow', level: 4, exp: 46 },
|
||||||
|
armor: { itemId: 'cloth-armor', level: 4, exp: 28 },
|
||||||
|
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 37,
|
||||||
|
y: 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-archer-c',
|
||||||
|
name: '장사 궁병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '동문 궁병',
|
||||||
|
classKey: 'archer',
|
||||||
|
level: 39,
|
||||||
|
exp: 60,
|
||||||
|
hp: 102,
|
||||||
|
maxHp: 102,
|
||||||
|
attack: 40,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 98, intelligence: 100, leadership: 106, agility: 114, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'short-bow', level: 4, exp: 50 },
|
||||||
|
armor: { itemId: 'cloth-armor', level: 4, exp: 30 },
|
||||||
|
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 44,
|
||||||
|
y: 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-cavalry-a',
|
||||||
|
name: '장사 기병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 기병',
|
||||||
|
classKey: 'cavalry',
|
||||||
|
level: 39,
|
||||||
|
exp: 60,
|
||||||
|
hp: 138,
|
||||||
|
maxHp: 138,
|
||||||
|
attack: 44,
|
||||||
|
move: 5,
|
||||||
|
stats: { might: 140, intelligence: 90, leadership: 116, agility: 118, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 56 },
|
||||||
|
armor: { itemId: 'lamellar-armor', level: 4, exp: 36 },
|
||||||
|
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 31,
|
||||||
|
y: 29
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-cavalry-b',
|
||||||
|
name: '장사 기병',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '동문 기병',
|
||||||
|
classKey: 'cavalry',
|
||||||
|
level: 39,
|
||||||
|
exp: 60,
|
||||||
|
hp: 138,
|
||||||
|
maxHp: 138,
|
||||||
|
attack: 44,
|
||||||
|
move: 5,
|
||||||
|
stats: { might: 140, intelligence: 90, leadership: 116, agility: 118, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 56 },
|
||||||
|
armor: { itemId: 'lamellar-armor', level: 4, exp: 36 },
|
||||||
|
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 42,
|
||||||
|
y: 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-guard-a',
|
||||||
|
name: '한현 친위대',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 친위대',
|
||||||
|
classKey: 'yellowTurban',
|
||||||
|
level: 40,
|
||||||
|
exp: 62,
|
||||||
|
hp: 144,
|
||||||
|
maxHp: 144,
|
||||||
|
attack: 45,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 144, intelligence: 92, leadership: 122, agility: 100, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 62 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 42 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 45,
|
||||||
|
y: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-guard-b',
|
||||||
|
name: '한현 친위대',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 친위대',
|
||||||
|
classKey: 'yellowTurban',
|
||||||
|
level: 40,
|
||||||
|
exp: 62,
|
||||||
|
hp: 144,
|
||||||
|
maxHp: 144,
|
||||||
|
attack: 45,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 144, intelligence: 92, leadership: 122, agility: 100, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 4, exp: 62 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 42 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 47,
|
||||||
|
y: 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-strategist-a',
|
||||||
|
name: '장사 책사',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 책사',
|
||||||
|
classKey: 'archer',
|
||||||
|
level: 39,
|
||||||
|
exp: 60,
|
||||||
|
hp: 102,
|
||||||
|
maxHp: 102,
|
||||||
|
attack: 40,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 90, intelligence: 116, leadership: 108, agility: 96, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'short-bow', level: 4, exp: 50 },
|
||||||
|
armor: { itemId: 'cloth-armor', level: 4, exp: 30 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 41,
|
||||||
|
y: 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-strategist-b',
|
||||||
|
name: '장사 책사',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '의심 많은 책사',
|
||||||
|
classKey: 'archer',
|
||||||
|
level: 39,
|
||||||
|
exp: 60,
|
||||||
|
hp: 102,
|
||||||
|
maxHp: 102,
|
||||||
|
attack: 40,
|
||||||
|
move: 3,
|
||||||
|
stats: { might: 90, intelligence: 116, leadership: 108, agility: 96, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'short-bow', level: 4, exp: 50 },
|
||||||
|
armor: { itemId: 'cloth-armor', level: 4, exp: 30 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 43,
|
||||||
|
y: 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-veteran-huang-zhong',
|
||||||
|
name: '황충',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 노장',
|
||||||
|
classKey: 'archer',
|
||||||
|
level: 41,
|
||||||
|
exp: 66,
|
||||||
|
hp: 156,
|
||||||
|
maxHp: 156,
|
||||||
|
attack: 48,
|
||||||
|
move: 4,
|
||||||
|
stats: { might: 150, intelligence: 96, leadership: 128, agility: 108, luck: 88 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'short-bow', level: 5, exp: 18 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 48 },
|
||||||
|
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 38,
|
||||||
|
y: 17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-officer-wei-yan',
|
||||||
|
name: '위연',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 돌격장',
|
||||||
|
classKey: 'yellowTurban',
|
||||||
|
level: 40,
|
||||||
|
exp: 64,
|
||||||
|
hp: 152,
|
||||||
|
maxHp: 152,
|
||||||
|
attack: 47,
|
||||||
|
move: 4,
|
||||||
|
stats: { might: 148, intelligence: 82, leadership: 120, agility: 112, luck: 80 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'iron-spear', level: 5, exp: 12 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 44 },
|
||||||
|
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 36,
|
||||||
|
y: 23
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-leader-han-xuan',
|
||||||
|
name: '한현',
|
||||||
|
faction: 'enemy',
|
||||||
|
className: '장사 태수',
|
||||||
|
classKey: 'rebelLeader',
|
||||||
|
level: 41,
|
||||||
|
exp: 66,
|
||||||
|
hp: 190,
|
||||||
|
maxHp: 190,
|
||||||
|
attack: 46,
|
||||||
|
move: 4,
|
||||||
|
stats: { might: 138, intelligence: 104, leadership: 130, agility: 100, luck: 84 },
|
||||||
|
equipment: {
|
||||||
|
weapon: { itemId: 'leader-axe', level: 5, exp: 10 },
|
||||||
|
armor: { itemId: 'reinforced-lamellar', level: 5, exp: 8 },
|
||||||
|
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||||
|
},
|
||||||
|
x: 48,
|
||||||
|
y: 16
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export const firstBattleBonds: BattleBond[] = [
|
export const firstBattleBonds: BattleBond[] = [
|
||||||
{
|
{
|
||||||
id: 'liu-bei__guan-yu',
|
id: 'liu-bei__guan-yu',
|
||||||
@@ -10266,6 +10792,41 @@ export const wulingRecruitBonds: BattleBond[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const changshaRecruitBonds: BattleBond[] = [
|
||||||
|
{
|
||||||
|
id: 'liu-bei__huang-zhong',
|
||||||
|
unitIds: ['liu-bei', 'huang-zhong'],
|
||||||
|
title: '노장을 예우하다',
|
||||||
|
level: 38,
|
||||||
|
exp: 0,
|
||||||
|
description: '유비의 예우와 황충의 노련한 활이 만나, 오래 버틴 장수의 마음을 군의 힘으로 바꿉니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'guan-yu__huang-zhong',
|
||||||
|
unitIds: ['guan-yu', 'huang-zhong'],
|
||||||
|
title: '무인의 존중',
|
||||||
|
level: 34,
|
||||||
|
exp: 0,
|
||||||
|
description: '관우와 황충은 서로의 무예와 체면을 인정하며 전열과 원거리 압박을 함께 세웁니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'liu-bei__wei-yan',
|
||||||
|
unitIds: ['liu-bei', 'wei-yan'],
|
||||||
|
title: '닫힌 문을 여는 창',
|
||||||
|
level: 32,
|
||||||
|
exp: 0,
|
||||||
|
description: '유비의 큰 뜻과 위연의 거친 돌파가 만나, 막힌 성문과 전열을 과감히 흔듭니다.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'huang-zhong__wei-yan',
|
||||||
|
unitIds: ['huang-zhong', 'wei-yan'],
|
||||||
|
title: '장사의 전환',
|
||||||
|
level: 30,
|
||||||
|
exp: 0,
|
||||||
|
description: '황충의 노련함과 위연의 결단이 장사의 흔들린 군심을 새 방향으로 묶어 냅니다.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export const secondBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
export const secondBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||||
export const thirdBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
export const thirdBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||||
export const fourthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
export const fourthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||||
@@ -10296,6 +10857,7 @@ export const twentySecondBattleBonds: BattleBond[] = [...twentyFirstBattleBonds]
|
|||||||
export const twentyThirdBattleBonds: BattleBond[] = [...twentySecondBattleBonds].map(cloneBattleBondForScenario);
|
export const twentyThirdBattleBonds: BattleBond[] = [...twentySecondBattleBonds].map(cloneBattleBondForScenario);
|
||||||
export const twentyFourthBattleBonds: BattleBond[] = [...twentyThirdBattleBonds, ...jingzhouRecruitBonds].map(cloneBattleBondForScenario);
|
export const twentyFourthBattleBonds: BattleBond[] = [...twentyThirdBattleBonds, ...jingzhouRecruitBonds].map(cloneBattleBondForScenario);
|
||||||
export const twentyFifthBattleBonds: BattleBond[] = [...twentyFourthBattleBonds, ...guiyangRecruitBonds].map(cloneBattleBondForScenario);
|
export const twentyFifthBattleBonds: BattleBond[] = [...twentyFourthBattleBonds, ...guiyangRecruitBonds].map(cloneBattleBondForScenario);
|
||||||
|
export const twentySixthBattleBonds: BattleBond[] = [...twentyFifthBattleBonds, ...wulingRecruitBonds].map(cloneBattleBondForScenario);
|
||||||
|
|
||||||
function createEighthBattleTerrain(): TerrainType[][] {
|
function createEighthBattleTerrain(): TerrainType[][] {
|
||||||
return Array.from({ length: 22 }, (_, y) =>
|
return Array.from({ length: 22 }, (_, y) =>
|
||||||
@@ -11212,6 +11774,55 @@ function createTwentyFifthBattleTerrain(): TerrainType[][] {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createTwentySixthBattleTerrain(): TerrainType[][] {
|
||||||
|
return Array.from({ length: 34 }, (_, y) =>
|
||||||
|
Array.from({ length: 52 }, (_, x): TerrainType => {
|
||||||
|
if (x <= 3 && y >= 26) {
|
||||||
|
return 'camp';
|
||||||
|
}
|
||||||
|
if ((x >= 42 && x <= 50 && y >= 11 && y <= 20) || (x >= 37 && x <= 43 && y >= 8 && y <= 13)) {
|
||||||
|
return 'fort';
|
||||||
|
}
|
||||||
|
if ((x >= 21 && x <= 24 && y >= 26 && y <= 28) || (x >= 30 && x <= 32 && y >= 19 && y <= 21) || (x >= 15 && x <= 17 && y >= 21 && y <= 23)) {
|
||||||
|
return 'village';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(y >= 27 && x >= 3 && x <= 18) ||
|
||||||
|
(x >= 18 && x <= 29 && y >= 23 && y <= 27) ||
|
||||||
|
(x >= 29 && x <= 38 && y >= 19 && y <= 23) ||
|
||||||
|
(x >= 37 && x <= 45 && y >= 15 && y <= 19) ||
|
||||||
|
(x >= 44 && x <= 49 && y >= 13 && y <= 17)
|
||||||
|
) {
|
||||||
|
return 'road';
|
||||||
|
}
|
||||||
|
if ((x === 11 || x === 12) && y >= 0 && y <= 24) {
|
||||||
|
return 'river';
|
||||||
|
}
|
||||||
|
if ((x === 33 || x === 34) && y >= 22 && y <= 33) {
|
||||||
|
return 'river';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(x >= 4 && x <= 14 && y >= 7 && y <= 18) ||
|
||||||
|
(x >= 18 && x <= 30 && y >= 8 && y <= 17) ||
|
||||||
|
(x >= 35 && x <= 47 && y >= 23 && y <= 33)
|
||||||
|
) {
|
||||||
|
return 'forest';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(x >= 0 && x <= 9 && y <= 10) ||
|
||||||
|
(x >= 21 && x <= 32 && y >= 1 && y <= 9) ||
|
||||||
|
(x >= 46 && x <= 51 && y >= 0 && y <= 10)
|
||||||
|
) {
|
||||||
|
return 'hill';
|
||||||
|
}
|
||||||
|
if ((x <= 1 && y <= 25) || (x >= 50 && y >= 21) || (x >= 39 && x <= 41 && y >= 0 && y <= 7)) {
|
||||||
|
return 'cliff';
|
||||||
|
}
|
||||||
|
return 'plain';
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
|
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
|
||||||
return {
|
return {
|
||||||
...cloneUnitForScenario(unit),
|
...cloneUnitForScenario(unit),
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ const unitTexture: Record<string, string> = {
|
|||||||
'ma-liang': 'unit-liu-bei',
|
'ma-liang': 'unit-liu-bei',
|
||||||
'yi-ji': 'unit-liu-bei',
|
'yi-ji': 'unit-liu-bei',
|
||||||
'gong-zhi': 'unit-guan-yu',
|
'gong-zhi': 'unit-guan-yu',
|
||||||
|
'huang-zhong': 'unit-rebel-archer',
|
||||||
|
'wei-yan': 'unit-guan-yu',
|
||||||
'rebel-a': 'unit-rebel',
|
'rebel-a': 'unit-rebel',
|
||||||
'rebel-b': 'unit-rebel',
|
'rebel-b': 'unit-rebel',
|
||||||
'rebel-c': 'unit-rebel-archer',
|
'rebel-c': 'unit-rebel-archer',
|
||||||
@@ -296,7 +298,25 @@ const unitTexture: Record<string, string> = {
|
|||||||
'wuling-guard-a': 'unit-rebel',
|
'wuling-guard-a': 'unit-rebel',
|
||||||
'wuling-guard-b': 'unit-rebel',
|
'wuling-guard-b': 'unit-rebel',
|
||||||
'wuling-strategist-a': 'unit-rebel-archer',
|
'wuling-strategist-a': 'unit-rebel-archer',
|
||||||
'wuling-leader-jin-xuan': 'unit-rebel-leader'
|
'wuling-leader-jin-xuan': 'unit-rebel-leader',
|
||||||
|
'changsha-scout-a': 'unit-rebel',
|
||||||
|
'changsha-scout-b': 'unit-rebel',
|
||||||
|
'changsha-infantry-a': 'unit-rebel',
|
||||||
|
'changsha-infantry-b': 'unit-rebel',
|
||||||
|
'changsha-infantry-c': 'unit-rebel',
|
||||||
|
'changsha-infantry-d': 'unit-rebel',
|
||||||
|
'changsha-archer-a': 'unit-rebel-archer',
|
||||||
|
'changsha-archer-b': 'unit-rebel-archer',
|
||||||
|
'changsha-archer-c': 'unit-rebel-archer',
|
||||||
|
'changsha-cavalry-a': 'unit-rebel-cavalry',
|
||||||
|
'changsha-cavalry-b': 'unit-rebel-cavalry',
|
||||||
|
'changsha-guard-a': 'unit-rebel',
|
||||||
|
'changsha-guard-b': 'unit-rebel',
|
||||||
|
'changsha-strategist-a': 'unit-rebel-archer',
|
||||||
|
'changsha-strategist-b': 'unit-rebel-archer',
|
||||||
|
'changsha-veteran-huang-zhong': 'unit-rebel-archer',
|
||||||
|
'changsha-officer-wei-yan': 'unit-guan-yu',
|
||||||
|
'changsha-leader-han-xuan': 'unit-rebel-leader'
|
||||||
};
|
};
|
||||||
|
|
||||||
const unitTextureByClass: Partial<Record<UnitClassKey, string>> = {
|
const unitTextureByClass: Partial<Record<UnitClassKey, string>> = {
|
||||||
@@ -720,7 +740,9 @@ const unitStrategyIds: Record<string, string[]> = {
|
|||||||
'zhuge-liang': ['aid', 'encourage', 'fireTactic'],
|
'zhuge-liang': ['aid', 'encourage', 'fireTactic'],
|
||||||
'ma-liang': ['aid', 'encourage', 'fireTactic'],
|
'ma-liang': ['aid', 'encourage', 'fireTactic'],
|
||||||
'yi-ji': ['aid', 'encourage'],
|
'yi-ji': ['aid', 'encourage'],
|
||||||
'gong-zhi': ['encourage']
|
'gong-zhi': ['encourage'],
|
||||||
|
'huang-zhong': ['encourage'],
|
||||||
|
'wei-yan': ['roar']
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialItemStocks: Record<string, Record<string, number>> = {
|
const initialItemStocks: Record<string, Record<string, number>> = {
|
||||||
@@ -734,7 +756,9 @@ const initialItemStocks: Record<string, Record<string, number>> = {
|
|||||||
'zhuge-liang': { bean: 1, wine: 1 },
|
'zhuge-liang': { bean: 1, wine: 1 },
|
||||||
'ma-liang': { bean: 1, salve: 1 },
|
'ma-liang': { bean: 1, salve: 1 },
|
||||||
'yi-ji': { bean: 2, salve: 1 },
|
'yi-ji': { bean: 2, salve: 1 },
|
||||||
'gong-zhi': { bean: 2 }
|
'gong-zhi': { bean: 2 },
|
||||||
|
'huang-zhong': { bean: 1, wine: 1 },
|
||||||
|
'wei-yan': { bean: 2 }
|
||||||
};
|
};
|
||||||
|
|
||||||
const attackRangeByClass: Partial<Record<UnitClassKey, number>> = {
|
const attackRangeByClass: Partial<Record<UnitClassKey, number>> = {
|
||||||
@@ -1051,7 +1075,25 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
|
|||||||
'wuling-guard-a': 'guard',
|
'wuling-guard-a': 'guard',
|
||||||
'wuling-guard-b': 'guard',
|
'wuling-guard-b': 'guard',
|
||||||
'wuling-strategist-a': 'hold',
|
'wuling-strategist-a': 'hold',
|
||||||
'wuling-leader-jin-xuan': 'guard'
|
'wuling-leader-jin-xuan': 'guard',
|
||||||
|
'changsha-scout-a': 'aggressive',
|
||||||
|
'changsha-scout-b': 'aggressive',
|
||||||
|
'changsha-infantry-a': 'guard',
|
||||||
|
'changsha-infantry-b': 'guard',
|
||||||
|
'changsha-infantry-c': 'guard',
|
||||||
|
'changsha-infantry-d': 'guard',
|
||||||
|
'changsha-archer-a': 'hold',
|
||||||
|
'changsha-archer-b': 'hold',
|
||||||
|
'changsha-archer-c': 'hold',
|
||||||
|
'changsha-cavalry-a': 'aggressive',
|
||||||
|
'changsha-cavalry-b': 'aggressive',
|
||||||
|
'changsha-guard-a': 'guard',
|
||||||
|
'changsha-guard-b': 'guard',
|
||||||
|
'changsha-strategist-a': 'hold',
|
||||||
|
'changsha-strategist-b': 'hold',
|
||||||
|
'changsha-veteran-huang-zhong': 'hold',
|
||||||
|
'changsha-officer-wei-yan': 'aggressive',
|
||||||
|
'changsha-leader-han-xuan': 'guard'
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {
|
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import twentySecondBattleMapUrl from '../../assets/images/battle/twenty-second-b
|
|||||||
import twentyThirdBattleMapUrl from '../../assets/images/battle/twenty-third-battle-map.svg';
|
import twentyThirdBattleMapUrl from '../../assets/images/battle/twenty-third-battle-map.svg';
|
||||||
import twentyFifthBattleMapUrl from '../../assets/images/battle/twenty-fifth-battle-map.svg';
|
import twentyFifthBattleMapUrl from '../../assets/images/battle/twenty-fifth-battle-map.svg';
|
||||||
import twentyFourthBattleMapUrl from '../../assets/images/battle/twenty-fourth-battle-map.svg';
|
import twentyFourthBattleMapUrl from '../../assets/images/battle/twenty-fourth-battle-map.svg';
|
||||||
|
import twentySixthBattleMapUrl from '../../assets/images/battle/twenty-sixth-battle-map.svg';
|
||||||
import twentiethBattleMapUrl from '../../assets/images/battle/twentieth-battle-map.svg';
|
import twentiethBattleMapUrl from '../../assets/images/battle/twentieth-battle-map.svg';
|
||||||
import twelfthBattleMapUrl from '../../assets/images/battle/twelfth-battle-map.svg';
|
import twelfthBattleMapUrl from '../../assets/images/battle/twelfth-battle-map.svg';
|
||||||
import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png';
|
import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png';
|
||||||
@@ -108,6 +109,7 @@ export class BootScene extends Phaser.Scene {
|
|||||||
this.load.image('battle-map-twenty-third', twentyThirdBattleMapUrl);
|
this.load.image('battle-map-twenty-third', twentyThirdBattleMapUrl);
|
||||||
this.load.image('battle-map-twenty-fourth', twentyFourthBattleMapUrl);
|
this.load.image('battle-map-twenty-fourth', twentyFourthBattleMapUrl);
|
||||||
this.load.image('battle-map-twenty-fifth', twentyFifthBattleMapUrl);
|
this.load.image('battle-map-twenty-fifth', twentyFifthBattleMapUrl);
|
||||||
|
this.load.image('battle-map-twenty-sixth', twentySixthBattleMapUrl);
|
||||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { getSortieFlow } from '../data/campaignFlow';
|
|||||||
import {
|
import {
|
||||||
caoBreakRecruitBonds,
|
caoBreakRecruitBonds,
|
||||||
caoBreakRecruitUnits,
|
caoBreakRecruitUnits,
|
||||||
|
changshaRecruitBonds,
|
||||||
|
changshaRecruitUnits,
|
||||||
firstBattleBonds,
|
firstBattleBonds,
|
||||||
firstBattleUnits,
|
firstBattleUnits,
|
||||||
guiyangRecruitBonds,
|
guiyangRecruitBonds,
|
||||||
@@ -304,7 +306,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
|
|||||||
battleIds: [
|
battleIds: [
|
||||||
'twenty-third-battle-jingzhou-south-entry',
|
'twenty-third-battle-jingzhou-south-entry',
|
||||||
'twenty-fourth-battle-guiyang-persuasion',
|
'twenty-fourth-battle-guiyang-persuasion',
|
||||||
'twenty-fifth-battle-wuling-mountain-road'
|
'twenty-fifth-battle-wuling-mountain-road',
|
||||||
|
'twenty-sixth-battle-changsha-veteran'
|
||||||
],
|
],
|
||||||
nextHints: ['형주 방위', '익주 진입', '장수 대거 합류']
|
nextHints: ['형주 방위', '익주 진입', '장수 대거 합류']
|
||||||
},
|
},
|
||||||
@@ -343,7 +346,8 @@ const campBattleIds = {
|
|||||||
twentySecond: 'twenty-second-battle-red-cliffs-fire',
|
twentySecond: 'twenty-second-battle-red-cliffs-fire',
|
||||||
twentyThird: 'twenty-third-battle-jingzhou-south-entry',
|
twentyThird: 'twenty-third-battle-jingzhou-south-entry',
|
||||||
twentyFourth: 'twenty-fourth-battle-guiyang-persuasion',
|
twentyFourth: 'twenty-fourth-battle-guiyang-persuasion',
|
||||||
twentyFifth: 'twenty-fifth-battle-wuling-mountain-road'
|
twentyFifth: 'twenty-fifth-battle-wuling-mountain-road',
|
||||||
|
twentySixth: 'twenty-sixth-battle-changsha-veteran'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const requiredSortieUnitIds = new Set(['liu-bei']);
|
const requiredSortieUnitIds = new Set(['liu-bei']);
|
||||||
@@ -618,6 +622,18 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
|
|||||||
{ unitId: 'zhang-fei', reason: '협곡 보병 전열을 밀어붙이는 돌파 역할입니다.' }
|
{ unitId: 'zhang-fei', reason: '협곡 보병 전열을 밀어붙이는 돌파 역할입니다.' }
|
||||||
],
|
],
|
||||||
note: '무릉 산길 확보전은 열 명 중 여섯 명만 고르는 첫 전장입니다. 마량과 이적을 모두 데려가면 설득은 강해지지만, 전열 무장 한 명을 줄여야 합니다.'
|
note: '무릉 산길 확보전은 열 명 중 여섯 명만 고르는 첫 전장입니다. 마량과 이적을 모두 데려가면 설득은 강해지지만, 전열 무장 한 명을 줄여야 합니다.'
|
||||||
|
},
|
||||||
|
'twenty-sixth-battle-changsha-veteran': {
|
||||||
|
maxUnits: 6,
|
||||||
|
recommended: [
|
||||||
|
{ unitId: 'liu-bei', reason: '장사 노장을 예우하고 한현을 물러나게 할 명분의 중심입니다.' },
|
||||||
|
{ unitId: 'gong-zhi', reason: '무릉에서 얻은 현지 정보로 장사 접근로와 성 안 소문을 읽습니다.' },
|
||||||
|
{ unitId: 'yi-ji', reason: '황충을 예우하고 항복 조건을 문장으로 다듬는 핵심 문사입니다.' },
|
||||||
|
{ unitId: 'zhuge-liang', reason: '한현의 의심과 성문 방어선을 끊는 작전 핵심입니다.' },
|
||||||
|
{ unitId: 'guan-yu', reason: '황충과 무인의 예를 나누며 전열을 받아낼 주력입니다.' },
|
||||||
|
{ unitId: 'zhao-yun', reason: '장사 성문 주변 기병과 척후를 빠르게 정리하기 좋습니다.' }
|
||||||
|
],
|
||||||
|
note: '장사 노장 대면전은 열한 명 중 여섯 명만 고르는 전장입니다. 황충과 위연은 승리 후 합류하므로, 이번에는 예우와 돌파의 균형을 먼저 준비하십시오.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2700,6 +2716,114 @@ const campDialogues: CampDialogue[] = [
|
|||||||
rewardExp: 9
|
rewardExp: 9
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'liu-huang-after-changsha',
|
||||||
|
title: '노장을 예우하다',
|
||||||
|
availableAfterBattleIds: [campBattleIds.twentySixth],
|
||||||
|
unitIds: ['liu-bei', 'huang-zhong'],
|
||||||
|
bondId: 'liu-bei__huang-zhong',
|
||||||
|
rewardExp: 30,
|
||||||
|
lines: [
|
||||||
|
'황충: 늙은 장수의 체면을 지켜 주신 일은 전장의 승리보다 오래 남을 것입니다.',
|
||||||
|
'유비: 활은 나이를 묻지 않고 뜻을 묻는다고 들었소. 장군의 활이 백성을 지키는 데 쓰인다면 더 바랄 것이 없소.',
|
||||||
|
'황충: 그 뜻이라면 제 활과 남은 세월을 맡기겠습니다.'
|
||||||
|
],
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
id: 'honor-veteran-bow',
|
||||||
|
label: '노장의 활을 예우한다',
|
||||||
|
response: '유비는 황충의 활과 전공을 병사들 앞에서 높였고, 노장은 조용히 고개를 숙였다.',
|
||||||
|
rewardExp: 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ask-changsha-people',
|
||||||
|
label: '장사 백성을 묻는다',
|
||||||
|
response: '황충은 장사 성 안팎의 창고와 마을 사정을 차분히 들려주었고, 유비는 먼저 살필 곳을 정했다.',
|
||||||
|
rewardExp: 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'guan-huang-after-changsha',
|
||||||
|
title: '무인의 존중',
|
||||||
|
availableAfterBattleIds: [campBattleIds.twentySixth],
|
||||||
|
unitIds: ['guan-yu', 'huang-zhong'],
|
||||||
|
bondId: 'guan-yu__huang-zhong',
|
||||||
|
rewardExp: 28,
|
||||||
|
lines: [
|
||||||
|
'관우: 장군의 활은 성루 위에서도 흔들리지 않았소. 칼을 든 몸으로도 그 기개를 알 수 있었소.',
|
||||||
|
'황충: 운장 장군의 칼 역시 병사를 물러서게 하지 않았습니다. 나이는 달라도 무인의 길은 통하는군요.',
|
||||||
|
'관우: 다음 전장에서는 서로의 거리와 호흡을 맞춰 봅시다.'
|
||||||
|
],
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
id: 'compare-front-and-bow',
|
||||||
|
label: '전열과 활의 거리를 맞춘다',
|
||||||
|
response: '관우와 황충은 전열이 버티는 거리와 활이 닿는 거리를 함께 맞추며 장사 병사들의 시선을 모았다.',
|
||||||
|
rewardExp: 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'share-warrior-courtesy',
|
||||||
|
label: '무인의 예를 나눈다',
|
||||||
|
response: '두 장수는 말보다 짧은 예로 서로를 인정했고, 군영의 새 긴장감은 단단한 신뢰로 바뀌기 시작했다.',
|
||||||
|
rewardExp: 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'liu-wei-after-changsha',
|
||||||
|
title: '닫힌 문을 여는 창',
|
||||||
|
availableAfterBattleIds: [campBattleIds.twentySixth],
|
||||||
|
unitIds: ['liu-bei', 'wei-yan'],
|
||||||
|
bondId: 'liu-bei__wei-yan',
|
||||||
|
rewardExp: 26,
|
||||||
|
lines: [
|
||||||
|
'위연: 저는 성문 안의 답답함을 참지 못했습니다. 주공의 군이라면 닫힌 문을 여는 데 제 창을 쓰겠습니다.',
|
||||||
|
'유비: 거친 창도 향할 곳이 바르면 백성을 살리는 길이 되오. 다만 급한 마음이 먼저 나서지 않도록 함께 다듬읍시다.',
|
||||||
|
'위연: 명령이 분명하다면, 제 창도 분명히 따르겠습니다.'
|
||||||
|
],
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
id: 'set-wei-yan-vanguard-rule',
|
||||||
|
label: '돌파 규칙을 정한다',
|
||||||
|
response: '유비는 위연에게 돌파 전에 반드시 백성의 길을 살피라 명했고, 위연은 거칠게나마 고개를 끄덕였다.',
|
||||||
|
rewardExp: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'temper-wei-yan-ambition',
|
||||||
|
label: '뜻을 먼저 묻는다',
|
||||||
|
response: '위연은 공을 세우고 싶다는 마음을 숨기지 않았지만, 그 공이 누구를 위한 것인지 다시 생각하게 되었다.',
|
||||||
|
rewardExp: 9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'huang-wei-after-changsha',
|
||||||
|
title: '장사의 전환',
|
||||||
|
availableAfterBattleIds: [campBattleIds.twentySixth],
|
||||||
|
unitIds: ['huang-zhong', 'wei-yan'],
|
||||||
|
bondId: 'huang-zhong__wei-yan',
|
||||||
|
rewardExp: 24,
|
||||||
|
lines: [
|
||||||
|
'황충: 성문이 열릴 때의 결단은 빠르더군. 다만 빠른 결단은 때로 먼저 상처를 남긴다.',
|
||||||
|
'위연: 장군처럼 오래 참는 법은 아직 모릅니다. 하지만 닫힌 성문을 그대로 두는 것도 참기 어렵습니다.',
|
||||||
|
'황충: 그러니 다음에는 내가 거리를 보고, 그대가 길을 열면 되겠군.'
|
||||||
|
],
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
id: 'pair-bow-and-breakthrough',
|
||||||
|
label: '활과 돌파를 맞춘다',
|
||||||
|
response: '황충은 위연이 뛰어들 거리의 위험을 짚었고, 위연은 노장의 활이 닿는 길을 익혔다.',
|
||||||
|
rewardExp: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settle-changsha-memory',
|
||||||
|
label: '장사의 기억을 정리한다',
|
||||||
|
response: '두 사람은 한현 아래에서 서로 달랐던 선택을 말했고, 이제 같은 군에서 다른 역할을 맡기로 했다.',
|
||||||
|
rewardExp: 9
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -3329,6 +3453,66 @@ const campVisits: CampVisitDefinition[] = [
|
|||||||
itemRewards: ['남부 군현 지도 1']
|
itemRewards: ['남부 군현 지도 1']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changsha-veteran-honor',
|
||||||
|
title: '장사 노장 예우',
|
||||||
|
location: '장사 성루',
|
||||||
|
availableAfterBattleIds: [campBattleIds.twentySixth],
|
||||||
|
bondId: 'liu-bei__huang-zhong',
|
||||||
|
description: '유비와 황충이 장사 성루에 올라 전투 뒤 흔들린 병사들을 달래고, 노장의 명예를 새 군율 안에 세웁니다.',
|
||||||
|
lines: [
|
||||||
|
'황충: 병사들은 제가 패한 것이 아니라 버림받은 것이라 여길까 두려워합니다.',
|
||||||
|
'유비: 장군을 예로 맞는다면 병사들도 자기 자리를 잃지 않을 것이오. 성루에서 함께 군율을 밝힙시다.',
|
||||||
|
'장사 성루 위에는 아직 화살 자국이 남아 있었지만, 병사들의 시선은 조금씩 아래로 내려왔습니다.'
|
||||||
|
],
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
id: 'honor-changsha-soldiers',
|
||||||
|
label: '장사 병사를 예우한다',
|
||||||
|
response: '황충의 이름으로 장사 병사들의 항복 절차가 정리되자, 성 안의 불안은 빠르게 가라앉았다.',
|
||||||
|
bondExp: 22,
|
||||||
|
itemRewards: ['장사 병적 기록 1']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'open-castle-storehouse',
|
||||||
|
label: '성내 창고를 점검한다',
|
||||||
|
response: '창고를 함부로 열지 않고 장부부터 확인하자, 장사 상인들이 다시 거래를 시작할 수 있게 되었다.',
|
||||||
|
bondExp: 18,
|
||||||
|
gold: 220,
|
||||||
|
itemRewards: ['콩 4']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'yi-province-road-council',
|
||||||
|
title: '익주 길 논의',
|
||||||
|
location: '장사 군막',
|
||||||
|
availableAfterBattleIds: [campBattleIds.twentySixth],
|
||||||
|
bondId: 'liu-bei__zhuge-liang',
|
||||||
|
description: '제갈량과 새로 합류한 장수들의 정보를 모아, 형주 남부 이후 익주로 향할 큰 방향을 처음 정리합니다.',
|
||||||
|
lines: [
|
||||||
|
'제갈량: 형주 남부는 뿌리가 되었지만, 뿌리만으로 천하를 받칠 수는 없습니다. 익주로 향할 명분과 길이 필요합니다.',
|
||||||
|
'유비: 길이 멀수록 함께 갈 사람을 더 살펴야 하오. 새로 온 장수들의 뜻도 함께 묶읍시다.',
|
||||||
|
'군막의 지도에는 형주 남부 표시 옆으로 익주로 향하는 긴 선이 처음 그어졌습니다.'
|
||||||
|
],
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
id: 'draft-yi-province-cause',
|
||||||
|
label: '익주 명분을 적는다',
|
||||||
|
response: '제갈량은 형주 남부 안정과 백성 보호의 기록을 바탕으로, 익주로 향할 첫 명분을 정리했다.',
|
||||||
|
bondExp: 24,
|
||||||
|
itemRewards: ['익주 진입 초안 1']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'assign-new-officers',
|
||||||
|
label: '새 장수 역할을 나눈다',
|
||||||
|
response: '황충, 위연, 공지의 역할이 정리되자 다음 대장정에서 누가 전열과 후방을 맡을지 윤곽이 생겼다.',
|
||||||
|
bondExp: 20,
|
||||||
|
gold: 220,
|
||||||
|
itemRewards: ['군영 편성 기록 1']
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -3414,6 +3598,33 @@ export class CampScene extends Phaser.Scene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (battleId === campBattleIds.twentySixth) {
|
||||||
|
this.campaign = ensureCampaignRosterUnits(
|
||||||
|
[
|
||||||
|
...xuzhouRecruitUnits,
|
||||||
|
...caoBreakRecruitUnits,
|
||||||
|
...liuBiaoRecruitUnits,
|
||||||
|
...zhugeRecruitUnits,
|
||||||
|
...jingzhouRecruitUnits,
|
||||||
|
...guiyangRecruitUnits,
|
||||||
|
...wulingRecruitUnits,
|
||||||
|
...changshaRecruitUnits
|
||||||
|
],
|
||||||
|
[
|
||||||
|
...xuzhouRecruitBonds,
|
||||||
|
...caoBreakRecruitBonds,
|
||||||
|
...liuBiaoRecruitBonds,
|
||||||
|
...zhugeRecruitBonds,
|
||||||
|
...jingzhouRecruitBonds,
|
||||||
|
...guiyangRecruitBonds,
|
||||||
|
...wulingRecruitBonds,
|
||||||
|
...changshaRecruitBonds
|
||||||
|
]
|
||||||
|
);
|
||||||
|
this.report = this.campaign.firstBattleReport ?? this.report;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (battleId === campBattleIds.twentyFifth) {
|
if (battleId === campBattleIds.twentyFifth) {
|
||||||
this.campaign = ensureCampaignRosterUnits(
|
this.campaign = ensureCampaignRosterUnits(
|
||||||
[
|
[
|
||||||
@@ -3535,6 +3746,9 @@ export class CampScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private currentCampTitle() {
|
private currentCampTitle() {
|
||||||
const battleId = this.currentCampBattleId();
|
const battleId = this.currentCampBattleId();
|
||||||
|
if (battleId === campBattleIds.twentySixth) {
|
||||||
|
return '장사 노장 대면 후 군영';
|
||||||
|
}
|
||||||
if (battleId === campBattleIds.twentyFifth) {
|
if (battleId === campBattleIds.twentyFifth) {
|
||||||
return '무릉 산길 확보 후 군영';
|
return '무릉 산길 확보 후 군영';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
twentyThirdBattleScenario,
|
twentyThirdBattleScenario,
|
||||||
twentyFifthBattleScenario,
|
twentyFifthBattleScenario,
|
||||||
twentyFourthBattleScenario,
|
twentyFourthBattleScenario,
|
||||||
|
twentySixthBattleScenario,
|
||||||
twentiethBattleScenario,
|
twentiethBattleScenario,
|
||||||
twelfthBattleScenario,
|
twelfthBattleScenario,
|
||||||
thirdBattleScenario
|
thirdBattleScenario
|
||||||
@@ -349,7 +350,8 @@ export class TitleScene extends Phaser.Scene {
|
|||||||
campaign.step === 'twenty-second-camp' ||
|
campaign.step === 'twenty-second-camp' ||
|
||||||
campaign.step === 'twenty-third-camp' ||
|
campaign.step === 'twenty-third-camp' ||
|
||||||
campaign.step === 'twenty-fourth-camp' ||
|
campaign.step === 'twenty-fourth-camp' ||
|
||||||
campaign.step === 'twenty-fifth-camp'
|
campaign.step === 'twenty-fifth-camp' ||
|
||||||
|
campaign.step === 'twenty-sixth-camp'
|
||||||
) {
|
) {
|
||||||
this.scene.start('CampScene');
|
this.scene.start('CampScene');
|
||||||
return;
|
return;
|
||||||
@@ -480,6 +482,11 @@ export class TitleScene extends Phaser.Scene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (campaign.step === 'twenty-sixth-battle') {
|
||||||
|
this.scene.start('BattleScene', { battleId: twentySixthBattleScenario.id });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (campaign.step === 'first-victory-story') {
|
if (campaign.step === 'first-victory-story') {
|
||||||
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
|
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -91,7 +91,9 @@ export type CampaignStep =
|
|||||||
| 'twenty-fourth-battle'
|
| 'twenty-fourth-battle'
|
||||||
| 'twenty-fourth-camp'
|
| 'twenty-fourth-camp'
|
||||||
| 'twenty-fifth-battle'
|
| 'twenty-fifth-battle'
|
||||||
| 'twenty-fifth-camp';
|
| 'twenty-fifth-camp'
|
||||||
|
| 'twenty-sixth-battle'
|
||||||
|
| 'twenty-sixth-camp';
|
||||||
|
|
||||||
export type CampaignUnitProgressSnapshot = {
|
export type CampaignUnitProgressSnapshot = {
|
||||||
unitId: string;
|
unitId: string;
|
||||||
@@ -178,7 +180,8 @@ const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: Campai
|
|||||||
'twenty-second-battle-red-cliffs-fire': { victory: 'twenty-second-camp', retry: 'twenty-second-battle' },
|
'twenty-second-battle-red-cliffs-fire': { victory: 'twenty-second-camp', retry: 'twenty-second-battle' },
|
||||||
'twenty-third-battle-jingzhou-south-entry': { victory: 'twenty-third-camp', retry: 'twenty-third-battle' },
|
'twenty-third-battle-jingzhou-south-entry': { victory: 'twenty-third-camp', retry: 'twenty-third-battle' },
|
||||||
'twenty-fourth-battle-guiyang-persuasion': { victory: 'twenty-fourth-camp', retry: 'twenty-fourth-battle' },
|
'twenty-fourth-battle-guiyang-persuasion': { victory: 'twenty-fourth-camp', retry: 'twenty-fourth-battle' },
|
||||||
'twenty-fifth-battle-wuling-mountain-road': { victory: 'twenty-fifth-camp', retry: 'twenty-fifth-battle' }
|
'twenty-fifth-battle-wuling-mountain-road': { victory: 'twenty-fifth-camp', retry: 'twenty-fifth-battle' },
|
||||||
|
'twenty-sixth-battle-changsha-veteran': { victory: 'twenty-sixth-camp', retry: 'twenty-sixth-battle' }
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CampaignSaveSlotSummary = {
|
export type CampaignSaveSlotSummary = {
|
||||||
|
|||||||
Reference in New Issue
Block a user