fix: harden campaign resume UX and flow verification

This commit is contained in:
2026-07-21 09:59:48 +09:00
parent be5fe3e880
commit c810f95d00
11 changed files with 1815 additions and 125 deletions

View File

@@ -172,11 +172,12 @@ import {
campaignSortiePresetIds,
campaignReserveTrainingFocusDefinitions,
defaultCampaignReserveTrainingFocusId,
dismissCampaignVictoryRewardNotice,
ensureCampaignRosterUnits,
getCampaignState,
getFirstBattleReport,
getPendingCampaignVictoryRewardBattleIds,
getPendingCampaignVictoryRewardReport,
getPendingCampaignVictoryRewardNoticeReport,
getPendingCampaignVictoryRewardCategories,
markCampaignStep,
listCampaignSaveSlots,
@@ -11476,7 +11477,7 @@ export class CampScene extends Phaser.Scene {
this.visitedTabs = new Set(['status']);
this.campaign = getCampaignState();
this.report = this.campaign.firstBattleReport ?? getFirstBattleReport() ?? this.createFallbackReport();
this.pendingVictoryRewardReport = getPendingCampaignVictoryRewardReport();
this.pendingVictoryRewardReport = getPendingCampaignVictoryRewardNoticeReport();
if (!this.retrySortieBattleId) {
this.ensureCurrentCampRecruitment();
}
@@ -13058,7 +13059,7 @@ export class CampScene extends Phaser.Scene {
}
private showVictoryRewardAcknowledgement() {
let report = this.pendingVictoryRewardReport ?? getPendingCampaignVictoryRewardReport();
let report = this.pendingVictoryRewardReport ?? getPendingCampaignVictoryRewardNoticeReport();
if (!report || report.outcome !== 'victory' || this.sortieObjects.length > 0) {
return;
}
@@ -13071,7 +13072,7 @@ export class CampScene extends Phaser.Scene {
this.campaign = acknowledgeCampaignVictoryReward(battleId, passiveCategories);
}
});
report = getPendingCampaignVictoryRewardReport();
report = getPendingCampaignVictoryRewardNoticeReport();
if (!report) {
this.pendingVictoryRewardReport = undefined;
return;
@@ -13366,6 +13367,10 @@ export class CampScene extends Phaser.Scene {
this.focusVictoryRewardEquipment(report);
}
this.acknowledgePendingVictoryRewardCategories(categories);
if (report) {
this.campaign = dismissCampaignVictoryRewardNotice(report.battleId);
this.pendingVictoryRewardReport = getPendingCampaignVictoryRewardNoticeReport();
}
soundDirector.playSelect();
this.hideVictoryRewardAcknowledgement();
this.updateTabButtons();
@@ -13409,7 +13414,7 @@ export class CampScene extends Phaser.Scene {
this.campaign = acknowledgeCampaignVictoryReward(battleId, relevantCategories);
}
});
this.pendingVictoryRewardReport = getPendingCampaignVictoryRewardReport();
this.pendingVictoryRewardReport = getPendingCampaignVictoryRewardNoticeReport();
}
private showRequestedSortiePrep() {

View File

@@ -487,6 +487,7 @@ export type CampaignState = {
completedCampVisits: string[];
acknowledgedVictoryRewardBattleIds: string[];
acknowledgedVictoryRewardCategories: CampaignVictoryRewardAcknowledgements;
dismissedVictoryRewardNoticeBattleIds: string[];
battleHistory: Record<string, CampaignBattleSettlement>;
latestBattleId?: string;
firstBattleReport?: FirstBattleReport;
@@ -694,6 +695,7 @@ export function createInitialCampaignState(): CampaignState {
completedCampVisits: [],
acknowledgedVictoryRewardBattleIds: [],
acknowledgedVictoryRewardCategories: {},
dismissedVictoryRewardNoticeBattleIds: [],
battleHistory: {}
};
}
@@ -1157,6 +1159,46 @@ export function getPendingCampaignVictoryRewardReport() {
return cloneCampaignVictoryRewardReport(report);
}
export function getPendingCampaignVictoryRewardNoticeReport() {
const state = ensureCampaignState();
const battleId = state.latestBattleId;
if (
!battleId ||
state.dismissedVictoryRewardNoticeBattleIds.includes(battleId) ||
pendingCampaignVictoryRewardCategoriesFor(state, battleId).length === 0
) {
return undefined;
}
const report = battleId ? campaignVictoryRewardSourceFor(state, battleId) : undefined;
if (!report || report.outcome !== 'victory') {
return undefined;
}
return cloneCampaignVictoryRewardReport(report);
}
export function dismissCampaignVictoryRewardNotice(battleId: string) {
const state = ensureCampaignState();
const normalizedBattleId = normalizeKeyString(battleId);
const report = campaignVictoryRewardSourceFor(state, normalizedBattleId);
if (
!normalizedBattleId ||
!(normalizedBattleId in battleScenarios) ||
!report ||
!isCampaignVictory(report) ||
state.dismissedVictoryRewardNoticeBattleIds.includes(normalizedBattleId)
) {
return cloneCampaignState(state);
}
state.dismissedVictoryRewardNoticeBattleIds = [
...state.dismissedVictoryRewardNoticeBattleIds,
normalizedBattleId
];
state.updatedAt = new Date().toISOString();
persistCampaignState(state);
return cloneCampaignState(state);
}
export function acknowledgeCampaignVictoryReward(
battleId: string,
requestedCategories?: readonly CampaignVictoryRewardCategoryId[]
@@ -1508,6 +1550,8 @@ function normalizeCampaignState(state: Partial<CampaignState>): CampaignState {
normalized.acknowledgedVictoryRewardCategories = normalizeCampaignVictoryRewardAcknowledgements(
normalized.acknowledgedVictoryRewardCategories
);
normalized.dismissedVictoryRewardNoticeBattleIds = uniqueStrings(normalized.dismissedVictoryRewardNoticeBattleIds)
.filter((battleId) => battleId in battleScenarios);
normalized.inventory = normalizeInventory(normalized.inventory);
normalized.battleHistory = normalizeBattleHistory(normalized.battleHistory);
normalized.firstBattleReport = normalizeFirstBattleReport(normalized.firstBattleReport);
@@ -1577,6 +1621,8 @@ function normalizeCampaignState(state: Partial<CampaignState>): CampaignState {
}
normalized.acknowledgedVictoryRewardBattleIds = normalized.acknowledgedVictoryRewardBattleIds
.filter((battleId) => recordedVictoryBattleIds.has(battleId));
normalized.dismissedVictoryRewardNoticeBattleIds = normalized.dismissedVictoryRewardNoticeBattleIds
.filter((battleId) => recordedVictoryBattleIds.has(battleId));
const acknowledgedVictoryRewardCategories = Object.entries(normalized.acknowledgedVictoryRewardCategories)
.reduce<CampaignVictoryRewardAcknowledgements>((acknowledgements, [battleId, categories]) => {
if (!recordedVictoryBattleIds.has(battleId)) {