feat: align campaign audiovisual story flow
This commit is contained in:
@@ -428,7 +428,7 @@ export type BattleScenarioDefinition = {
|
||||
itemRewards: string[];
|
||||
campaignReward?: BattleCampaignRewardDefinition;
|
||||
victoryPages: StoryPage[];
|
||||
nextCampScene: string;
|
||||
nextCampScene: 'CampScene';
|
||||
};
|
||||
|
||||
export const firstBattleScenario: BattleScenarioDefinition = {
|
||||
|
||||
@@ -71,137 +71,74 @@ import {
|
||||
import {
|
||||
baidiEntrustmentPages,
|
||||
eighthBattleIntroPages,
|
||||
eighthBattleVictoryPages,
|
||||
eighteenthBattleIntroPages,
|
||||
eighteenthBattleVictoryPages,
|
||||
eleventhBattleIntroPages,
|
||||
eleventhBattleVictoryPages,
|
||||
endingEpiloguePages,
|
||||
fiftiethBattleIntroPages,
|
||||
fiftiethBattleVictoryPages,
|
||||
fiftyEighthBattleIntroPages,
|
||||
fiftyEighthBattleVictoryPages,
|
||||
fiftyNinthBattleIntroPages,
|
||||
fiftyNinthBattleVictoryPages,
|
||||
sixtiethBattleIntroPages,
|
||||
sixtiethBattleVictoryPages,
|
||||
sixtyFirstBattleIntroPages,
|
||||
sixtyFirstBattleVictoryPages,
|
||||
sixtySecondBattleIntroPages,
|
||||
sixtySecondBattleVictoryPages,
|
||||
sixtyThirdBattleIntroPages,
|
||||
sixtyThirdBattleVictoryPages,
|
||||
sixtyFourthBattleIntroPages,
|
||||
sixtyFourthBattleVictoryPages,
|
||||
sixtyFifthBattleIntroPages,
|
||||
sixtyFifthBattleVictoryPages,
|
||||
sixtySixthBattleIntroPages,
|
||||
sixtySixthBattleVictoryPages,
|
||||
fiftyFirstBattleIntroPages,
|
||||
fiftyFirstBattleVictoryPages,
|
||||
fiftySecondBattleIntroPages,
|
||||
fiftySecondBattleVictoryPages,
|
||||
fiftyThirdBattleIntroPages,
|
||||
fiftyThirdBattleVictoryPages,
|
||||
fiftyFourthBattleIntroPages,
|
||||
fiftyFourthBattleVictoryPages,
|
||||
fiftyFifthBattleIntroPages,
|
||||
fiftyFifthBattleVictoryPages,
|
||||
fiftySixthBattleIntroPages,
|
||||
fiftySixthBattleVictoryPages,
|
||||
fiftySeventhBattleIntroPages,
|
||||
fiftySeventhBattleVictoryPages,
|
||||
fortiethBattleIntroPages,
|
||||
fortiethBattleVictoryPages,
|
||||
fortyFirstBattleIntroPages,
|
||||
fortyFirstBattleVictoryPages,
|
||||
fortySecondBattleIntroPages,
|
||||
fortySecondBattleVictoryPages,
|
||||
fortyThirdBattleIntroPages,
|
||||
fortyThirdBattleVictoryPages,
|
||||
fortyFourthBattleIntroPages,
|
||||
fortyFourthBattleVictoryPages,
|
||||
fortyFifthBattleIntroPages,
|
||||
fortyFifthBattleVictoryPages,
|
||||
fortyEighthBattleIntroPages,
|
||||
fortyEighthBattleVictoryPages,
|
||||
fortyNinthBattleIntroPages,
|
||||
fortyNinthBattleVictoryPages,
|
||||
fortySixthBattleIntroPages,
|
||||
fortySeventhBattleIntroPages,
|
||||
fortySeventhBattleVictoryPages,
|
||||
fifthBattleIntroPages,
|
||||
fifthBattleVictoryPages,
|
||||
fifteenthBattleIntroPages,
|
||||
fifteenthBattleVictoryPages,
|
||||
fourteenthBattleIntroPages,
|
||||
fourteenthBattleVictoryPages,
|
||||
fourthBattleIntroPages,
|
||||
fourthBattleVictoryPages,
|
||||
hanzhongKingCouncilPages,
|
||||
ninthBattleIntroPages,
|
||||
ninthBattleVictoryPages,
|
||||
nineteenthBattleIntroPages,
|
||||
nineteenthBattleVictoryPages,
|
||||
northernCampaignPrepPages,
|
||||
secondBattleIntroPages,
|
||||
secondBattleVictoryPages,
|
||||
seventhBattleIntroPages,
|
||||
seventhBattleVictoryPages,
|
||||
sixthBattleIntroPages,
|
||||
sixthBattleVictoryPages,
|
||||
sixteenthBattleIntroPages,
|
||||
sixteenthBattleVictoryPages,
|
||||
seventeenthBattleIntroPages,
|
||||
seventeenthBattleVictoryPages,
|
||||
shuHanFoundationPages,
|
||||
tenthBattleIntroPages,
|
||||
tenthBattleVictoryPages,
|
||||
thirteenthBattleIntroPages,
|
||||
thirteenthBattleVictoryPages,
|
||||
thirtyFifthBattleIntroPages,
|
||||
thirtyFifthBattleVictoryPages,
|
||||
thirtyFourthBattleIntroPages,
|
||||
thirtyFourthBattleVictoryPages,
|
||||
thirtyEighthBattleIntroPages,
|
||||
thirtyEighthBattleVictoryPages,
|
||||
thirtyFirstBattleIntroPages,
|
||||
thirtyFirstBattleVictoryPages,
|
||||
thirtyNinthBattleIntroPages,
|
||||
thirtyNinthBattleVictoryPages,
|
||||
thirtySecondBattleIntroPages,
|
||||
thirtySecondBattleVictoryPages,
|
||||
thirtyThirdBattleIntroPages,
|
||||
thirtyThirdBattleVictoryPages,
|
||||
thirtySixthBattleIntroPages,
|
||||
thirtySixthBattleVictoryPages,
|
||||
thirtySeventhBattleIntroPages,
|
||||
thirtiethBattleIntroPages,
|
||||
thirtiethBattleVictoryPages,
|
||||
twentyEighthBattleIntroPages,
|
||||
twentyEighthBattleVictoryPages,
|
||||
twentyNinthBattleIntroPages,
|
||||
twentyNinthBattleVictoryPages,
|
||||
twentyFirstBattleIntroPages,
|
||||
twentyFirstBattleVictoryPages,
|
||||
twentySecondBattleIntroPages,
|
||||
twentySecondBattleVictoryPages,
|
||||
twentyThirdBattleIntroPages,
|
||||
twentyThirdBattleVictoryPages,
|
||||
twentyFifthBattleIntroPages,
|
||||
twentyFifthBattleVictoryPages,
|
||||
twentyFourthBattleIntroPages,
|
||||
twentyFourthBattleVictoryPages,
|
||||
twentySixthBattleIntroPages,
|
||||
twentySixthBattleVictoryPages,
|
||||
twentySeventhBattleIntroPages,
|
||||
twentySeventhBattleVictoryPages,
|
||||
twentiethBattleIntroPages,
|
||||
twentiethBattleVictoryPages,
|
||||
twelfthBattleIntroPages,
|
||||
twelfthBattleVictoryPages,
|
||||
thirdBattleIntroPages,
|
||||
thirdBattleVictoryPages,
|
||||
type StoryPage
|
||||
} from './scenario';
|
||||
|
||||
@@ -236,7 +173,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: '지난 보상: 콩, 상처약, 탁주, 황건 부적 · 다음 보상: 단궁, 의용군 명성',
|
||||
nextBattleId: thirdBattleScenario.id,
|
||||
campaignStep: 'third-battle',
|
||||
pages: [...secondBattleVictoryPages, ...thirdBattleIntroPages]
|
||||
pages: thirdBattleIntroPages
|
||||
},
|
||||
[thirdBattleScenario.id]: {
|
||||
afterBattleId: thirdBattleScenario.id,
|
||||
@@ -246,7 +183,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fourthBattleScenario.title} 개방`,
|
||||
nextBattleId: fourthBattleScenario.id,
|
||||
campaignStep: 'fourth-battle',
|
||||
pages: [...thirdBattleVictoryPages, ...fourthBattleIntroPages]
|
||||
pages: fourthBattleIntroPages
|
||||
},
|
||||
[fourthBattleScenario.id]: {
|
||||
afterBattleId: fourthBattleScenario.id,
|
||||
@@ -256,7 +193,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fifthBattleScenario.title} 개방`,
|
||||
nextBattleId: fifthBattleScenario.id,
|
||||
campaignStep: 'fifth-battle',
|
||||
pages: [...fourthBattleVictoryPages, ...fifthBattleIntroPages]
|
||||
pages: fifthBattleIntroPages
|
||||
},
|
||||
[fifthBattleScenario.id]: {
|
||||
afterBattleId: fifthBattleScenario.id,
|
||||
@@ -266,7 +203,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixthBattleScenario.title} 개방`,
|
||||
nextBattleId: sixthBattleScenario.id,
|
||||
campaignStep: 'sixth-battle',
|
||||
pages: [...fifthBattleVictoryPages, ...sixthBattleIntroPages]
|
||||
pages: sixthBattleIntroPages
|
||||
},
|
||||
[sixthBattleScenario.id]: {
|
||||
afterBattleId: sixthBattleScenario.id,
|
||||
@@ -276,7 +213,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${seventhBattleScenario.title} 개방`,
|
||||
nextBattleId: seventhBattleScenario.id,
|
||||
campaignStep: 'seventh-battle',
|
||||
pages: [...sixthBattleVictoryPages, ...seventhBattleIntroPages]
|
||||
pages: seventhBattleIntroPages
|
||||
},
|
||||
[seventhBattleScenario.id]: {
|
||||
afterBattleId: seventhBattleScenario.id,
|
||||
@@ -286,7 +223,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${eighthBattleScenario.title} 개방`,
|
||||
nextBattleId: eighthBattleScenario.id,
|
||||
campaignStep: 'eighth-battle',
|
||||
pages: [...seventhBattleVictoryPages, ...eighthBattleIntroPages]
|
||||
pages: eighthBattleIntroPages
|
||||
},
|
||||
[eighthBattleScenario.id]: {
|
||||
afterBattleId: eighthBattleScenario.id,
|
||||
@@ -296,7 +233,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${ninthBattleScenario.title} 개방`,
|
||||
nextBattleId: ninthBattleScenario.id,
|
||||
campaignStep: 'ninth-battle',
|
||||
pages: [...eighthBattleVictoryPages, ...ninthBattleIntroPages]
|
||||
pages: ninthBattleIntroPages
|
||||
},
|
||||
[ninthBattleScenario.id]: {
|
||||
afterBattleId: ninthBattleScenario.id,
|
||||
@@ -306,7 +243,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${tenthBattleScenario.title} 개방`,
|
||||
nextBattleId: tenthBattleScenario.id,
|
||||
campaignStep: 'tenth-battle',
|
||||
pages: [...ninthBattleVictoryPages, ...tenthBattleIntroPages]
|
||||
pages: tenthBattleIntroPages
|
||||
},
|
||||
[tenthBattleScenario.id]: {
|
||||
afterBattleId: tenthBattleScenario.id,
|
||||
@@ -316,7 +253,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${eleventhBattleScenario.title} 개방`,
|
||||
nextBattleId: eleventhBattleScenario.id,
|
||||
campaignStep: 'eleventh-battle',
|
||||
pages: [...tenthBattleVictoryPages, ...eleventhBattleIntroPages]
|
||||
pages: eleventhBattleIntroPages
|
||||
},
|
||||
[eleventhBattleScenario.id]: {
|
||||
afterBattleId: eleventhBattleScenario.id,
|
||||
@@ -327,7 +264,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twelfthBattleScenario.title} 개방`,
|
||||
nextBattleId: twelfthBattleScenario.id,
|
||||
campaignStep: 'twelfth-battle',
|
||||
pages: [...eleventhBattleVictoryPages, ...twelfthBattleIntroPages]
|
||||
pages: twelfthBattleIntroPages
|
||||
},
|
||||
[twelfthBattleScenario.id]: {
|
||||
afterBattleId: twelfthBattleScenario.id,
|
||||
@@ -338,7 +275,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirteenthBattleScenario.title} 개방`,
|
||||
nextBattleId: thirteenthBattleScenario.id,
|
||||
campaignStep: 'thirteenth-battle',
|
||||
pages: [...twelfthBattleVictoryPages, ...thirteenthBattleIntroPages]
|
||||
pages: thirteenthBattleIntroPages
|
||||
},
|
||||
[thirteenthBattleScenario.id]: {
|
||||
afterBattleId: thirteenthBattleScenario.id,
|
||||
@@ -349,7 +286,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fourteenthBattleScenario.title} 개방 / 손건 합류`,
|
||||
nextBattleId: fourteenthBattleScenario.id,
|
||||
campaignStep: 'fourteenth-battle',
|
||||
pages: [...thirteenthBattleVictoryPages, ...fourteenthBattleIntroPages]
|
||||
pages: fourteenthBattleIntroPages
|
||||
},
|
||||
[fourteenthBattleScenario.id]: {
|
||||
afterBattleId: fourteenthBattleScenario.id,
|
||||
@@ -360,7 +297,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fifteenthBattleScenario.title} 개방 / 원소 접선 명분`,
|
||||
nextBattleId: fifteenthBattleScenario.id,
|
||||
campaignStep: 'fifteenth-battle',
|
||||
pages: [...fourteenthBattleVictoryPages, ...fifteenthBattleIntroPages]
|
||||
pages: fifteenthBattleIntroPages
|
||||
},
|
||||
[fifteenthBattleScenario.id]: {
|
||||
afterBattleId: fifteenthBattleScenario.id,
|
||||
@@ -371,7 +308,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixteenthBattleScenario.title} 개방 / 조운 합류`,
|
||||
nextBattleId: sixteenthBattleScenario.id,
|
||||
campaignStep: 'sixteenth-battle',
|
||||
pages: [...fifteenthBattleVictoryPages, ...sixteenthBattleIntroPages]
|
||||
pages: sixteenthBattleIntroPages
|
||||
},
|
||||
[sixteenthBattleScenario.id]: {
|
||||
afterBattleId: sixteenthBattleScenario.id,
|
||||
@@ -382,7 +319,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${seventeenthBattleScenario.title} 개방 / 와룡 방문 예우`,
|
||||
nextBattleId: seventeenthBattleScenario.id,
|
||||
campaignStep: 'seventeenth-battle',
|
||||
pages: [...sixteenthBattleVictoryPages, ...seventeenthBattleIntroPages]
|
||||
pages: seventeenthBattleIntroPages
|
||||
},
|
||||
[seventeenthBattleScenario.id]: {
|
||||
afterBattleId: seventeenthBattleScenario.id,
|
||||
@@ -393,7 +330,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${eighteenthBattleScenario.title} 개방 / 제갈량 첫 계책`,
|
||||
nextBattleId: eighteenthBattleScenario.id,
|
||||
campaignStep: 'eighteenth-battle',
|
||||
pages: [...seventeenthBattleVictoryPages, ...eighteenthBattleIntroPages]
|
||||
pages: eighteenthBattleIntroPages
|
||||
},
|
||||
[eighteenthBattleScenario.id]: {
|
||||
afterBattleId: eighteenthBattleScenario.id,
|
||||
@@ -404,7 +341,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${nineteenthBattleScenario.title} 개방 / 박망파 지연 성공`,
|
||||
nextBattleId: nineteenthBattleScenario.id,
|
||||
campaignStep: 'nineteenth-battle',
|
||||
pages: [...eighteenthBattleVictoryPages, ...nineteenthBattleIntroPages]
|
||||
pages: nineteenthBattleIntroPages
|
||||
},
|
||||
[nineteenthBattleScenario.id]: {
|
||||
afterBattleId: nineteenthBattleScenario.id,
|
||||
@@ -415,7 +352,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentiethBattleScenario.title} 개방 / 강동 사절 문서`,
|
||||
nextBattleId: twentiethBattleScenario.id,
|
||||
campaignStep: 'twentieth-battle',
|
||||
pages: [...nineteenthBattleVictoryPages, ...twentiethBattleIntroPages]
|
||||
pages: twentiethBattleIntroPages
|
||||
},
|
||||
[twentiethBattleScenario.id]: {
|
||||
afterBattleId: twentiethBattleScenario.id,
|
||||
@@ -426,7 +363,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentyFirstBattleScenario.title} 개방 / 화공 논의 진전`,
|
||||
nextBattleId: twentyFirstBattleScenario.id,
|
||||
campaignStep: 'twenty-first-battle',
|
||||
pages: [...twentiethBattleVictoryPages, ...twentyFirstBattleIntroPages]
|
||||
pages: twentyFirstBattleIntroPages
|
||||
},
|
||||
[twentyFirstBattleScenario.id]: {
|
||||
afterBattleId: twentyFirstBattleScenario.id,
|
||||
@@ -437,7 +374,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentySecondBattleScenario.title} 개방 / 화공 논의 본격화`,
|
||||
nextBattleId: twentySecondBattleScenario.id,
|
||||
campaignStep: 'twenty-second-battle',
|
||||
pages: [...twentyFirstBattleVictoryPages, ...twentySecondBattleIntroPages]
|
||||
pages: twentySecondBattleIntroPages
|
||||
},
|
||||
[twentySecondBattleScenario.id]: {
|
||||
afterBattleId: twentySecondBattleScenario.id,
|
||||
@@ -448,7 +385,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentyThirdBattleScenario.title} 개방 / 마량 합류 / 적벽 명성`,
|
||||
nextBattleId: twentyThirdBattleScenario.id,
|
||||
campaignStep: 'twenty-third-battle',
|
||||
pages: [...twentySecondBattleVictoryPages, ...twentyThirdBattleIntroPages]
|
||||
pages: twentyThirdBattleIntroPages
|
||||
},
|
||||
[twentyThirdBattleScenario.id]: {
|
||||
afterBattleId: twentyThirdBattleScenario.id,
|
||||
@@ -459,7 +396,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentyFourthBattleScenario.title} 개방 / 이적 합류 / 계양 설득 명분`,
|
||||
nextBattleId: twentyFourthBattleScenario.id,
|
||||
campaignStep: 'twenty-fourth-battle',
|
||||
pages: [...twentyThirdBattleVictoryPages, ...twentyFourthBattleIntroPages]
|
||||
pages: twentyFourthBattleIntroPages
|
||||
},
|
||||
[twentyFourthBattleScenario.id]: {
|
||||
afterBattleId: twentyFourthBattleScenario.id,
|
||||
@@ -470,7 +407,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentyFifthBattleScenario.title} 개방 / 공지 합류 / 무릉 산길 정보`,
|
||||
nextBattleId: twentyFifthBattleScenario.id,
|
||||
campaignStep: 'twenty-fifth-battle',
|
||||
pages: [...twentyFourthBattleVictoryPages, ...twentyFifthBattleIntroPages]
|
||||
pages: twentyFifthBattleIntroPages
|
||||
},
|
||||
[twentyFifthBattleScenario.id]: {
|
||||
afterBattleId: twentyFifthBattleScenario.id,
|
||||
@@ -481,7 +418,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentySixthBattleScenario.title} 개방 / 공지의 무릉 길 정보 / 황충·위연 합류`,
|
||||
nextBattleId: twentySixthBattleScenario.id,
|
||||
campaignStep: 'twenty-sixth-battle',
|
||||
pages: [...twentyFifthBattleVictoryPages, ...twentySixthBattleIntroPages]
|
||||
pages: twentySixthBattleIntroPages
|
||||
},
|
||||
[twentySixthBattleScenario.id]: {
|
||||
afterBattleId: twentySixthBattleScenario.id,
|
||||
@@ -492,7 +429,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentySeventhBattleScenario.title} 개방 / 황충·위연 전력화 / 방통 합류`,
|
||||
nextBattleId: twentySeventhBattleScenario.id,
|
||||
campaignStep: 'twenty-seventh-battle',
|
||||
pages: [...twentySixthBattleVictoryPages, ...twentySeventhBattleIntroPages]
|
||||
pages: twentySeventhBattleIntroPages
|
||||
},
|
||||
[twentySeventhBattleScenario.id]: {
|
||||
afterBattleId: twentySeventhBattleScenario.id,
|
||||
@@ -503,7 +440,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentyEighthBattleScenario.title} 개방 / 방통 전력화 / 법정 합류`,
|
||||
nextBattleId: twentyEighthBattleScenario.id,
|
||||
campaignStep: 'twenty-eighth-battle',
|
||||
pages: [...twentySeventhBattleVictoryPages, ...twentyEighthBattleIntroPages]
|
||||
pages: twentyEighthBattleIntroPages
|
||||
},
|
||||
[twentyEighthBattleScenario.id]: {
|
||||
afterBattleId: twentyEighthBattleScenario.id,
|
||||
@@ -514,7 +451,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${twentyNinthBattleScenario.title} 개방 / 오의 합류`,
|
||||
nextBattleId: twentyNinthBattleScenario.id,
|
||||
campaignStep: 'twenty-ninth-battle',
|
||||
pages: [...twentyEighthBattleVictoryPages, ...twentyNinthBattleIntroPages]
|
||||
pages: twentyNinthBattleIntroPages
|
||||
},
|
||||
[twentyNinthBattleScenario.id]: {
|
||||
afterBattleId: twentyNinthBattleScenario.id,
|
||||
@@ -525,7 +462,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtiethBattleScenario.title} 개방 / 봉추 생환`,
|
||||
nextBattleId: thirtiethBattleScenario.id,
|
||||
campaignStep: 'thirtieth-battle',
|
||||
pages: [...twentyNinthBattleVictoryPages, ...thirtiethBattleIntroPages]
|
||||
pages: thirtiethBattleIntroPages
|
||||
},
|
||||
[thirtiethBattleScenario.id]: {
|
||||
afterBattleId: thirtiethBattleScenario.id,
|
||||
@@ -536,7 +473,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtyFirstBattleScenario.title} 개방 / 엄안 합류`,
|
||||
nextBattleId: thirtyFirstBattleScenario.id,
|
||||
campaignStep: 'thirty-first-battle',
|
||||
pages: [...thirtiethBattleVictoryPages, ...thirtyFirstBattleIntroPages]
|
||||
pages: thirtyFirstBattleIntroPages
|
||||
},
|
||||
[thirtyFirstBattleScenario.id]: {
|
||||
afterBattleId: thirtyFirstBattleScenario.id,
|
||||
@@ -547,7 +484,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtySecondBattleScenario.title} 개방 / 이엄 합류`,
|
||||
nextBattleId: thirtySecondBattleScenario.id,
|
||||
campaignStep: 'thirty-second-battle',
|
||||
pages: [...thirtyFirstBattleVictoryPages, ...thirtySecondBattleIntroPages]
|
||||
pages: thirtySecondBattleIntroPages
|
||||
},
|
||||
[thirtySecondBattleScenario.id]: {
|
||||
afterBattleId: thirtySecondBattleScenario.id,
|
||||
@@ -558,7 +495,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtyThirdBattleScenario.title} 개방 / 황권 합류`,
|
||||
nextBattleId: thirtyThirdBattleScenario.id,
|
||||
campaignStep: 'thirty-third-battle',
|
||||
pages: [...thirtySecondBattleVictoryPages, ...thirtyThirdBattleIntroPages]
|
||||
pages: thirtyThirdBattleIntroPages
|
||||
},
|
||||
[thirtyThirdBattleScenario.id]: {
|
||||
afterBattleId: thirtyThirdBattleScenario.id,
|
||||
@@ -569,7 +506,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtyFourthBattleScenario.title} 개방 / 마초 합류`,
|
||||
nextBattleId: thirtyFourthBattleScenario.id,
|
||||
campaignStep: 'thirty-fourth-battle',
|
||||
pages: [...thirtyThirdBattleVictoryPages, ...thirtyFourthBattleIntroPages]
|
||||
pages: thirtyFourthBattleIntroPages
|
||||
},
|
||||
[thirtyFourthBattleScenario.id]: {
|
||||
afterBattleId: thirtyFourthBattleScenario.id,
|
||||
@@ -580,7 +517,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtyFifthBattleScenario.title} 개방 / 마대 합류`,
|
||||
nextBattleId: thirtyFifthBattleScenario.id,
|
||||
campaignStep: 'thirty-fifth-battle',
|
||||
pages: [...thirtyFourthBattleVictoryPages, ...thirtyFifthBattleIntroPages]
|
||||
pages: thirtyFifthBattleIntroPages
|
||||
},
|
||||
[thirtyFifthBattleScenario.id]: {
|
||||
afterBattleId: thirtyFifthBattleScenario.id,
|
||||
@@ -591,7 +528,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtySixthBattleScenario.title} 개방 / 왕평 합류`,
|
||||
nextBattleId: thirtySixthBattleScenario.id,
|
||||
campaignStep: 'thirty-sixth-battle',
|
||||
pages: [...thirtyFifthBattleVictoryPages, ...thirtySixthBattleIntroPages]
|
||||
pages: thirtySixthBattleIntroPages
|
||||
},
|
||||
[thirtySixthBattleScenario.id]: {
|
||||
afterBattleId: thirtySixthBattleScenario.id,
|
||||
@@ -602,7 +539,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtySeventhBattleScenario.title} 개방 / 한중왕 즉위 준비`,
|
||||
nextBattleId: thirtySeventhBattleScenario.id,
|
||||
campaignStep: 'thirty-seventh-battle',
|
||||
pages: [...thirtySixthBattleVictoryPages, ...thirtySeventhBattleIntroPages]
|
||||
pages: thirtySeventhBattleIntroPages
|
||||
},
|
||||
[thirtySeventhBattleScenario.id]: {
|
||||
afterBattleId: thirtySeventhBattleScenario.id,
|
||||
@@ -646,7 +583,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${thirtyNinthBattleScenario.title} 개방 / 번성 외곽도`,
|
||||
nextBattleId: thirtyNinthBattleScenario.id,
|
||||
campaignStep: 'thirty-ninth-battle',
|
||||
pages: [...thirtyEighthBattleVictoryPages, ...thirtyNinthBattleIntroPages]
|
||||
pages: thirtyNinthBattleIntroPages
|
||||
},
|
||||
[thirtyNinthBattleScenario.id]: {
|
||||
afterBattleId: thirtyNinthBattleScenario.id,
|
||||
@@ -657,7 +594,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortiethBattleScenario.title} 개방 / 칠군 항복문`,
|
||||
nextBattleId: fortiethBattleScenario.id,
|
||||
campaignStep: 'fortieth-battle',
|
||||
pages: [...thirtyNinthBattleVictoryPages, ...fortiethBattleIntroPages]
|
||||
pages: fortiethBattleIntroPages
|
||||
},
|
||||
[fortiethBattleScenario.id]: {
|
||||
afterBattleId: fortiethBattleScenario.id,
|
||||
@@ -668,7 +605,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortyFirstBattleScenario.title} 개방 / 번성 성문도`,
|
||||
nextBattleId: fortyFirstBattleScenario.id,
|
||||
campaignStep: 'forty-first-battle',
|
||||
pages: [...fortiethBattleVictoryPages, ...fortyFirstBattleIntroPages]
|
||||
pages: fortyFirstBattleIntroPages
|
||||
},
|
||||
[fortyFirstBattleScenario.id]: {
|
||||
afterBattleId: fortyFirstBattleScenario.id,
|
||||
@@ -679,7 +616,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortySecondBattleScenario.title} 개방 / 강릉 창고 장부`,
|
||||
nextBattleId: fortySecondBattleScenario.id,
|
||||
campaignStep: 'forty-second-battle',
|
||||
pages: [...fortyFirstBattleVictoryPages, ...fortySecondBattleIntroPages]
|
||||
pages: fortySecondBattleIntroPages
|
||||
},
|
||||
[fortySecondBattleScenario.id]: {
|
||||
afterBattleId: fortySecondBattleScenario.id,
|
||||
@@ -690,7 +627,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortyThirdBattleScenario.title} 개방 / 공안 성문 기록`,
|
||||
nextBattleId: fortyThirdBattleScenario.id,
|
||||
campaignStep: 'forty-third-battle',
|
||||
pages: [...fortySecondBattleVictoryPages, ...fortyThirdBattleIntroPages]
|
||||
pages: fortyThirdBattleIntroPages
|
||||
},
|
||||
[fortyThirdBattleScenario.id]: {
|
||||
afterBattleId: fortyThirdBattleScenario.id,
|
||||
@@ -701,7 +638,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortyFourthBattleScenario.title} 개방 / 맥성 북문 지도`,
|
||||
nextBattleId: fortyFourthBattleScenario.id,
|
||||
campaignStep: 'forty-fourth-battle',
|
||||
pages: [...fortyThirdBattleVictoryPages, ...fortyFourthBattleIntroPages]
|
||||
pages: fortyFourthBattleIntroPages
|
||||
},
|
||||
[fortyFourthBattleScenario.id]: {
|
||||
afterBattleId: fortyFourthBattleScenario.id,
|
||||
@@ -712,7 +649,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortyFifthBattleScenario.title} 개방 / 이릉 동진 지도`,
|
||||
nextBattleId: fortyFifthBattleScenario.id,
|
||||
campaignStep: 'forty-fifth-battle',
|
||||
pages: [...fortyFourthBattleVictoryPages, ...fortyFifthBattleIntroPages]
|
||||
pages: fortyFifthBattleIntroPages
|
||||
},
|
||||
[fortyFifthBattleScenario.id]: {
|
||||
afterBattleId: fortyFifthBattleScenario.id,
|
||||
@@ -723,7 +660,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortySixthBattleScenario.title} 개방 / 화공 방화선 표식`,
|
||||
nextBattleId: fortySixthBattleScenario.id,
|
||||
campaignStep: 'forty-sixth-battle',
|
||||
pages: [...fortyFifthBattleVictoryPages, ...fortySixthBattleIntroPages]
|
||||
pages: fortySixthBattleIntroPages
|
||||
},
|
||||
[fortySixthBattleScenario.id]: {
|
||||
afterBattleId: fortySixthBattleScenario.id,
|
||||
@@ -756,7 +693,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortyEighthBattleScenario.title} 개방 / 맹획 회유 서신`,
|
||||
nextBattleId: fortyEighthBattleScenario.id,
|
||||
campaignStep: 'forty-eighth-battle',
|
||||
pages: [...fortySeventhBattleVictoryPages, ...fortyEighthBattleIntroPages]
|
||||
pages: fortyEighthBattleIntroPages
|
||||
},
|
||||
[fortyEighthBattleScenario.id]: {
|
||||
afterBattleId: fortyEighthBattleScenario.id,
|
||||
@@ -767,7 +704,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fortyNinthBattleScenario.title} 개방 / 칠종칠금 회유문`,
|
||||
nextBattleId: fortyNinthBattleScenario.id,
|
||||
campaignStep: 'forty-ninth-battle',
|
||||
pages: [...fortyEighthBattleVictoryPages, ...fortyNinthBattleIntroPages]
|
||||
pages: fortyNinthBattleIntroPages
|
||||
},
|
||||
[fortyNinthBattleScenario.id]: {
|
||||
afterBattleId: fortyNinthBattleScenario.id,
|
||||
@@ -778,7 +715,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftiethBattleScenario.title} 개방 / 남중 호족 설득문`,
|
||||
nextBattleId: fiftiethBattleScenario.id,
|
||||
campaignStep: 'fiftieth-battle',
|
||||
pages: [...fortyNinthBattleVictoryPages, ...fiftiethBattleIntroPages]
|
||||
pages: fiftiethBattleIntroPages
|
||||
},
|
||||
[fiftiethBattleScenario.id]: {
|
||||
afterBattleId: fiftiethBattleScenario.id,
|
||||
@@ -789,7 +726,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftyFirstBattleScenario.title} 개방 / 호족 보증 장부`,
|
||||
nextBattleId: fiftyFirstBattleScenario.id,
|
||||
campaignStep: 'fifty-first-battle',
|
||||
pages: [...fiftiethBattleVictoryPages, ...fiftyFirstBattleIntroPages]
|
||||
pages: fiftyFirstBattleIntroPages
|
||||
},
|
||||
[fiftyFirstBattleScenario.id]: {
|
||||
afterBattleId: fiftyFirstBattleScenario.id,
|
||||
@@ -800,7 +737,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftySecondBattleScenario.title} 개방 / 강경파 분리 장부`,
|
||||
nextBattleId: fiftySecondBattleScenario.id,
|
||||
campaignStep: 'fifty-second-battle',
|
||||
pages: [...fiftyFirstBattleVictoryPages, ...fiftySecondBattleIntroPages]
|
||||
pages: fiftySecondBattleIntroPages
|
||||
},
|
||||
[fiftySecondBattleScenario.id]: {
|
||||
afterBattleId: fiftySecondBattleScenario.id,
|
||||
@@ -811,7 +748,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftyThirdBattleScenario.title} 개방 / 남중 신뢰 장부`,
|
||||
nextBattleId: fiftyThirdBattleScenario.id,
|
||||
campaignStep: 'fifty-third-battle',
|
||||
pages: [...fiftySecondBattleVictoryPages, ...fiftyThirdBattleIntroPages]
|
||||
pages: fiftyThirdBattleIntroPages
|
||||
},
|
||||
[fiftyThirdBattleScenario.id]: {
|
||||
afterBattleId: fiftyThirdBattleScenario.id,
|
||||
@@ -822,7 +759,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftyFourthBattleScenario.title} 개방 / 남중 평정 장부`,
|
||||
nextBattleId: fiftyFourthBattleScenario.id,
|
||||
campaignStep: 'fifty-fourth-battle',
|
||||
pages: [...fiftyThirdBattleVictoryPages, ...fiftyFourthBattleIntroPages]
|
||||
pages: fiftyFourthBattleIntroPages
|
||||
},
|
||||
[fiftyFourthBattleScenario.id]: {
|
||||
afterBattleId: fiftyFourthBattleScenario.id,
|
||||
@@ -832,7 +769,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
'맹획은 일곱 번의 생포 끝에 항복했고, 남중은 촉한의 약속을 받아들였습니다. 출진 준비를 통해 새 전투가 아니라 북벌 준비 회의를 진행하고, 한중 창고와 출전 무장 재편을 군영 안에서 정리합니다.',
|
||||
rewardHint: '군영 목표: 북벌 준비 회의 / 한중 창고와 출전 무장 재편',
|
||||
campaignStep: 'northern-campaign-prep-camp',
|
||||
pages: [...fiftyFourthBattleVictoryPages, ...northernCampaignPrepPages],
|
||||
pages: northernCampaignPrepPages,
|
||||
unavailableNotice: '북벌 준비 회의가 완료되었습니다. 제1차 북벌 출진 편성을 다시 점검하세요.'
|
||||
},
|
||||
'northern-campaign-prep-camp': {
|
||||
@@ -855,7 +792,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftySixthBattleScenario.title} 개방 / 천수 진군 장부`,
|
||||
nextBattleId: fiftySixthBattleScenario.id,
|
||||
campaignStep: 'fifty-sixth-battle',
|
||||
pages: [...fiftyFifthBattleVictoryPages, ...fiftySixthBattleIntroPages]
|
||||
pages: fiftySixthBattleIntroPages
|
||||
},
|
||||
[fiftySixthBattleScenario.id]: {
|
||||
afterBattleId: fiftySixthBattleScenario.id,
|
||||
@@ -866,7 +803,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftySeventhBattleScenario.title} 개방 / 강유 합류 단서`,
|
||||
nextBattleId: fiftySeventhBattleScenario.id,
|
||||
campaignStep: 'fifty-seventh-battle',
|
||||
pages: [...fiftySixthBattleVictoryPages, ...fiftySeventhBattleIntroPages]
|
||||
pages: fiftySeventhBattleIntroPages
|
||||
},
|
||||
[fiftySeventhBattleScenario.id]: {
|
||||
afterBattleId: fiftySeventhBattleScenario.id,
|
||||
@@ -877,7 +814,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftyEighthBattleScenario.title} 개방 / 강유 첫 출전 기록`,
|
||||
nextBattleId: fiftyEighthBattleScenario.id,
|
||||
campaignStep: 'fifty-eighth-battle',
|
||||
pages: [...fiftySeventhBattleVictoryPages, ...fiftyEighthBattleIntroPages]
|
||||
pages: fiftyEighthBattleIntroPages
|
||||
},
|
||||
[fiftyEighthBattleScenario.id]: {
|
||||
afterBattleId: fiftyEighthBattleScenario.id,
|
||||
@@ -888,7 +825,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${fiftyNinthBattleScenario.title} 개방 / 진창 공성 장부`,
|
||||
nextBattleId: fiftyNinthBattleScenario.id,
|
||||
campaignStep: 'fifty-ninth-battle',
|
||||
pages: [...fiftyEighthBattleVictoryPages, ...fiftyNinthBattleIntroPages]
|
||||
pages: fiftyNinthBattleIntroPages
|
||||
},
|
||||
[fiftyNinthBattleScenario.id]: {
|
||||
afterBattleId: fiftyNinthBattleScenario.id,
|
||||
@@ -899,7 +836,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtiethBattleScenario.title} 개방 / 무도·음평 장부`,
|
||||
nextBattleId: sixtiethBattleScenario.id,
|
||||
campaignStep: 'sixtieth-battle',
|
||||
pages: [...fiftyNinthBattleVictoryPages, ...sixtiethBattleIntroPages]
|
||||
pages: sixtiethBattleIntroPages
|
||||
},
|
||||
[sixtiethBattleScenario.id]: {
|
||||
afterBattleId: sixtiethBattleScenario.id,
|
||||
@@ -910,7 +847,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtyFirstBattleScenario.title} 개방 / 한중 우로 장부`,
|
||||
nextBattleId: sixtyFirstBattleScenario.id,
|
||||
campaignStep: 'sixty-first-battle',
|
||||
pages: [...sixtiethBattleVictoryPages, ...sixtyFirstBattleIntroPages]
|
||||
pages: sixtyFirstBattleIntroPages
|
||||
},
|
||||
[sixtyFirstBattleScenario.id]: {
|
||||
afterBattleId: sixtyFirstBattleScenario.id,
|
||||
@@ -921,7 +858,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtySecondBattleScenario.title} 개방 / 기산 재출정 장부`,
|
||||
nextBattleId: sixtySecondBattleScenario.id,
|
||||
campaignStep: 'sixty-second-battle',
|
||||
pages: [...sixtyFirstBattleVictoryPages, ...sixtySecondBattleIntroPages]
|
||||
pages: sixtySecondBattleIntroPages
|
||||
},
|
||||
[sixtySecondBattleScenario.id]: {
|
||||
afterBattleId: sixtySecondBattleScenario.id,
|
||||
@@ -932,7 +869,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtyThirdBattleScenario.title} 개방 / 노성 추격 장부`,
|
||||
nextBattleId: sixtyThirdBattleScenario.id,
|
||||
campaignStep: 'sixty-third-battle',
|
||||
pages: [...sixtySecondBattleVictoryPages, ...sixtyThirdBattleIntroPages]
|
||||
pages: sixtyThirdBattleIntroPages
|
||||
},
|
||||
[sixtyThirdBattleScenario.id]: {
|
||||
afterBattleId: sixtyThirdBattleScenario.id,
|
||||
@@ -943,7 +880,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtyFourthBattleScenario.title} 개방 / 위수 진영 장부`,
|
||||
nextBattleId: sixtyFourthBattleScenario.id,
|
||||
campaignStep: 'sixty-fourth-battle',
|
||||
pages: [...sixtyThirdBattleVictoryPages, ...sixtyFourthBattleIntroPages]
|
||||
pages: sixtyFourthBattleIntroPages
|
||||
},
|
||||
[sixtyFourthBattleScenario.id]: {
|
||||
afterBattleId: sixtyFourthBattleScenario.id,
|
||||
@@ -954,7 +891,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtyFifthBattleScenario.title} 개방 / 북안 전진 장부`,
|
||||
nextBattleId: sixtyFifthBattleScenario.id,
|
||||
campaignStep: 'sixty-fifth-battle',
|
||||
pages: [...sixtyFourthBattleVictoryPages, ...sixtyFifthBattleIntroPages]
|
||||
pages: sixtyFifthBattleIntroPages
|
||||
},
|
||||
'sixty-fifth-camp': {
|
||||
afterBattleId: sixtyFifthBattleScenario.id,
|
||||
@@ -965,7 +902,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtySixthBattleScenario.title} 개방 / 오장원 귀환 장부`,
|
||||
nextBattleId: sixtySixthBattleScenario.id,
|
||||
campaignStep: 'sixty-sixth-battle',
|
||||
pages: [...sixtyFifthBattleVictoryPages, ...sixtySixthBattleIntroPages]
|
||||
pages: sixtySixthBattleIntroPages
|
||||
},
|
||||
[sixtyFifthBattleScenario.id]: {
|
||||
afterBattleId: sixtyFifthBattleScenario.id,
|
||||
@@ -976,7 +913,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
rewardHint: `예상 보상: ${sixtySixthBattleScenario.title} 개방 / 오장원 귀환 장부`,
|
||||
nextBattleId: sixtySixthBattleScenario.id,
|
||||
campaignStep: 'sixty-sixth-battle',
|
||||
pages: [...sixtyFifthBattleVictoryPages, ...sixtySixthBattleIntroPages]
|
||||
pages: sixtySixthBattleIntroPages
|
||||
},
|
||||
[sixtySixthBattleScenario.id]: {
|
||||
afterBattleId: sixtySixthBattleScenario.id,
|
||||
@@ -986,7 +923,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
'오장원 본영을 지켜 낸 뒤 촉한군은 북벌의 마지막 장부를 닫습니다. 강유는 이어 받은 지도를 품고, 남은 장수들은 병사들이 돌아갈 길을 정리합니다.',
|
||||
rewardHint: '최종 보상: 오장원 에필로그 / 엔딩 개방',
|
||||
campaignStep: 'ending-complete',
|
||||
pages: [...sixtySixthBattleVictoryPages, ...endingEpiloguePages],
|
||||
pages: endingEpiloguePages,
|
||||
unavailableNotice: '최종 에필로그와 엔딩을 이미 확인했습니다. 타이틀에서 이어하기를 선택하면 엔딩 화면으로 돌아갑니다.'
|
||||
},
|
||||
'northern-campaign-prep-camp-legacy': {
|
||||
|
||||
490
src/game/data/campaignPresentationProfiles.ts
Normal file
490
src/game/data/campaignPresentationProfiles.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
import type { AmbienceTrackKey, MusicTrackKey } from '../audio/audioAssets';
|
||||
import { battleEnvironmentProfileFor } from './battleEnvironmentProfiles';
|
||||
import { battleScenarios, type BattleScenarioId } from './battles';
|
||||
import {
|
||||
getCampSoundscape,
|
||||
type CampMusicGroupId
|
||||
} from './campSoundscapes';
|
||||
import { campSkinDefinitions, type CampSkinId } from './campSkins';
|
||||
|
||||
export type CampaignPresentationArcId = CampSkinId;
|
||||
|
||||
export type CampaignPresentationStage =
|
||||
| 'camp'
|
||||
| 'story'
|
||||
| 'sortie'
|
||||
| 'battle'
|
||||
| 'aftermath';
|
||||
|
||||
export type CampaignPresentationIntensity = 'quiet' | 'measured' | 'urgent' | 'climactic';
|
||||
|
||||
export type CampaignPresentationMusicRole = 'anchor' | 'tension' | 'battle' | 'resolution';
|
||||
|
||||
export type CampaignPresentationAmbienceMode =
|
||||
| 'arc-default'
|
||||
| 'battle-environment'
|
||||
| 'scene-cue';
|
||||
|
||||
export type CampaignPresentationStatePolicyId =
|
||||
| 'founding-rally'
|
||||
| 'coalition-fire'
|
||||
| 'fractured-road'
|
||||
| 'strategic-ascent'
|
||||
| 'tragic-collapse'
|
||||
| 'frontier-pacification'
|
||||
| 'legacy-expedition';
|
||||
|
||||
export type CampaignPresentationStateRule = {
|
||||
intensity: CampaignPresentationIntensity;
|
||||
musicRole: CampaignPresentationMusicRole;
|
||||
ambienceMode: CampaignPresentationAmbienceMode;
|
||||
preserveSceneMusic: boolean;
|
||||
preserveSceneAmbience: boolean;
|
||||
};
|
||||
|
||||
export type CampaignPresentationStatePolicy = Readonly<
|
||||
Record<CampaignPresentationStage, CampaignPresentationStateRule>
|
||||
>;
|
||||
|
||||
export type CampaignPresentationPalette = {
|
||||
accentColor: number;
|
||||
headerColor: number;
|
||||
washColor: number;
|
||||
battleTintColor: number;
|
||||
battleTintAlpha: number;
|
||||
};
|
||||
|
||||
export type CampaignPresentationAudioFamily = {
|
||||
familyId: CampMusicGroupId;
|
||||
anchorMusicKey: MusicTrackKey;
|
||||
tensionMusicKey: MusicTrackKey;
|
||||
battleMusicKey: MusicTrackKey;
|
||||
resolutionMusicKey: MusicTrackKey;
|
||||
defaultAmbienceKey?: AmbienceTrackKey;
|
||||
anchorMusicVolume: number;
|
||||
defaultAmbienceVolume?: number;
|
||||
};
|
||||
|
||||
export type CampaignArcPresentationProfile = {
|
||||
id: CampaignPresentationArcId;
|
||||
label: string;
|
||||
firstBattleOrdinal: number;
|
||||
lastBattleOrdinal: number;
|
||||
skinId: CampSkinId;
|
||||
statePolicyId: CampaignPresentationStatePolicyId;
|
||||
audio: CampaignPresentationAudioFamily;
|
||||
palette: CampaignPresentationPalette;
|
||||
motifs: readonly string[];
|
||||
};
|
||||
|
||||
export type CampaignBattlePresentationProfile = {
|
||||
battleId: BattleScenarioId;
|
||||
battleOrdinal: number;
|
||||
arc: CampaignArcPresentationProfile;
|
||||
};
|
||||
|
||||
export type CampaignPresentationSoundscapeInput = {
|
||||
battleId: BattleScenarioId | string;
|
||||
stage: CampaignPresentationStage;
|
||||
sceneMusicKey?: MusicTrackKey;
|
||||
sceneAmbienceKey?: AmbienceTrackKey;
|
||||
sceneAmbienceVolume?: number;
|
||||
};
|
||||
|
||||
export type CampaignPresentationSoundscape = {
|
||||
battleId: BattleScenarioId;
|
||||
arcId: CampaignPresentationArcId;
|
||||
stage: CampaignPresentationStage;
|
||||
intensity: CampaignPresentationIntensity;
|
||||
musicKey: MusicTrackKey;
|
||||
musicVolume: number;
|
||||
ambienceKey?: AmbienceTrackKey;
|
||||
ambienceVolume?: number;
|
||||
transition: typeof campaignPresentationTransitionPolicy;
|
||||
};
|
||||
|
||||
/**
|
||||
* This mirrors the fixed loop-channel behavior in SoundDirector. Presentation
|
||||
* consumers should submit one soundscape request and let SoundDirector keep a
|
||||
* same-key loop or perform its existing crossfade.
|
||||
*/
|
||||
export const campaignPresentationTransitionPolicy = Object.freeze({
|
||||
sameTrackBehavior: 'keep-current-loop' as const,
|
||||
musicCrossfadeMs: 900,
|
||||
ambienceCrossfadeMs: 1200,
|
||||
battleAmbienceBehavior: 'specific-profile-only' as const
|
||||
});
|
||||
|
||||
export const campaignPresentationStatePolicies: Readonly<
|
||||
Record<CampaignPresentationStatePolicyId, CampaignPresentationStatePolicy>
|
||||
> = {
|
||||
'founding-rally': statePolicy('quiet', 'measured', 'urgent', 'urgent', 'measured'),
|
||||
'coalition-fire': statePolicy('measured', 'urgent', 'urgent', 'climactic', 'measured'),
|
||||
'fractured-road': statePolicy('quiet', 'urgent', 'urgent', 'climactic', 'quiet'),
|
||||
'strategic-ascent': statePolicy('quiet', 'measured', 'urgent', 'climactic', 'measured'),
|
||||
'tragic-collapse': statePolicy('quiet', 'urgent', 'urgent', 'climactic', 'quiet'),
|
||||
'frontier-pacification': statePolicy('measured', 'measured', 'urgent', 'urgent', 'measured'),
|
||||
'legacy-expedition': statePolicy('quiet', 'measured', 'urgent', 'climactic', 'quiet')
|
||||
};
|
||||
|
||||
type ArcProfileSpec = Omit<
|
||||
CampaignArcPresentationProfile,
|
||||
'id' | 'skinId' | 'audio' | 'palette'
|
||||
> & {
|
||||
tensionMusicKey: MusicTrackKey;
|
||||
battleMusicKey: MusicTrackKey;
|
||||
resolutionMusicKey: MusicTrackKey;
|
||||
battleTintColor: number;
|
||||
battleTintAlpha: number;
|
||||
};
|
||||
|
||||
export const campaignArcPresentationProfiles: Readonly<
|
||||
Record<CampaignPresentationArcId, CampaignArcPresentationProfile>
|
||||
> = {
|
||||
'yellow-turban': arcProfile('yellow-turban', {
|
||||
label: '황건적 토벌과 의병의 시작',
|
||||
firstBattleOrdinal: 1,
|
||||
lastBattleOrdinal: 4,
|
||||
statePolicyId: 'founding-rally',
|
||||
tensionMusicKey: 'militia-theme',
|
||||
battleMusicKey: 'militia-theme',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x6a4b24,
|
||||
battleTintAlpha: 0.035,
|
||||
motifs: ['짚신과 의병 깃발', '복숭아빛 맹세', '황진 속 민가']
|
||||
}),
|
||||
'anti-dong': arcProfile('anti-dong', {
|
||||
label: '반동탁 연합과 갈라지는 맹세',
|
||||
firstBattleOrdinal: 5,
|
||||
lastBattleOrdinal: 6,
|
||||
statePolicyId: 'coalition-fire',
|
||||
tensionMusicKey: 'story-dark',
|
||||
battleMusicKey: 'battle-prep',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x61251d,
|
||||
battleTintAlpha: 0.045,
|
||||
motifs: ['연합군 봉화', '붉은 군막', '갈라지는 맹세']
|
||||
}),
|
||||
xuzhou: arcProfile('xuzhou', {
|
||||
label: '서주 수호와 첫 터전',
|
||||
firstBattleOrdinal: 7,
|
||||
lastBattleOrdinal: 9,
|
||||
statePolicyId: 'fractured-road',
|
||||
tensionMusicKey: 'story-dark',
|
||||
battleMusicKey: 'story-dark',
|
||||
resolutionMusicKey: 'militia-theme',
|
||||
battleTintColor: 0x1b3850,
|
||||
battleTintAlpha: 0.045,
|
||||
motifs: ['성문 등불', '푸른 비안개', '지켜야 할 백성']
|
||||
}),
|
||||
wandering: arcProfile('wandering', {
|
||||
label: '서주 상실과 천하 유랑',
|
||||
firstBattleOrdinal: 10,
|
||||
lastBattleOrdinal: 15,
|
||||
statePolicyId: 'fractured-road',
|
||||
tensionMusicKey: 'story-dark',
|
||||
battleMusicKey: 'story-dark',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x172d46,
|
||||
battleTintAlpha: 0.05,
|
||||
motifs: ['해진 깃발', '객군의 밤바람', '끊기지 않는 길']
|
||||
}),
|
||||
wolong: arcProfile('wolong', {
|
||||
label: '형주 의탁과 와룡의 계책',
|
||||
firstBattleOrdinal: 16,
|
||||
lastBattleOrdinal: 18,
|
||||
statePolicyId: 'strategic-ascent',
|
||||
tensionMusicKey: 'militia-theme',
|
||||
battleMusicKey: 'battle-prep',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x4b3921,
|
||||
battleTintAlpha: 0.035,
|
||||
motifs: ['대나무 그림자', '가을 서재', '세 번의 발걸음']
|
||||
}),
|
||||
'red-cliffs': arcProfile('red-cliffs', {
|
||||
label: '장판과 적벽대전',
|
||||
firstBattleOrdinal: 19,
|
||||
lastBattleOrdinal: 22,
|
||||
statePolicyId: 'coalition-fire',
|
||||
tensionMusicKey: 'story-dark',
|
||||
battleMusicKey: 'camp-grand-strategy',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x6b2714,
|
||||
battleTintAlpha: 0.055,
|
||||
motifs: ['강 위 불빛', '동남풍', '붉은 돛과 불화살']
|
||||
}),
|
||||
'jing-yi': arcProfile('jing-yi', {
|
||||
label: '형주 남부와 익주 확보',
|
||||
firstBattleOrdinal: 23,
|
||||
lastBattleOrdinal: 34,
|
||||
statePolicyId: 'strategic-ascent',
|
||||
tensionMusicKey: 'militia-theme',
|
||||
battleMusicKey: 'camp-grand-strategy',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x24452c,
|
||||
battleTintAlpha: 0.035,
|
||||
motifs: ['강과 산길', '입촉 지도', '넓어지는 군영']
|
||||
}),
|
||||
'hanzhong-shuhan': arcProfile('hanzhong-shuhan', {
|
||||
label: '한중 확보와 촉한 건국',
|
||||
firstBattleOrdinal: 35,
|
||||
lastBattleOrdinal: 37,
|
||||
statePolicyId: 'strategic-ascent',
|
||||
tensionMusicKey: 'militia-theme',
|
||||
battleMusicKey: 'battle-prep',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x5a421c,
|
||||
battleTintAlpha: 0.04,
|
||||
motifs: ['금빛 왕기', '산성 봉화', '한실의 명분']
|
||||
}),
|
||||
'jingzhou-crisis': arcProfile('jingzhou-crisis', {
|
||||
label: '형주 위기와 맥성의 고립',
|
||||
firstBattleOrdinal: 38,
|
||||
lastBattleOrdinal: 44,
|
||||
statePolicyId: 'tragic-collapse',
|
||||
tensionMusicKey: 'story-dark',
|
||||
battleMusicKey: 'story-dark',
|
||||
resolutionMusicKey: 'story-dark',
|
||||
battleTintColor: 0x4c2f23,
|
||||
battleTintAlpha: 0.055,
|
||||
motifs: ['강가 감시대', '꺼져 가는 봉화', '비어 가는 자리']
|
||||
}),
|
||||
'yiling-baidi': arcProfile('yiling-baidi', {
|
||||
label: '이릉의 불길과 백제성 유탁',
|
||||
firstBattleOrdinal: 45,
|
||||
lastBattleOrdinal: 46,
|
||||
statePolicyId: 'tragic-collapse',
|
||||
tensionMusicKey: 'story-dark',
|
||||
battleMusicKey: 'story-dark',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x3f2630,
|
||||
battleTintAlpha: 0.06,
|
||||
motifs: ['재가 된 숲', '백제성 등불', '남겨진 자리']
|
||||
}),
|
||||
nanzhong: arcProfile('nanzhong', {
|
||||
label: '남중 평정과 칠종칠금',
|
||||
firstBattleOrdinal: 47,
|
||||
lastBattleOrdinal: 54,
|
||||
statePolicyId: 'frontier-pacification',
|
||||
tensionMusicKey: 'militia-theme',
|
||||
battleMusicKey: 'camp-frontier',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x274d2d,
|
||||
battleTintAlpha: 0.045,
|
||||
motifs: ['남중 강안', '우림의 북', '일곱 번의 약속 장부']
|
||||
}),
|
||||
northern: arcProfile('northern', {
|
||||
label: '북벌과 오장원의 계승',
|
||||
firstBattleOrdinal: 55,
|
||||
lastBattleOrdinal: 66,
|
||||
statePolicyId: 'legacy-expedition',
|
||||
tensionMusicKey: 'story-dark',
|
||||
battleMusicKey: 'camp-frontier',
|
||||
resolutionMusicKey: 'oath-theme',
|
||||
battleTintColor: 0x263d5c,
|
||||
battleTintAlpha: 0.05,
|
||||
motifs: ['설원 군기', '긴 보급로', '강유에게 남긴 계승 지도']
|
||||
})
|
||||
};
|
||||
|
||||
const orderedBattleIds = Object.keys(battleScenarios) as BattleScenarioId[];
|
||||
|
||||
export const campaignBattlePresentationProfiles: Readonly<
|
||||
Record<BattleScenarioId, CampaignBattlePresentationProfile>
|
||||
> = Object.freeze(
|
||||
Object.fromEntries(
|
||||
orderedBattleIds.map((battleId, index) => {
|
||||
const battleOrdinal = index + 1;
|
||||
const arc = campaignPresentationArcForOrdinal(battleOrdinal);
|
||||
if (!arc) {
|
||||
throw new Error(`${battleId}: no campaign presentation arc covers battle ${battleOrdinal}.`);
|
||||
}
|
||||
return [battleId, Object.freeze({ battleId, battleOrdinal, arc })];
|
||||
})
|
||||
) as Record<BattleScenarioId, CampaignBattlePresentationProfile>
|
||||
);
|
||||
|
||||
export function campaignPresentationArcForOrdinal(battleOrdinal: number) {
|
||||
return Object.values(campaignArcPresentationProfiles).find(
|
||||
(profile) =>
|
||||
battleOrdinal >= profile.firstBattleOrdinal && battleOrdinal <= profile.lastBattleOrdinal
|
||||
);
|
||||
}
|
||||
|
||||
export function campaignPresentationProfileFor(battleId: BattleScenarioId | string) {
|
||||
return campaignBattlePresentationProfiles[battleId as BattleScenarioId];
|
||||
}
|
||||
|
||||
export function campaignPresentationStateRuleFor(
|
||||
battleId: BattleScenarioId | string,
|
||||
stage: CampaignPresentationStage
|
||||
) {
|
||||
const profile = campaignPresentationProfileFor(battleId);
|
||||
return profile
|
||||
? campaignPresentationStatePolicies[profile.arc.statePolicyId][stage]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function resolveCampaignPresentationSoundscape({
|
||||
battleId,
|
||||
stage,
|
||||
sceneMusicKey,
|
||||
sceneAmbienceKey,
|
||||
sceneAmbienceVolume
|
||||
}: CampaignPresentationSoundscapeInput): CampaignPresentationSoundscape | undefined {
|
||||
const profile = campaignPresentationProfileFor(battleId);
|
||||
if (!profile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { arc } = profile;
|
||||
const rule = campaignPresentationStatePolicies[arc.statePolicyId][stage];
|
||||
const musicKey = presentationMusicKeyForStage(arc.audio, rule, stage, sceneMusicKey);
|
||||
let ambienceKey: AmbienceTrackKey | undefined;
|
||||
let ambienceVolume: number | undefined;
|
||||
|
||||
if (rule.preserveSceneAmbience && sceneAmbienceKey) {
|
||||
ambienceKey = sceneAmbienceKey;
|
||||
ambienceVolume = sceneAmbienceVolume;
|
||||
} else if (rule.ambienceMode === 'arc-default') {
|
||||
ambienceKey = arc.audio.defaultAmbienceKey;
|
||||
ambienceVolume = arc.audio.defaultAmbienceVolume;
|
||||
} else if (rule.ambienceMode === 'battle-environment') {
|
||||
const environment = battleEnvironmentProfileFor(profile.battleId);
|
||||
ambienceKey = environment?.soundscape.ambienceKey;
|
||||
ambienceVolume = environment?.soundscape.ambienceVolume;
|
||||
}
|
||||
|
||||
return {
|
||||
battleId: profile.battleId,
|
||||
arcId: arc.id,
|
||||
stage,
|
||||
intensity: rule.intensity,
|
||||
musicKey,
|
||||
musicVolume: arc.audio.anchorMusicVolume,
|
||||
ambienceKey,
|
||||
ambienceVolume,
|
||||
transition: campaignPresentationTransitionPolicy
|
||||
};
|
||||
}
|
||||
|
||||
function presentationMusicKeyForStage(
|
||||
audio: CampaignPresentationAudioFamily,
|
||||
rule: CampaignPresentationStateRule,
|
||||
stage: CampaignPresentationStage,
|
||||
sceneMusicKey?: MusicTrackKey
|
||||
) {
|
||||
if (!sceneMusicKey) {
|
||||
return musicKeyForRole(audio, rule.musicRole);
|
||||
}
|
||||
|
||||
// Story data uses a small set of semantic cues. Reinterpret those cues
|
||||
// through the current campaign arc so preparation, travel, and aftermath
|
||||
// retain their intent without forcing all 66 battles through one track.
|
||||
if (stage === 'story') {
|
||||
if (sceneMusicKey === 'battle-prep') {
|
||||
return audio.battleMusicKey;
|
||||
}
|
||||
if (sceneMusicKey === 'militia-theme') {
|
||||
return audio.anchorMusicKey;
|
||||
}
|
||||
if (sceneMusicKey === 'story-dark') {
|
||||
return audio.tensionMusicKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (stage === 'aftermath') {
|
||||
if (sceneMusicKey === 'battle-prep' || sceneMusicKey === 'militia-theme') {
|
||||
return audio.resolutionMusicKey;
|
||||
}
|
||||
if (sceneMusicKey === 'story-dark') {
|
||||
return audio.tensionMusicKey;
|
||||
}
|
||||
}
|
||||
|
||||
return rule.preserveSceneMusic
|
||||
? sceneMusicKey
|
||||
: musicKeyForRole(audio, rule.musicRole);
|
||||
}
|
||||
|
||||
function arcProfile(
|
||||
id: CampaignPresentationArcId,
|
||||
spec: ArcProfileSpec
|
||||
): CampaignArcPresentationProfile {
|
||||
const skin = campSkinDefinitions[id];
|
||||
const soundscape = getCampSoundscape(id);
|
||||
return Object.freeze({
|
||||
id,
|
||||
label: spec.label,
|
||||
firstBattleOrdinal: spec.firstBattleOrdinal,
|
||||
lastBattleOrdinal: spec.lastBattleOrdinal,
|
||||
skinId: id,
|
||||
statePolicyId: spec.statePolicyId,
|
||||
audio: Object.freeze({
|
||||
familyId: soundscape.music.id,
|
||||
anchorMusicKey: soundscape.music.trackKey,
|
||||
tensionMusicKey: spec.tensionMusicKey,
|
||||
battleMusicKey: spec.battleMusicKey,
|
||||
resolutionMusicKey: spec.resolutionMusicKey,
|
||||
defaultAmbienceKey: soundscape.ambience.trackKey,
|
||||
anchorMusicVolume: soundscape.music.volume,
|
||||
defaultAmbienceVolume: soundscape.ambience.volume
|
||||
}),
|
||||
palette: Object.freeze({
|
||||
accentColor: skin.accentColor,
|
||||
headerColor: skin.headerColor,
|
||||
washColor: skin.washColor,
|
||||
battleTintColor: spec.battleTintColor,
|
||||
battleTintAlpha: spec.battleTintAlpha
|
||||
}),
|
||||
motifs: Object.freeze([...spec.motifs])
|
||||
});
|
||||
}
|
||||
|
||||
function statePolicy(
|
||||
camp: CampaignPresentationIntensity,
|
||||
story: CampaignPresentationIntensity,
|
||||
sortie: CampaignPresentationIntensity,
|
||||
battle: CampaignPresentationIntensity,
|
||||
aftermath: CampaignPresentationIntensity
|
||||
): CampaignPresentationStatePolicy {
|
||||
return Object.freeze({
|
||||
camp: stateRule(camp, 'anchor', 'arc-default'),
|
||||
story: stateRule(story, 'tension', 'scene-cue', true, true),
|
||||
sortie: stateRule(sortie, 'anchor', 'arc-default'),
|
||||
battle: stateRule(battle, 'battle', 'battle-environment'),
|
||||
aftermath: stateRule(aftermath, 'resolution', 'scene-cue', true, true)
|
||||
});
|
||||
}
|
||||
|
||||
function stateRule(
|
||||
intensity: CampaignPresentationIntensity,
|
||||
musicRole: CampaignPresentationMusicRole,
|
||||
ambienceMode: CampaignPresentationAmbienceMode,
|
||||
preserveSceneMusic = false,
|
||||
preserveSceneAmbience = false
|
||||
): CampaignPresentationStateRule {
|
||||
return Object.freeze({
|
||||
intensity,
|
||||
musicRole,
|
||||
ambienceMode,
|
||||
preserveSceneMusic,
|
||||
preserveSceneAmbience
|
||||
});
|
||||
}
|
||||
|
||||
function musicKeyForRole(
|
||||
audio: CampaignPresentationAudioFamily,
|
||||
role: CampaignPresentationMusicRole
|
||||
): MusicTrackKey {
|
||||
switch (role) {
|
||||
case 'anchor':
|
||||
return audio.anchorMusicKey;
|
||||
case 'tension':
|
||||
return audio.tensionMusicKey;
|
||||
case 'battle':
|
||||
return audio.battleMusicKey;
|
||||
case 'resolution':
|
||||
return audio.resolutionMusicKey;
|
||||
}
|
||||
}
|
||||
@@ -364,7 +364,7 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
|
||||
'first-victory-next': {
|
||||
chapter: '다음 길',
|
||||
speaker: '전황',
|
||||
text: '탁현에서부터 유비를 도와 온 간옹이 의용군의 군정과 책략을 맡기로 했다. 다음 전투부터 유비·관우·장비·간옹 네 명 중 최대 세 명을 골라 전장에 맞는 편성을 꾸릴 수 있다.',
|
||||
text: '탁현에서부터 유비를 도와 온 간옹이 의용군의 군정과 책략을 맡기로 했다. 군량과 말이 넉넉지 않아, 유비·관우·장비·간옹 네 사람 가운데 셋이 선두를 맡고 나머지 한 사람은 후방을 돌보기로 했다.',
|
||||
cutscene: {
|
||||
kind: 'operation',
|
||||
title: '간옹 합류 · 첫 편성 선택',
|
||||
@@ -686,7 +686,7 @@ export const fourthBattleVictoryPages: StoryPage[] = [
|
||||
bgm: 'battle-prep',
|
||||
chapter: '새로운 혼란',
|
||||
background: 'story-yellow-pursuit',
|
||||
text: '황건의 불길이 잦아들 무렵, 조정 안에서는 또 다른 그림자가 자라났다. 세 형제의 길은 이제 반동탁의 흐름으로 이어진다.'
|
||||
text: '황건의 불길이 잦아들 무렵, 조정 안에서는 또 다른 그림자가 자라났다. 동탁의 폭정에 맞서 제후들이 격문을 돌리기 시작했고, 세 형제에게도 그 소식이 닿았다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -887,7 +887,7 @@ export const eighthBattleIntroPages: StoryPage[] = [
|
||||
chapter: '첫 방위전',
|
||||
background: 'story-xuzhou',
|
||||
speaker: '간옹',
|
||||
text: '현덕, 탁현에서 시작한 편성 선택이 이제 더 넓어졌네. 칼이 필요한 곳과 책략이 필요한 곳, 그리고 말보다 장부가 필요한 곳을 구분해 미축까지 알맞게 배치해야 하네.'
|
||||
text: '현덕, 이제 미축까지 장부를 맡아 줄 사람이 늘었네. 칼이 필요한 곳, 책략이 필요한 곳, 그리고 군량을 지킬 곳을 가려 누구를 선두에 세우고 누구를 후방에 남길지 정해야 하네.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -2104,7 +2104,7 @@ export const twentyEighthBattleIntroPages: StoryPage[] = [
|
||||
chapter: '와룡과 봉추의 첫 편성',
|
||||
background: 'story-yizhou-luo-road',
|
||||
speaker: '마량',
|
||||
text: '이번 전투부터 방통도 출전 후보가 됩니다. 와룡과 봉추를 모두 데려가면 성루와 보급선 계책은 강해지지만, 전열과 회복 중 하나가 얇아질 수 있습니다.'
|
||||
text: '방통 선생도 이번 길에 나설 뜻을 밝혔습니다. 와룡과 봉추를 함께 모시면 성루와 보급선을 흔들 계책은 두터워지지만, 전열을 받치거나 부상자를 돌볼 손은 그만큼 줄어듭니다.'
|
||||
},
|
||||
{
|
||||
id: 'twenty-eighth-fu-pass-sortie',
|
||||
@@ -2166,7 +2166,7 @@ export const twentyNinthBattleIntroPages: StoryPage[] = [
|
||||
chapter: '법정의 첫 출전 선택',
|
||||
background: 'story-yizhou-luo-road',
|
||||
speaker: '제갈량',
|
||||
text: '이번 전투부터 법정도 출전 후보입니다. 그를 데려가면 오의와 성문 장수들의 속내를 읽기 쉽지만, 책사가 늘어나는 만큼 고개를 열 전열 무장은 더 신중히 골라야 합니다.'
|
||||
text: '법정이 낙성의 길잡이를 자청했습니다. 그가 함께하면 오의와 성문 장수들의 속내를 읽기 쉬우나, 군막의 책사가 늘어나는 만큼 고개를 열 선봉은 더욱 신중히 정해야 합니다.'
|
||||
},
|
||||
{
|
||||
id: 'twenty-ninth-luo-sortie',
|
||||
@@ -2228,7 +2228,7 @@ export const thirtiethBattleIntroPages: StoryPage[] = [
|
||||
chapter: '오의의 첫 출전 선택',
|
||||
background: 'story-yizhou-luo-road',
|
||||
speaker: '법정',
|
||||
text: '이번 전투부터 오의도 출전 후보입니다. 낙성의 길을 아는 오의를 데려가면 샛길과 매복 고지를 읽기 쉬우나, 방통까지 지키려면 전열과 보급 후보는 더 좁아집니다.'
|
||||
text: '오의가 낙성의 샛길과 매복 고지를 안내하겠다고 나섰습니다. 그에게 길을 맡기면 적의 눈을 피하기 쉬우나, 방통까지 호위하려면 전열과 군량을 지킬 사람은 더 적어집니다.'
|
||||
},
|
||||
{
|
||||
id: 'thirtieth-luofeng-sortie',
|
||||
@@ -2449,7 +2449,7 @@ export const thirtyThirdBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '익주 확보',
|
||||
background: 'story-chengdu-surrender',
|
||||
speaker: '제갈량',
|
||||
text: '형주와 익주의 기반이 마련되었습니다. 유비군은 이제 유랑군이 아니라 나라의 뿌리를 갖춘 세력으로 바뀌며, 한중과 촉한 건국을 향한 다음 장을 준비합니다.'
|
||||
text: '형주와 익주의 기반이 마련되었습니다. 우리 군은 이제 떠도는 의병이 아니라 백성과 땅을 지킬 뿌리를 얻었습니다. 북쪽 한중을 바라보고, 마침내 한실을 다시 세울 깃발을 올릴 때입니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3091,7 +3091,7 @@ export const fortySecondBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '갈라지는 형주',
|
||||
background: 'story-jingzhou-crisis',
|
||||
speaker: '제갈량',
|
||||
text: '나루 하나는 지켰으나, 강릉과 공안의 장수들이 흔들리기 시작했습니다. 다음 장에서는 형주 후방의 균열이 더 노골적인 배신과 침투로 이어질 것입니다.'
|
||||
text: '나루 하나는 지켰으나 강릉과 공안의 장수들이 흔들리기 시작했습니다. 지금 후방의 틈을 막지 못하면, 강동의 손길이 배신과 침투로 그 균열을 벌릴 것입니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3216,7 +3216,7 @@ export const fortyFourthBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '형주의 상실',
|
||||
background: 'story-maicheng-isolation',
|
||||
speaker: '마량',
|
||||
text: '맥성의 퇴로는 열렸으나 강릉과 공안의 마음은 이미 갈라졌습니다. 다음 장에서는 형주 상실의 대가와 유비의 분노가 이릉으로 번지는 흐름을 준비해야 합니다.'
|
||||
text: '맥성의 퇴로는 열렸으나 강릉과 공안의 마음은 이미 갈라졌습니다. 형주를 잃었다는 급보가 익주에 닿으면, 주군의 슬픔과 분노는 끝내 이릉까지 군을 몰고 갈 것입니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3344,7 +3344,7 @@ export const fortySixthBattleVictoryPages: StoryPage[] = [
|
||||
background: 'story-yiling-fire-attack',
|
||||
speaker: '유비',
|
||||
portrait: 'liuBei',
|
||||
text: '뜻을 세운 날은 멀어졌고, 손에 남은 것은 불탄 깃발뿐이오. 그러나 남은 이들이 있다면 뜻도 아직 끝나지 않았소. 다음 장에서는 백제성에서 남은 나라의 길을 정해야 하오.'
|
||||
text: '뜻을 세운 날은 멀어졌고, 손에 남은 것은 불탄 깃발뿐이오. 그러나 남은 이들이 있다면 뜻도 아직 끝나지 않았소. 백제성으로 가서 남은 나라의 길을 정해야 하오.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3449,7 +3449,7 @@ export const fortySeventhBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '남만 평정의 시작',
|
||||
background: 'story-nanzhong',
|
||||
speaker: '제갈량',
|
||||
text: '첫 길은 열렸습니다. 이제 남중의 여러 세력을 모두 적으로 만들지 않으면서, 반란의 중심을 하나씩 마주해야 합니다. 다음 장에서는 맹획의 본대와 처음으로 맞섭니다.'
|
||||
text: '첫 길은 열렸습니다. 남중의 여러 세력을 모두 적으로 돌리지 않으면서 반란의 중심을 하나씩 마주해야 합니다. 맹획이 깊은 산채에서 본대를 모으고 있으니, 이제 그와 직접 맞설 때입니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3555,7 +3555,7 @@ export const fortyNinthBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '남중의 마음',
|
||||
background: 'story-nanzhong-captures',
|
||||
speaker: '황권',
|
||||
text: '포로 명부와 군량 장부가 어긋나지 않았습니다. 다음 싸움에서는 맹획만이 아니라 그를 따르는 호족들의 마음도 흔들릴 것입니다. 회유는 이제 전투 뒤의 일이 아니라 전투 중의 목표가 되었습니다.'
|
||||
text: '포로 명부와 군량 장부가 어긋나지 않았습니다. 맹획만이 아니라 그를 따르는 호족들의 마음도 흔들릴 것입니다. 이제는 칼을 맞대는 동안에도 그들이 돌아설 길을 함께 열어 두어야 합니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3608,7 +3608,7 @@ export const fiftiethBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '전장 밖의 회유',
|
||||
background: 'story-nanzhong-captures',
|
||||
speaker: '제갈량',
|
||||
text: '남중을 얻는 길은 점점 분명해지고 있습니다. 다음 장에서는 맹획을 따르던 호족들과 직접 이야기를 나누고, 전투 전 선택이 전장 목표에 영향을 주는 흐름을 준비해야 합니다.'
|
||||
text: '남중을 얻는 길은 점점 분명해지고 있습니다. 맹획을 따르던 호족들을 군막으로 불러 속내를 듣겠습니다. 우리가 어떤 보증을 내어 주느냐에 따라, 다음 싸움에서 지켜야 할 마을과 물러날 적이 달라질 것입니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3892,7 +3892,7 @@ export const fiftyFifthBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '북벌의 첫 발',
|
||||
background: 'story-northern',
|
||||
speaker: '제갈량',
|
||||
text: '첫 길은 열렸으나, 위나라의 본대는 아직 물러나지 않았습니다. 오늘 얻은 것은 승리 하나가 아니라 산길, 보급, 민심이 이어진 첫 전선입니다. 이제부터는 무장들의 배치와 보급의 선택이 더 중요해질 것입니다.'
|
||||
text: '첫 길은 열렸으나 위나라의 본대는 아직 물러나지 않았습니다. 오늘 얻은 것은 승리 하나가 아니라 산길과 보급, 민심이 이어진 첫 전선입니다. 이제는 장수마다 맡길 길을 가르고, 군량이 끊기지 않도록 후방까지 헤아려야 합니다.'
|
||||
},
|
||||
{
|
||||
id: 'fifty-fifth-next-front',
|
||||
@@ -3998,7 +3998,7 @@ export const fiftySeventhBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '새 장수',
|
||||
background: 'story-jieting-crisis',
|
||||
speaker: '강유',
|
||||
text: '강유는 위군의 의심과 백성의 말 사이에서 끝내 마음을 정했습니다. 오늘부터 그는 촉한의 깃발 아래 서지만, 새로 얻은 장수 하나가 곧바로 모든 길을 열어 주지는 않습니다. 다음 출전부터는 더 넓어진 장수진 안에서 누구를 고를지 판단해야 합니다.'
|
||||
text: '위군의 의심과 백성의 말 사이에서 오래 망설였으나, 이제 마음을 정했습니다. 오늘부터 촉한의 깃발 아래 서겠습니다. 다만 제가 안다는 길이 모든 전장을 열지는 못하니, 장수마다 가장 잘 지킬 곳을 맡겨 주십시오.'
|
||||
},
|
||||
{
|
||||
id: 'fifty-seventh-next-front',
|
||||
@@ -4006,7 +4006,7 @@ export const fiftySeventhBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '북벌의 다음 부담',
|
||||
background: 'story-jieting-crisis',
|
||||
speaker: '제갈량',
|
||||
text: '가정의 위기는 수습되었으나 위군은 이제 우리의 길과 사람을 모두 보았습니다. 강유를 얻은 기쁨과 마속의 실책을 함께 품고, 다음 북벌에서는 더 엄격한 편성과 보급 판단이 필요할 것입니다.'
|
||||
text: '가정의 위기는 수습되었으나 위군은 이제 우리의 길과 사람을 모두 보았습니다. 강유를 얻은 기쁨에 취하거나 마속의 실책을 덮지 않겠습니다. 다음 북벌에서는 장수마다 맡을 길을 분명히 하고, 군량이 닿는 거리부터 헤아리겠습니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -4033,7 +4033,7 @@ export const fiftyEighthBattleIntroPages: StoryPage[] = [
|
||||
chapter: '새로운 그림자',
|
||||
background: 'story-jieting-crisis',
|
||||
speaker: '마량',
|
||||
text: '장합의 추격만 막으면 끝나는 전장이 아닙니다. 위군 본영 뒤에서 사마의가 군량 수레가 어디로 빠지는지 보고 있습니다. 강유를 내보내면 길은 넓어지지만, 출전 자리는 하나 줄어듭니다. 이번 편성은 누구를 믿고 퇴로를 맡길지의 선택입니다.'
|
||||
text: '장합의 추격만 막으면 끝나는 전장이 아닙니다. 위군 본영 뒤에서 사마의가 군량 수레가 어디로 빠지는지 보고 있습니다. 강유에게 낮은 길을 맡기려면 다른 장수 한 사람은 후방을 지켜야 합니다. 누구를 믿고 퇴로를 맡길지 먼저 정해야 합니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -4051,7 +4051,7 @@ export const fiftyEighthBattleVictoryPages: StoryPage[] = [
|
||||
chapter: '강유의 첫 평가',
|
||||
background: 'story-jieting-crisis',
|
||||
speaker: '제갈량',
|
||||
text: '강유는 천수의 길을 숨기지 않았고, 조운과 마대는 병사들이 돌아올 시간을 벌었습니다. 왕평의 물길 판단도 여전히 군의 목숨줄입니다. 이제부터 출전 명단은 단순히 강한 장수를 고르는 일이 아니라, 어떤 길을 택할지 정하는 일이 됩니다.'
|
||||
text: '강유는 천수의 길을 숨기지 않았고, 조운과 마대는 병사들이 돌아올 시간을 벌었습니다. 왕평의 물길 판단도 여전히 군의 목숨줄입니다. 앞으로 장수를 정할 때에는 힘만 볼 것이 아니라, 그에게 어떤 길을 맡길지도 함께 헤아려야 합니다.'
|
||||
},
|
||||
{
|
||||
id: 'fifty-eighth-next-northern-plan',
|
||||
@@ -4192,7 +4192,7 @@ export const sixtyFirstBattleIntroPages: StoryPage[] = [
|
||||
chapter: '넓어진 책임',
|
||||
background: 'story-hanzhong-rain',
|
||||
speaker: '마량',
|
||||
text: '백성은 촉한의 깃발만 보고 안심하지 않습니다. 비가 오면 수레는 늦고, 군문이 흔들리면 새 고을은 먼저 두려워합니다. 출전 명단에서 누가 길을 잡고 누가 장부를 지킬지 분명히 해야 합니다.'
|
||||
text: '백성은 촉한의 깃발만 보고 안심하지 않습니다. 비가 오면 수레는 늦고, 군문이 흔들리면 새 고을은 먼저 두려워합니다. 군의를 열어 누가 길을 잡고 누가 장부를 지킬지 분명히 해야 합니다.'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -4245,7 +4245,7 @@ export const sixtySecondBattleIntroPages: StoryPage[] = [
|
||||
chapter: '보급을 공세로',
|
||||
background: 'story-qishan-renewed',
|
||||
speaker: '황권',
|
||||
text: '지난 전투에서 지킨 장부를 이번에는 밀어 올릴 차례입니다. 이엄을 후방에 남길지, 마대의 측면 기동을 넣을지, 혹은 마량의 민심 문서를 앞세울지 출전 명단에서 먼저 정해야 합니다.'
|
||||
text: '지난 싸움에서 지킨 장부를 이제 북쪽으로 밀어 올릴 차례입니다. 이엄을 후방에 남길지, 마대에게 측면을 맡길지, 혹은 마량의 민심 문서를 앞세울지 군의를 열어 먼저 정해야 합니다.'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { soundDirector, type StrategyImpactKind } from '../audio/SoundDirector';
|
||||
import { loadAudioPreferences, saveAudioPreferences } from '../settings/audioPreferences';
|
||||
import { isVisualMotionReduced } from '../settings/visualMotion';
|
||||
import { battleMapAssets } from '../data/battleMapAssets';
|
||||
import { firstBattleVictoryPages, type BattleBond, type UnitData, type UnitStats } from '../data/scenario';
|
||||
import { type BattleBond, type UnitData, type UnitStats } from '../data/scenario';
|
||||
import {
|
||||
defaultBattleScenario,
|
||||
getBattleScenario,
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
type BattleEnvironmentParticleProfile,
|
||||
type BattleEnvironmentProfile
|
||||
} from '../data/battleEnvironmentProfiles';
|
||||
import {
|
||||
campaignPresentationProfileFor,
|
||||
resolveCampaignPresentationSoundscape
|
||||
} from '../data/campaignPresentationProfiles';
|
||||
import { getSortieFlow } from '../data/campaignFlow';
|
||||
import {
|
||||
enemyIntentOpeningGuide,
|
||||
@@ -1750,8 +1754,6 @@ type ResultButtonView = {
|
||||
type ResultButtonTone = 'primary' | 'secondary' | 'utility' | 'ghost';
|
||||
type ResultButtonKind = 'continue' | 'retry' | 'title' | 'detail';
|
||||
type ResultSettlementStatus = 'hidden' | 'pending' | 'animating' | 'complete';
|
||||
type ResultContinueDestination = 'victory-story' | 'camp';
|
||||
|
||||
type ThreatTile = {
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -4018,10 +4020,15 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.combatPresentationLast = undefined;
|
||||
this.fastForwardHeld = false;
|
||||
const environmentProfile = battleEnvironmentProfileFor(battleScenario.id);
|
||||
const presentationSoundscape = resolveCampaignPresentationSoundscape({
|
||||
battleId: battleScenario.id,
|
||||
stage: 'battle'
|
||||
});
|
||||
soundDirector.playSoundscape({
|
||||
musicKey: 'battle-prep',
|
||||
ambienceKey: environmentProfile?.soundscape.ambienceKey,
|
||||
ambienceVolume: environmentProfile?.soundscape.ambienceVolume
|
||||
musicKey: presentationSoundscape?.musicKey ?? 'battle-prep',
|
||||
musicVolume: presentationSoundscape?.musicVolume,
|
||||
ambienceKey: presentationSoundscape?.ambienceKey ?? environmentProfile?.soundscape.ambienceKey,
|
||||
ambienceVolume: presentationSoundscape?.ambienceVolume ?? environmentProfile?.soundscape.ambienceVolume
|
||||
});
|
||||
this.input.mouse?.disableContextMenu();
|
||||
this.installBattleAudioUnlock();
|
||||
@@ -5349,22 +5356,29 @@ export class BattleScene extends Phaser.Scene {
|
||||
private drawBattleEnvironment() {
|
||||
this.clearBattleEnvironment();
|
||||
const profile = battleEnvironmentProfileFor(battleScenario.id);
|
||||
const presentation = campaignPresentationProfileFor(battleScenario.id);
|
||||
this.battleEnvironmentProfile = profile;
|
||||
if (!profile || !this.mapMask) {
|
||||
if (!this.mapMask || (!profile && !presentation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.battleEnvironmentRandomState = this.battleEnvironmentSeed(profile.id);
|
||||
this.battleEnvironmentRandomState = this.battleEnvironmentSeed(profile?.id ?? `arc-${presentation!.arc.id}`);
|
||||
const { mapX, mapY, mapWidth, mapHeight } = this.layout;
|
||||
if (profile.tint.alpha > 0) {
|
||||
const tint = this.add.rectangle(mapX, mapY, mapWidth, mapHeight, profile.tint.color, profile.tint.alpha);
|
||||
const tintColor = profile?.tint.color ?? presentation!.arc.palette.battleTintColor;
|
||||
const tintAlpha = profile?.tint.alpha ?? presentation!.arc.palette.battleTintAlpha;
|
||||
if (tintAlpha > 0) {
|
||||
const tint = this.add.rectangle(mapX, mapY, mapWidth, mapHeight, tintColor, tintAlpha);
|
||||
tint.setOrigin(0);
|
||||
tint.setDepth(battleEnvironmentTintDepth);
|
||||
tint.setMask(this.mapMask);
|
||||
tint.setName(`battle-environment-${profile.id}-tint`);
|
||||
tint.setName(`battle-environment-${profile?.id ?? `arc-${presentation!.arc.id}`}-tint`);
|
||||
this.battleEnvironmentObjects.push(tint);
|
||||
}
|
||||
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reducedMotion = isVisualMotionReduced();
|
||||
if (!reducedMotion && profile.haze) {
|
||||
this.createBattleEnvironmentHaze(profile.haze);
|
||||
@@ -13377,16 +13391,16 @@ export class BattleScene extends Phaser.Scene {
|
||||
void this.playResultSettlementAnimation(animationToken);
|
||||
}
|
||||
|
||||
private resultContinueRoute(): { destination: ResultContinueDestination; label: string } {
|
||||
if (battleScenario.id === 'first-battle-zhuo-commandery') {
|
||||
return { destination: 'victory-story', label: '승리 이야기로' };
|
||||
private resultContinueRoute(outcome = this.battleOutcome) {
|
||||
if (outcome !== 'victory') {
|
||||
return undefined;
|
||||
}
|
||||
return { destination: 'camp', label: '군영으로' };
|
||||
return { destination: 'victory-story' as const, label: '승리 이야기로' };
|
||||
}
|
||||
|
||||
private resultContinueCtaLabel() {
|
||||
return this.resultSettlementStatus === 'complete'
|
||||
? this.resultContinueRoute().label
|
||||
? this.resultContinueRoute()?.label ?? '군영으로'
|
||||
: '정산 완료하기';
|
||||
}
|
||||
|
||||
@@ -13464,15 +13478,17 @@ export class BattleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
const { destination } = this.resultContinueRoute();
|
||||
if (destination === 'victory-story') {
|
||||
this.beginResultNavigation(() => startLazyScene(this, 'StoryScene', {
|
||||
pages: firstBattleVictoryPages,
|
||||
nextScene: 'CampScene'
|
||||
}));
|
||||
if (!this.resultContinueRoute()) {
|
||||
return;
|
||||
}
|
||||
this.beginResultNavigation(() => startLazyScene(this, 'CampScene'));
|
||||
|
||||
this.beginResultNavigation(() => startLazyScene(this, 'StoryScene', {
|
||||
pages: battleScenario.victoryPages,
|
||||
nextScene: battleScenario.nextCampScene,
|
||||
nextSceneData: { completedAftermathBattleId: battleScenario.id },
|
||||
presentationBattleId: battleScenario.id,
|
||||
presentationStage: 'aftermath'
|
||||
}));
|
||||
}
|
||||
|
||||
private hideBattleResult() {
|
||||
@@ -13749,11 +13765,16 @@ export class BattleScene extends Phaser.Scene {
|
||||
: {})
|
||||
};
|
||||
this.hideResultFormationReview();
|
||||
if (outcome === 'victory' && battleScenario.id === 'first-battle-zhuo-commandery') {
|
||||
if (outcome === 'victory') {
|
||||
this.beginResultNavigation(() => startLazyScene(this, 'StoryScene', {
|
||||
pages: firstBattleVictoryPages,
|
||||
nextScene: 'CampScene',
|
||||
nextSceneData: campSceneData
|
||||
pages: battleScenario.victoryPages,
|
||||
nextScene: battleScenario.nextCampScene,
|
||||
nextSceneData: {
|
||||
...campSceneData,
|
||||
completedAftermathBattleId: battleScenario.id
|
||||
},
|
||||
presentationBattleId: battleScenario.id,
|
||||
presentationStage: 'aftermath'
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@@ -28207,6 +28228,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
private battleEnvironmentDebugState() {
|
||||
const profile = this.battleEnvironmentProfile;
|
||||
const presentation = campaignPresentationProfileFor(battleScenario.id);
|
||||
const soundscape = resolveCampaignPresentationSoundscape({ battleId: battleScenario.id, stage: 'battle' });
|
||||
const tintAlpha = profile?.tint.alpha ?? presentation?.arc.palette.battleTintAlpha ?? 0;
|
||||
const activeObjects = this.battleEnvironmentObjects.filter((object) => object.active);
|
||||
const depths = activeObjects.map((object) => (
|
||||
object as Phaser.GameObjects.GameObject & { depth: number }
|
||||
@@ -28215,16 +28239,18 @@ export class BattleScene extends Phaser.Scene {
|
||||
(object as Phaser.GameObjects.GameObject & { mask?: Phaser.Display.Masks.GeometryMask | null }).mask === this.mapMask
|
||||
)).length;
|
||||
return {
|
||||
profileId: profile?.id ?? null,
|
||||
label: profile?.label ?? null,
|
||||
effect: profile?.effect ?? null,
|
||||
ambienceKey: profile?.soundscape.ambienceKey ?? null,
|
||||
ambienceVolume: profile?.soundscape.ambienceVolume ?? 0,
|
||||
profileId: profile?.id ?? (presentation ? `arc-${presentation.arc.id}` : null),
|
||||
presentationArcId: presentation?.arc.id ?? null,
|
||||
label: profile?.label ?? presentation?.arc.label ?? null,
|
||||
effect: profile?.effect ?? (presentation ? 'arc-wash' : null),
|
||||
musicKey: soundscape?.musicKey ?? null,
|
||||
ambienceKey: soundscape?.ambienceKey ?? null,
|
||||
ambienceVolume: soundscape?.ambienceVolume ?? 0,
|
||||
reducedMotion: isVisualMotionReduced(),
|
||||
active: Boolean(profile && activeObjects.length > 0),
|
||||
active: activeObjects.length > 0,
|
||||
particleCount: this.battleEnvironmentParticles.filter((view) => view.object.active).length,
|
||||
hazeCount: this.battleEnvironmentHaze.filter((view) => view.object.active).length,
|
||||
tintCount: profile?.tint.alpha && activeObjects.length > 0 ? 1 : 0,
|
||||
tintCount: tintAlpha > 0 && activeObjects.length > 0 ? 1 : 0,
|
||||
objectCount: activeObjects.length,
|
||||
maskedObjectCount,
|
||||
masked: activeObjects.length === 0 ? true : maskedObjectCount === activeObjects.length,
|
||||
@@ -28239,7 +28265,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
height: this.layout.mapHeight
|
||||
}
|
||||
: null,
|
||||
tintAlpha: profile?.tint.alpha ?? 0,
|
||||
tintAlpha,
|
||||
fixedPool: true,
|
||||
verification: battleEnvironmentProfileVerification
|
||||
};
|
||||
|
||||
@@ -171,6 +171,7 @@ import {
|
||||
campaignVictoryRewardPresentation,
|
||||
campaignSortiePresetIds,
|
||||
campaignReserveTrainingFocusDefinitions,
|
||||
completeCampaignAftermath,
|
||||
defaultCampaignReserveTrainingFocusId,
|
||||
dismissCampaignVictoryRewardNotice,
|
||||
ensureCampaignRosterUnits,
|
||||
@@ -825,6 +826,7 @@ type CampSceneData = {
|
||||
openSortieImprovement?: boolean;
|
||||
retryBattleId?: BattleScenarioId;
|
||||
skipIntroStory?: boolean;
|
||||
completedAftermathBattleId?: BattleScenarioId;
|
||||
};
|
||||
|
||||
const campSupplies: CampSupplyDefinition[] = [
|
||||
@@ -11398,6 +11400,9 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
init(data?: CampSceneData) {
|
||||
this.ownedCampTextureKeys.clear();
|
||||
if (data?.completedAftermathBattleId) {
|
||||
completeCampaignAftermath(data.completedAftermathBattleId);
|
||||
}
|
||||
this.openSortiePrepOnCreate = Boolean(data?.openSortiePrep);
|
||||
this.openSortieImprovementOnCreate = Boolean(data?.openSortieImprovement);
|
||||
this.retrySortieBattleId = data?.retryBattleId;
|
||||
@@ -19858,14 +19863,18 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
this.startCampNavigationWithCampaignStep(flow.campaignStep, 'StoryScene', {
|
||||
pages: flow.pages,
|
||||
nextScene: flow.campaignStep === 'ending-complete' ? 'EndingScene' : 'CampScene'
|
||||
nextScene: flow.campaignStep === 'ending-complete' ? 'EndingScene' : 'CampScene',
|
||||
presentationBattleId: flow.afterBattleId,
|
||||
presentationStage: flow.campaignStep === 'ending-complete' ? 'aftermath' : 'story'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (flow.pages.length > 0) {
|
||||
this.startCampNavigation('StoryScene', {
|
||||
pages: flow.pages,
|
||||
nextScene: 'CampScene'
|
||||
nextScene: 'CampScene',
|
||||
presentationBattleId: flow.afterBattleId,
|
||||
presentationStage: 'story'
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -19905,6 +19914,8 @@ export class CampScene extends Phaser.Scene {
|
||||
this.startCampNavigationWithCampaignStep(flow.campaignStep, 'StoryScene', {
|
||||
pages: flow.pages,
|
||||
nextScene: 'BattleScene',
|
||||
presentationBattleId: flow.nextBattleId,
|
||||
presentationStage: 'story',
|
||||
nextSceneData: {
|
||||
battleId: flow.nextBattleId,
|
||||
selectedSortieUnitIds,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { soundDirector } from '../audio/SoundDirector';
|
||||
import { musicTracks, type MusicTrackKey } from '../audio/audioAssets';
|
||||
import { equipmentItemIdForInventoryLabel } from '../data/battleItems';
|
||||
import {
|
||||
battleUiIconFrames,
|
||||
@@ -26,6 +27,11 @@ import {
|
||||
type StoryEnvironmentParticleProfile,
|
||||
type StoryEnvironmentProfile
|
||||
} from '../data/storyEnvironmentProfiles';
|
||||
import {
|
||||
campaignPresentationProfileFor,
|
||||
resolveCampaignPresentationSoundscape,
|
||||
type CampaignPresentationStage
|
||||
} from '../data/campaignPresentationProfiles';
|
||||
import {
|
||||
ensureUnitAnimations,
|
||||
loadUnitBaseSheets,
|
||||
@@ -53,6 +59,8 @@ type StorySceneData = {
|
||||
pages?: StoryPage[];
|
||||
nextScene?: string;
|
||||
nextSceneData?: Record<string, unknown>;
|
||||
presentationBattleId?: string;
|
||||
presentationStage?: Extract<CampaignPresentationStage, 'story' | 'aftermath'>;
|
||||
};
|
||||
|
||||
type StoryPageAssetRequest = {
|
||||
@@ -183,6 +191,9 @@ export class StoryScene extends Phaser.Scene {
|
||||
private selectedStoryBackgroundKeys = storyBackgroundKeysForPages(prologuePages);
|
||||
private nextScene = 'BattleScene';
|
||||
private nextSceneData?: Record<string, unknown>;
|
||||
private presentationBattleId?: string;
|
||||
private presentationStage: Extract<CampaignPresentationStage, 'story' | 'aftermath'> = 'story';
|
||||
private presentationWash?: Phaser.GameObjects.Rectangle;
|
||||
private transitioning = false;
|
||||
private background?: Phaser.GameObjects.Image;
|
||||
private chapterText?: Phaser.GameObjects.Text;
|
||||
@@ -223,6 +234,9 @@ export class StoryScene extends Phaser.Scene {
|
||||
this.selectedStoryBackgroundKeys = storyBackgroundKeysForPages(this.pages);
|
||||
this.nextScene = data?.nextScene ?? 'BattleScene';
|
||||
this.nextSceneData = data?.nextSceneData;
|
||||
this.presentationBattleId = data?.presentationBattleId;
|
||||
this.presentationStage = data?.presentationStage ?? 'story';
|
||||
this.presentationWash = undefined;
|
||||
this.pageIndex = 0;
|
||||
this.transitioning = false;
|
||||
this.readyStoryPageIndexes.clear();
|
||||
@@ -260,7 +274,7 @@ export class StoryScene extends Phaser.Scene {
|
||||
|
||||
return {
|
||||
scene: this.scene.key,
|
||||
ready: this.loadingText === undefined && this.background !== undefined,
|
||||
ready: this.sys.isActive() && this.loadingText === undefined && this.background?.active === true,
|
||||
pageIndex: this.pageIndex,
|
||||
totalPages: this.pages.length,
|
||||
currentPageId: page?.id,
|
||||
@@ -300,6 +314,7 @@ export class StoryScene extends Phaser.Scene {
|
||||
items: this.rewardDisplay.items.map((item) => ({ ...item, icon: { ...item.icon } }))
|
||||
},
|
||||
environment: this.storyEnvironmentDebugState(),
|
||||
presentation: this.storyPresentationDebugState(),
|
||||
assetStreaming: {
|
||||
currentPageReady: this.readyStoryPageIndexes.has(this.pageIndex),
|
||||
nextPageIndex: this.pageIndex + 1 < this.pages.length ? this.pageIndex + 1 : null,
|
||||
@@ -354,6 +369,9 @@ export class StoryScene extends Phaser.Scene {
|
||||
this.loadingText = undefined;
|
||||
this.background = this.add.image(width / 2, height / 2, this.resolveBackgroundKey(this.pageBackgroundKey(this.pages[0])));
|
||||
this.background.setDepth(0);
|
||||
this.presentationWash = this.add.rectangle(0, 0, width, height, 0x000000, 0);
|
||||
this.presentationWash.setOrigin(0);
|
||||
this.presentationWash.setDepth(0.12);
|
||||
this.drawSceneShade(width, height);
|
||||
this.drawDialogPanel(width, height);
|
||||
this.showPage(0, true);
|
||||
@@ -771,11 +789,23 @@ export class StoryScene extends Phaser.Scene {
|
||||
private applyPage(page: StoryPage) {
|
||||
const backgroundKey = this.pageBackgroundKey(page);
|
||||
const environmentProfile = storyEnvironmentProfileFor(page.background, page.id);
|
||||
const sceneMusicKey = this.storyMusicTrackKey(page.bgm);
|
||||
const presentationSoundscape = this.presentationBattleId && sceneMusicKey
|
||||
? resolveCampaignPresentationSoundscape({
|
||||
battleId: this.presentationBattleId,
|
||||
stage: this.presentationStage,
|
||||
sceneMusicKey,
|
||||
sceneAmbienceKey: environmentProfile?.ambienceKey,
|
||||
sceneAmbienceVolume: environmentProfile?.ambienceVolume
|
||||
})
|
||||
: undefined;
|
||||
soundDirector.playSoundscape({
|
||||
musicKey: page.bgm,
|
||||
ambienceKey: environmentProfile?.ambienceKey,
|
||||
ambienceVolume: environmentProfile?.ambienceVolume
|
||||
musicKey: presentationSoundscape?.musicKey ?? page.bgm,
|
||||
musicVolume: presentationSoundscape?.musicVolume,
|
||||
ambienceKey: presentationSoundscape?.ambienceKey ?? environmentProfile?.ambienceKey,
|
||||
ambienceVolume: presentationSoundscape?.ambienceVolume ?? environmentProfile?.ambienceVolume
|
||||
});
|
||||
this.applyStoryPresentationWash();
|
||||
this.applyBackground(page);
|
||||
this.applyStoryEnvironment(environmentProfile, backgroundKey, page.id);
|
||||
this.renderCutscene(page);
|
||||
@@ -809,6 +839,37 @@ export class StoryScene extends Phaser.Scene {
|
||||
);
|
||||
}
|
||||
|
||||
private applyStoryPresentationWash() {
|
||||
const profile = this.presentationBattleId
|
||||
? campaignPresentationProfileFor(this.presentationBattleId)
|
||||
: undefined;
|
||||
if (!profile || !this.presentationWash) {
|
||||
this.presentationWash?.setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const alpha = Phaser.Math.Clamp(profile.arc.palette.battleTintAlpha * 0.62, 0.018, 0.038);
|
||||
this.presentationWash.setFillStyle(profile.arc.palette.washColor, alpha);
|
||||
this.presentationWash.setVisible(true);
|
||||
}
|
||||
|
||||
private storyMusicTrackKey(value: string | undefined): MusicTrackKey | undefined {
|
||||
return value && value in musicTracks ? value as MusicTrackKey : undefined;
|
||||
}
|
||||
|
||||
private storyPresentationDebugState() {
|
||||
const profile = this.presentationBattleId
|
||||
? campaignPresentationProfileFor(this.presentationBattleId)
|
||||
: undefined;
|
||||
return {
|
||||
battleId: profile?.battleId ?? this.presentationBattleId ?? null,
|
||||
arcId: profile?.arc.id ?? null,
|
||||
stage: this.presentationStage,
|
||||
washColor: profile?.arc.palette.washColor ?? null,
|
||||
washVisible: this.presentationWash?.visible ?? false
|
||||
};
|
||||
}
|
||||
|
||||
private progressActionLabel(isLastPage = this.pageIndex >= this.pages.length - 1) {
|
||||
if (!isLastPage) {
|
||||
return '다음';
|
||||
|
||||
@@ -882,7 +882,10 @@ export class TitleScene extends Phaser.Scene {
|
||||
soundDirector.resume();
|
||||
soundDirector.playSelect();
|
||||
startNewCampaign();
|
||||
void this.navigateTo('StoryScene');
|
||||
void this.navigateTo('StoryScene', {
|
||||
presentationBattleId: 'first-battle-zhuo-commandery',
|
||||
presentationStage: 'story'
|
||||
});
|
||||
}
|
||||
|
||||
private continueGame(slot?: number) {
|
||||
@@ -905,6 +908,18 @@ export class TitleScene extends Phaser.Scene {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (campaign.pendingAftermathBattleId) {
|
||||
const { battleScenarios } = await import('../data/battles');
|
||||
const aftermathScenario = battleScenarios[campaign.pendingAftermathBattleId];
|
||||
await this.navigateTo('StoryScene', {
|
||||
pages: aftermathScenario.victoryPages,
|
||||
nextScene: aftermathScenario.nextCampScene,
|
||||
nextSceneData: { completedAftermathBattleId: aftermathScenario.id },
|
||||
presentationBattleId: aftermathScenario.id,
|
||||
presentationStage: 'aftermath'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (campaign.step === 'ending-complete') {
|
||||
await this.navigateTo('EndingScene');
|
||||
return;
|
||||
@@ -931,11 +946,19 @@ export class TitleScene extends Phaser.Scene {
|
||||
|
||||
if (campaign.step === 'first-victory-story') {
|
||||
const { firstBattleVictoryPages } = await import('../data/scenario');
|
||||
await this.navigateTo('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
|
||||
await this.navigateTo('StoryScene', {
|
||||
pages: firstBattleVictoryPages,
|
||||
nextScene: 'TitleScene',
|
||||
presentationBattleId: 'first-battle-zhuo-commandery',
|
||||
presentationStage: 'aftermath'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.navigateTo('StoryScene');
|
||||
await this.navigateTo('StoryScene', {
|
||||
presentationBattleId: 'first-battle-zhuo-commandery',
|
||||
presentationStage: 'story'
|
||||
});
|
||||
}
|
||||
|
||||
private async navigateTo(sceneKey: 'StoryScene' | 'BattleScene' | 'CampScene' | 'EndingScene', data?: Record<string, unknown>) {
|
||||
|
||||
@@ -490,6 +490,7 @@ export type CampaignState = {
|
||||
dismissedVictoryRewardNoticeBattleIds: string[];
|
||||
battleHistory: Record<string, CampaignBattleSettlement>;
|
||||
latestBattleId?: string;
|
||||
pendingAftermathBattleId?: BattleScenarioId;
|
||||
firstBattleReport?: FirstBattleReport;
|
||||
};
|
||||
|
||||
@@ -606,6 +607,16 @@ const campaignStepProgressLabels: Partial<Record<CampaignStep, { title: string;
|
||||
};
|
||||
|
||||
export function summarizeCampaignProgress(state: CampaignState) {
|
||||
const pendingAftermath = state.pendingAftermathBattleId
|
||||
? state.battleHistory[state.pendingAftermathBattleId]
|
||||
: undefined;
|
||||
if (pendingAftermath?.outcome === 'victory') {
|
||||
return {
|
||||
title: pendingAftermath.battleTitle,
|
||||
meta: '승리 후 이야기'
|
||||
};
|
||||
}
|
||||
|
||||
const battleId = battleIdForCampaignStep(state.step);
|
||||
const battleTitle = battleId ? scenarioTitle(battleId) : undefined;
|
||||
if (battleId && battleTitle) {
|
||||
@@ -988,6 +999,7 @@ export function setFirstBattleReport(report: FirstBattleReport) {
|
||||
}
|
||||
state.step = retryStep;
|
||||
state.latestBattleId = battleId;
|
||||
delete state.pendingAftermathBattleId;
|
||||
state.updatedAt = new Date().toISOString();
|
||||
persistCampaignState(state);
|
||||
return cloneReport(reportClone);
|
||||
@@ -1036,6 +1048,7 @@ export function setFirstBattleReport(report: FirstBattleReport) {
|
||||
delete state.sortieRecommendationSelection;
|
||||
}
|
||||
state.latestBattleId = battleId;
|
||||
state.pendingAftermathBattleId = battleId as BattleScenarioId;
|
||||
state.updatedAt = new Date().toISOString();
|
||||
|
||||
persistCampaignState(state);
|
||||
@@ -1047,6 +1060,18 @@ export function getFirstBattleReport() {
|
||||
return report ? cloneReport(report) : undefined;
|
||||
}
|
||||
|
||||
export function completeCampaignAftermath(battleId: BattleScenarioId) {
|
||||
const state = ensureCampaignState();
|
||||
if (state.pendingAftermathBattleId !== battleId) {
|
||||
return cloneCampaignState(state);
|
||||
}
|
||||
|
||||
delete state.pendingAftermathBattleId;
|
||||
state.updatedAt = new Date().toISOString();
|
||||
persistCampaignState(state);
|
||||
return cloneCampaignState(state);
|
||||
}
|
||||
|
||||
export type CampaignVictoryRewardReport = Pick<
|
||||
FirstBattleReport,
|
||||
'battleId' | 'battleTitle' | 'outcome' | 'rewardGold' | 'itemRewards' | 'campaignRewards' | 'sortieOrder'
|
||||
@@ -1608,6 +1633,25 @@ function normalizeCampaignState(state: Partial<CampaignState>): CampaignState {
|
||||
);
|
||||
}
|
||||
normalized.latestBattleId = normalizeLatestBattleId(normalized.latestBattleId, normalized.battleHistory);
|
||||
const pendingAftermathBattleId = typeof normalized.pendingAftermathBattleId === 'string' &&
|
||||
normalized.pendingAftermathBattleId in battleScenarios
|
||||
? normalized.pendingAftermathBattleId as BattleScenarioId
|
||||
: undefined;
|
||||
const pendingAftermathStep = pendingAftermathBattleId
|
||||
? campaignBattleSteps[pendingAftermathBattleId]?.victory
|
||||
: undefined;
|
||||
if (
|
||||
pendingAftermathBattleId &&
|
||||
normalized.latestBattleId === pendingAftermathBattleId &&
|
||||
normalized.step === pendingAftermathStep &&
|
||||
normalized.firstBattleReport?.battleId === pendingAftermathBattleId &&
|
||||
normalized.firstBattleReport.outcome === 'victory' &&
|
||||
normalized.battleHistory[pendingAftermathBattleId]?.outcome === 'victory'
|
||||
) {
|
||||
normalized.pendingAftermathBattleId = pendingAftermathBattleId;
|
||||
} else {
|
||||
delete normalized.pendingAftermathBattleId;
|
||||
}
|
||||
if (normalized.firstBattleReport && sortieUnitFilter) {
|
||||
normalized.firstBattleReport = filterFirstBattleReportRosterReferences(normalized.firstBattleReport, sortieUnitFilter);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user