Cap campaign battle history snapshots
This commit is contained in:
@@ -269,6 +269,11 @@ const campaignInventoryCaps: Record<string, number> = {
|
||||
const maxCampaignStringListEntries = 128;
|
||||
const maxCampaignStringListLength = 96;
|
||||
const maxCampaignRewardNoteLength = 180;
|
||||
const maxCampaignBattleObjectiveEntries = 24;
|
||||
const maxCampaignBattleUnitEntries = 96;
|
||||
const maxCampaignBattleBondEntries = 96;
|
||||
const maxCampaignReserveTrainingEntries = 96;
|
||||
const maxCampaignDisplayTextLength = 180;
|
||||
|
||||
export const campaignReserveTrainingFocusDefinitions: CampaignReserveTrainingFocusDefinition[] = [
|
||||
{
|
||||
@@ -890,6 +895,37 @@ function uniqueStrings(value: unknown): string[] {
|
||||
].slice(0, maxCampaignStringListEntries);
|
||||
}
|
||||
|
||||
function normalizeKeyString(value: unknown): string {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const text = value.trim();
|
||||
return text.length > 0 && text.length <= maxCampaignStringListLength ? text : '';
|
||||
}
|
||||
|
||||
function normalizeDisplayString(value: unknown, maxLength = maxCampaignDisplayTextLength): string {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value.trim().slice(0, maxLength);
|
||||
}
|
||||
|
||||
function normalizeLimitedArray<T>(value: unknown, normalizer: (entry: unknown) => T | undefined, maxEntries: number): T[] {
|
||||
const normalized: T[] = [];
|
||||
for (const entry of arrayOrEmpty<unknown>(value)) {
|
||||
const normalizedEntry = normalizer(entry);
|
||||
if (normalizedEntry) {
|
||||
normalized.push(normalizedEntry);
|
||||
if (normalized.length >= maxEntries) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizedRosterUnitIds(roster: UnitData[]) {
|
||||
return new Set(
|
||||
roster
|
||||
@@ -986,44 +1022,44 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle
|
||||
|
||||
return {
|
||||
battleId,
|
||||
battleTitle: settlement.battleTitle,
|
||||
battleTitle: normalizeDisplayString(settlement.battleTitle),
|
||||
outcome: settlement.outcome,
|
||||
rewardGold: normalizeNonNegativeInteger(settlement.rewardGold),
|
||||
itemRewards: uniqueStrings(settlement.itemRewards),
|
||||
campaignRewards: cloneCampaignRewardSnapshot(settlement.campaignRewards),
|
||||
objectives: arrayOrEmpty<unknown>(settlement.objectives)
|
||||
.map(normalizeBattleObjectiveSnapshot)
|
||||
.filter((objective): objective is BattleObjectiveSnapshot => Boolean(objective)),
|
||||
units: arrayOrEmpty<unknown>(settlement.units)
|
||||
.map(normalizeCampaignUnitProgressSnapshot)
|
||||
.filter((unit): unit is CampaignUnitProgressSnapshot => Boolean(unit)),
|
||||
bonds: arrayOrEmpty<unknown>(settlement.bonds)
|
||||
.map(normalizeCampaignBondProgressSnapshot)
|
||||
.filter((bond): bond is CampaignBondProgressSnapshot => Boolean(bond)),
|
||||
reserveTraining: arrayOrEmpty<unknown>(settlement.reserveTraining)
|
||||
.map(normalizeReserveTrainingSnapshot)
|
||||
.filter((entry): entry is CampaignReserveTrainingSnapshot => Boolean(entry)),
|
||||
objectives: normalizeLimitedArray(settlement.objectives, normalizeBattleObjectiveSnapshot, maxCampaignBattleObjectiveEntries),
|
||||
units: normalizeLimitedArray(settlement.units, normalizeCampaignUnitProgressSnapshot, maxCampaignBattleUnitEntries),
|
||||
bonds: normalizeLimitedArray(settlement.bonds, normalizeCampaignBondProgressSnapshot, maxCampaignBattleBondEntries),
|
||||
reserveTraining: normalizeLimitedArray(settlement.reserveTraining, normalizeReserveTrainingSnapshot, maxCampaignReserveTrainingEntries),
|
||||
completedAt
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeBattleObjectiveSnapshot(value: unknown): BattleObjectiveSnapshot | undefined {
|
||||
if (!isPlainObject(value) || typeof value.id !== 'string' || value.id.length === 0 || typeof value.label !== 'string' || value.label.length === 0) {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = normalizeKeyString(value.id);
|
||||
const label = normalizeDisplayString(value.label);
|
||||
if (!id || !label) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const status = value.status === 'active' || value.status === 'done' || value.status === 'failed' ? value.status : undefined;
|
||||
const category = value.category === 'primary' || value.category === 'required' || value.category === 'bonus' ? value.category : undefined;
|
||||
const targetTile = normalizeObjectiveTargetTile(value.targetTile);
|
||||
const summary = normalizeDisplayString(value.summary);
|
||||
const failureReason = normalizeDisplayString(value.failureReason);
|
||||
return {
|
||||
id: value.id,
|
||||
label: value.label,
|
||||
id,
|
||||
label,
|
||||
achieved: Boolean(value.achieved),
|
||||
...(status ? { status } : {}),
|
||||
detail: typeof value.detail === 'string' ? value.detail : '',
|
||||
detail: normalizeDisplayString(value.detail),
|
||||
...(category ? { category } : {}),
|
||||
...(typeof value.summary === 'string' ? { summary: value.summary } : {}),
|
||||
...(typeof value.failureReason === 'string' ? { failureReason: value.failureReason } : {}),
|
||||
...(summary ? { summary } : {}),
|
||||
...(failureReason ? { failureReason } : {}),
|
||||
...(targetTile ? { targetTile } : {}),
|
||||
rewardGold: normalizeNonNegativeInteger(value.rewardGold)
|
||||
};
|
||||
@@ -1045,15 +1081,21 @@ function normalizeObjectiveTargetTile(value: unknown): BattleObjectiveSnapshot['
|
||||
}
|
||||
|
||||
function normalizeCampaignUnitProgressSnapshot(value: unknown): CampaignUnitProgressSnapshot | undefined {
|
||||
if (!isPlainObject(value) || typeof value.unitId !== 'string' || value.unitId.length === 0 || typeof value.name !== 'string' || value.name.length === 0) {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const unitId = normalizeKeyString(value.unitId);
|
||||
const name = normalizeDisplayString(value.name);
|
||||
if (!unitId || !name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hp = normalizeNonNegativeInteger(value.hp);
|
||||
const maxHp = Math.max(hp, normalizeNonNegativeInteger(value.maxHp));
|
||||
return {
|
||||
unitId: value.unitId,
|
||||
name: value.name,
|
||||
unitId,
|
||||
name,
|
||||
level: Math.max(1, normalizeNonNegativeInteger(value.level)),
|
||||
exp: normalizeNonNegativeInteger(value.exp),
|
||||
hp,
|
||||
@@ -1063,13 +1105,19 @@ function normalizeCampaignUnitProgressSnapshot(value: unknown): CampaignUnitProg
|
||||
}
|
||||
|
||||
function normalizeCampaignBondProgressSnapshot(value: unknown): CampaignBondProgressSnapshot | undefined {
|
||||
if (!isPlainObject(value) || typeof value.id !== 'string' || value.id.length === 0 || typeof value.title !== 'string' || value.title.length === 0) {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = normalizeKeyString(value.id);
|
||||
const title = normalizeDisplayString(value.title);
|
||||
if (!id || !title) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: value.id,
|
||||
title: value.title,
|
||||
id,
|
||||
title,
|
||||
level: normalizeNonNegativeInteger(value.level),
|
||||
exp: normalizeNonNegativeInteger(value.exp),
|
||||
battleExp: normalizeNonNegativeInteger(value.battleExp)
|
||||
@@ -1077,18 +1125,25 @@ function normalizeCampaignBondProgressSnapshot(value: unknown): CampaignBondProg
|
||||
}
|
||||
|
||||
function normalizeReserveTrainingSnapshot(value: unknown): CampaignReserveTrainingSnapshot | undefined {
|
||||
if (!isPlainObject(value) || typeof value.unitId !== 'string' || value.unitId.length === 0 || typeof value.name !== 'string' || value.name.length === 0) {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const unitId = normalizeKeyString(value.unitId);
|
||||
const name = normalizeDisplayString(value.name);
|
||||
const focusLabel = normalizeDisplayString(value.focusLabel);
|
||||
if (!unitId || !name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
unitId: value.unitId,
|
||||
name: value.name,
|
||||
unitId,
|
||||
name,
|
||||
expGained: normalizeNonNegativeInteger(value.expGained),
|
||||
equipmentExpGained: normalizeNonNegativeInteger(value.equipmentExpGained),
|
||||
bondExpGained: normalizeNonNegativeInteger(value.bondExpGained),
|
||||
focusId: normalizeReserveTrainingFocusId(typeof value.focusId === 'string' ? value.focusId : undefined),
|
||||
...(typeof value.focusLabel === 'string' ? { focusLabel: value.focusLabel } : {}),
|
||||
...(focusLabel ? { focusLabel } : {}),
|
||||
level: normalizeNonNegativeInteger(value.level),
|
||||
exp: normalizeNonNegativeInteger(value.exp)
|
||||
};
|
||||
@@ -1153,21 +1208,15 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
|
||||
|
||||
return {
|
||||
battleId,
|
||||
battleTitle,
|
||||
battleTitle: normalizeDisplayString(battleTitle),
|
||||
outcome: report.outcome,
|
||||
turnNumber: normalizeNonNegativeInteger(report.turnNumber),
|
||||
rewardGold: normalizeNonNegativeInteger(report.rewardGold),
|
||||
defeatedEnemies: normalizeNonNegativeInteger(report.defeatedEnemies),
|
||||
totalEnemies: normalizeNonNegativeInteger(report.totalEnemies),
|
||||
objectives: arrayOrEmpty<unknown>(report.objectives)
|
||||
.map(normalizeBattleObjectiveSnapshot)
|
||||
.filter((objective): objective is BattleObjectiveSnapshot => Boolean(objective)),
|
||||
units: arrayOrEmpty<unknown>(report.units)
|
||||
.map(normalizeUnitDataSnapshot)
|
||||
.filter((unit): unit is UnitData => Boolean(unit)),
|
||||
bonds: arrayOrEmpty<unknown>(report.bonds)
|
||||
.map(normalizeCampBondSnapshot)
|
||||
.filter((bond): bond is CampBondSnapshot => Boolean(bond)),
|
||||
objectives: normalizeLimitedArray(report.objectives, normalizeBattleObjectiveSnapshot, maxCampaignBattleObjectiveEntries),
|
||||
units: normalizeLimitedArray(report.units, normalizeUnitDataSnapshot, maxCampaignBattleUnitEntries),
|
||||
bonds: normalizeLimitedArray(report.bonds, normalizeCampBondSnapshot, maxCampaignBattleBondEntries),
|
||||
mvp: normalizeCampMvpSnapshot(report.mvp),
|
||||
itemRewards: uniqueStrings(report.itemRewards),
|
||||
campaignRewards: cloneCampaignRewardSnapshot(
|
||||
@@ -1180,14 +1229,13 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
|
||||
}
|
||||
|
||||
function normalizeUnitDataSnapshot(value: unknown): UnitData | undefined {
|
||||
if (
|
||||
!isPlainObject(value) ||
|
||||
typeof value.id !== 'string' ||
|
||||
value.id.length === 0 ||
|
||||
typeof value.name !== 'string' ||
|
||||
value.name.length === 0 ||
|
||||
(value.faction !== 'ally' && value.faction !== 'enemy')
|
||||
) {
|
||||
if (!isPlainObject(value) || (value.faction !== 'ally' && value.faction !== 'enemy')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = normalizeKeyString(value.id);
|
||||
const name = normalizeDisplayString(value.name);
|
||||
if (!id || !name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1195,10 +1243,10 @@ function normalizeUnitDataSnapshot(value: unknown): UnitData | undefined {
|
||||
const hp = normalizeNonNegativeInteger(value.hp);
|
||||
const maxHp = Math.max(hp, normalizeNonNegativeInteger(value.maxHp));
|
||||
return {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
id,
|
||||
name,
|
||||
faction: value.faction,
|
||||
className: typeof value.className === 'string' ? value.className : '',
|
||||
className: normalizeDisplayString(value.className),
|
||||
classKey: normalizeUnitClassKey(value.classKey),
|
||||
level: Math.max(1, normalizeNonNegativeInteger(value.level)),
|
||||
exp: normalizeNonNegativeInteger(value.exp),
|
||||
@@ -1246,19 +1294,19 @@ function normalizeEquipmentLevel(value: unknown) {
|
||||
}
|
||||
|
||||
function normalizeCampMvpSnapshot(value: unknown): CampMvpSnapshot | undefined {
|
||||
if (
|
||||
!isPlainObject(value) ||
|
||||
typeof value.unitId !== 'string' ||
|
||||
value.unitId.length === 0 ||
|
||||
typeof value.name !== 'string' ||
|
||||
value.name.length === 0
|
||||
) {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const unitId = normalizeKeyString(value.unitId);
|
||||
const name = normalizeDisplayString(value.name);
|
||||
if (!unitId || !name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
unitId: value.unitId,
|
||||
name: value.name,
|
||||
unitId,
|
||||
name,
|
||||
damageDealt: normalizeNonNegativeInteger(value.damageDealt),
|
||||
defeats: normalizeNonNegativeInteger(value.defeats)
|
||||
};
|
||||
@@ -1270,20 +1318,16 @@ function normalizeCampBondSnapshot(value: unknown): CampBondSnapshot | undefined
|
||||
}
|
||||
|
||||
const unitIds = arrayOrEmpty<unknown>(value.unitIds).filter((unitId): unitId is string => typeof unitId === 'string' && unitId.length > 0);
|
||||
if (
|
||||
typeof value.id !== 'string' ||
|
||||
value.id.length === 0 ||
|
||||
typeof value.title !== 'string' ||
|
||||
value.title.length === 0 ||
|
||||
unitIds.length < 2
|
||||
) {
|
||||
const id = normalizeKeyString(value.id);
|
||||
const title = normalizeDisplayString(value.title);
|
||||
if (!id || !title || unitIds.length < 2) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...(value as CampBondSnapshot),
|
||||
id: value.id,
|
||||
title: value.title,
|
||||
id,
|
||||
title,
|
||||
unitIds: [unitIds[0], unitIds[1]] as [string, string],
|
||||
level: normalizeNonNegativeInteger(value.level),
|
||||
exp: normalizeNonNegativeInteger(value.exp),
|
||||
@@ -1522,8 +1566,8 @@ function cloneCampaignState(state: CampaignState): CampaignState {
|
||||
|
||||
function cloneReport(report: FirstBattleReport): FirstBattleReport {
|
||||
const cloned = JSON.parse(JSON.stringify(report)) as FirstBattleReport;
|
||||
cloned.completedCampDialogues = [...new Set(cloned.completedCampDialogues ?? [])];
|
||||
cloned.completedCampVisits = [...new Set(cloned.completedCampVisits ?? [])];
|
||||
cloned.completedCampDialogues = uniqueStrings(cloned.completedCampDialogues);
|
||||
cloned.completedCampVisits = uniqueStrings(cloned.completedCampVisits);
|
||||
if (cloned.campaignRewards) {
|
||||
cloned.campaignRewards = cloneCampaignRewardSnapshot(cloned.campaignRewards);
|
||||
}
|
||||
@@ -1544,17 +1588,19 @@ function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot): Campaign
|
||||
recruits: arrayOrEmpty<unknown>(rewardRecord.recruits)
|
||||
.filter(isPlainObject)
|
||||
.map((recruit) => ({
|
||||
unitId: typeof recruit.unitId === 'string' ? recruit.unitId.trim() : '',
|
||||
name: typeof recruit.name === 'string' ? recruit.name.trim() : ''
|
||||
unitId: normalizeKeyString(recruit.unitId),
|
||||
name: normalizeDisplayString(recruit.name)
|
||||
}))
|
||||
.filter((recruit) => recruit.unitId && recruit.name),
|
||||
.filter((recruit) => recruit.unitId && recruit.name)
|
||||
.slice(0, maxCampaignStringListEntries),
|
||||
unlocks: arrayOrEmpty<unknown>(rewardRecord.unlocks)
|
||||
.filter(isPlainObject)
|
||||
.map((unlock) => ({
|
||||
battleId: typeof unlock.battleId === 'string' ? unlock.battleId.trim() : '',
|
||||
title: typeof unlock.title === 'string' ? unlock.title.trim() : ''
|
||||
battleId: normalizeKeyString(unlock.battleId),
|
||||
title: normalizeDisplayString(unlock.title)
|
||||
}))
|
||||
.filter((unlock) => unlock.battleId && unlock.title),
|
||||
.filter((unlock) => unlock.battleId && unlock.title)
|
||||
.slice(0, maxCampaignStringListEntries),
|
||||
note: note.length > 0 && note.length <= maxCampaignRewardNoteLength ? note : undefined
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user