Deduplicate campaign reward references

This commit is contained in:
2026-07-05 14:13:27 +09:00
parent 7196ab3dbd
commit d0085eeb18
2 changed files with 38 additions and 14 deletions

View File

@@ -649,11 +649,13 @@ try {
reputation: ['Village Thanks', ' Village Thanks '], reputation: ['Village Thanks', ' Village Thanks '],
recruits: [ recruits: [
{ unitId: ' guan-yu ', name: ' Guan Yu ' }, { unitId: ' guan-yu ', name: ' Guan Yu ' },
{ unitId: 'guan-yu', name: 'Duplicate Guan Yu' },
{ unitId: '', name: 'Broken' }, { unitId: '', name: 'Broken' },
...Array.from({ length: 140 }, (_, index) => ({ unitId: `recruit-${index}`, name: `Recruit ${index}` })) ...Array.from({ length: 140 }, (_, index) => ({ unitId: `recruit-${index}`, name: `Recruit ${index}` }))
], ],
unlocks: [ unlocks: [
{ battleId: ' second-battle-yellow-turban-pursuit ', title: ' Next ' }, { battleId: ' second-battle-yellow-turban-pursuit ', title: ' Next ' },
{ battleId: 'second-battle-yellow-turban-pursuit', title: 'Duplicate Next' },
{ battleId: '', title: 'Broken' }, { battleId: '', title: 'Broken' },
...Array.from({ length: 140 }, (_, index) => ({ battleId: `battle-${index}`, title: `Battle ${index}` })) ...Array.from({ length: 140 }, (_, index) => ({ battleId: `battle-${index}`, title: `Battle ${index}` }))
], ],
@@ -690,8 +692,12 @@ try {
malformedReport.firstBattleReport.campaignRewards?.reputation.length === 1 && malformedReport.firstBattleReport.campaignRewards?.reputation.length === 1 &&
malformedReport.firstBattleReport.campaignRewards?.recruits.length === 128 && malformedReport.firstBattleReport.campaignRewards?.recruits.length === 128 &&
malformedReport.firstBattleReport.campaignRewards?.recruits[0].unitId === 'guan-yu' && 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 &&
malformedReport.firstBattleReport.campaignRewards?.unlocks.length === 128 && malformedReport.firstBattleReport.campaignRewards?.unlocks.length === 128 &&
malformedReport.firstBattleReport.campaignRewards?.unlocks[0].battleId === 'second-battle-yellow-turban-pursuit' && malformedReport.firstBattleReport.campaignRewards?.unlocks[0].battleId === 'second-battle-yellow-turban-pursuit' &&
malformedReport.firstBattleReport.campaignRewards?.unlocks[0].title === 'Next' &&
malformedReport.firstBattleReport.campaignRewards?.unlocks.filter((unlock) => unlock.battleId === 'second-battle-yellow-turban-pursuit').length === 1 &&
malformedReport.firstBattleReport.campaignRewards?.note === 'Reward note' && malformedReport.firstBattleReport.campaignRewards?.note === 'Reward note' &&
malformedReport.firstBattleReport.completedCampDialogues.length === 1 && malformedReport.firstBattleReport.completedCampDialogues.length === 1 &&
malformedReport.firstBattleReport.completedCampVisits.length === 0, malformedReport.firstBattleReport.completedCampVisits.length === 0,

View File

@@ -905,6 +905,18 @@ function uniqueStrings(value: unknown): string[] {
].slice(0, maxCampaignStringListEntries); ].slice(0, maxCampaignStringListEntries);
} }
function uniqueByKey<T>(entries: T[], keyForEntry: (entry: T) => string) {
const seen = new Set<string>();
return entries.filter((entry) => {
const key = keyForEntry(entry);
if (!key || seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
function normalizeKeyString(value: unknown): string { function normalizeKeyString(value: unknown): string {
if (typeof value !== 'string') { if (typeof value !== 'string') {
return ''; return '';
@@ -1658,21 +1670,27 @@ function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot): Campaign
supplies: uniqueStrings(rewardRecord.supplies), supplies: uniqueStrings(rewardRecord.supplies),
equipment: uniqueStrings(rewardRecord.equipment), equipment: uniqueStrings(rewardRecord.equipment),
reputation: uniqueStrings(rewardRecord.reputation), reputation: uniqueStrings(rewardRecord.reputation),
recruits: arrayOrEmpty<unknown>(rewardRecord.recruits) recruits: uniqueByKey(
.filter(isPlainObject) arrayOrEmpty<unknown>(rewardRecord.recruits)
.map((recruit) => ({ .filter(isPlainObject)
unitId: normalizeKeyString(recruit.unitId), .map((recruit) => ({
name: normalizeDisplayString(recruit.name) unitId: normalizeKeyString(recruit.unitId),
})) name: normalizeDisplayString(recruit.name)
.filter((recruit) => recruit.unitId && recruit.name) }))
.filter((recruit) => recruit.unitId && recruit.name),
(recruit) => recruit.unitId
)
.slice(0, maxCampaignStringListEntries), .slice(0, maxCampaignStringListEntries),
unlocks: arrayOrEmpty<unknown>(rewardRecord.unlocks) unlocks: uniqueByKey(
.filter(isPlainObject) arrayOrEmpty<unknown>(rewardRecord.unlocks)
.map((unlock) => ({ .filter(isPlainObject)
battleId: normalizeKeyString(unlock.battleId), .map((unlock) => ({
title: normalizeDisplayString(unlock.title) battleId: normalizeKeyString(unlock.battleId),
})) title: normalizeDisplayString(unlock.title)
.filter((unlock) => unlock.battleId && unlock.title) }))
.filter((unlock) => unlock.battleId && unlock.title),
(unlock) => unlock.battleId
)
.slice(0, maxCampaignStringListEntries), .slice(0, maxCampaignStringListEntries),
note: note.length > 0 && note.length <= maxCampaignRewardNoteLength ? note : undefined note: note.length > 0 && note.length <= maxCampaignRewardNoteLength ? note : undefined
}; };