feat: complete campaign flow and optimize battle assets

This commit is contained in:
2026-07-19 21:31:07 +09:00
parent f83296db7b
commit 31e1396ac8
211 changed files with 4219 additions and 281 deletions

View File

@@ -7,6 +7,7 @@ import {
saveCombatPresentationMode
} from '../settings/combatPresentation';
import { battleIdForCampaignStep, isCampCampaignStep } from '../state/campaignRouting';
import { readCampaignBattleResume, type CampaignBattleResume } from '../state/battleSaveStorage';
import {
hasCampaignSave,
listCampaignSaveSlots,
@@ -283,7 +284,15 @@ export class TitleScene extends Phaser.Scene {
badge.add([background, title, detail, meta]);
}
private campaignSaveDetail(campaign: ReturnType<typeof loadCampaignState>, isEndingComplete: boolean) {
private campaignSaveDetail(
campaign: ReturnType<typeof loadCampaignState>,
isEndingComplete: boolean,
battleResume = readCampaignBattleResume(campaign)
) {
if (battleResume) {
const progress = summarizeCampaignProgress(campaign);
return this.compactSaveDetail(`전투 이어하기 · ${progress.title}`);
}
if (isEndingComplete) {
return this.compactSaveDetail(`${Object.keys(campaign.battleHistory).length}전 완료 · ${campaign.roster.length}명 합류`);
}
@@ -296,10 +305,21 @@ export class TitleScene extends Phaser.Scene {
return label.length > 19 ? `${label.slice(0, 18)}...` : label;
}
private campaignSaveMeta(campaign: ReturnType<typeof loadCampaignState>) {
private campaignSaveMeta(
campaign: ReturnType<typeof loadCampaignState>,
battleResume = readCampaignBattleResume(campaign)
) {
if (battleResume) {
return this.campaignBattleResumeMeta(battleResume);
}
return `슬롯 ${campaign.activeSaveSlot} · ${this.formatSaveUpdatedAt(campaign.updatedAt)}`;
}
private campaignBattleResumeMeta(resume: CampaignBattleResume) {
const faction = resume.activeFaction === 'ally' ? '아군 차례' : '적군 차례';
return `슬롯 ${resume.slot} · ${resume.turnNumber}턴 · ${faction}`;
}
private formatSaveUpdatedAt(updatedAt: string) {
const date = new Date(updatedAt);
if (Number.isNaN(date.getTime())) {
@@ -528,6 +548,7 @@ export class TitleScene extends Phaser.Scene {
const campaign = enabled ? loadCampaignState(slot.slot) : undefined;
const isEndingComplete = campaign?.step === 'ending-complete';
const battleResume = campaign ? readCampaignBattleResume(campaign, slot.slot) : undefined;
const title = this.add.text(-ui(122), -ui(12), `슬롯 ${slot.slot}`, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: uiPx(16),
@@ -538,7 +559,7 @@ export class TitleScene extends Phaser.Scene {
});
title.setOrigin(0, 0.5);
const detail = this.add.text(-ui(42), -ui(12), campaign ? this.campaignSaveDetail(campaign, isEndingComplete) : '저장 없음', {
const detail = this.add.text(-ui(42), -ui(12), campaign ? this.campaignSaveDetail(campaign, isEndingComplete, battleResume) : '저장 없음', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: uiPx(13),
color: enabled ? '#d8b15f' : '#747c86',
@@ -547,7 +568,7 @@ export class TitleScene extends Phaser.Scene {
});
detail.setOrigin(0, 0.5);
const meta = this.add.text(-ui(42), ui(12), campaign ? this.campaignSaveMeta(campaign) : '빈 슬롯', {
const meta = this.add.text(-ui(42), ui(12), campaign ? this.campaignSaveMeta(campaign, battleResume) : '빈 슬롯', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: uiPx(11),
color: enabled ? '#9fb0bf' : '#626b75',
@@ -759,6 +780,14 @@ export class TitleScene extends Phaser.Scene {
private async continueGameAsync(slot?: number) {
const campaign = loadCampaignState(slot);
const battleResume = readCampaignBattleResume(campaign, slot ?? campaign.activeSaveSlot);
if (battleResume) {
await this.navigateTo('BattleScene', {
battleId: battleResume.battleId,
resumeBattleSaveSlot: battleResume.slot
});
return;
}
if (campaign.step === 'ending-complete') {
await this.navigateTo('EndingScene');
return;