feat: carry first-battle camaraderie into sortie prep
This commit is contained in:
@@ -85,6 +85,12 @@ export type BattleSaveCoreResonanceStats = {
|
||||
additionalDamage: number;
|
||||
};
|
||||
|
||||
export type BattleSaveCooperationStats = {
|
||||
attempts: number;
|
||||
successes: number;
|
||||
additionalDamage: number;
|
||||
};
|
||||
|
||||
export type BattleSaveEventPriority = 'critical' | 'high' | 'normal' | 'low';
|
||||
|
||||
export type BattleSavePendingEvent = {
|
||||
@@ -104,6 +110,7 @@ export type BattleSaveState = {
|
||||
sortieResonanceBondId?: string;
|
||||
sortieRecommendation?: CampaignSortieRecommendationSnapshot;
|
||||
coreResonanceStats?: BattleSaveCoreResonanceStats;
|
||||
cooperationStatsByBond?: Record<string, BattleSaveCooperationStats>;
|
||||
savedAt: string;
|
||||
turnNumber: number;
|
||||
activeFaction: BattleSaveFaction;
|
||||
@@ -199,6 +206,7 @@ export function normalizeBattleSaveState(
|
||||
}
|
||||
|
||||
normalizeCoreResonanceSaveFields(cloned, options);
|
||||
normalizeCooperationSaveFields(cloned, options);
|
||||
const recommendation = normalizeCampaignSortieRecommendationSnapshot(cloned.sortieRecommendation, {
|
||||
expectedBattleId: typeof cloned.battleId === 'string' ? cloned.battleId : options.expectedBattleId,
|
||||
allowedUnitIds: options.validAllyUnitIds
|
||||
@@ -244,6 +252,10 @@ export function isValidBattleSaveState(state: unknown, options: BattleSaveValida
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isOptionalCooperationStatsByBond(state.cooperationStatsByBond, state, options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const attackIntents = state.attackIntents;
|
||||
const units = state.units;
|
||||
|
||||
@@ -368,6 +380,131 @@ function normalizeCoreResonanceStatValue(value: unknown) {
|
||||
return Math.min(maxBattleUnitStatValue, Math.max(0, Math.floor(value)));
|
||||
}
|
||||
|
||||
function normalizeCooperationSaveFields(
|
||||
state: Record<string, unknown>,
|
||||
options: BattleSaveValidationOptions
|
||||
) {
|
||||
const validBondIds = validDeployedCooperationBondIds(state, options);
|
||||
const normalizedStats: Record<string, BattleSaveCooperationStats> = {};
|
||||
for (const [bondId, rawValue] of Object.entries(
|
||||
isRecord(state.cooperationStatsByBond) ? state.cooperationStatsByBond : {}
|
||||
).sort(([leftBondId], [rightBondId]) => leftBondId.localeCompare(rightBondId))) {
|
||||
if (Object.keys(normalizedStats).length >= maxBattleBondEntries) {
|
||||
break;
|
||||
}
|
||||
const normalizedBondId = bondId.trim();
|
||||
if (
|
||||
normalizedBondId !== bondId ||
|
||||
!validBondIds.has(normalizedBondId) ||
|
||||
!isRecord(rawValue)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const attempts = normalizeCoreResonanceStatValue(rawValue.attempts);
|
||||
if (attempts <= 0) {
|
||||
continue;
|
||||
}
|
||||
const successes = Math.min(
|
||||
attempts,
|
||||
normalizeCoreResonanceStatValue(rawValue.successes)
|
||||
);
|
||||
normalizedStats[normalizedBondId] = {
|
||||
attempts,
|
||||
successes,
|
||||
additionalDamage: successes > 0
|
||||
? normalizeCoreResonanceStatValue(rawValue.additionalDamage)
|
||||
: 0
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
Object.keys(normalizedStats).length === 0 &&
|
||||
typeof state.sortieResonanceBondId === 'string' &&
|
||||
validBondIds.has(state.sortieResonanceBondId) &&
|
||||
isRecord(state.coreResonanceStats)
|
||||
) {
|
||||
const attempts = normalizeCoreResonanceStatValue(state.coreResonanceStats.attempts);
|
||||
if (attempts > 0) {
|
||||
const successes = Math.min(
|
||||
attempts,
|
||||
normalizeCoreResonanceStatValue(state.coreResonanceStats.successes)
|
||||
);
|
||||
normalizedStats[state.sortieResonanceBondId] = {
|
||||
attempts,
|
||||
successes,
|
||||
additionalDamage: successes > 0
|
||||
? normalizeCoreResonanceStatValue(state.coreResonanceStats.additionalDamage)
|
||||
: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(normalizedStats).length > 0) {
|
||||
state.cooperationStatsByBond = normalizedStats;
|
||||
} else {
|
||||
delete state.cooperationStatsByBond;
|
||||
}
|
||||
}
|
||||
|
||||
function validDeployedCooperationBondIds(
|
||||
state: Record<string, unknown>,
|
||||
options: BattleSaveValidationOptions
|
||||
) {
|
||||
const deployedUnitIds = new Set(
|
||||
(Array.isArray(state.units) ? state.units : [])
|
||||
.filter(isRecord)
|
||||
.map((unit) => unit.id)
|
||||
.filter((unitId): unitId is string => typeof unitId === 'string')
|
||||
);
|
||||
return new Set(
|
||||
(Array.isArray(state.bonds) ? state.bonds : [])
|
||||
.filter(isRecord)
|
||||
.filter((bond) => (
|
||||
typeof bond.id === 'string' &&
|
||||
bond.id.trim() === bond.id &&
|
||||
bond.id.length > 0 &&
|
||||
Array.isArray(bond.unitIds) &&
|
||||
bond.unitIds.length === 2 &&
|
||||
bond.unitIds[0] !== bond.unitIds[1] &&
|
||||
bond.unitIds.every((unitId) => (
|
||||
typeof unitId === 'string' &&
|
||||
deployedUnitIds.has(unitId) &&
|
||||
(!options.validAllyUnitIds || options.validAllyUnitIds.has(unitId))
|
||||
))
|
||||
))
|
||||
.map((bond) => bond.id as string)
|
||||
);
|
||||
}
|
||||
|
||||
function isOptionalCooperationStatsByBond(
|
||||
value: unknown,
|
||||
state: Record<string, unknown>,
|
||||
options: BattleSaveValidationOptions
|
||||
) {
|
||||
if (value === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
const entries = Object.entries(value);
|
||||
if (entries.length > maxBattleBondEntries) {
|
||||
return false;
|
||||
}
|
||||
const validBondIds = validDeployedCooperationBondIds(state, options);
|
||||
return entries.every(([bondId, stats]) => (
|
||||
bondId.trim() === bondId &&
|
||||
validBondIds.has(bondId) &&
|
||||
isRecord(stats) &&
|
||||
isPositiveInteger(stats.attempts) &&
|
||||
isBattleUnitStatValue(stats.attempts) &&
|
||||
isBattleUnitStatValue(stats.successes) &&
|
||||
isBattleUnitStatValue(stats.additionalDamage) &&
|
||||
Number(stats.successes) <= Number(stats.attempts) &&
|
||||
(Number(stats.successes) > 0 || Number(stats.additionalDamage) === 0)
|
||||
));
|
||||
}
|
||||
|
||||
function isOptionalCoreResonanceState(
|
||||
state: Record<string, unknown>,
|
||||
options: BattleSaveValidationOptions
|
||||
|
||||
Reference in New Issue
Block a user