feat: carry camp promises into first victory
This commit is contained in:
299
src/game/data/firstBattleNarrativeMemory.ts
Normal file
299
src/game/data/firstBattleNarrativeMemory.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import type { StoryPage } from './scenario';
|
||||
import {
|
||||
sortieOrderCheckIdsByOrder,
|
||||
sortieOrderDisplayLabel,
|
||||
sortieOrderIds,
|
||||
type CampaignSortieOrderId,
|
||||
type CampaignSortieOrderProgressId,
|
||||
type CampaignSortieOrderProgressSnapshot
|
||||
} from './sortieOrders';
|
||||
|
||||
export const firstBattleNarrativeBattleId = 'first-battle-zhuo-commandery';
|
||||
export const firstBattleVolunteerPromiseEventKey = 'first-engagement';
|
||||
export const firstBattleVolunteerPromiseText =
|
||||
'젊은 의병 · 말씀대로 곁의 동료와 보조를 맞추겠습니다.';
|
||||
|
||||
const victoryMemoryPageIds = [
|
||||
'first-victory-guan-yu',
|
||||
'first-victory-zhang-fei'
|
||||
] as const;
|
||||
|
||||
const missedCriterionLabels: Record<Exclude<CampaignSortieOrderProgressId, 'victory'>, string> = {
|
||||
'elite-grade': '전투 평가',
|
||||
'elite-survival': '전원 생존',
|
||||
'mobile-quick': '제한 턴',
|
||||
'mobile-breakthrough': '돌파·의도 파훼',
|
||||
'siege-objective': '추가 목표',
|
||||
'siege-front': '전열 기여',
|
||||
'siege-support': '후원 기여'
|
||||
};
|
||||
|
||||
export type FirstBattleNarrativeReport = {
|
||||
battleId?: unknown;
|
||||
outcome?: unknown;
|
||||
turnNumber?: unknown;
|
||||
mvp?: {
|
||||
unitId?: unknown;
|
||||
name?: unknown;
|
||||
};
|
||||
sortieOrder?: {
|
||||
orderId?: unknown;
|
||||
achieved?: unknown;
|
||||
progress?: readonly {
|
||||
id?: unknown;
|
||||
achieved?: unknown;
|
||||
value?: unknown;
|
||||
target?: unknown;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
export type FirstBattleVictoryNarrativeFallbackReason =
|
||||
| 'missing-report'
|
||||
| 'battle-mismatch'
|
||||
| 'non-victory'
|
||||
| 'missing-order'
|
||||
| 'invalid-order-progress';
|
||||
|
||||
export type FirstBattleVictoryNarrativeMemory = {
|
||||
source: 'campaign' | 'fallback';
|
||||
fallbackReason: FirstBattleVictoryNarrativeFallbackReason | null;
|
||||
battleId: string | null;
|
||||
orderId: CampaignSortieOrderId | null;
|
||||
orderLabel: string | null;
|
||||
achieved: boolean | null;
|
||||
turnNumber: number | null;
|
||||
mvp: { unitId: string; name: string } | null;
|
||||
progress: CampaignSortieOrderProgressSnapshot[];
|
||||
achievedCriterionCount: number;
|
||||
criterionCount: number;
|
||||
missedCriterionIds: CampaignSortieOrderProgressId[];
|
||||
appliedPageIds: string[];
|
||||
};
|
||||
|
||||
export type FirstBattleVictoryNarrativeResolution = {
|
||||
pages: StoryPage[];
|
||||
memory: FirstBattleVictoryNarrativeMemory;
|
||||
};
|
||||
|
||||
export function volunteerPromiseLineForBattle(
|
||||
battleId: string,
|
||||
reassuredInCamp: boolean
|
||||
) {
|
||||
return battleId === firstBattleNarrativeBattleId && reassuredInCamp
|
||||
? firstBattleVolunteerPromiseText
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function resolveFirstBattleVictoryNarrative(
|
||||
sourcePages: readonly StoryPage[],
|
||||
report?: FirstBattleNarrativeReport | null
|
||||
): FirstBattleVictoryNarrativeResolution {
|
||||
const pages = sourcePages.map((page) => ({ ...page }));
|
||||
const fallback = fallbackReason(report);
|
||||
if (fallback) {
|
||||
return {
|
||||
pages,
|
||||
memory: fallbackMemory(report, fallback)
|
||||
};
|
||||
}
|
||||
|
||||
const order = report!.sortieOrder!;
|
||||
const orderId = order.orderId as CampaignSortieOrderId;
|
||||
const progress = normalizeProgress(orderId, order.progress!);
|
||||
const criterionProgress = progress.filter((entry) => entry.id !== 'victory');
|
||||
const missedCriterionIds = criterionProgress
|
||||
.filter((entry) => !entry.achieved)
|
||||
.map((entry) => entry.id);
|
||||
const orderLabel = sortieOrderDisplayLabel(firstBattleNarrativeBattleId, orderId);
|
||||
const turnNumber = positiveInteger(report!.turnNumber);
|
||||
const mvp = normalizeMvp(report!.mvp);
|
||||
const achieved = order.achieved === true;
|
||||
const textByPageId = new Map<string, string>([
|
||||
[
|
||||
victoryMemoryPageIds[0],
|
||||
guanYuVictoryMemoryText(orderLabel, achieved, missedCriterionIds, turnNumber)
|
||||
],
|
||||
[
|
||||
victoryMemoryPageIds[1],
|
||||
zhangFeiVictoryMemoryText(orderLabel, achieved, missedCriterionIds, turnNumber, mvp)
|
||||
]
|
||||
]);
|
||||
const appliedPageIds: string[] = [];
|
||||
|
||||
pages.forEach((page) => {
|
||||
const text = textByPageId.get(page.id);
|
||||
if (text) {
|
||||
page.text = text;
|
||||
appliedPageIds.push(page.id);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
pages,
|
||||
memory: {
|
||||
source: 'campaign',
|
||||
fallbackReason: null,
|
||||
battleId: firstBattleNarrativeBattleId,
|
||||
orderId,
|
||||
orderLabel,
|
||||
achieved,
|
||||
turnNumber,
|
||||
mvp,
|
||||
progress,
|
||||
achievedCriterionCount: criterionProgress.filter((entry) => entry.achieved).length,
|
||||
criterionCount: criterionProgress.length,
|
||||
missedCriterionIds,
|
||||
appliedPageIds
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function fallbackReason(
|
||||
report?: FirstBattleNarrativeReport | null
|
||||
): FirstBattleVictoryNarrativeFallbackReason | undefined {
|
||||
if (!report) {
|
||||
return 'missing-report';
|
||||
}
|
||||
if (report.battleId !== firstBattleNarrativeBattleId) {
|
||||
return 'battle-mismatch';
|
||||
}
|
||||
if (report.outcome !== 'victory') {
|
||||
return 'non-victory';
|
||||
}
|
||||
if (!report.sortieOrder) {
|
||||
return 'missing-order';
|
||||
}
|
||||
if (
|
||||
typeof report.sortieOrder.orderId !== 'string' ||
|
||||
!(sortieOrderIds as readonly string[]).includes(report.sortieOrder.orderId) ||
|
||||
typeof report.sortieOrder.achieved !== 'boolean' ||
|
||||
!Array.isArray(report.sortieOrder.progress)
|
||||
) {
|
||||
return 'invalid-order-progress';
|
||||
}
|
||||
const orderId = report.sortieOrder.orderId as CampaignSortieOrderId;
|
||||
const expectedIds = sortieOrderCheckIdsByOrder[orderId];
|
||||
const progressIds = new Set(
|
||||
report.sortieOrder.progress
|
||||
.filter((entry) => typeof entry?.id === 'string' && typeof entry.achieved === 'boolean')
|
||||
.map((entry) => entry.id)
|
||||
);
|
||||
return expectedIds.every((id) => progressIds.has(id))
|
||||
? undefined
|
||||
: 'invalid-order-progress';
|
||||
}
|
||||
|
||||
function fallbackMemory(
|
||||
report: FirstBattleNarrativeReport | null | undefined,
|
||||
reason: FirstBattleVictoryNarrativeFallbackReason
|
||||
): FirstBattleVictoryNarrativeMemory {
|
||||
return {
|
||||
source: 'fallback',
|
||||
fallbackReason: reason,
|
||||
battleId: typeof report?.battleId === 'string' ? report.battleId : null,
|
||||
orderId: null,
|
||||
orderLabel: null,
|
||||
achieved: null,
|
||||
turnNumber: positiveInteger(report?.turnNumber),
|
||||
mvp: normalizeMvp(report?.mvp),
|
||||
progress: [],
|
||||
achievedCriterionCount: 0,
|
||||
criterionCount: 0,
|
||||
missedCriterionIds: [],
|
||||
appliedPageIds: []
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeProgress(
|
||||
orderId: CampaignSortieOrderId,
|
||||
rawProgress: NonNullable<NonNullable<FirstBattleNarrativeReport['sortieOrder']>['progress']>
|
||||
) {
|
||||
return sortieOrderCheckIdsByOrder[orderId].map((id) => {
|
||||
const raw = rawProgress.find((entry) => entry?.id === id)!;
|
||||
return {
|
||||
id,
|
||||
achieved: raw.achieved === true,
|
||||
value: nonNegativeNumber(raw.value),
|
||||
target: nonNegativeNumber(raw.target)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeMvp(raw: FirstBattleNarrativeReport['mvp']) {
|
||||
const name = typeof raw?.name === 'string'
|
||||
? raw.name.trim().replace(/\s+/g, ' ').slice(0, 12)
|
||||
: '';
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
const unitId = typeof raw?.unitId === 'string'
|
||||
? raw.unitId.trim().slice(0, 80)
|
||||
: '';
|
||||
return { unitId, name };
|
||||
}
|
||||
|
||||
function positiveInteger(value: unknown) {
|
||||
const number = typeof value === 'number' ? value : Number.NaN;
|
||||
return Number.isFinite(number) && number > 0
|
||||
? Math.floor(number)
|
||||
: null;
|
||||
}
|
||||
|
||||
function nonNegativeNumber(value: unknown) {
|
||||
const number = typeof value === 'number' ? value : Number.NaN;
|
||||
return Number.isFinite(number) && number >= 0 ? number : 0;
|
||||
}
|
||||
|
||||
function missedCriterionSummary(ids: CampaignSortieOrderProgressId[]) {
|
||||
const firstId = ids.find((id): id is Exclude<CampaignSortieOrderProgressId, 'victory'> => (
|
||||
id !== 'victory'
|
||||
));
|
||||
if (!firstId) {
|
||||
return '전투의 완성도';
|
||||
}
|
||||
const additionalCount = ids.filter((id) => id !== 'victory').length - 1;
|
||||
return additionalCount > 0
|
||||
? `${missedCriterionLabels[firstId]} 외 ${additionalCount}가지`
|
||||
: missedCriterionLabels[firstId];
|
||||
}
|
||||
|
||||
function guanYuVictoryMemoryText(
|
||||
orderLabel: string,
|
||||
achieved: boolean,
|
||||
missedIds: CampaignSortieOrderProgressId[],
|
||||
turnNumber: number | null
|
||||
) {
|
||||
if (achieved) {
|
||||
const record = turnNumber
|
||||
? `${turnNumber}턴의 전황도 정리했습니다.`
|
||||
: '모두 함께 돌아온 것이 오늘의 가장 큰 전공입니다.';
|
||||
return `${orderLabel} 군령의 조건을 모두 지켰습니다. ${record}`;
|
||||
}
|
||||
const record = turnNumber
|
||||
? `${turnNumber}턴의 기록을 다음 출전에서 보완하겠습니다.`
|
||||
: '다음 출전에서는 그 약속부터 바로 세우겠습니다.';
|
||||
return `${orderLabel} 군령으로 승리했지만 남은 과제는 ${missedCriterionSummary(missedIds)}입니다. ${record}`;
|
||||
}
|
||||
|
||||
function zhangFeiVictoryMemoryText(
|
||||
orderLabel: string,
|
||||
achieved: boolean,
|
||||
missedIds: CampaignSortieOrderProgressId[],
|
||||
turnNumber: number | null,
|
||||
mvp: { unitId: string; name: string } | null
|
||||
) {
|
||||
const mvpLine = mvp && mvp.unitId !== 'zhang-fei'
|
||||
? `전공의 주인공은 ${mvp.name}요!`
|
||||
: '전공은 셋 모두의 몫이오!';
|
||||
if (achieved) {
|
||||
const turnLine = turnNumber ? `${turnNumber}턴 만에 길을 열었고, ` : '';
|
||||
return `하하, ${orderLabel} 군령까지 완수했소! ${turnLine}${mvpLine}`;
|
||||
}
|
||||
const subject = turnNumber
|
||||
? `${turnNumber}턴의 전공에`
|
||||
: mvp && mvp.unitId !== 'zhang-fei'
|
||||
? `${mvp.name}의 전공에`
|
||||
: '우리의 전공에';
|
||||
return `이기긴 했지만 ${orderLabel} 군령은 놓쳤구려. ${subject} ${missedCriterionSummary(missedIds)}까지 더해 다음에는 약속도 지키겠소!`;
|
||||
}
|
||||
@@ -285,6 +285,13 @@ export const prologueMilitiaCampNpcDefinitions: readonly PrologueMilitiaCampNpcD
|
||||
textureKey: 'unit-liu-bei',
|
||||
text: '두려움을 숨길 필요는 없네. 곁의 동료와 보조를 맞추고 위험하면 신호하게. 혼자 싸우게 두지 않겠네.'
|
||||
}
|
||||
],
|
||||
repeatDialogue: [
|
||||
{
|
||||
speaker: '젊은 의병',
|
||||
textureKey: 'unit-shu-infantry',
|
||||
text: '아직 떨리지만, 곁의 동료와 보조를 맞추겠습니다. 혼자 싸우지 않겠습니다.'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -116,6 +116,10 @@ import {
|
||||
sortieRecommendationDisplayUnitIds,
|
||||
type SortieRecommendationOutcome
|
||||
} from '../data/sortieRecommendationOutcome';
|
||||
import {
|
||||
firstBattleVolunteerPromiseEventKey,
|
||||
volunteerPromiseLineForBattle
|
||||
} from '../data/firstBattleNarrativeMemory';
|
||||
import {
|
||||
evaluateSortieOrder,
|
||||
evaluateSortieOrderProgress,
|
||||
@@ -3878,6 +3882,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
private launchSortieResonanceBondId?: string;
|
||||
private launchSortieRecommendation?: CampaignSortieRecommendationSnapshot;
|
||||
private firstBattlePreparation: FirstBattlePreparationState = { ...emptyFirstBattlePreparationState };
|
||||
private prologueVolunteerReassured = false;
|
||||
private coreResonancePursuitAttempts = 0;
|
||||
private coreResonancePursuitSuccesses = 0;
|
||||
private coreResonancePursuitFailures = 0;
|
||||
@@ -3982,6 +3987,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.firstBattleTutorialSkipButton = undefined;
|
||||
const campaign = getCampaignState();
|
||||
this.firstBattlePreparation = this.resolveFirstBattlePreparation(campaign);
|
||||
this.prologueVolunteerReassured = campaign.completedTutorialIds.includes(
|
||||
prologueMilitiaCampCampaignTutorialIds.reassureVolunteer
|
||||
);
|
||||
const selectedOrderId = campaign.sortieOrderSelection?.battleId === battleScenario.id
|
||||
? campaign.sortieOrderSelection.orderId
|
||||
: undefined;
|
||||
@@ -12168,10 +12176,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerBattleEvent('first-engagement', '첫 교전', [
|
||||
`${attacker.name}이 ${target.name}을 공격합니다.`,
|
||||
'공격 후 경험치와 장비 경험치가 전투창에서 정산됩니다.'
|
||||
], { playCue: false });
|
||||
this.triggerFirstEngagementEvent(attacker, target);
|
||||
this.phase = 'animating';
|
||||
const result = this.resolveCombatAction(attacker, target, action, false, usable);
|
||||
this.clearMarkers();
|
||||
@@ -12262,6 +12267,18 @@ export class BattleScene extends Phaser.Scene {
|
||||
return parts.join(' ') || '보정 없음';
|
||||
}
|
||||
|
||||
private triggerFirstEngagementEvent(attacker: UnitData, target: UnitData) {
|
||||
const volunteerPromiseLine = volunteerPromiseLineForBattle(
|
||||
battleScenario.id,
|
||||
this.prologueVolunteerReassured
|
||||
);
|
||||
this.triggerBattleEvent(firstBattleVolunteerPromiseEventKey, '첫 교전', [
|
||||
`${attacker.name} · ${target.name} 공격을 시작합니다.`,
|
||||
'공격 후 경험치와 장비 경험치가 전투창에서 정산됩니다.',
|
||||
...(volunteerPromiseLine ? [volunteerPromiseLine] : [])
|
||||
], { playCue: false });
|
||||
}
|
||||
|
||||
private battleBuffShortText(buff: BattleBuffState) {
|
||||
const parts = [
|
||||
buff.attackBonus > 0 ? `공+${buff.attackBonus}` : '',
|
||||
@@ -28790,6 +28807,31 @@ export class BattleScene extends Phaser.Scene {
|
||||
};
|
||||
}
|
||||
|
||||
private firstBattleVolunteerPromiseDebugState() {
|
||||
const line = volunteerPromiseLineForBattle(
|
||||
battleScenario.id,
|
||||
this.prologueVolunteerReassured
|
||||
);
|
||||
const eventState = this.triggeredBattleEvents.has(firstBattleVolunteerPromiseEventKey)
|
||||
? 'completed'
|
||||
: this.activeBattleEvent?.key === firstBattleVolunteerPromiseEventKey
|
||||
? 'active'
|
||||
: this.battleEventQueue.some((event) => event.key === firstBattleVolunteerPromiseEventKey)
|
||||
? 'queued'
|
||||
: 'waiting';
|
||||
return {
|
||||
completedInCamp: this.prologueVolunteerReassured,
|
||||
eligible: Boolean(line),
|
||||
eventKey: firstBattleVolunteerPromiseEventKey,
|
||||
line: line ?? null,
|
||||
eventState,
|
||||
bannerBounds:
|
||||
this.activeBattleEvent?.key === firstBattleVolunteerPromiseEventKey
|
||||
? this.gameObjectCollectionBoundsDebug(this.battleEventObjects.slice(1))
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
private firstBattleTutorialDebugState() {
|
||||
const path = this.firstBattleTutorialPath;
|
||||
return {
|
||||
@@ -28914,6 +28956,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
: null,
|
||||
battleHud: this.battleHudDebugState(),
|
||||
firstBattlePreparation: this.firstBattlePreparationDebugState(),
|
||||
firstBattleNarrativeMemory: {
|
||||
volunteerPromise: this.firstBattleVolunteerPromiseDebugState()
|
||||
},
|
||||
firstBattleTutorial: this.firstBattleTutorialDebugState(),
|
||||
combatCutIn: combatCutInDebug,
|
||||
combatCutInStage: combatCutInDebug,
|
||||
@@ -29305,6 +29350,23 @@ export class BattleScene extends Phaser.Scene {
|
||||
return battleUnits.find((unit) => unit.id === unitId);
|
||||
}
|
||||
|
||||
debugTriggerFirstEngagementEvent() {
|
||||
if (
|
||||
!this.debugToolsEnabled() ||
|
||||
this.battleOutcome ||
|
||||
this.isBattleEventKnown(firstBattleVolunteerPromiseEventKey)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const attacker = battleUnits.find((unit) => unit.faction === 'ally' && unit.hp > 0);
|
||||
const target = battleUnits.find((unit) => unit.faction === 'enemy' && unit.hp > 0);
|
||||
if (!attacker || !target) {
|
||||
return false;
|
||||
}
|
||||
this.triggerFirstEngagementEvent(attacker, target);
|
||||
return this.isBattleEventKnown(firstBattleVolunteerPromiseEventKey);
|
||||
}
|
||||
|
||||
debugBattleUnits() {
|
||||
return this.debugToolsEnabled() ? battleUnits : undefined;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,10 @@ const objectiveTutorialIds: Record<PrologueMilitiaCampRequiredObjectiveId, Campa
|
||||
'inspect-arms': prologueMilitiaCampCampaignTutorialIds.inspectArms
|
||||
};
|
||||
|
||||
const optionalDialogueTutorialIds: Partial<Record<string, CampaignTutorialId>> = {
|
||||
'nervous-volunteer': prologueMilitiaCampCampaignTutorialIds.reassureVolunteer
|
||||
};
|
||||
|
||||
function campDialoguePortraitEntries() {
|
||||
return campDialoguePortraitKeys
|
||||
.map((portraitKey) => portraitAssetEntryForStoryContext(
|
||||
@@ -408,6 +412,11 @@ export class PrologueMilitiaCampScene extends Phaser.Scene {
|
||||
})) ?? []
|
||||
};
|
||||
}),
|
||||
optionalDialogues: Object.entries(optionalDialogueTutorialIds).map(([npcId, tutorialId]) => ({
|
||||
npcId,
|
||||
tutorialId,
|
||||
completed: tutorialId ? hasCompletedCampaignTutorial(tutorialId) : false
|
||||
})),
|
||||
exitUnlocked: this.allRequiredObjectivesComplete(),
|
||||
npcs: Array.from(this.npcViews.values()).map(({ definition, sprite }) => ({
|
||||
id: definition.id,
|
||||
@@ -1538,9 +1547,12 @@ export class PrologueMilitiaCampScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
const optionalTutorialId = optionalDialogueTutorialIds[view.definition.id];
|
||||
const completed = view.definition.objectiveId
|
||||
? this.isObjectiveComplete(view.definition.objectiveId)
|
||||
: false;
|
||||
: optionalTutorialId
|
||||
? hasCompletedCampaignTutorial(optionalTutorialId)
|
||||
: false;
|
||||
const dialogue = completed && view.definition.repeatDialogue?.length
|
||||
? view.definition.repeatDialogue
|
||||
: view.definition.dialogue;
|
||||
@@ -1548,6 +1560,10 @@ export class PrologueMilitiaCampScene extends Phaser.Scene {
|
||||
if (view.definition.objectiveId && !completed) {
|
||||
this.completeObjective(view.definition.objectiveId);
|
||||
}
|
||||
if (optionalTutorialId && !completed) {
|
||||
completeCampaignTutorial(optionalTutorialId);
|
||||
this.showCompletionToast('젊은 의병이 약속을 기억합니다.');
|
||||
}
|
||||
}, view.definition.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,11 @@ import {
|
||||
resolveCampaignPresentationSoundscape,
|
||||
type CampaignPresentationStage
|
||||
} from '../data/campaignPresentationProfiles';
|
||||
import {
|
||||
firstBattleNarrativeBattleId,
|
||||
resolveFirstBattleVictoryNarrative,
|
||||
type FirstBattleVictoryNarrativeMemory
|
||||
} from '../data/firstBattleNarrativeMemory';
|
||||
import {
|
||||
ensureUnitAnimations,
|
||||
loadUnitBaseSheets,
|
||||
@@ -216,6 +221,7 @@ export class StoryScene extends Phaser.Scene {
|
||||
private storyAssetFailures = new Set<string>();
|
||||
private ownedStoryTextureKeys = new Set<string>();
|
||||
private rewardDisplay: StoryRewardDisplayModel = this.emptyRewardDisplay();
|
||||
private firstBattleNarrativeMemory?: FirstBattleVictoryNarrativeMemory;
|
||||
private visualMotionReduced = false;
|
||||
private storyEnvironmentProfile?: StoryEnvironmentProfile;
|
||||
private storyEnvironmentBackgroundKey?: string;
|
||||
@@ -231,12 +237,20 @@ export class StoryScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
init(data?: StorySceneData) {
|
||||
this.pages = data?.pages?.length ? data.pages : prologueOpeningPages();
|
||||
this.selectedStoryBackgroundKeys = storyBackgroundKeysForPages(this.pages);
|
||||
const sourcePages = data?.pages?.length ? data.pages : prologueOpeningPages();
|
||||
this.nextScene = data?.nextScene ?? 'PrologueVillageScene';
|
||||
this.nextSceneData = data?.nextSceneData;
|
||||
this.presentationBattleId = data?.presentationBattleId;
|
||||
this.presentationStage = data?.presentationStage ?? 'story';
|
||||
const firstBattleAftermath = (
|
||||
this.presentationBattleId === firstBattleNarrativeBattleId &&
|
||||
this.presentationStage === 'aftermath'
|
||||
)
|
||||
? resolveFirstBattleVictoryNarrative(sourcePages, getFirstBattleReport())
|
||||
: undefined;
|
||||
this.pages = firstBattleAftermath?.pages ?? sourcePages;
|
||||
this.firstBattleNarrativeMemory = firstBattleAftermath?.memory;
|
||||
this.selectedStoryBackgroundKeys = storyBackgroundKeysForPages(this.pages);
|
||||
this.presentationWash = undefined;
|
||||
this.pageIndex = 0;
|
||||
this.transitioning = false;
|
||||
@@ -279,6 +293,8 @@ export class StoryScene extends Phaser.Scene {
|
||||
pageIndex: this.pageIndex,
|
||||
totalPages: this.pages.length,
|
||||
currentPageId: page?.id,
|
||||
currentSpeaker: page?.speaker ?? null,
|
||||
currentText: this.bodyText?.text ?? page?.text ?? '',
|
||||
requestedBackground: page?.background,
|
||||
background: page ? this.pageBackgroundKey(page) : undefined,
|
||||
backgroundReady: page ? this.textures.exists(this.pageBackgroundKey(page)) : false,
|
||||
@@ -319,6 +335,20 @@ export class StoryScene extends Phaser.Scene {
|
||||
...this.rewardDisplay,
|
||||
items: this.rewardDisplay.items.map((item) => ({ ...item, icon: { ...item.icon } }))
|
||||
},
|
||||
firstBattleNarrativeMemory: this.firstBattleNarrativeMemory
|
||||
? {
|
||||
...this.firstBattleNarrativeMemory,
|
||||
mvp: this.firstBattleNarrativeMemory.mvp
|
||||
? { ...this.firstBattleNarrativeMemory.mvp }
|
||||
: null,
|
||||
progress: this.firstBattleNarrativeMemory.progress.map((entry) => ({ ...entry })),
|
||||
missedCriterionIds: [...this.firstBattleNarrativeMemory.missedCriterionIds],
|
||||
appliedPageIds: [...this.firstBattleNarrativeMemory.appliedPageIds],
|
||||
currentPageApplied: Boolean(
|
||||
page && this.firstBattleNarrativeMemory.appliedPageIds.includes(page.id)
|
||||
)
|
||||
}
|
||||
: null,
|
||||
environment: this.storyEnvironmentDebugState(),
|
||||
presentation: this.storyPresentationDebugState(),
|
||||
assetStreaming: {
|
||||
|
||||
@@ -479,6 +479,7 @@ export const prologueMilitiaCampCampaignTutorialIds = {
|
||||
reviewScoutReport: 'prologue-camp-review-scout-report',
|
||||
readyVanguard: 'prologue-camp-ready-vanguard',
|
||||
inspectArms: 'prologue-camp-inspect-arms',
|
||||
reassureVolunteer: 'prologue-camp-reassure-volunteer',
|
||||
complete: 'prologue-camp-complete'
|
||||
} as const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user