From 0950f458a838b799e3e4036b71a06e4f93b4f6f3 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 5 Jul 2026 14:29:52 +0900 Subject: [PATCH] Filter invalid campaign reward recruits --- .../verify-campaign-save-normalization.mjs | 10 ++++++-- src/game/state/campaignState.ts | 25 ++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/scripts/verify-campaign-save-normalization.mjs b/scripts/verify-campaign-save-normalization.mjs index 3bd8c01..e2689c4 100644 --- a/scripts/verify-campaign-save-normalization.mjs +++ b/scripts/verify-campaign-save-normalization.mjs @@ -650,6 +650,7 @@ try { recruits: [ { unitId: ' guan-yu ', name: ' Guan Yu ' }, { unitId: 'guan-yu', name: 'Duplicate Guan Yu' }, + { unitId: 'ghost-recruit', name: 'Ghost Recruit' }, { unitId: '', name: 'Broken' }, ...Array.from({ length: 140 }, (_, index) => ({ unitId: `recruit-${index}`, name: `Recruit ${index}` })) ], @@ -691,7 +692,7 @@ try { malformedReport.firstBattleReport.campaignRewards?.supplies[0] === 'Bean' && malformedReport.firstBattleReport.campaignRewards?.equipment.length === 1 && malformedReport.firstBattleReport.campaignRewards?.reputation.length === 1 && - malformedReport.firstBattleReport.campaignRewards?.recruits.length === 128 && + malformedReport.firstBattleReport.campaignRewards?.recruits.length === 1 && malformedReport.firstBattleReport.campaignRewards?.recruits[0].unitId === 'guan-yu' && malformedReport.firstBattleReport.campaignRewards?.recruits[0].name === 'Guan Yu' && malformedReport.firstBattleReport.campaignRewards?.recruits.filter((recruit) => recruit.unitId === 'guan-yu').length === 1 && @@ -757,7 +758,11 @@ try { supplies: { corrupted: true }, equipment: ['Iron Sword'], reputation: 'Fame', - recruits: [{ unitId: 'test-recruit', name: 'Test Recruit' }, { broken: true }], + recruits: [ + { unitId: 'test-recruit', name: 'Test Recruit' }, + { unitId: ' guan-yu ', name: ' Guan Yu ' }, + { broken: true } + ], unlocks: [ { battleId: 'ghost-battle', title: 'Ghost Battle' }, { battleId: 'second-battle-yellow-turban-pursuit', title: 'Next Pursuit' } @@ -883,6 +888,7 @@ try { malformedHistory.battleHistory['first-battle-zhuo-commandery']?.itemRewards.length === 1 && malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.equipment[0] === 'Iron Sword' && malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.recruits.length === 1 && + malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.recruits[0].unitId === 'guan-yu' && malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.unlocks.length === 1 && malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.unlocks[0].battleId === 'second-battle-yellow-turban-pursuit' && malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.note === undefined, diff --git a/src/game/state/campaignState.ts b/src/game/state/campaignState.ts index 07984d8..9e94c86 100644 --- a/src/game/state/campaignState.ts +++ b/src/game/state/campaignState.ts @@ -1084,7 +1084,7 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle outcome: settlement.outcome, rewardGold: normalizeNonNegativeInteger(settlement.rewardGold), itemRewards: uniqueStrings(settlement.itemRewards), - campaignRewards: cloneCampaignRewardSnapshot(settlement.campaignRewards), + campaignRewards: cloneCampaignRewardSnapshot(settlement.campaignRewards, battleId), objectives: normalizeLimitedArray(settlement.objectives, normalizeBattleObjectiveSnapshot, maxCampaignBattleObjectiveEntries), units: normalizeLimitedArray(settlement.units, normalizeCampaignUnitProgressSnapshot, maxCampaignBattleUnitEntries), bonds: normalizeLimitedArray(settlement.bonds, normalizeCampaignBondProgressSnapshot, maxCampaignBattleBondEntries), @@ -1291,7 +1291,8 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi mvp: normalizeCampMvpSnapshot(report.mvp), itemRewards: uniqueStrings(report.itemRewards), campaignRewards: cloneCampaignRewardSnapshot( - isPlainObject(report.campaignRewards) ? report.campaignRewards as CampaignRewardSnapshot : undefined + isPlainObject(report.campaignRewards) ? report.campaignRewards as CampaignRewardSnapshot : undefined, + battleId ), completedCampDialogues: uniqueStrings(report.completedCampDialogues), completedCampVisits: uniqueStrings(report.completedCampVisits), @@ -1506,7 +1507,7 @@ function createBattleSettlement(report: FirstBattleReport, reserveTraining: Camp outcome: report.outcome, rewardGold: report.rewardGold, itemRewards: [...report.itemRewards], - campaignRewards: cloneCampaignRewardSnapshot(report.campaignRewards), + campaignRewards: cloneCampaignRewardSnapshot(report.campaignRewards, report.battleId), objectives: report.objectives.map((objective) => ({ ...objective })), units: report.units .filter((unit) => unit.faction === 'ally') @@ -1655,12 +1656,12 @@ function cloneReport(report: FirstBattleReport): FirstBattleReport { cloned.completedCampDialogues = uniqueStrings(cloned.completedCampDialogues); cloned.completedCampVisits = uniqueStrings(cloned.completedCampVisits); if (cloned.campaignRewards) { - cloned.campaignRewards = cloneCampaignRewardSnapshot(cloned.campaignRewards); + cloned.campaignRewards = cloneCampaignRewardSnapshot(cloned.campaignRewards, cloned.battleId); } return cloned; } -function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot): CampaignRewardSnapshot | undefined { +function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot, battleId?: string): CampaignRewardSnapshot | undefined { if (!rewards) { return undefined; } @@ -1678,7 +1679,7 @@ function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot): Campaign unitId: normalizeKeyString(recruit.unitId), name: normalizeDisplayString(recruit.name) })) - .filter((recruit) => recruit.unitId && recruit.name), + .filter((recruit) => recruit.unitId && recruit.name && isKnownCampaignRewardRecruit(recruit.unitId, battleId)), (recruit) => recruit.unitId ) .slice(0, maxCampaignStringListEntries), @@ -1697,6 +1698,18 @@ function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot): Campaign }; } +function isKnownCampaignRewardRecruit(unitId: string, battleId?: string) { + if (campaignRecruitUnitById.has(unitId)) { + return true; + } + + if (!battleId || !(battleId in battleScenarios)) { + return false; + } + + return battleScenarios[battleId as BattleScenarioId].units.some((unit) => unit.faction === 'ally' && unit.id === unitId); +} + function cloneUnit(unit: UnitData): UnitData { return JSON.parse(JSON.stringify(unit)) as UnitData; }