feat: add sortie formation history
This commit is contained in:
@@ -68,6 +68,17 @@ export type CampaignSortiePerformanceSnapshot = {
|
||||
unitStats: CampaignSortieUnitPerformanceSnapshot[];
|
||||
};
|
||||
|
||||
export type CampaignSortieReviewGrade = 'S' | 'A' | 'B' | 'C' | 'D';
|
||||
|
||||
export type CampaignSortieReviewSnapshot = {
|
||||
version: 1;
|
||||
score: number;
|
||||
grade: CampaignSortieReviewGrade;
|
||||
activeBondCount: number;
|
||||
enemyCount: number;
|
||||
sourcePresetIds: CampaignSortiePresetId[];
|
||||
};
|
||||
|
||||
export type FirstBattleReport = {
|
||||
battleId: string;
|
||||
battleTitle: string;
|
||||
@@ -83,6 +94,7 @@ export type FirstBattleReport = {
|
||||
itemRewards: string[];
|
||||
campaignRewards?: CampaignRewardSnapshot;
|
||||
sortiePerformance?: CampaignSortiePerformanceSnapshot;
|
||||
sortieReview?: CampaignSortieReviewSnapshot;
|
||||
completedCampDialogues: string[];
|
||||
completedCampVisits: string[];
|
||||
createdAt: string;
|
||||
@@ -346,6 +358,7 @@ export type CampaignBattleSettlement = {
|
||||
bonds: CampaignBondProgressSnapshot[];
|
||||
reserveTraining?: CampaignReserveTrainingSnapshot[];
|
||||
sortiePerformance?: CampaignSortiePerformanceSnapshot;
|
||||
sortieReview?: CampaignSortieReviewSnapshot;
|
||||
completedAt: string;
|
||||
};
|
||||
|
||||
@@ -1144,15 +1157,23 @@ function normalizeLatestBattleId(value: unknown, battleHistory: Record<string, C
|
||||
|
||||
function filterFirstBattleReportRosterReferences(report: FirstBattleReport, rosterUnitIds: Set<string>): FirstBattleReport {
|
||||
const sortiePerformance = normalizeCampaignSortiePerformanceSnapshot(report.sortiePerformance, rosterUnitIds);
|
||||
const sortiePerformanceUnchanged = sortiePerformanceSnapshotsShareRoster(report.sortiePerformance, sortiePerformance);
|
||||
const sortieReview = sortiePerformanceUnchanged
|
||||
? normalizeCampaignSortieReviewSnapshot(report.sortieReview, sortiePerformance)
|
||||
: undefined;
|
||||
const filtered = {
|
||||
...report,
|
||||
units: report.units.filter((unit) => unit.faction !== 'ally' || rosterUnitIds.has(unit.id)),
|
||||
bonds: report.bonds.filter((bond) => bond.unitIds.every((unitId) => rosterUnitIds.has(unitId))),
|
||||
...(sortiePerformance ? { sortiePerformance } : {})
|
||||
...(sortiePerformance ? { sortiePerformance } : {}),
|
||||
...(sortieReview ? { sortieReview } : {})
|
||||
};
|
||||
if (!sortiePerformance) {
|
||||
delete filtered.sortiePerformance;
|
||||
}
|
||||
if (!sortieReview) {
|
||||
delete filtered.sortieReview;
|
||||
}
|
||||
if (filtered.mvp && !rosterUnitIds.has(filtered.mvp.unitId)) {
|
||||
delete filtered.mvp;
|
||||
}
|
||||
@@ -1166,16 +1187,24 @@ function filterBattleHistoryRosterReferences(
|
||||
): Record<string, CampaignBattleSettlement> {
|
||||
return Object.entries(history).reduce<Record<string, CampaignBattleSettlement>>((filteredHistory, [battleId, settlement]) => {
|
||||
const sortiePerformance = normalizeCampaignSortiePerformanceSnapshot(settlement.sortiePerformance, rosterUnitIds);
|
||||
const sortiePerformanceUnchanged = sortiePerformanceSnapshotsShareRoster(settlement.sortiePerformance, sortiePerformance);
|
||||
const sortieReview = sortiePerformanceUnchanged
|
||||
? normalizeCampaignSortieReviewSnapshot(settlement.sortieReview, sortiePerformance)
|
||||
: undefined;
|
||||
filteredHistory[battleId] = {
|
||||
...settlement,
|
||||
units: settlement.units.filter((unit) => rosterUnitIds.has(unit.unitId)),
|
||||
bonds: bondIds.size > 0 ? settlement.bonds.filter((bond) => bondIds.has(bond.id)) : settlement.bonds,
|
||||
reserveTraining: settlement.reserveTraining?.filter((entry) => rosterUnitIds.has(entry.unitId)),
|
||||
...(sortiePerformance ? { sortiePerformance } : {})
|
||||
...(sortiePerformance ? { sortiePerformance } : {}),
|
||||
...(sortieReview ? { sortieReview } : {})
|
||||
};
|
||||
if (!sortiePerformance) {
|
||||
delete filteredHistory[battleId].sortiePerformance;
|
||||
}
|
||||
if (!sortieReview) {
|
||||
delete filteredHistory[battleId].sortieReview;
|
||||
}
|
||||
return filteredHistory;
|
||||
}, {});
|
||||
}
|
||||
@@ -1200,6 +1229,7 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle
|
||||
}
|
||||
|
||||
const sortiePerformance = normalizeCampaignSortiePerformanceSnapshot(settlement.sortiePerformance);
|
||||
const sortieReview = normalizeCampaignSortieReviewSnapshot(settlement.sortieReview, sortiePerformance);
|
||||
|
||||
return {
|
||||
battleId,
|
||||
@@ -1213,6 +1243,7 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle
|
||||
bonds: normalizeLimitedArray(settlement.bonds, normalizeCampaignBondProgressSnapshot, maxCampaignBattleBondEntries),
|
||||
reserveTraining: normalizeLimitedArray(settlement.reserveTraining, normalizeReserveTrainingSnapshot, maxCampaignReserveTrainingEntries),
|
||||
...(sortiePerformance ? { sortiePerformance } : {}),
|
||||
...(sortieReview ? { sortieReview } : {}),
|
||||
completedAt
|
||||
};
|
||||
}
|
||||
@@ -1448,6 +1479,53 @@ export function normalizeCampaignSortiePerformanceSnapshot(
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeCampaignSortieReviewSnapshot(
|
||||
value: unknown,
|
||||
performance?: CampaignSortiePerformanceSnapshot
|
||||
): CampaignSortieReviewSnapshot | undefined {
|
||||
if (!isPlainObject(value) || value.version !== 1 || !campaignSortiePerformanceSupportsReview(performance)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const score = Math.min(100, normalizeNonNegativeInteger(value.score));
|
||||
const grade: CampaignSortieReviewGrade = score >= 90 ? 'S' : score >= 75 ? 'A' : score >= 60 ? 'B' : score >= 45 ? 'C' : 'D';
|
||||
const sourcePresetIdSet = new Set(arrayOrEmpty<unknown>(value.sourcePresetIds));
|
||||
const sourcePresetIds = campaignSortiePresetIds.filter((presetId) => sourcePresetIdSet.has(presetId));
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
score,
|
||||
grade,
|
||||
activeBondCount: Math.min(maxCampaignBattleBondEntries, normalizeNonNegativeInteger(value.activeBondCount)),
|
||||
enemyCount: Math.min(maxCampaignBattleUnitEntries, normalizeNonNegativeInteger(value.enemyCount)),
|
||||
sourcePresetIds
|
||||
};
|
||||
}
|
||||
|
||||
function campaignSortiePerformanceSupportsReview(
|
||||
performance?: CampaignSortiePerformanceSnapshot
|
||||
): performance is CampaignSortiePerformanceSnapshot {
|
||||
if (!performance || performance.selectedUnitIds.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const statsUnitIds = new Set(performance.unitStats.map((stats) => stats.unitId));
|
||||
return performance.selectedUnitIds.every((unitId) => (
|
||||
Boolean(performance.formationAssignments[unitId]) && statsUnitIds.has(unitId)
|
||||
));
|
||||
}
|
||||
|
||||
function sortiePerformanceSnapshotsShareRoster(
|
||||
source?: CampaignSortiePerformanceSnapshot,
|
||||
filtered?: CampaignSortiePerformanceSnapshot
|
||||
) {
|
||||
return Boolean(
|
||||
source &&
|
||||
filtered &&
|
||||
source.selectedUnitIds.length === filtered.selectedUnitIds.length &&
|
||||
source.selectedUnitIds.every((unitId, index) => filtered.selectedUnitIds[index] === unitId)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeReserveTrainingFocusId(focusId?: string): CampaignReserveTrainingFocusId {
|
||||
return isReserveTrainingFocusId(focusId)
|
||||
? focusId as CampaignReserveTrainingFocusId
|
||||
@@ -1486,6 +1564,7 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
|
||||
}
|
||||
|
||||
const sortiePerformance = normalizeCampaignSortiePerformanceSnapshot(report.sortiePerformance);
|
||||
const sortieReview = normalizeCampaignSortieReviewSnapshot(report.sortieReview, sortiePerformance);
|
||||
|
||||
return {
|
||||
battleId,
|
||||
@@ -1505,6 +1584,7 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
|
||||
battleId
|
||||
),
|
||||
...(sortiePerformance ? { sortiePerformance } : {}),
|
||||
...(sortieReview ? { sortieReview } : {}),
|
||||
completedCampDialogues: uniqueStrings(report.completedCampDialogues),
|
||||
completedCampVisits: uniqueStrings(report.completedCampVisits),
|
||||
createdAt: normalizeCampaignTimestamp(report.createdAt) ?? new Date().toISOString()
|
||||
@@ -1746,6 +1826,14 @@ function createBattleSettlement(report: FirstBattleReport, reserveTraining: Camp
|
||||
formationAssignments: { ...report.sortiePerformance.formationAssignments },
|
||||
unitStats: report.sortiePerformance.unitStats.map((stats) => ({ ...stats }))
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(report.sortieReview
|
||||
? {
|
||||
sortieReview: {
|
||||
...report.sortieReview,
|
||||
sourcePresetIds: [...report.sortieReview.sourcePresetIds]
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
completedAt: report.createdAt
|
||||
|
||||
Reference in New Issue
Block a user