Add camp save slot selection

This commit is contained in:
2026-07-05 02:54:02 +09:00
parent 2de9f15e7d
commit 1171b122d6

View File

@@ -103,8 +103,11 @@ import {
getCampaignState,
getFirstBattleReport,
markCampaignStep,
listCampaignSaveSlots,
campaignSaveSlotCount,
saveCampaignState,
setCampaignReserveTrainingFocus,
type CampaignSaveSlotSummary,
type CampaignStep,
type CampaignState,
type CampaignReserveTrainingFocusDefinition,
@@ -10713,6 +10716,7 @@ export class CampScene extends Phaser.Scene {
private contentObjects: Phaser.GameObjects.GameObject[] = [];
private dialogueObjects: Phaser.GameObjects.GameObject[] = [];
private sortieObjects: Phaser.GameObjects.GameObject[] = [];
private saveSlotObjects: Phaser.GameObjects.GameObject[] = [];
private tabButtons: CampTabButtonView[] = [];
private visitedTabs = new Set<CampTab>();
private selectedSortieUnitIds: string[] = [];
@@ -10737,6 +10741,7 @@ export class CampScene extends Phaser.Scene {
this.contentObjects = [];
this.dialogueObjects = [];
this.sortieObjects = [];
this.saveSlotObjects = [];
this.tabButtons = [];
this.visitedTabs = new Set(['status']);
this.campaign = getCampaignState();
@@ -11825,9 +11830,7 @@ export class CampScene extends Phaser.Scene {
this.addTabButton('연표', 'progress', 966, 38);
this.addCommandButton('저장', 1044, 38, 76, () => {
soundDirector.playSelect();
this.campaign = saveCampaignState(getCampaignState());
this.showCampNotice(`슬롯 ${this.campaign.activeSaveSlot}에 군영 진행을 저장했습니다.`);
this.render();
this.showCampSaveSlotPanel();
});
this.addCommandButton('다음 이야기', 1160, 38, 142, () => {
soundDirector.playSelect();
@@ -11840,6 +11843,141 @@ export class CampScene extends Phaser.Scene {
});
}
private showCampSaveSlotPanel() {
if (this.saveSlotObjects.length > 0) {
this.hideCampSaveSlotPanel();
return;
}
const depth = 48;
const width = 430;
const height = 252;
const left = this.scale.width - width - 36;
const top = 70;
const activeSlot = this.campaign?.activeSaveSlot ?? getCampaignState().activeSaveSlot;
const slots = listCampaignSaveSlots();
const panel = this.add.rectangle(left, top, width, height, 0x0f1720, 0.98);
panel.setOrigin(0);
panel.setDepth(depth);
panel.setStrokeStyle(2, palette.gold, 0.86);
panel.setInteractive();
this.saveSlotObjects.push(panel);
const title = this.add.text(left + 22, top + 16, '군영 저장 슬롯', this.textStyle(22, '#f2e3bf', true));
title.setDepth(depth + 1);
this.saveSlotObjects.push(title);
const guide = this.add.text(
left + 22,
top + 48,
'현재 군영 진행, 보유 장비, 편성 정보를 선택한 슬롯에 저장합니다.',
this.textStyle(13, '#aeb7c2')
);
guide.setDepth(depth + 1);
this.saveSlotObjects.push(guide);
for (let slot = 1; slot <= campaignSaveSlotCount; slot += 1) {
const summary = slots.find((candidate) => candidate.slot === slot) ?? { slot, exists: false };
this.renderCampSaveSlotRow(summary, activeSlot, left + 18, top + 82 + (slot - 1) * 52, width - 36, depth + 1);
}
const close = this.add.text(left + width - 58, top + 18, '닫기', this.textStyle(14, '#d8b15f', true));
close.setDepth(depth + 2);
close.setInteractive({ useHandCursor: true });
close.on('pointerdown', () => {
soundDirector.playSelect();
this.hideCampSaveSlotPanel();
});
this.saveSlotObjects.push(close);
}
private renderCampSaveSlotRow(
summary: CampaignSaveSlotSummary,
activeSlot: number,
x: number,
y: number,
width: number,
depth: number
) {
const active = summary.slot === activeSlot;
const row = this.add.rectangle(x, y, width, 44, active ? 0x263a2d : 0x172230, active ? 0.98 : 0.94);
row.setOrigin(0);
row.setDepth(depth);
row.setStrokeStyle(1, active ? palette.gold : palette.blue, active ? 0.86 : 0.62);
this.saveSlotObjects.push(row);
const title = this.add.text(x + 14, y + 8, `슬롯 ${summary.slot}`, this.textStyle(15, '#f2e3bf', true));
title.setDepth(depth + 1);
this.saveSlotObjects.push(title);
const detail = this.add.text(
x + 92,
y + 7,
summary.exists ? this.campSaveSlotDetail(summary) : '비어 있음',
this.textStyle(12, summary.exists ? '#d4dce6' : '#87919c', summary.exists)
);
detail.setDepth(depth + 1);
this.saveSlotObjects.push(detail);
const meta = this.add.text(
x + 92,
y + 24,
summary.exists ? this.formatCampSaveUpdatedAt(summary.updatedAt) : '새 저장으로 사용',
this.textStyle(10, summary.exists ? '#9fb0bf' : '#d8b15f', true)
);
meta.setDepth(depth + 1);
this.saveSlotObjects.push(meta);
const saveLabel = this.add.text(x + width - 50, y + 13, active ? '현재' : '저장', this.textStyle(12, active ? '#a8ffd0' : '#ffdf7b', true));
saveLabel.setOrigin(0.5, 0);
saveLabel.setDepth(depth + 1);
this.saveSlotObjects.push(saveLabel);
const run = () => this.saveCampToSlot(summary.slot);
[row, title, detail, meta, saveLabel].forEach((object) => {
object.setInteractive({ useHandCursor: true });
object.on('pointerdown', run);
});
row.on('pointerover', () => row.setFillStyle(active ? 0x314a38 : 0x263647, 0.98));
row.on('pointerout', () => row.setFillStyle(active ? 0x263a2d : 0x172230, active ? 0.98 : 0.94));
}
private campSaveSlotDetail(summary: CampaignSaveSlotSummary) {
const battleTitle = summary.battleTitle ?? '군영 진행';
return this.compactText(`군자금 ${summary.gold ?? 0} · ${battleTitle}`, 28);
}
private formatCampSaveUpdatedAt(updatedAt?: string) {
if (!updatedAt) {
return '저장 시각 없음';
}
const date = new Date(updatedAt);
if (Number.isNaN(date.getTime())) {
return '저장 시각 미상';
}
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
return `${month}/${day} ${hour}:${minute}`;
}
private saveCampToSlot(slot: number) {
soundDirector.playSelect();
this.hideCampSaveSlotPanel();
this.campaign = saveCampaignState(getCampaignState(), slot);
this.showCampNotice(`슬롯 ${this.campaign.activeSaveSlot}에 군영 진행을 저장했습니다.`);
this.render();
}
private hideCampSaveSlotPanel() {
this.saveSlotObjects.forEach((object) => object.destroy());
this.saveSlotObjects = [];
}
private addTabButton(label: string, tab: CampTab, x: number, y: number) {
const bg = this.add.rectangle(x, y, 76, 34, 0x182431, 0.94);
bg.setStrokeStyle(1, palette.blue, 0.62);