Solidify campaign progression state

This commit is contained in:
2026-06-22 17:05:59 +09:00
parent 797051ac9f
commit 635b3a7a81
6 changed files with 369 additions and 31 deletions

View File

@@ -8,6 +8,7 @@ import {
type UnitData,
type UnitStats
} from '../data/scenario';
import { firstBattleScenario } from '../data/battles';
import { getTerrainRule, getUnitClass, type TerrainType, type UnitClassKey } from '../data/battleRules';
import {
equipmentExpToNext,
@@ -16,7 +17,7 @@ import {
getItem,
type EquipmentSlot
} from '../data/battleItems';
import { setFirstBattleReport } from '../state/campaignState';
import { getCampaignState, markCampaignStep, saveCampaignState, setFirstBattleReport, type CampaignStep } from '../state/campaignState';
import { palette } from '../ui/palette';
const unitTexture: Record<string, string> = {
@@ -269,6 +270,8 @@ type SavedUnitState = Pick<UnitData, 'id' | 'level' | 'exp' | 'hp' | 'maxHp' | '
type BattleSaveState = {
version: 1;
battleId: string;
campaignStep?: CampaignStep;
savedAt: string;
turnNumber: number;
activeFaction: ActiveFaction;
@@ -286,7 +289,9 @@ type BattleSaveState = {
const maxEquipmentLevel = 9;
const maxCharacterLevel = 99;
const battleSaveStorageKey = 'heros-web:first-battle-state';
const battleScenario = firstBattleScenario;
const legacyBattleSaveStorageKey = 'heros-web:first-battle-state';
const battleSaveStorageKey = `heros-web:battle:${battleScenario.id}`;
const initialFirstBattleUnits = firstBattleUnits.map(cloneUnitData);
const initialFirstBattleBonds = firstBattleBonds.map(cloneBattleBond);
@@ -413,9 +418,9 @@ const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {
};
const guardDetectionRange = 5;
const leaderUnitId = 'rebel-leader';
const quickVictoryTurnLimit = 8;
const baseVictoryGold = 300;
const leaderUnitId = battleScenario.leaderUnitId;
const quickVictoryTurnLimit = battleScenario.quickVictoryTurnLimit;
const baseVictoryGold = battleScenario.baseVictoryGold;
const commandLabels: Record<BattleCommand, string> = {
attack: '공격',
@@ -529,6 +534,10 @@ export class BattleScene extends Phaser.Scene {
create() {
this.resetBattleData();
const campaign = getCampaignState();
if (campaign.step === 'new' || campaign.step === 'prologue' || campaign.step === 'first-battle') {
markCampaignStep('first-battle');
}
const { width, height } = this.scale;
this.layout = this.createLayout(width, height);
const startUnit = firstBattleUnits.find((unit) => unit.id === 'liu-bei') ?? firstBattleUnits[0];
@@ -625,7 +634,7 @@ export class BattleScene extends Phaser.Scene {
this.mapMask = maskShape.createGeometryMask();
const mapMask = this.mapMask;
const background = this.add.image(0, 0, 'battle-map-first');
const background = this.add.image(0, 0, battleScenario.mapTextureKey);
background.setOrigin(0);
background.setDepth(0);
background.setDisplaySize(firstBattleMap.width * layout.tileSize, firstBattleMap.height * layout.tileSize);
@@ -1912,7 +1921,7 @@ export class BattleScene extends Phaser.Scene {
defeats: mvp.stats.defeats
}
: undefined,
itemRewards: outcome === 'victory' ? ['콩 1', '탁주 1', '의용군 명성 +1'] : [],
itemRewards: outcome === 'victory' ? [...battleScenario.itemRewards] : [],
completedCampDialogues: [],
createdAt: new Date().toISOString()
});
@@ -2838,13 +2847,15 @@ export class BattleScene extends Phaser.Scene {
}
private hasBattleSave() {
return Boolean(window.localStorage.getItem(battleSaveStorageKey));
return Boolean(window.localStorage.getItem(battleSaveStorageKey) ?? window.localStorage.getItem(legacyBattleSaveStorageKey));
}
private saveBattleState() {
try {
const state = this.createBattleSaveState();
window.localStorage.setItem(battleSaveStorageKey, JSON.stringify(state));
window.localStorage.removeItem(legacyBattleSaveStorageKey);
saveCampaignState();
this.renderSituationPanel(`전투 상태를 저장했습니다.\n${this.formatSavedAt(state.savedAt)}`);
} catch {
this.renderSituationPanel('전투 상태를 저장하지 못했습니다.');
@@ -2852,7 +2863,7 @@ export class BattleScene extends Phaser.Scene {
}
private loadBattleState() {
const raw = window.localStorage.getItem(battleSaveStorageKey);
const raw = window.localStorage.getItem(battleSaveStorageKey) ?? window.localStorage.getItem(legacyBattleSaveStorageKey);
if (!raw) {
this.renderSituationPanel('불러올 저장 데이터가 없습니다.');
return;
@@ -2863,6 +2874,9 @@ export class BattleScene extends Phaser.Scene {
if (!state || state.version !== 1 || !Array.isArray(state.units)) {
throw new Error('Invalid battle save');
}
if (state.battleId && state.battleId !== battleScenario.id) {
throw new Error('Battle save does not belong to this scenario');
}
this.applyBattleSaveState(state);
this.renderSituationPanel(`저장된 전투를 불러왔습니다.\n${this.formatSavedAt(state.savedAt)}`);
} catch {
@@ -2873,6 +2887,8 @@ export class BattleScene extends Phaser.Scene {
private createBattleSaveState(): BattleSaveState {
return {
version: 1,
battleId: battleScenario.id,
campaignStep: getCampaignState().step,
savedAt: new Date().toISOString(),
turnNumber: this.turnNumber,
activeFaction: this.activeFaction,
@@ -5366,6 +5382,7 @@ export class BattleScene extends Phaser.Scene {
getDebugState() {
return {
scene: this.scene.key,
battleId: battleScenario.id,
turnNumber: this.turnNumber,
activeFaction: this.activeFaction,
phase: this.phase,