Cap campaign battle history snapshots
This commit is contained in:
@@ -452,8 +452,15 @@ try {
|
||||
defeatedEnemies: '3',
|
||||
totalEnemies: '5',
|
||||
objectives: [
|
||||
{ id: 'village', label: 'Village', achieved: true, detail: 12, rewardGold: '80' },
|
||||
{ id: '', label: 'Broken', achieved: true }
|
||||
{ id: 'village', label: 'Village', achieved: true, detail: 12, summary: 'x'.repeat(220), rewardGold: '80' },
|
||||
{ id: '', label: 'Broken', achieved: true },
|
||||
...Array.from({ length: 30 }, (_, index) => ({
|
||||
id: `bonus-${index}`,
|
||||
label: `Bonus ${index}`,
|
||||
achieved: false,
|
||||
detail: 'Hold the ridge',
|
||||
rewardGold: 10
|
||||
}))
|
||||
],
|
||||
units: [
|
||||
{
|
||||
@@ -473,11 +480,36 @@ try {
|
||||
x: '2',
|
||||
y: '3'
|
||||
},
|
||||
{ id: 'broken-unit', name: 'Broken' }
|
||||
{ id: 'broken-unit', name: 'Broken' },
|
||||
...Array.from({ length: 120 }, (_, index) => ({
|
||||
id: `enemy-${index}`,
|
||||
name: `Enemy ${index}`,
|
||||
faction: 'enemy',
|
||||
className: 'Infantry',
|
||||
classKey: 'infantry',
|
||||
level: 1,
|
||||
exp: 0,
|
||||
hp: 20,
|
||||
maxHp: 20,
|
||||
attack: 12,
|
||||
move: 4,
|
||||
stats: { might: 8, intelligence: 5, leadership: 6, agility: 7, luck: 5 },
|
||||
equipment: {},
|
||||
x: index % 12,
|
||||
y: Math.floor(index / 12)
|
||||
}))
|
||||
],
|
||||
bonds: [
|
||||
{ id: 'liu-bei__guan-yu', title: 'Oath Bond', unitIds: ['liu-bei', 'guan-yu'], level: '2', exp: '30', battleExp: '4' },
|
||||
{ id: 'broken-bond', title: 'Broken Bond', unitIds: ['liu-bei'] }
|
||||
{ id: 'broken-bond', title: 'Broken Bond', unitIds: ['liu-bei'] },
|
||||
...Array.from({ length: 120 }, (_, index) => ({
|
||||
id: `bond-${index}`,
|
||||
title: `Bond ${index}`,
|
||||
unitIds: ['liu-bei', 'guan-yu'],
|
||||
level: 1,
|
||||
exp: 0,
|
||||
battleExp: 1
|
||||
}))
|
||||
],
|
||||
mvp: { unitId: 'liu-bei', name: 'Liu Bei', damageDealt: '88', defeats: '2' },
|
||||
itemRewards: ['Bean', 'Bean', 10, 'x'.repeat(97)],
|
||||
@@ -485,8 +517,16 @@ try {
|
||||
supplies: [' Bean ', 'Bean', 20, 'x'.repeat(97)],
|
||||
equipment: [' Iron Sword ', 'Iron Sword'],
|
||||
reputation: ['Village Thanks', ' Village Thanks '],
|
||||
recruits: [{ unitId: ' guan-yu ', name: ' Guan Yu ' }, { unitId: '', name: 'Broken' }],
|
||||
unlocks: [{ battleId: ' second-battle-yellow-turban-pursuit ', title: ' Next ' }, { battleId: '', title: 'Broken' }],
|
||||
recruits: [
|
||||
{ unitId: ' guan-yu ', name: ' Guan Yu ' },
|
||||
{ unitId: '', name: 'Broken' },
|
||||
...Array.from({ length: 140 }, (_, index) => ({ unitId: `recruit-${index}`, name: `Recruit ${index}` }))
|
||||
],
|
||||
unlocks: [
|
||||
{ battleId: ' second-battle-yellow-turban-pursuit ', title: ' Next ' },
|
||||
{ battleId: '', title: 'Broken' },
|
||||
...Array.from({ length: 140 }, (_, index) => ({ battleId: `battle-${index}`, title: `Battle ${index}` }))
|
||||
],
|
||||
note: ' Reward note '
|
||||
},
|
||||
completedCampDialogues: ['intro', 'intro', 7],
|
||||
@@ -504,11 +544,12 @@ try {
|
||||
malformedReport.firstBattleReport?.battleId === 'first-battle-zhuo-commandery' &&
|
||||
malformedReport.firstBattleReport.battleTitle.length > 0 &&
|
||||
malformedReport.firstBattleReport.turnNumber === 4 &&
|
||||
malformedReport.firstBattleReport.objectives.length === 1 &&
|
||||
malformedReport.firstBattleReport.objectives.length === 24 &&
|
||||
malformedReport.firstBattleReport.objectives[0].rewardGold === 80 &&
|
||||
malformedReport.firstBattleReport.units.length === 1 &&
|
||||
malformedReport.firstBattleReport.objectives[0].summary.length === 180 &&
|
||||
malformedReport.firstBattleReport.units.length === 96 &&
|
||||
malformedReport.firstBattleReport.units[0].level === 5 &&
|
||||
malformedReport.firstBattleReport.bonds.length === 1 &&
|
||||
malformedReport.firstBattleReport.bonds.length === 96 &&
|
||||
malformedReport.firstBattleReport.bonds[0].battleExp === 4 &&
|
||||
malformedReport.firstBattleReport.mvp?.damageDealt === 88 &&
|
||||
malformedReport.firstBattleReport.itemRewards.length === 1 &&
|
||||
@@ -516,8 +557,9 @@ try {
|
||||
malformedReport.firstBattleReport.campaignRewards?.supplies[0] === 'Bean' &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.equipment.length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.reputation.length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits.length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits.length === 128 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits[0].unitId === 'guan-yu' &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.unlocks.length === 128 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.unlocks[0].battleId === 'second-battle-yellow-turban-pursuit' &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.note === 'Reward note' &&
|
||||
malformedReport.firstBattleReport.completedCampDialogues.length === 1 &&
|
||||
@@ -589,15 +631,38 @@ try {
|
||||
rewardGold: '80',
|
||||
targetTile: { x: '4', y: '5', radius: '2' }
|
||||
},
|
||||
'broken-objective'
|
||||
'broken-objective',
|
||||
...Array.from({ length: 30 }, (_, index) => ({
|
||||
id: `history-bonus-${index}`,
|
||||
label: `History Bonus ${index}`,
|
||||
achieved: false,
|
||||
detail: 'Hold formation',
|
||||
rewardGold: 5
|
||||
}))
|
||||
],
|
||||
units: [
|
||||
{ unitId: 'liu-bei', name: 'Liu Bei', level: '4', exp: '33', hp: '28', maxHp: '30', equipment: 'broken' },
|
||||
{ unitId: '', name: 'Broken Unit', level: 99 }
|
||||
{ unitId: '', name: 'Broken Unit', level: 99 },
|
||||
...Array.from({ length: 120 }, (_, index) => ({
|
||||
unitId: `history-unit-${index}`,
|
||||
name: `History Unit ${index}`,
|
||||
level: 1,
|
||||
exp: 0,
|
||||
hp: 12,
|
||||
maxHp: 12,
|
||||
equipment: {}
|
||||
}))
|
||||
],
|
||||
bonds: [
|
||||
{ id: 'oath-bond', title: 'Oath Bond', level: '2', exp: '22', battleExp: '4' },
|
||||
{ id: '', title: 'Broken Bond' }
|
||||
{ id: '', title: 'Broken Bond' },
|
||||
...Array.from({ length: 120 }, (_, index) => ({
|
||||
id: `history-bond-${index}`,
|
||||
title: `History Bond ${index}`,
|
||||
level: 1,
|
||||
exp: 0,
|
||||
battleExp: 1
|
||||
}))
|
||||
],
|
||||
reserveTraining: [
|
||||
{
|
||||
@@ -611,7 +676,17 @@ try {
|
||||
level: '5',
|
||||
exp: '44'
|
||||
},
|
||||
'broken-training'
|
||||
'broken-training',
|
||||
...Array.from({ length: 120 }, (_, index) => ({
|
||||
unitId: `reserve-${index}`,
|
||||
name: `Reserve ${index}`,
|
||||
expGained: 3,
|
||||
equipmentExpGained: 1,
|
||||
focusId: 'balanced',
|
||||
focusLabel: 'Balanced',
|
||||
level: 1,
|
||||
exp: 3
|
||||
}))
|
||||
],
|
||||
completedAt: '2026-07-04T01:00:00.000Z'
|
||||
},
|
||||
@@ -664,18 +739,18 @@ try {
|
||||
`Expected stale latestBattleId to recover to the latest valid settlement: ${JSON.stringify(malformedHistory)}`
|
||||
);
|
||||
assert(
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.objectives.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.objectives.length === 24 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.objectives[0].rewardGold === 80 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.objectives[0].targetTile?.x === 4 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.objectives[0].status === undefined &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units.length === 96 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units[0].level === 4 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units[0].equipment.weapon.itemId === 'training-sword' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units[0].equipment.armor.itemId === 'cloth-armor' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units[0].equipment.accessory.itemId === 'grain-pouch' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds.length === 96 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds[0].battleExp === 4 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.reserveTraining?.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.reserveTraining?.length === 96 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.reserveTraining?.[0].focusId === 'balanced',
|
||||
`Expected nested battle history progress arrays to filter invalid entries and normalize numbers: ${JSON.stringify(malformedHistory)}`
|
||||
);
|
||||
|
||||
@@ -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