Add Jieqiao relief campaign battle
This commit is contained in:
@@ -23,12 +23,13 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of
|
||||
- Fourth battle Guangzong main camp confrontation with Zhang Jue, expanded enemy formation, campaign persistence, and anti-Dong Zhuo setup
|
||||
- Fifth battle Sishui Gate vanguard path starting the anti-Dong Zhuo chapter, with campaign persistence and title continue support
|
||||
- Battle-specific camp conversations with selectable bond-growth choices from the first camp through the Gongsun Zan camp
|
||||
- Flow verification script from title through the fifth battle victory and camp save state
|
||||
- Sixth battle Jieqiao relief route under Gongsun Zan, with a story bridge toward Tao Qian and Xu Province
|
||||
- Flow verification script from title through the sixth battle victory and camp save state
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Build the Gongsun Zan camp chapter after the Sishui Gate vanguard battle
|
||||
2. Add the next Gongsun Zan-era battle and the story bridge toward Xu Province
|
||||
1. Add the Tao Qian/Xu Province rescue battle and first Xu Province camp
|
||||
2. Turn story-only endpoint bridges into a more explicit chapter selection/progress view
|
||||
3. Apply treasure equipment effects to damage, defense, recovery, and command rules
|
||||
4. Add more distinctive victory/defeat branches and optional objectives
|
||||
5. Keep expanding scenarios through the long campaign instead of treating the current slice as an ending
|
||||
|
||||
@@ -663,6 +663,117 @@ try {
|
||||
throw new Error(`Expected fifth camp to expose Gongsun Zan/Sishui dialogue set: ${JSON.stringify(fifthCampState)}`);
|
||||
}
|
||||
|
||||
await page.mouse.click(1120, 38);
|
||||
await page.waitForTimeout(160);
|
||||
const fifthCampSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
if (!fifthCampSortieState?.sortieVisible) {
|
||||
throw new Error(`Expected fifth camp sortie preparation overlay: ${JSON.stringify(fifthCampSortieState)}`);
|
||||
}
|
||||
await page.mouse.click(938, 596);
|
||||
await page.waitForFunction(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
return activeScenes.includes('StoryScene');
|
||||
});
|
||||
|
||||
for (let i = 0; i < 26; i += 1) {
|
||||
const isSixthBattleActive = await page.evaluate(() => {
|
||||
const state = window.__HEROS_DEBUG__?.battle();
|
||||
return state?.scene === 'BattleScene' && state?.battleId === 'sixth-battle-jieqiao-relief';
|
||||
});
|
||||
|
||||
if (isSixthBattleActive) {
|
||||
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 === 'sixth-battle-jieqiao-relief' && state?.battleOutcome === null && state?.phase === 'idle';
|
||||
});
|
||||
await page.screenshot({ path: 'dist/verification-sixth-battle.png', fullPage: true });
|
||||
|
||||
const sixthBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
|
||||
const sixthEnemies = sixthBattleState.units.filter((unit) => unit.faction === 'enemy');
|
||||
const sixthEnemyBehaviors = new Set(sixthEnemies.map((unit) => unit.ai));
|
||||
if (
|
||||
sixthBattleState.camera?.mapWidth !== 24 ||
|
||||
sixthBattleState.camera?.mapHeight !== 20 ||
|
||||
sixthBattleState.victoryConditionLabel !== '곡의 격파' ||
|
||||
sixthEnemies.length < 12 ||
|
||||
!sixthEnemyBehaviors.has('aggressive') ||
|
||||
!sixthEnemyBehaviors.has('guard') ||
|
||||
!sixthEnemyBehaviors.has('hold')
|
||||
) {
|
||||
throw new Error(`Expected sixth battle map, objective, and mixed enemy AI: ${JSON.stringify(sixthBattleState)}`);
|
||||
}
|
||||
|
||||
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 campaignSaveAfterSixthBattle = await page.evaluate(() => {
|
||||
const raw = window.localStorage.getItem('heros-web:campaign-state');
|
||||
return raw ? JSON.parse(raw) : undefined;
|
||||
});
|
||||
if (
|
||||
!campaignSaveAfterSixthBattle ||
|
||||
campaignSaveAfterSixthBattle.step !== 'sixth-camp' ||
|
||||
campaignSaveAfterSixthBattle.latestBattleId !== 'sixth-battle-jieqiao-relief' ||
|
||||
!campaignSaveAfterSixthBattle.battleHistory?.['sixth-battle-jieqiao-relief']
|
||||
) {
|
||||
throw new Error(`Expected campaign save to persist sixth battle victory: ${JSON.stringify(campaignSaveAfterSixthBattle)}`);
|
||||
}
|
||||
|
||||
const sixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
if (
|
||||
sixthCampState?.campBattleId !== 'sixth-battle-jieqiao-relief' ||
|
||||
sixthCampState.campTitle !== '서주 원군 준비 군영' ||
|
||||
sixthCampState.availableDialogueIds?.length !== 3 ||
|
||||
!sixthCampState.availableDialogueIds.every((id) => id.endsWith('jieqiao'))
|
||||
) {
|
||||
throw new Error(`Expected sixth camp to expose Xu Province bridge dialogue set: ${JSON.stringify(sixthCampState)}`);
|
||||
}
|
||||
|
||||
await page.mouse.click(1120, 38);
|
||||
await page.waitForTimeout(160);
|
||||
const sixthCampSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
if (!sixthCampSortieState?.sortieVisible) {
|
||||
throw new Error(`Expected sixth camp story bridge overlay: ${JSON.stringify(sixthCampSortieState)}`);
|
||||
}
|
||||
await page.mouse.click(938, 596);
|
||||
await page.waitForFunction(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
return activeScenes.includes('StoryScene');
|
||||
});
|
||||
await page.screenshot({ path: 'dist/verification-xuzhou-bridge-story.png', fullPage: true });
|
||||
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
const returnedToCamp = await page.evaluate(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
return activeScenes.includes('CampScene');
|
||||
});
|
||||
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');
|
||||
});
|
||||
|
||||
await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene'));
|
||||
await page.waitForFunction(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
@@ -674,7 +785,7 @@ try {
|
||||
return activeScenes.includes('CampScene');
|
||||
});
|
||||
|
||||
console.log(`Verified title-to-fifth-battle flow, result states, and debug API at ${targetUrl}`);
|
||||
console.log(`Verified title-to-sixth-battle flow, result states, and debug API at ${targetUrl}`);
|
||||
} finally {
|
||||
await browser?.close();
|
||||
if (serverProcess && !serverProcess.killed) {
|
||||
|
||||
86
src/assets/images/battle/sixth-battle-map.svg
Normal file
86
src/assets/images/battle/sixth-battle-map.svg
Normal file
@@ -0,0 +1,86 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2400 2000" width="2400" height="2000">
|
||||
<defs>
|
||||
<filter id="grain">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.016" numOctaves="4" seed="112" />
|
||||
<feColorMatrix type="saturate" values="0.52" />
|
||||
<feBlend mode="multiply" in2="SourceGraphic" />
|
||||
</filter>
|
||||
<pattern id="grass" width="76" height="76" patternUnits="userSpaceOnUse">
|
||||
<rect width="76" height="76" fill="#60754f" />
|
||||
<path d="M10 23h22M43 51h20M22 64l14-11M56 17l12 9" stroke="#a5b476" stroke-width="3" opacity="0.25" />
|
||||
<path d="M8 54l13-19M35 27l11-15M62 69l7-21" stroke="#344d32" stroke-width="2" opacity="0.34" />
|
||||
</pattern>
|
||||
<pattern id="earth" width="86" height="86" patternUnits="userSpaceOnUse">
|
||||
<rect width="86" height="86" fill="#947247" />
|
||||
<path d="M4 22c30-15 54 9 80-6M0 58c32-17 60 13 84-4" stroke="#c69b62" stroke-width="6" opacity="0.25" />
|
||||
<path d="M20 74l12-20M52 36l20-13M66 70l11-7" stroke="#57462f" stroke-width="3" opacity="0.34" />
|
||||
</pattern>
|
||||
<pattern id="forest" width="108" height="94" patternUnits="userSpaceOnUse">
|
||||
<rect width="108" height="94" fill="#30462d" />
|
||||
<circle cx="22" cy="34" r="26" fill="#213923" />
|
||||
<circle cx="53" cy="26" r="30" fill="#526f3f" />
|
||||
<circle cx="82" cy="42" r="27" fill="#2b4229" />
|
||||
<circle cx="46" cy="69" r="24" fill="#657f49" />
|
||||
<path d="M12 52c26 15 62 14 90-5M32 23c19 16 47 14 64-5" stroke="#142515" stroke-width="5" opacity="0.36" />
|
||||
</pattern>
|
||||
<linearGradient id="river" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#4a839f" />
|
||||
<stop offset="0.55" stop-color="#25536f" />
|
||||
<stop offset="1" stop-color="#18384f" />
|
||||
</linearGradient>
|
||||
<linearGradient id="wall" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#81705a" />
|
||||
<stop offset="1" stop-color="#46382a" />
|
||||
</linearGradient>
|
||||
<linearGradient id="roof" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#c88c38" />
|
||||
<stop offset="1" stop-color="#7b5120" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="2400" height="2000" fill="url(#grass)" />
|
||||
<g filter="url(#grain)">
|
||||
<path d="M-110 1690C214 1452 450 1295 650 1082C826 895 994 764 1218 632C1412 518 1588 356 1780 92" fill="none" stroke="#a78455" stroke-width="244" stroke-linecap="round" opacity="0.98" />
|
||||
<path d="M-110 1690C214 1452 450 1295 650 1082C826 895 994 764 1218 632C1412 518 1588 356 1780 92" fill="none" stroke="url(#earth)" stroke-width="166" stroke-linecap="round" opacity="0.92" />
|
||||
<path d="M-80 980C240 914 474 840 690 690C912 536 1068 434 1300 422C1500 412 1668 326 1840 150" fill="none" stroke="#9e784b" stroke-width="174" stroke-linecap="round" opacity="0.9" />
|
||||
<path d="M-80 980C240 914 474 840 690 690C912 536 1068 434 1300 422C1500 412 1668 326 1840 150" fill="none" stroke="url(#earth)" stroke-width="112" stroke-linecap="round" opacity="0.86" />
|
||||
|
||||
<path d="M1248 10c-44 220-4 396 126 532c154 162 186 334 130 534c-42 152-12 290 112 424c104 112 132 242 82 388" fill="none" stroke="#173c52" stroke-width="256" stroke-linecap="round" opacity="0.95" />
|
||||
<path d="M1248 10c-44 220-4 396 126 532c154 162 186 334 130 534c-42 152-12 290 112 424c104 112 132 242 82 388" fill="none" stroke="url(#river)" stroke-width="172" stroke-linecap="round" opacity="0.98" />
|
||||
<path d="M1310 146c38 116 56 220 10 332M1480 755c84 100 104 220 50 360M1648 1510c55 92 72 174 44 276" fill="none" stroke="#a9dce4" stroke-width="13" opacity="0.34" />
|
||||
|
||||
<path d="M-30 48c248-82 496-52 720 98c34 144-46 278-238 386c-218-8-384-82-500-222Z" fill="url(#forest)" opacity="0.88" />
|
||||
<path d="M610 214c252-100 488-68 704 96c6 146-96 254-300 322c-238-34-366-152-404-418Z" fill="url(#forest)" opacity="0.82" />
|
||||
<path d="M42 600c318-118 600-64 838 158c-86 222-340 344-760 354C-6 964-28 792 42 600Z" fill="url(#forest)" opacity="0.86" />
|
||||
<path d="M1730 430c268-90 478-52 670 110v410c-248 72-486 8-702-190c-36-130-25-238 32-330Z" fill="url(#forest)" opacity="0.84" />
|
||||
<path d="M650 1542c330-124 622-72 866 136c-62 220-304 330-724 322c-158-112-206-260-142-458Z" fill="url(#forest)" opacity="0.84" />
|
||||
|
||||
<g transform="translate(1570 70)">
|
||||
<rect x="-30" y="-12" width="500" height="292" rx="18" fill="#2d261d" opacity="0.55" />
|
||||
<rect x="0" y="0" width="438" height="250" rx="15" fill="url(#wall)" />
|
||||
<rect x="38" y="48" width="362" height="162" fill="#756044" stroke="#241d15" stroke-width="12" />
|
||||
<path d="M38 48h362M38 210h362M38 48v162M400 48v162" stroke="#35291e" stroke-width="10" />
|
||||
<rect x="76" y="98" width="78" height="82" fill="#98714a" />
|
||||
<rect x="190" y="72" width="88" height="108" fill="#9d7650" />
|
||||
<rect x="318" y="98" width="56" height="82" fill="#8c6846" />
|
||||
<path d="M58 98l58-45l60 45M176 72l58-50l68 50M304 98l42-36l52 36" fill="url(#roof)" stroke="#302316" stroke-width="9" />
|
||||
<path d="M212 180v-58h44v58" fill="#1f1712" />
|
||||
</g>
|
||||
|
||||
<g transform="translate(1720 560)">
|
||||
<rect x="0" y="0" width="220" height="158" rx="12" fill="#5f4b36" />
|
||||
<rect x="34" y="46" width="66" height="82" fill="#98714d" />
|
||||
<rect x="124" y="36" width="70" height="92" fill="#96704a" />
|
||||
<path d="M18 46l50-40l54 40M112 36l48-38l58 38" fill="url(#roof)" stroke="#322317" stroke-width="9" />
|
||||
</g>
|
||||
|
||||
<g transform="translate(86 1422)">
|
||||
<path d="M0 88c88-70 190-92 306-64c82 76 96 166 40 268c-122 22-234-4-332-78c-34-48-38-90-14-126Z" fill="#4f6264" opacity="0.48" />
|
||||
<rect x="76" y="104" width="184" height="138" rx="10" fill="#604b36" />
|
||||
<rect x="116" y="148" width="54" height="72" fill="#98724a" />
|
||||
<rect x="190" y="136" width="46" height="84" fill="#96704a" />
|
||||
<path d="M94 148l48-36l50 36M180 136l36-28l44 28" fill="url(#roof)" stroke="#2f2218" stroke-width="8" />
|
||||
</g>
|
||||
</g>
|
||||
<rect width="2400" height="2000" fill="#0b0d12" opacity="0.07" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
@@ -11,6 +11,10 @@ import {
|
||||
secondBattleMap,
|
||||
secondBattleUnits,
|
||||
secondBattleVictoryPages,
|
||||
sixthBattleBonds,
|
||||
sixthBattleMap,
|
||||
sixthBattleUnits,
|
||||
sixthBattleVictoryPages,
|
||||
thirdBattleBonds,
|
||||
fourthBattleBonds,
|
||||
fourthBattleMap,
|
||||
@@ -30,7 +34,8 @@ export type BattleScenarioId =
|
||||
| 'second-battle-yellow-turban-pursuit'
|
||||
| 'third-battle-guangzong-road'
|
||||
| 'fourth-battle-guangzong-camp'
|
||||
| 'fifth-battle-sishui-vanguard';
|
||||
| 'fifth-battle-sishui-vanguard'
|
||||
| 'sixth-battle-jieqiao-relief';
|
||||
|
||||
export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
|
||||
|
||||
@@ -334,6 +339,59 @@ export const fifthBattleScenario: BattleScenarioDefinition = {
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
|
||||
export const sixthBattleScenario: BattleScenarioDefinition = {
|
||||
id: 'sixth-battle-jieqiao-relief',
|
||||
title: '계교 원군로',
|
||||
victoryConditionLabel: '곡의 격파',
|
||||
defeatConditionLabel: '유비 퇴각',
|
||||
openingObjectiveLines: [
|
||||
'공손찬의 보급로를 원소군 교위 곡의가 끊으려 합니다. 곡의를 격파하면 계교로 향하는 길을 다시 열 수 있습니다.',
|
||||
'강가의 궁병과 측면 기병이 동시에 압박합니다. 숲길 척후를 먼저 밀어내고 중앙 길을 확보하십시오.',
|
||||
'15턴 이내에 승리하면 공손찬 진영에서 세 형제의 신뢰가 커집니다.'
|
||||
],
|
||||
map: sixthBattleMap,
|
||||
units: sixthBattleUnits,
|
||||
bonds: sixthBattleBonds,
|
||||
mapTextureKey: 'battle-map-sixth',
|
||||
leaderUnitId: 'jieqiao-leader-qu-yi',
|
||||
quickVictoryTurnLimit: 15,
|
||||
baseVictoryGold: 860,
|
||||
objectives: [
|
||||
{
|
||||
id: 'leader',
|
||||
kind: 'defeat-leader',
|
||||
label: '곡의 격파',
|
||||
rewardGold: 520,
|
||||
unitId: 'jieqiao-leader-qu-yi'
|
||||
},
|
||||
{
|
||||
id: 'liu-bei',
|
||||
kind: 'keep-unit-alive',
|
||||
label: '유비 생존',
|
||||
rewardGold: 210,
|
||||
unitId: 'liu-bei'
|
||||
},
|
||||
{
|
||||
id: 'village',
|
||||
kind: 'secure-terrain',
|
||||
label: '계교 보급촌 확보',
|
||||
rewardGold: 300,
|
||||
terrain: 'village'
|
||||
},
|
||||
{
|
||||
id: 'quick',
|
||||
kind: 'quick-victory',
|
||||
label: '15턴 이내 승리',
|
||||
rewardGold: 250,
|
||||
maxTurn: 15
|
||||
}
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 2', '상처약 2', '탁주 1', '공손찬 신뢰 +1'],
|
||||
victoryPages: sixthBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
|
||||
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
|
||||
|
||||
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
|
||||
@@ -341,7 +399,8 @@ export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition>
|
||||
'second-battle-yellow-turban-pursuit': secondBattleScenario,
|
||||
'third-battle-guangzong-road': thirdBattleScenario,
|
||||
'fourth-battle-guangzong-camp': fourthBattleScenario,
|
||||
'fifth-battle-sishui-vanguard': fifthBattleScenario
|
||||
'fifth-battle-sishui-vanguard': fifthBattleScenario,
|
||||
'sixth-battle-jieqiao-relief': sixthBattleScenario
|
||||
};
|
||||
|
||||
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
fifthBattleScenario,
|
||||
fourthBattleScenario,
|
||||
secondBattleScenario,
|
||||
sixthBattleScenario,
|
||||
thirdBattleScenario,
|
||||
type BattleScenarioId
|
||||
} from './battles';
|
||||
@@ -15,6 +16,8 @@ import {
|
||||
fourthBattleVictoryPages,
|
||||
secondBattleIntroPages,
|
||||
secondBattleVictoryPages,
|
||||
sixthBattleIntroPages,
|
||||
sixthBattleVictoryPages,
|
||||
thirdBattleIntroPages,
|
||||
thirdBattleVictoryPages,
|
||||
type StoryPage
|
||||
@@ -75,12 +78,22 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
},
|
||||
[fifthBattleScenario.id]: {
|
||||
afterBattleId: fifthBattleScenario.id,
|
||||
eyebrow: '다음 전장',
|
||||
title: sixthBattleScenario.title,
|
||||
description: '공손찬의 군영에 의탁한 세 형제는 북방 전선의 보급로를 지키는 임무를 맡습니다. 계교로 향하는 길목에서 원소군 별동대를 상대해야 합니다.',
|
||||
rewardHint: `예상 보상: ${sixthBattleScenario.title} 개방`,
|
||||
nextBattleId: sixthBattleScenario.id,
|
||||
campaignStep: 'sixth-battle',
|
||||
pages: [...fifthBattleVictoryPages, ...sixthBattleIntroPages]
|
||||
},
|
||||
[sixthBattleScenario.id]: {
|
||||
afterBattleId: sixthBattleScenario.id,
|
||||
eyebrow: '다음 장 준비',
|
||||
title: '공손찬 의탁',
|
||||
description: '사수관 앞 전초전을 넘긴 유비군은 공손찬 진영에서 연합군의 큰 흐름을 살피게 됩니다. 다음 줄기는 공손찬 의탁과 그 뒤의 서주로 이어집니다.',
|
||||
rewardHint: '다음 장: 공손찬 의탁 준비 중',
|
||||
pages: fifthBattleVictoryPages,
|
||||
unavailableNotice: '공손찬 의탁 흐름은 다음 장으로 이어집니다. 군영에서 성장 상태를 정비하세요.'
|
||||
title: '도겸의 서주',
|
||||
description: '계교 원군로를 열어 공손찬의 신뢰를 얻은 유비군에게 서주의 도겸이 원군을 청합니다. 다음 큰 줄기는 서주 인수로 이어집니다.',
|
||||
rewardHint: '다음 장: 도겸에게 서주 인수 준비 중',
|
||||
pages: sixthBattleVictoryPages,
|
||||
unavailableNotice: '도겸과 서주의 흐름은 다음 장으로 이어집니다. 군영에서 성장 상태를 정비하세요.'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -428,6 +428,62 @@ export const fifthBattleVictoryPages: StoryPage[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const sixthBattleIntroPages: StoryPage[] = [
|
||||
{
|
||||
id: 'sixth-gongsun-zan-order',
|
||||
bgm: 'militia-theme',
|
||||
chapter: '공손찬의 명',
|
||||
background: 'story-three-heroes',
|
||||
text: '사수관 전초전 뒤, 유비군은 공손찬의 군영에 머물며 북방의 정세를 살폈다. 원소군의 별동대가 계교로 향하는 보급로를 위협하자, 공손찬은 세 형제에게 길목을 맡겼다.'
|
||||
},
|
||||
{
|
||||
id: 'sixth-liu-bei-service',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '의탁의 무게',
|
||||
background: 'story-liu-bei',
|
||||
speaker: '유비',
|
||||
portrait: 'liuBei',
|
||||
text: '남의 깃발 아래에 서더라도 백성을 지키는 뜻은 변하지 않는다. 공손찬 장군의 군영에 머무는 동안에도 우리의 길을 분명히 하자.'
|
||||
},
|
||||
{
|
||||
id: 'sixth-guan-yu-road',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '계교 원군로',
|
||||
background: 'story-militia',
|
||||
speaker: '관우',
|
||||
portrait: 'guanYu',
|
||||
text: '계교로 향하는 길은 강과 숲이 얽혀 있습니다. 원소군의 궁병과 기병을 나누어 상대해야 길을 열 수 있습니다.'
|
||||
},
|
||||
{
|
||||
id: 'sixth-zhang-fei-sortie',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '북방의 먼지',
|
||||
background: 'story-sortie',
|
||||
speaker: '장비',
|
||||
portrait: 'zhangFei',
|
||||
text: '원소군이든 누구든 길을 막는 놈은 비켜서게 만들면 됩니다. 형님, 이번엔 제가 먼저 먼지를 일으키겠습니다!'
|
||||
}
|
||||
];
|
||||
|
||||
export const sixthBattleVictoryPages: StoryPage[] = [
|
||||
{
|
||||
id: 'sixth-victory-jieqiao',
|
||||
bgm: 'militia-theme',
|
||||
chapter: '계교의 길',
|
||||
background: 'story-militia',
|
||||
text: '계교로 향하는 보급로가 열리자 공손찬의 군영에도 한숨 돌릴 여유가 생겼다. 그러나 북방의 전공은 세 형제의 마음을 오래 붙잡아 두지 못했다.'
|
||||
},
|
||||
{
|
||||
id: 'sixth-xuzhou-envoy',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '서주의 사신',
|
||||
background: 'story-liu-bei',
|
||||
speaker: '유비',
|
||||
portrait: 'liuBei',
|
||||
text: '도겸 공이 서주에서 원군을 청한다면, 백성의 고통을 외면할 수 없소. 공손찬 장군께 허락을 구하고 서주로 향할 준비를 하자.'
|
||||
}
|
||||
];
|
||||
|
||||
export const firstBattleMap: BattleMap = {
|
||||
width: 20,
|
||||
height: 18,
|
||||
@@ -561,6 +617,33 @@ export const fifthBattleMap: BattleMap = {
|
||||
]
|
||||
};
|
||||
|
||||
export const sixthBattleMap: BattleMap = {
|
||||
width: 24,
|
||||
height: 20,
|
||||
terrain: [
|
||||
['forest', 'hill', 'hill', 'plain', 'plain', 'road', 'road', 'plain', 'hill', 'hill', 'plain', 'plain', 'river', 'river', 'plain', 'plain', 'fort', 'fort', 'plain', 'hill', 'hill', 'forest', 'forest', 'plain'],
|
||||
['hill', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'hill', 'plain', 'plain', 'road', 'river', 'river', 'road', 'plain', 'fort', 'plain', 'plain', 'plain', 'hill', 'forest', 'plain', 'plain'],
|
||||
['plain', 'plain', 'forest', 'plain', 'road', 'plain', 'plain', 'forest', 'plain', 'plain', 'road', 'road', 'road', 'road', 'road', 'plain', 'plain', 'plain', 'forest', 'plain', 'plain', 'hill', 'plain', 'plain'],
|
||||
['plain', 'forest', 'forest', 'plain', 'road', 'plain', 'hill', 'forest', 'plain', 'road', 'road', 'plain', 'plain', 'river', 'river', 'road', 'plain', 'hill', 'plain', 'forest', 'plain', 'plain', 'plain', 'plain'],
|
||||
['plain', 'plain', 'forest', 'road', 'road', 'plain', 'hill', 'plain', 'plain', 'road', 'plain', 'plain', 'river', 'river', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'forest', 'plain', 'plain', 'hill'],
|
||||
['plain', 'plain', 'plain', 'road', 'plain', 'plain', 'plain', 'plain', 'forest', 'road', 'plain', 'plain', 'river', 'river', 'plain', 'plain', 'road', 'plain', 'village', 'plain', 'forest', 'forest', 'plain', 'hill'],
|
||||
['forest', 'plain', 'plain', 'road', 'plain', 'hill', 'hill', 'plain', 'forest', 'road', 'plain', 'plain', 'plain', 'river', 'river', 'plain', 'road', 'plain', 'village', 'plain', 'plain', 'forest', 'plain', 'plain'],
|
||||
['forest', 'plain', 'road', 'road', 'plain', 'plain', 'hill', 'plain', 'plain', 'road', 'forest', 'plain', 'plain', 'river', 'river', 'plain', 'road', 'road', 'plain', 'plain', 'hill', 'plain', 'plain', 'plain'],
|
||||
['plain', 'plain', 'road', 'plain', 'plain', 'forest', 'plain', 'plain', 'hill', 'road', 'forest', 'plain', 'plain', 'plain', 'river', 'river', 'plain', 'road', 'plain', 'plain', 'hill', 'plain', 'forest', 'plain'],
|
||||
['plain', 'road', 'road', 'plain', 'forest', 'forest', 'plain', 'plain', 'hill', 'road', 'road', 'road', 'road', 'road', 'road', 'river', 'river', 'road', 'road', 'plain', 'plain', 'plain', 'forest', 'plain'],
|
||||
['road', 'road', 'plain', 'plain', 'forest', 'plain', 'plain', 'hill', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'river', 'river', 'road', 'plain', 'plain', 'hill', 'plain', 'plain'],
|
||||
['plain', 'road', 'plain', 'forest', 'plain', 'plain', 'hill', 'plain', 'plain', 'forest', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'river', 'river', 'plain', 'plain', 'hill', 'plain', 'plain'],
|
||||
['plain', 'road', 'plain', 'forest', 'forest', 'plain', 'plain', 'plain', 'hill', 'plain', 'forest', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'river', 'river', 'plain', 'plain', 'plain', 'plain'],
|
||||
['plain', 'road', 'plain', 'plain', 'plain', 'hill', 'plain', 'forest', 'plain', 'plain', 'plain', 'forest', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'river', 'river', 'plain', 'hill', 'plain'],
|
||||
['camp', 'road', 'road', 'plain', 'plain', 'hill', 'plain', 'forest', 'forest', 'plain', 'plain', 'plain', 'hill', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'river', 'river', 'plain', 'plain'],
|
||||
['camp', 'camp', 'road', 'plain', 'forest', 'plain', 'plain', 'plain', 'forest', 'plain', 'plain', 'hill', 'plain', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'river', 'river', 'plain'],
|
||||
['plain', 'camp', 'road', 'road', 'plain', 'plain', 'forest', 'plain', 'plain', 'plain', 'hill', 'plain', 'plain', 'forest', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'river', 'plain'],
|
||||
['plain', 'plain', 'camp', 'road', 'plain', 'forest', 'plain', 'plain', 'plain', 'hill', 'plain', 'plain', 'forest', 'forest', 'plain', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain', 'plain'],
|
||||
['plain', 'plain', 'plain', 'road', 'road', 'plain', 'forest', 'forest', 'plain', 'plain', 'plain', 'hill', 'plain', 'forest', 'forest', 'plain', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'plain', 'plain'],
|
||||
['plain', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'forest', 'forest', 'plain', 'plain', 'plain', 'hill', 'plain', 'plain', 'forest', 'plain', 'plain', 'plain', 'plain', 'road', 'road', 'plain', 'plain']
|
||||
]
|
||||
};
|
||||
|
||||
export const firstBattleUnits: UnitData[] = [
|
||||
{
|
||||
id: 'liu-bei',
|
||||
@@ -1872,6 +1955,270 @@ export const fifthBattleUnits: UnitData[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const sixthBattleAllyPositions: Record<string, { x: number; y: number }> = {
|
||||
'liu-bei': { x: 2, y: 16 },
|
||||
'guan-yu': { x: 3, y: 16 },
|
||||
'zhang-fei': { x: 2, y: 17 }
|
||||
};
|
||||
|
||||
export const sixthBattleUnits: UnitData[] = [
|
||||
...firstBattleUnits
|
||||
.filter((unit) => unit.faction === 'ally')
|
||||
.map((unit) => placeScenarioUnit(unit, sixthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
|
||||
{
|
||||
id: 'jieqiao-skirmisher-a',
|
||||
name: '원소군 척후',
|
||||
faction: 'enemy',
|
||||
className: '척후병',
|
||||
classKey: 'bandit',
|
||||
level: 6,
|
||||
exp: 36,
|
||||
hp: 32,
|
||||
maxHp: 32,
|
||||
attack: 11,
|
||||
move: 4,
|
||||
stats: { might: 72, intelligence: 43, leadership: 50, agility: 68, luck: 50 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 14 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 6 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 5,
|
||||
y: 12
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-infantry-a',
|
||||
name: '원소 보병',
|
||||
faction: 'enemy',
|
||||
className: '원소 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 6,
|
||||
exp: 36,
|
||||
hp: 36,
|
||||
maxHp: 36,
|
||||
attack: 11,
|
||||
move: 3,
|
||||
stats: { might: 73, intelligence: 42, leadership: 58, agility: 56, luck: 49 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 14 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 6 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 7,
|
||||
y: 11
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-archer-a',
|
||||
name: '원소 궁병',
|
||||
faction: 'enemy',
|
||||
className: '원소 궁병',
|
||||
classKey: 'archer',
|
||||
level: 6,
|
||||
exp: 38,
|
||||
hp: 29,
|
||||
maxHp: 29,
|
||||
attack: 12,
|
||||
move: 3,
|
||||
stats: { might: 66, intelligence: 54, leadership: 54, agility: 66, luck: 50 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'short-bow', level: 2, exp: 16 },
|
||||
armor: { itemId: 'cloth-armor', level: 2, exp: 4 },
|
||||
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
|
||||
},
|
||||
x: 9,
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-cavalry-a',
|
||||
name: '원소 기병',
|
||||
faction: 'enemy',
|
||||
className: '원소 경기병',
|
||||
classKey: 'cavalry',
|
||||
level: 7,
|
||||
exp: 40,
|
||||
hp: 39,
|
||||
maxHp: 39,
|
||||
attack: 13,
|
||||
move: 5,
|
||||
stats: { might: 78, intelligence: 42, leadership: 60, agility: 78, luck: 52 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 18 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 8 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 12,
|
||||
y: 9
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-skirmisher-b',
|
||||
name: '강가 척후',
|
||||
faction: 'enemy',
|
||||
className: '척후병',
|
||||
classKey: 'bandit',
|
||||
level: 6,
|
||||
exp: 38,
|
||||
hp: 33,
|
||||
maxHp: 33,
|
||||
attack: 12,
|
||||
move: 4,
|
||||
stats: { might: 74, intelligence: 44, leadership: 51, agility: 69, luck: 51 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 16 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 6 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 6,
|
||||
y: 7
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-archer-b',
|
||||
name: '강안 궁병',
|
||||
faction: 'enemy',
|
||||
className: '원소 궁병',
|
||||
classKey: 'archer',
|
||||
level: 7,
|
||||
exp: 40,
|
||||
hp: 30,
|
||||
maxHp: 30,
|
||||
attack: 13,
|
||||
move: 3,
|
||||
stats: { might: 68, intelligence: 56, leadership: 56, agility: 67, luck: 51 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'short-bow', level: 2, exp: 18 },
|
||||
armor: { itemId: 'cloth-armor', level: 2, exp: 6 },
|
||||
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
|
||||
},
|
||||
x: 11,
|
||||
y: 6
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-cavalry-b',
|
||||
name: '원소 기병',
|
||||
faction: 'enemy',
|
||||
className: '원소 경기병',
|
||||
classKey: 'cavalry',
|
||||
level: 7,
|
||||
exp: 42,
|
||||
hp: 40,
|
||||
maxHp: 40,
|
||||
attack: 13,
|
||||
move: 5,
|
||||
stats: { might: 79, intelligence: 42, leadership: 61, agility: 79, luck: 52 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 20 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 8 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 15,
|
||||
y: 8
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-guard-a',
|
||||
name: '계교 수비병',
|
||||
faction: 'enemy',
|
||||
className: '원소 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 7,
|
||||
exp: 42,
|
||||
hp: 38,
|
||||
maxHp: 38,
|
||||
attack: 12,
|
||||
move: 3,
|
||||
stats: { might: 76, intelligence: 45, leadership: 62, agility: 58, luck: 51 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 20 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 8 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 16,
|
||||
y: 5
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-cavalry-c',
|
||||
name: '측면 기병',
|
||||
faction: 'enemy',
|
||||
className: '원소 경기병',
|
||||
classKey: 'cavalry',
|
||||
level: 7,
|
||||
exp: 44,
|
||||
hp: 41,
|
||||
maxHp: 41,
|
||||
attack: 13,
|
||||
move: 5,
|
||||
stats: { might: 80, intelligence: 43, leadership: 62, agility: 80, luck: 53 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 22 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 10 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 20,
|
||||
y: 5
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-guard-b',
|
||||
name: '계교 수비병',
|
||||
faction: 'enemy',
|
||||
className: '원소 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 7,
|
||||
exp: 44,
|
||||
hp: 39,
|
||||
maxHp: 39,
|
||||
attack: 12,
|
||||
move: 3,
|
||||
stats: { might: 77, intelligence: 45, leadership: 63, agility: 58, luck: 52 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 22 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 8 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 18,
|
||||
y: 3
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-archer-c',
|
||||
name: '계교 궁병',
|
||||
faction: 'enemy',
|
||||
className: '원소 궁병',
|
||||
classKey: 'archer',
|
||||
level: 7,
|
||||
exp: 44,
|
||||
hp: 31,
|
||||
maxHp: 31,
|
||||
attack: 13,
|
||||
move: 3,
|
||||
stats: { might: 69, intelligence: 57, leadership: 57, agility: 68, luck: 52 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'short-bow', level: 2, exp: 22 },
|
||||
armor: { itemId: 'cloth-armor', level: 2, exp: 8 },
|
||||
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
|
||||
},
|
||||
x: 16,
|
||||
y: 1
|
||||
},
|
||||
{
|
||||
id: 'jieqiao-leader-qu-yi',
|
||||
name: '곡의',
|
||||
faction: 'enemy',
|
||||
className: '원소군 교위',
|
||||
classKey: 'rebelLeader',
|
||||
level: 8,
|
||||
exp: 48,
|
||||
hp: 54,
|
||||
maxHp: 54,
|
||||
attack: 15,
|
||||
move: 3,
|
||||
stats: { might: 84, intelligence: 62, leadership: 78, agility: 62, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'leader-axe', level: 2, exp: 26 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 14 },
|
||||
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||
},
|
||||
x: 17,
|
||||
y: 1
|
||||
}
|
||||
];
|
||||
|
||||
export const firstBattleBonds: BattleBond[] = [
|
||||
{
|
||||
id: 'liu-bei__guan-yu',
|
||||
@@ -1903,6 +2250,7 @@ export const secondBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleB
|
||||
export const thirdBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const fourthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const fifthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const sixthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||
|
||||
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
|
||||
return {
|
||||
|
||||
@@ -3,6 +3,7 @@ import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg';
|
||||
import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png';
|
||||
import fourthBattleMapUrl from '../../assets/images/battle/fourth-battle-map.svg';
|
||||
import secondBattleMapUrl from '../../assets/images/battle/second-battle-map.svg';
|
||||
import sixthBattleMapUrl from '../../assets/images/battle/sixth-battle-map.svg';
|
||||
import thirdBattleMapUrl from '../../assets/images/battle/third-battle-map.svg';
|
||||
import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png';
|
||||
import liuBeiPortraitUrl from '../../assets/images/portraits/liu-bei.png';
|
||||
@@ -68,6 +69,7 @@ export class BootScene extends Phaser.Scene {
|
||||
this.load.image('battle-map-third', thirdBattleMapUrl);
|
||||
this.load.image('battle-map-fourth', fourthBattleMapUrl);
|
||||
this.load.image('battle-map-fifth', fifthBattleMapUrl);
|
||||
this.load.image('battle-map-sixth', sixthBattleMapUrl);
|
||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||
|
||||
@@ -127,7 +127,8 @@ const campBattleIds = {
|
||||
second: 'second-battle-yellow-turban-pursuit',
|
||||
third: 'third-battle-guangzong-road',
|
||||
fourth: 'fourth-battle-guangzong-camp',
|
||||
fifth: 'fifth-battle-sishui-vanguard'
|
||||
fifth: 'fifth-battle-sishui-vanguard',
|
||||
sixth: 'sixth-battle-jieqiao-relief'
|
||||
} as const;
|
||||
|
||||
const campDialogues: CampDialogue[] = [
|
||||
@@ -535,6 +536,87 @@ const campDialogues: CampDialogue[] = [
|
||||
rewardExp: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-guan-after-jieqiao',
|
||||
title: '서주로 향하는 뜻',
|
||||
availableAfterBattleIds: [campBattleIds.sixth],
|
||||
unitIds: ['liu-bei', 'guan-yu'],
|
||||
bondId: 'liu-bei__guan-yu',
|
||||
rewardExp: 20,
|
||||
lines: [
|
||||
'유비: 공손찬 장군의 은혜는 크나, 서주의 백성이 위태롭다는 소식을 들으니 마음이 편치 않소.',
|
||||
'관우: 의탁은 몸을 잠시 맡기는 일이고, 의리는 마음이 향하는 곳을 따르는 일입니다.',
|
||||
'유비: 운장의 말이 나를 깨우는구려. 도겸 공의 청을 외면하지 않겠소.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'ask-permission',
|
||||
label: '공손찬에게 허락을 구한다',
|
||||
response: '관우는 예를 갖추어 떠나는 것이 유비의 이름을 더 높인다고 답했다.',
|
||||
rewardExp: 8
|
||||
},
|
||||
{
|
||||
id: 'put-people-first',
|
||||
label: '백성을 먼저 생각한다',
|
||||
response: '두 사람은 새 주인의 눈치보다 고통받는 백성을 먼저 보겠다고 뜻을 모았다.',
|
||||
rewardExp: 7
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-zhang-after-jieqiao',
|
||||
title: '떠나는 술잔',
|
||||
availableAfterBattleIds: [campBattleIds.sixth],
|
||||
unitIds: ['liu-bei', 'zhang-fei'],
|
||||
bondId: 'liu-bei__zhang-fei',
|
||||
rewardExp: 20,
|
||||
lines: [
|
||||
'장비: 형님, 북방 군영도 나쁘진 않았지만 서주 백성이 부른다면 가야지요.',
|
||||
'유비: 익덕은 떠나는 길에서도 성급히 다투지 말아야 하오. 우리는 도움을 주러 가는 것이오.',
|
||||
'장비: 알겠습니다. 술잔은 비우고, 창은 백성을 괴롭히는 자에게만 겨누겠습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'steady-departure',
|
||||
label: '차분한 출발을 당부한다',
|
||||
response: '장비는 떠나는 순간의 소란도 형님의 이름에 흠이 될 수 있음을 받아들였다.',
|
||||
rewardExp: 7
|
||||
},
|
||||
{
|
||||
id: 'promise-xuzhou-vanguard',
|
||||
label: '서주 선봉을 맡긴다',
|
||||
response: '장비는 서주 길에서도 가장 앞에서 백성을 지키겠다며 창을 움켜쥐었다.',
|
||||
rewardExp: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'guan-zhang-after-jieqiao',
|
||||
title: '새 길의 두 기둥',
|
||||
availableAfterBattleIds: [campBattleIds.sixth],
|
||||
unitIds: ['guan-yu', 'zhang-fei'],
|
||||
bondId: 'guan-yu__zhang-fei',
|
||||
rewardExp: 18,
|
||||
lines: [
|
||||
'관우: 익덕, 서주로 가면 우리 힘만으로는 감당하기 어려운 일도 많을 것이다.',
|
||||
'장비: 그러니 더더욱 형님 곁을 단단히 지켜야지요. 운장 형님이 오른쪽이면 저는 왼쪽입니다.',
|
||||
'관우: 좋다. 길이 바뀌어도 형님을 받치는 두 기둥은 흔들리지 않는다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'two-pillars',
|
||||
label: '좌우의 역할을 정한다',
|
||||
response: '두 사람은 유비의 좌우에서 전선과 군심을 함께 지키기로 했다.',
|
||||
rewardExp: 8
|
||||
},
|
||||
{
|
||||
id: 'guard-the-oath',
|
||||
label: '도원 맹세를 되새긴다',
|
||||
response: '낯선 서주 길 앞에서도 도원의 맹세가 세 사람의 중심이 되었다.',
|
||||
rewardExp: 6
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -623,6 +705,9 @@ export class CampScene extends Phaser.Scene {
|
||||
if (battleId === campBattleIds.fifth) {
|
||||
return '공손찬 진영 군영';
|
||||
}
|
||||
if (battleId === campBattleIds.sixth) {
|
||||
return '서주 원군 준비 군영';
|
||||
}
|
||||
if (battleId === campBattleIds.fourth) {
|
||||
return '광종 토벌 후 군영';
|
||||
}
|
||||
@@ -925,6 +1010,13 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
if (!flow.nextBattleId || !flow.campaignStep || flow.pages.length === 0) {
|
||||
this.hideSortiePrep();
|
||||
if (flow.pages.length > 0) {
|
||||
this.scene.start('StoryScene', {
|
||||
pages: flow.pages,
|
||||
nextScene: 'CampScene'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.showCampNotice(flow.unavailableNotice ?? '다음 장은 아직 준비 중입니다. 군영에서 성장 상태를 정비하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Phaser from 'phaser';
|
||||
import { soundDirector } from '../audio/SoundDirector';
|
||||
import { firstBattleVictoryPages } from '../data/scenario';
|
||||
import { fifthBattleScenario, fourthBattleScenario, secondBattleScenario, thirdBattleScenario } from '../data/battles';
|
||||
import { fifthBattleScenario, fourthBattleScenario, secondBattleScenario, sixthBattleScenario, thirdBattleScenario } from '../data/battles';
|
||||
import { hasCampaignSave, loadCampaignState, startNewCampaign } from '../state/campaignState';
|
||||
import { palette } from '../ui/palette';
|
||||
|
||||
@@ -304,7 +304,8 @@ export class TitleScene extends Phaser.Scene {
|
||||
campaign.step === 'second-camp' ||
|
||||
campaign.step === 'third-camp' ||
|
||||
campaign.step === 'fourth-camp' ||
|
||||
campaign.step === 'fifth-camp'
|
||||
campaign.step === 'fifth-camp' ||
|
||||
campaign.step === 'sixth-camp'
|
||||
) {
|
||||
this.scene.start('CampScene');
|
||||
return;
|
||||
@@ -335,6 +336,11 @@ export class TitleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'sixth-battle') {
|
||||
this.scene.start('BattleScene', { battleId: sixthBattleScenario.id });
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'first-victory-story') {
|
||||
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
|
||||
return;
|
||||
|
||||
@@ -49,7 +49,9 @@ export type CampaignStep =
|
||||
| 'fourth-battle'
|
||||
| 'fourth-camp'
|
||||
| 'fifth-battle'
|
||||
| 'fifth-camp';
|
||||
| 'fifth-camp'
|
||||
| 'sixth-battle'
|
||||
| 'sixth-camp';
|
||||
|
||||
export type CampaignUnitProgressSnapshot = {
|
||||
unitId: string;
|
||||
@@ -104,7 +106,8 @@ const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: Campai
|
||||
'second-battle-yellow-turban-pursuit': { victory: 'second-camp', retry: 'second-battle' },
|
||||
'third-battle-guangzong-road': { victory: 'third-camp', retry: 'third-battle' },
|
||||
'fourth-battle-guangzong-camp': { victory: 'fourth-camp', retry: 'fourth-battle' },
|
||||
'fifth-battle-sishui-vanguard': { victory: 'fifth-camp', retry: 'fifth-battle' }
|
||||
'fifth-battle-sishui-vanguard': { victory: 'fifth-camp', retry: 'fifth-battle' },
|
||||
'sixth-battle-jieqiao-relief': { victory: 'sixth-camp', retry: 'sixth-battle' }
|
||||
};
|
||||
|
||||
export type CampaignSaveSlotSummary = {
|
||||
|
||||
Reference in New Issue
Block a user