feat: carry first-battle camaraderie into sortie prep
This commit is contained in:
@@ -133,6 +133,15 @@ export type CampaignSortieRecommendationSnapshot = {
|
||||
unitReasons: Record<string, string>;
|
||||
};
|
||||
|
||||
export type CampaignSortieCooperationSnapshot = {
|
||||
version: 1;
|
||||
bondId: string;
|
||||
unitIds: [string, string];
|
||||
attempts: number;
|
||||
successes: number;
|
||||
additionalDamage: number;
|
||||
};
|
||||
|
||||
export type CampaignSortieOrderResultSnapshot = SortieOrderResultSnapshot;
|
||||
|
||||
export type FirstBattleReport = {
|
||||
@@ -153,6 +162,7 @@ export type FirstBattleReport = {
|
||||
sortieReview?: CampaignSortieReviewSnapshot;
|
||||
sortieOrder?: CampaignSortieOrderResultSnapshot;
|
||||
sortieRecommendation?: CampaignSortieRecommendationSnapshot;
|
||||
sortieCooperation?: CampaignSortieCooperationSnapshot;
|
||||
completedCampDialogues: string[];
|
||||
completedCampVisits: string[];
|
||||
createdAt: string;
|
||||
@@ -436,6 +446,7 @@ export type CampaignBattleSettlement = {
|
||||
sortieReview?: CampaignSortieReviewSnapshot;
|
||||
sortieOrder?: CampaignSortieOrderResultSnapshot;
|
||||
sortieRecommendation?: CampaignSortieRecommendationSnapshot;
|
||||
sortieCooperation?: CampaignSortieCooperationSnapshot;
|
||||
completedAt: string;
|
||||
};
|
||||
|
||||
@@ -1759,7 +1770,7 @@ function normalizeCampaignState(state: Partial<CampaignState>): CampaignState {
|
||||
normalized.battleHistory = filterBattleHistoryRosterReferences(
|
||||
normalized.battleHistory,
|
||||
sortieUnitFilter,
|
||||
new Set(normalized.bonds.map((bond) => bond.id))
|
||||
normalized.bonds
|
||||
);
|
||||
}
|
||||
normalized.latestBattleId = normalizeLatestBattleId(normalized.latestBattleId, normalized.battleHistory);
|
||||
@@ -2131,6 +2142,72 @@ export function normalizeCampaignSortieRecommendationSnapshot(
|
||||
};
|
||||
}
|
||||
|
||||
type CampaignSortieCooperationNormalizationOptions = {
|
||||
allowedUnitIds?: ReadonlySet<string>;
|
||||
allowedBondIds?: ReadonlySet<string>;
|
||||
bonds?: readonly {
|
||||
id: string;
|
||||
unitIds: readonly string[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export function normalizeCampaignSortieCooperationSnapshot(
|
||||
value: unknown,
|
||||
options: CampaignSortieCooperationNormalizationOptions = {}
|
||||
): CampaignSortieCooperationSnapshot | undefined {
|
||||
if (!isPlainObject(value) || value.version !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
const bondId = normalizeKeyString(value.bondId);
|
||||
const rawUnitIds = arrayOrEmpty<unknown>(value.unitIds);
|
||||
if (!bondId || rawUnitIds.length !== 2) {
|
||||
return undefined;
|
||||
}
|
||||
const unitIds = rawUnitIds.map(normalizeKeyString);
|
||||
if (
|
||||
!unitIds[0] ||
|
||||
!unitIds[1] ||
|
||||
unitIds[0] === unitIds[1] ||
|
||||
(options.allowedBondIds && !options.allowedBondIds.has(bondId)) ||
|
||||
unitIds.some((unitId) => options.allowedUnitIds && !options.allowedUnitIds.has(unitId))
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const matchingBond = options.bonds?.find((bond) => bond.id === bondId);
|
||||
if (options.bonds && !matchingBond) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
matchingBond &&
|
||||
(
|
||||
matchingBond.unitIds.length !== 2 ||
|
||||
!matchingBond.unitIds.every((unitId) => unitIds.includes(unitId))
|
||||
)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attempts = normalizeNonNegativeInteger(value.attempts);
|
||||
if (attempts <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
const normalizedUnitIds = matchingBond
|
||||
? [matchingBond.unitIds[0], matchingBond.unitIds[1]] as [string, string]
|
||||
: [unitIds[0], unitIds[1]] as [string, string];
|
||||
const successes = Math.min(attempts, normalizeNonNegativeInteger(value.successes));
|
||||
return {
|
||||
version: 1,
|
||||
bondId,
|
||||
unitIds: normalizedUnitIds,
|
||||
attempts,
|
||||
successes,
|
||||
additionalDamage: successes > 0
|
||||
? normalizeNonNegativeInteger(value.additionalDamage)
|
||||
: 0
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCampaignSortieResonanceBond(
|
||||
battleId: string,
|
||||
bondId: string,
|
||||
@@ -2398,14 +2475,21 @@ function filterFirstBattleReportRosterReferences(report: FirstBattleReport, rost
|
||||
allowedUnitIds: rosterUnitIds
|
||||
})
|
||||
: undefined;
|
||||
const filteredBonds = report.bonds.filter((bond) => bond.unitIds.every((unitId) => rosterUnitIds.has(unitId)));
|
||||
const sortieCooperation = normalizeCampaignSortieCooperationSnapshot(report.sortieCooperation, {
|
||||
allowedUnitIds: rosterUnitIds,
|
||||
allowedBondIds: new Set(filteredBonds.map((bond) => bond.id)),
|
||||
bonds: filteredBonds
|
||||
});
|
||||
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))),
|
||||
bonds: filteredBonds,
|
||||
...(sortiePerformance ? { sortiePerformance } : {}),
|
||||
...(sortieReview ? { sortieReview } : {}),
|
||||
...(sortieOrder ? { sortieOrder } : {}),
|
||||
...(sortieRecommendation ? { sortieRecommendation } : {})
|
||||
...(sortieRecommendation ? { sortieRecommendation } : {}),
|
||||
...(sortieCooperation ? { sortieCooperation } : {})
|
||||
};
|
||||
if (!sortiePerformance) {
|
||||
delete filtered.sortiePerformance;
|
||||
@@ -2419,6 +2503,9 @@ function filterFirstBattleReportRosterReferences(report: FirstBattleReport, rost
|
||||
if (!sortieRecommendation) {
|
||||
delete filtered.sortieRecommendation;
|
||||
}
|
||||
if (!sortieCooperation) {
|
||||
delete filtered.sortieCooperation;
|
||||
}
|
||||
if (filtered.mvp && !rosterUnitIds.has(filtered.mvp.unitId)) {
|
||||
delete filtered.mvp;
|
||||
}
|
||||
@@ -2428,8 +2515,9 @@ function filterFirstBattleReportRosterReferences(report: FirstBattleReport, rost
|
||||
function filterBattleHistoryRosterReferences(
|
||||
history: Record<string, CampaignBattleSettlement>,
|
||||
rosterUnitIds: Set<string>,
|
||||
bondIds: Set<string>
|
||||
campaignBonds: readonly CampBondSnapshot[]
|
||||
): Record<string, CampaignBattleSettlement> {
|
||||
const bondIds = new Set(campaignBonds.map((bond) => bond.id));
|
||||
return Object.entries(history).reduce<Record<string, CampaignBattleSettlement>>((filteredHistory, [battleId, settlement]) => {
|
||||
const sortiePerformance = normalizeCampaignSortiePerformanceSnapshot(settlement.sortiePerformance, rosterUnitIds);
|
||||
const sortiePerformanceUnchanged = sortiePerformanceSnapshotsShareRoster(settlement.sortiePerformance, sortiePerformance);
|
||||
@@ -2448,6 +2536,11 @@ function filterBattleHistoryRosterReferences(
|
||||
allowedUnitIds: rosterUnitIds
|
||||
})
|
||||
: undefined;
|
||||
const sortieCooperation = normalizeCampaignSortieCooperationSnapshot(settlement.sortieCooperation, {
|
||||
allowedUnitIds: rosterUnitIds,
|
||||
allowedBondIds: bondIds,
|
||||
bonds: campaignBonds
|
||||
});
|
||||
filteredHistory[battleId] = {
|
||||
...settlement,
|
||||
units: settlement.units.filter((unit) => rosterUnitIds.has(unit.unitId)),
|
||||
@@ -2456,7 +2549,8 @@ function filterBattleHistoryRosterReferences(
|
||||
...(sortiePerformance ? { sortiePerformance } : {}),
|
||||
...(sortieReview ? { sortieReview } : {}),
|
||||
...(sortieOrder ? { sortieOrder } : {}),
|
||||
...(sortieRecommendation ? { sortieRecommendation } : {})
|
||||
...(sortieRecommendation ? { sortieRecommendation } : {}),
|
||||
...(sortieCooperation ? { sortieCooperation } : {})
|
||||
};
|
||||
if (!sortiePerformance) {
|
||||
delete filteredHistory[battleId].sortiePerformance;
|
||||
@@ -2470,6 +2564,9 @@ function filterBattleHistoryRosterReferences(
|
||||
if (!sortieRecommendation) {
|
||||
delete filteredHistory[battleId].sortieRecommendation;
|
||||
}
|
||||
if (!sortieCooperation) {
|
||||
delete filteredHistory[battleId].sortieCooperation;
|
||||
}
|
||||
return filteredHistory;
|
||||
}, {});
|
||||
}
|
||||
@@ -2505,6 +2602,20 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle
|
||||
turnNumber
|
||||
})
|
||||
: undefined;
|
||||
const units = normalizeLimitedArray(
|
||||
settlement.units,
|
||||
normalizeCampaignUnitProgressSnapshot,
|
||||
maxCampaignBattleUnitEntries
|
||||
);
|
||||
const bonds = normalizeLimitedArray(
|
||||
settlement.bonds,
|
||||
normalizeCampaignBondProgressSnapshot,
|
||||
maxCampaignBattleBondEntries
|
||||
);
|
||||
const sortieCooperation = normalizeCampaignSortieCooperationSnapshot(settlement.sortieCooperation, {
|
||||
allowedUnitIds: new Set(units.map((unit) => unit.unitId)),
|
||||
allowedBondIds: new Set(bonds.map((bond) => bond.id))
|
||||
});
|
||||
|
||||
return {
|
||||
battleId,
|
||||
@@ -2515,13 +2626,14 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle
|
||||
itemRewards: normalizeRewardStrings(settlement.itemRewards),
|
||||
campaignRewards: cloneCampaignRewardSnapshot(settlement.campaignRewards, battleId),
|
||||
objectives: normalizeLimitedArray(settlement.objectives, normalizeBattleObjectiveSnapshot, maxCampaignBattleObjectiveEntries),
|
||||
units: normalizeLimitedArray(settlement.units, normalizeCampaignUnitProgressSnapshot, maxCampaignBattleUnitEntries),
|
||||
bonds: normalizeLimitedArray(settlement.bonds, normalizeCampaignBondProgressSnapshot, maxCampaignBattleBondEntries),
|
||||
units,
|
||||
bonds,
|
||||
reserveTraining: normalizeLimitedArray(settlement.reserveTraining, normalizeReserveTrainingSnapshot, maxCampaignReserveTrainingEntries),
|
||||
...(sortiePerformance ? { sortiePerformance } : {}),
|
||||
...(sortieReview ? { sortieReview } : {}),
|
||||
...(sortieOrder ? { sortieOrder } : {}),
|
||||
...(sortieRecommendation ? { sortieRecommendation } : {}),
|
||||
...(sortieCooperation ? { sortieCooperation } : {}),
|
||||
completedAt
|
||||
};
|
||||
}
|
||||
@@ -2856,6 +2968,23 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
|
||||
turnNumber
|
||||
})
|
||||
: undefined;
|
||||
const units = normalizeLimitedArray(
|
||||
report.units,
|
||||
normalizeUnitDataSnapshot,
|
||||
maxCampaignBattleUnitEntries
|
||||
);
|
||||
const bonds = normalizeLimitedArray(
|
||||
report.bonds,
|
||||
normalizeCampBondSnapshot,
|
||||
maxCampaignBattleBondEntries
|
||||
);
|
||||
const sortieCooperation = normalizeCampaignSortieCooperationSnapshot(report.sortieCooperation, {
|
||||
allowedUnitIds: new Set(
|
||||
units.filter((unit) => unit.faction === 'ally').map((unit) => unit.id)
|
||||
),
|
||||
allowedBondIds: new Set(bonds.map((bond) => bond.id)),
|
||||
bonds
|
||||
});
|
||||
|
||||
return {
|
||||
battleId,
|
||||
@@ -2866,8 +2995,8 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
|
||||
defeatedEnemies: normalizeNonNegativeInteger(report.defeatedEnemies),
|
||||
totalEnemies: normalizeNonNegativeInteger(report.totalEnemies),
|
||||
objectives: normalizeLimitedArray(report.objectives, normalizeBattleObjectiveSnapshot, maxCampaignBattleObjectiveEntries),
|
||||
units: normalizeLimitedArray(report.units, normalizeUnitDataSnapshot, maxCampaignBattleUnitEntries),
|
||||
bonds: normalizeLimitedArray(report.bonds, normalizeCampBondSnapshot, maxCampaignBattleBondEntries),
|
||||
units,
|
||||
bonds,
|
||||
mvp: normalizeCampMvpSnapshot(report.mvp),
|
||||
itemRewards: normalizeRewardStrings(report.itemRewards),
|
||||
campaignRewards: cloneCampaignRewardSnapshot(
|
||||
@@ -2878,6 +3007,7 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
|
||||
...(sortieReview ? { sortieReview } : {}),
|
||||
...(sortieOrder ? { sortieOrder } : {}),
|
||||
...(sortieRecommendation ? { sortieRecommendation } : {}),
|
||||
...(sortieCooperation ? { sortieCooperation } : {}),
|
||||
completedCampDialogues: uniqueCampHistoryIds(report.completedCampDialogues),
|
||||
completedCampVisits: uniqueCampHistoryIds(report.completedCampVisits),
|
||||
createdAt: normalizeCampaignTimestamp(report.createdAt) ?? new Date().toISOString()
|
||||
@@ -3142,6 +3272,9 @@ function createBattleSettlement(report: FirstBattleReport, reserveTraining: Camp
|
||||
...(report.sortieRecommendation
|
||||
? { sortieRecommendation: cloneCampaignSortieRecommendationSnapshot(report.sortieRecommendation) }
|
||||
: {}),
|
||||
...(report.sortieCooperation
|
||||
? { sortieCooperation: cloneCampaignSortieCooperationSnapshot(report.sortieCooperation) }
|
||||
: {}),
|
||||
completedAt: report.createdAt
|
||||
};
|
||||
}
|
||||
@@ -3302,6 +3435,9 @@ function cloneReport(report: FirstBattleReport): FirstBattleReport {
|
||||
if (cloned.campaignRewards) {
|
||||
cloned.campaignRewards = cloneCampaignRewardSnapshot(cloned.campaignRewards, cloned.battleId);
|
||||
}
|
||||
if (cloned.sortieCooperation) {
|
||||
cloned.sortieCooperation = cloneCampaignSortieCooperationSnapshot(cloned.sortieCooperation);
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
@@ -3314,6 +3450,13 @@ export function cloneCampaignSortieRecommendationSnapshot(snapshot: CampaignSort
|
||||
} satisfies CampaignSortieRecommendationSnapshot;
|
||||
}
|
||||
|
||||
export function cloneCampaignSortieCooperationSnapshot(snapshot: CampaignSortieCooperationSnapshot) {
|
||||
return {
|
||||
...snapshot,
|
||||
unitIds: [...snapshot.unitIds] as [string, string]
|
||||
} satisfies CampaignSortieCooperationSnapshot;
|
||||
}
|
||||
|
||||
function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot, battleId?: string): CampaignRewardSnapshot | undefined {
|
||||
if (!rewards) {
|
||||
return undefined;
|
||||
|
||||
Reference in New Issue
Block a user