feat: complete campaign flow and optimize battle assets
This commit is contained in:
@@ -175,7 +175,9 @@ import {
|
||||
ensureCampaignRosterUnits,
|
||||
getCampaignState,
|
||||
getFirstBattleReport,
|
||||
getPendingCampaignVictoryRewardBattleIds,
|
||||
getPendingCampaignVictoryRewardReport,
|
||||
getPendingCampaignVictoryRewardCategories,
|
||||
markCampaignStep,
|
||||
listCampaignSaveSlots,
|
||||
campaignSaveSlotCount,
|
||||
@@ -195,8 +197,12 @@ import {
|
||||
type CampaignSortiePerformanceSnapshot,
|
||||
type CampaignSortiePresetId,
|
||||
type CampaignSortieRecommendationSnapshot,
|
||||
type CampaignVictoryRewardCategoryId,
|
||||
type CampaignVictoryRewardReport,
|
||||
type FirstBattleReport
|
||||
} from '../state/campaignState';
|
||||
import { clearCampaignBattleSavesForSlot } from '../state/battleSaveKeys';
|
||||
import { releaseTextureSource } from '../render/loaderLifecycle';
|
||||
import { palette } from '../ui/palette';
|
||||
import { startLazyScene } from './lazyScenes';
|
||||
|
||||
@@ -255,7 +261,7 @@ type CampTabButtonView = {
|
||||
hovered: boolean;
|
||||
};
|
||||
|
||||
type VictoryRewardCardId = 'gold' | 'equipment' | 'supplies' | 'reputation' | 'recruits' | 'unlocks';
|
||||
type VictoryRewardCardId = CampaignVictoryRewardCategoryId;
|
||||
|
||||
type VictoryRewardActionId = 'equipment' | 'supplies' | 'sortie' | 'close';
|
||||
|
||||
@@ -11276,6 +11282,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private campSkinTransitionRevision = 0;
|
||||
private campSkinTransitionCompletedRevision = 0;
|
||||
private campSkinTransitionActive = false;
|
||||
private ownedCampTextureKeys = new Set<string>();
|
||||
private campSkinTransitionLast?: {
|
||||
kind: 'backdrop-fade';
|
||||
durationMs: number;
|
||||
@@ -11310,7 +11317,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private saveSlotObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private saveSlotConfirmObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private pendingSaveSlot?: number;
|
||||
private pendingVictoryRewardReport?: FirstBattleReport;
|
||||
private pendingVictoryRewardReport?: CampaignVictoryRewardReport;
|
||||
private victoryRewardObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private victoryRewardCards: VictoryRewardCardView[] = [];
|
||||
private victoryRewardActions: VictoryRewardActionView[] = [];
|
||||
@@ -11382,6 +11389,7 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
init(data?: CampSceneData) {
|
||||
this.ownedCampTextureKeys.clear();
|
||||
this.openSortiePrepOnCreate = Boolean(data?.openSortiePrep);
|
||||
this.openSortieImprovementOnCreate = Boolean(data?.openSortieImprovement);
|
||||
this.retrySortieBattleId = data?.retryBattleId;
|
||||
@@ -11389,6 +11397,7 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
create() {
|
||||
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => this.releaseOwnedCampTextures());
|
||||
this.contentObjects = [];
|
||||
this.campRosterPage = 0;
|
||||
this.campRosterLayout = undefined;
|
||||
@@ -11667,15 +11676,27 @@ export class CampScene extends Phaser.Scene {
|
||||
});
|
||||
|
||||
if (missingCampBackdrop) {
|
||||
this.ownedCampTextureKeys.add(selection.skin.textureKey);
|
||||
this.load.image(selection.skin.textureKey, selection.skin.assetUrl);
|
||||
}
|
||||
if (missingFirstSortieArtwork) {
|
||||
this.ownedCampTextureKeys.add('story-first-sortie');
|
||||
this.load.image('story-first-sortie', storyFirstSortieUrl);
|
||||
}
|
||||
missingPortraits.forEach(({ textureKey, url }) => this.load.image(textureKey, url));
|
||||
missingPortraits.forEach(({ textureKey, url }) => {
|
||||
this.ownedCampTextureKeys.add(textureKey);
|
||||
this.load.image(textureKey, url);
|
||||
});
|
||||
this.load.start();
|
||||
}
|
||||
|
||||
private releaseOwnedCampTextures() {
|
||||
this.ownedCampTextureKeys.forEach((textureKey) => {
|
||||
releaseTextureSource(this, textureKey);
|
||||
});
|
||||
this.ownedCampTextureKeys.clear();
|
||||
}
|
||||
|
||||
private createFallbackReport(): FirstBattleReport {
|
||||
const allies = firstBattleUnits.filter((unit) => unit.faction === 'ally');
|
||||
return {
|
||||
@@ -12944,6 +12965,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.sortiePresetUndoState = undefined;
|
||||
this.closeSortiePresetBrowserState();
|
||||
this.hideCampSaveSlotPanel();
|
||||
clearCampaignBattleSavesForSlot(slot);
|
||||
this.campaign = saveCampaignState(getCampaignState(), slot);
|
||||
if (returnToSortiePrep) {
|
||||
this.showSortiePrep();
|
||||
@@ -12992,15 +13014,28 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private showVictoryRewardAcknowledgement() {
|
||||
const report = this.pendingVictoryRewardReport ?? getPendingCampaignVictoryRewardReport();
|
||||
let report = this.pendingVictoryRewardReport ?? getPendingCampaignVictoryRewardReport();
|
||||
if (!report || report.outcome !== 'victory' || this.sortieObjects.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingVictoryRewardReport = report;
|
||||
this.hideVictoryRewardAcknowledgement();
|
||||
getPendingCampaignVictoryRewardBattleIds().forEach((battleId) => {
|
||||
const passiveCategories = getPendingCampaignVictoryRewardCategories(battleId)
|
||||
.filter((category) => category === 'gold' || category === 'reputation');
|
||||
if (passiveCategories.length > 0) {
|
||||
this.campaign = acknowledgeCampaignVictoryReward(battleId, passiveCategories);
|
||||
}
|
||||
});
|
||||
report = getPendingCampaignVictoryRewardReport();
|
||||
if (!report) {
|
||||
this.pendingVictoryRewardReport = undefined;
|
||||
return;
|
||||
}
|
||||
this.pendingVictoryRewardReport = report;
|
||||
const rewards = report.campaignRewards;
|
||||
const presentation = campaignVictoryRewardPresentation(report);
|
||||
const pendingCategorySet = new Set(getPendingCampaignVictoryRewardCategories(report.battleId));
|
||||
const categorizedRewardItems = new Set([
|
||||
...(rewards?.supplies ?? []),
|
||||
...(rewards?.equipment ?? []),
|
||||
@@ -13097,7 +13132,7 @@ export class CampScene extends Phaser.Scene {
|
||||
icon: equipmentItem ? { kind: 'item', itemId: equipmentItem.id } : { kind: 'battle-ui', iconKey: 'sword' },
|
||||
actionId: 'equipment',
|
||||
actionHint: '눌러서 장비 비교',
|
||||
isNew: Boolean(equipmentReward)
|
||||
isNew: Boolean(equipmentReward && pendingCategorySet.has('equipment'))
|
||||
},
|
||||
{
|
||||
id: 'supplies',
|
||||
@@ -13106,7 +13141,8 @@ export class CampScene extends Phaser.Scene {
|
||||
accent: palette.green,
|
||||
icon: { kind: 'battle-ui', iconKey: supply?.usableId ?? 'bean' },
|
||||
actionId: 'supplies',
|
||||
actionHint: '눌러서 보급 확인'
|
||||
actionHint: '눌러서 보급 확인',
|
||||
isNew: pendingCategorySet.has('supplies')
|
||||
},
|
||||
{
|
||||
id: 'reputation',
|
||||
@@ -13124,7 +13160,7 @@ export class CampScene extends Phaser.Scene {
|
||||
icon: recruit ? { kind: 'portrait', unitId: recruit.unitId } : { kind: 'battle-ui', iconKey: 'leadership' },
|
||||
actionId: 'sortie',
|
||||
actionHint: '눌러서 편성에 배치',
|
||||
isNew: Boolean(recruit)
|
||||
isNew: Boolean(recruit && pendingCategorySet.has('recruits'))
|
||||
},
|
||||
{
|
||||
id: 'unlocks',
|
||||
@@ -13134,7 +13170,7 @@ export class CampScene extends Phaser.Scene {
|
||||
icon: { kind: 'battle-ui', iconKey: 'terrain' },
|
||||
actionId: 'sortie',
|
||||
actionHint: '눌러서 다음 전장 준비',
|
||||
isNew: Boolean(rewards?.unlocks.length)
|
||||
isNew: Boolean(rewards?.unlocks.length && pendingCategorySet.has('unlocks'))
|
||||
}
|
||||
];
|
||||
const cardWidth = 252;
|
||||
@@ -13200,7 +13236,7 @@ export class CampScene extends Phaser.Scene {
|
||||
background.setStrokeStyle(2, definition.accent, 0.78);
|
||||
iconPlate.setStrokeStyle(1, definition.accent, 0.72);
|
||||
});
|
||||
background.on('pointerdown', () => this.completeVictoryRewardAcknowledgement(definition.actionId!));
|
||||
background.on('pointerdown', () => this.completeVictoryRewardAcknowledgement(definition.actionId!, [definition.id]));
|
||||
}
|
||||
this.victoryRewardCards.push({
|
||||
id: definition.id,
|
||||
@@ -13260,7 +13296,10 @@ export class CampScene extends Phaser.Scene {
|
||||
label.setDepth(depth + 3);
|
||||
label.setInteractive({ useHandCursor: true });
|
||||
|
||||
const run = () => this.completeVictoryRewardAcknowledgement(definition.id);
|
||||
const run = () => this.completeVictoryRewardAcknowledgement(
|
||||
definition.id,
|
||||
this.victoryRewardCategoriesForAction(definition.id)
|
||||
);
|
||||
[button, label].forEach((target) => {
|
||||
target.on('pointerover', () => button.setFillStyle(hoverFill, 0.99));
|
||||
target.on('pointerout', () => button.setFillStyle(restingFill, 0.99));
|
||||
@@ -13270,7 +13309,10 @@ export class CampScene extends Phaser.Scene {
|
||||
});
|
||||
}
|
||||
|
||||
private completeVictoryRewardAcknowledgement(action: VictoryRewardActionId) {
|
||||
private completeVictoryRewardAcknowledgement(
|
||||
action: VictoryRewardActionId,
|
||||
categories = this.victoryRewardCategoriesForAction(action)
|
||||
) {
|
||||
const report = this.pendingVictoryRewardReport;
|
||||
const rewardedRecruitId = report?.campaignRewards?.recruits[0]?.unitId;
|
||||
if (action === 'sortie' && rewardedRecruitId && this.sortieAllies().some((unit) => unit.id === rewardedRecruitId)) {
|
||||
@@ -13279,12 +13321,7 @@ export class CampScene extends Phaser.Scene {
|
||||
if (action === 'equipment' && report) {
|
||||
this.focusVictoryRewardEquipment(report);
|
||||
}
|
||||
if (report) {
|
||||
this.campaign = acknowledgeCampaignVictoryReward(report.battleId);
|
||||
if (this.campaign.acknowledgedVictoryRewardBattleIds.includes(report.battleId)) {
|
||||
this.pendingVictoryRewardReport = undefined;
|
||||
}
|
||||
}
|
||||
this.acknowledgePendingVictoryRewardCategories(categories);
|
||||
soundDirector.playSelect();
|
||||
this.hideVictoryRewardAcknowledgement();
|
||||
this.updateTabButtons();
|
||||
@@ -13304,6 +13341,33 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
}
|
||||
|
||||
private victoryRewardCategoriesForAction(action: VictoryRewardActionId): CampaignVictoryRewardCategoryId[] {
|
||||
if (action === 'equipment') {
|
||||
return ['equipment'];
|
||||
}
|
||||
if (action === 'supplies') {
|
||||
return ['supplies'];
|
||||
}
|
||||
if (action === 'sortie') {
|
||||
return ['recruits', 'unlocks'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private acknowledgePendingVictoryRewardCategories(categories: readonly CampaignVictoryRewardCategoryId[]) {
|
||||
if (categories.length === 0) {
|
||||
return;
|
||||
}
|
||||
getPendingCampaignVictoryRewardBattleIds().forEach((battleId) => {
|
||||
const relevantCategories = getPendingCampaignVictoryRewardCategories(battleId)
|
||||
.filter((category) => categories.includes(category));
|
||||
if (relevantCategories.length > 0) {
|
||||
this.campaign = acknowledgeCampaignVictoryReward(battleId, relevantCategories);
|
||||
}
|
||||
});
|
||||
this.pendingVictoryRewardReport = getPendingCampaignVictoryRewardReport();
|
||||
}
|
||||
|
||||
private showRequestedSortiePrep() {
|
||||
if (this.openSortieImprovementOnCreate) {
|
||||
this.prepareGuidedSortieImprovement();
|
||||
@@ -13332,7 +13396,7 @@ export class CampScene extends Phaser.Scene {
|
||||
return campSupplyByLabel.get(this.rewardInventoryLabel(label));
|
||||
}
|
||||
|
||||
private focusVictoryRewardEquipment(report: FirstBattleReport) {
|
||||
private focusVictoryRewardEquipment(report: CampaignVictoryRewardReport) {
|
||||
const rewardItemIds = new Set(
|
||||
(report.campaignRewards?.equipment ?? [])
|
||||
.map((label) => this.itemForRewardLabel(label)?.id)
|
||||
@@ -13540,6 +13604,7 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private updateTabButtons() {
|
||||
const accentColor = this.campSkinSelection?.skin.accentColor ?? palette.gold;
|
||||
const pendingCategories = new Set(getPendingCampaignVictoryRewardCategories());
|
||||
this.tabButtons.forEach(({ tab, bg, text, indicator, newBadge, hovered }) => {
|
||||
const active = this.activeTab === tab;
|
||||
bg.setFillStyle(active ? 0x2b4052 : hovered ? 0x243747 : 0x182431, active || hovered ? 0.98 : 0.94);
|
||||
@@ -13549,7 +13614,8 @@ export class CampScene extends Phaser.Scene {
|
||||
indicator.setVisible(active || hovered);
|
||||
indicator.setAlpha(active ? 1 : 0.66);
|
||||
newBadge?.setVisible(Boolean(
|
||||
this.pendingVictoryRewardReport && (tab === 'supplies' || tab === 'equipment')
|
||||
(tab === 'supplies' && pendingCategories.has('supplies')) ||
|
||||
(tab === 'equipment' && pendingCategories.has('equipment'))
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -13586,6 +13652,11 @@ export class CampScene extends Phaser.Scene {
|
||||
this.hideReportFormationHistory();
|
||||
this.hideCampSaveSlotPanel();
|
||||
this.clearContent();
|
||||
if (this.activeTab === 'equipment') {
|
||||
this.acknowledgePendingVictoryRewardCategories(['equipment']);
|
||||
} else if (this.activeTab === 'supplies') {
|
||||
this.acknowledgePendingVictoryRewardCategories(['supplies']);
|
||||
}
|
||||
this.visitedTabs.add(this.activeTab);
|
||||
this.updateTabButtons();
|
||||
this.renderUnitColumn();
|
||||
@@ -13621,6 +13692,7 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private showSortiePrep() {
|
||||
const wasVisible = this.sortieObjects.length > 0;
|
||||
this.acknowledgePendingVictoryRewardCategories(['recruits', 'unlocks']);
|
||||
this.hideReportFormationReview();
|
||||
this.hideReportFormationHistory();
|
||||
this.hideCampSaveSlotPanel();
|
||||
|
||||
Reference in New Issue
Block a user