feat: connect Xuzhou equipment to battle outcomes
This commit is contained in:
@@ -16,6 +16,9 @@ try {
|
||||
thirdCampPreparationSelectionRecordId,
|
||||
thirdCampPreparationTargetBattleId
|
||||
} = await server.ssrLoadModule('/src/game/data/thirdCampPreparation.ts');
|
||||
const {
|
||||
cityEquipmentPreparationSelectionRecordId
|
||||
} = await server.ssrLoadModule('/src/game/data/cityEquipmentPreparation.ts');
|
||||
|
||||
const options = {
|
||||
expectedBattleId: 'first-battle-zhuo-commandery',
|
||||
@@ -406,6 +409,227 @@ try {
|
||||
parsedLegacyFourthBattleState?.thirdCampPreparation === undefined,
|
||||
'Expected legacy fourth-battle saves without preparation progress to remain valid.'
|
||||
);
|
||||
|
||||
const eighthBattleId = 'eighth-battle-xiaopei-supply-road';
|
||||
const eighthBattleOptions = {
|
||||
...options,
|
||||
expectedBattleId: eighthBattleId,
|
||||
validEquipmentItemIds: new Set([
|
||||
...options.validEquipmentItemIds,
|
||||
'iron-spear'
|
||||
]),
|
||||
validEquipmentSlotItems: {
|
||||
...options.validEquipmentSlotItems,
|
||||
weapon: new Set(['training-sword', 'iron-spear'])
|
||||
}
|
||||
};
|
||||
const eighthBattleUnits = validState.units.map((unit) => ({
|
||||
...unit,
|
||||
stats: unit.stats ? { ...unit.stats } : undefined,
|
||||
equipment: {
|
||||
weapon: {
|
||||
...(unit.id === 'liu-bei'
|
||||
? { itemId: 'iron-spear', level: 2, exp: 17 }
|
||||
: unit.equipment.weapon)
|
||||
},
|
||||
armor: { ...unit.equipment.armor },
|
||||
accessory: { ...unit.equipment.accessory }
|
||||
}
|
||||
}));
|
||||
const validCityEquipmentContribution = {
|
||||
version: 1,
|
||||
selectionRecordId: cityEquipmentPreparationSelectionRecordId,
|
||||
cityStayId: 'xuzhou',
|
||||
sourceBattleId: 'seventh-battle-xuzhou-rescue',
|
||||
targetBattleId: eighthBattleId,
|
||||
offerId: 'city-xuzhou-iron-spear',
|
||||
itemId: 'iron-spear',
|
||||
slot: 'weapon',
|
||||
price: 620,
|
||||
purchaseNumber: 2,
|
||||
unitId: 'liu-bei',
|
||||
previousItemId: 'training-sword',
|
||||
deployed: true,
|
||||
offensiveActions: 3,
|
||||
defensiveHits: 0,
|
||||
bonusDamage: 7,
|
||||
preventedDamage: 0
|
||||
};
|
||||
const eighthBattleBaseState = {
|
||||
...validState,
|
||||
battleId: eighthBattleId,
|
||||
campaignStep: 'eighth-battle',
|
||||
units: eighthBattleUnits,
|
||||
enemyUsableUseKeys: []
|
||||
};
|
||||
const validCityEquipmentState = {
|
||||
...eighthBattleBaseState,
|
||||
cityEquipmentContribution: validCityEquipmentContribution
|
||||
};
|
||||
const parsedCityEquipmentState = parseBattleSaveState(
|
||||
JSON.stringify(validCityEquipmentState),
|
||||
eighthBattleOptions
|
||||
);
|
||||
assert(
|
||||
isValidBattleSaveState(
|
||||
validCityEquipmentState,
|
||||
eighthBattleOptions
|
||||
) &&
|
||||
JSON.stringify(
|
||||
parsedCityEquipmentState?.cityEquipmentContribution
|
||||
) === JSON.stringify(validCityEquipmentContribution) &&
|
||||
parsedCityEquipmentState?.cityEquipmentContribution !==
|
||||
validCityEquipmentContribution,
|
||||
'Expected a deployed city-purchase contribution to round-trip as a deep-cloned battle-save field.'
|
||||
);
|
||||
|
||||
const unsafeCityEquipmentState = {
|
||||
...validCityEquipmentState,
|
||||
cityEquipmentContribution: {
|
||||
...validCityEquipmentContribution,
|
||||
offensiveActions: 2.9,
|
||||
defensiveHits: -4,
|
||||
bonusDamage: Number.MAX_SAFE_INTEGER,
|
||||
preventedDamage: 18
|
||||
}
|
||||
};
|
||||
const normalizedUnsafeCityEquipment = parseBattleSaveState(
|
||||
JSON.stringify(unsafeCityEquipmentState),
|
||||
eighthBattleOptions
|
||||
)?.cityEquipmentContribution;
|
||||
assert(
|
||||
normalizedUnsafeCityEquipment?.offensiveActions === 2 &&
|
||||
normalizedUnsafeCityEquipment.defensiveHits === 0 &&
|
||||
normalizedUnsafeCityEquipment.bonusDamage === 999999 &&
|
||||
normalizedUnsafeCityEquipment.preventedDamage === 0 &&
|
||||
!isValidBattleSaveState(
|
||||
unsafeCityEquipmentState,
|
||||
eighthBattleOptions
|
||||
),
|
||||
`Expected unsafe city-equipment counters to normalize safely and fail strict direct validation: ${JSON.stringify(normalizedUnsafeCityEquipment)}`
|
||||
);
|
||||
|
||||
const noRecordedUseCityEquipment = parseBattleSaveState(
|
||||
JSON.stringify({
|
||||
...validCityEquipmentState,
|
||||
cityEquipmentContribution: {
|
||||
...validCityEquipmentContribution,
|
||||
offensiveActions: 0,
|
||||
bonusDamage: 77,
|
||||
defensiveHits: 0,
|
||||
preventedDamage: 88
|
||||
}
|
||||
}),
|
||||
eighthBattleOptions
|
||||
)?.cityEquipmentContribution;
|
||||
assert(
|
||||
noRecordedUseCityEquipment?.bonusDamage === 0 &&
|
||||
noRecordedUseCityEquipment.preventedDamage === 0,
|
||||
'Expected contribution damage totals to clear when no matching actions or hits were recorded.'
|
||||
);
|
||||
|
||||
const wrongBattleCityEquipment = {
|
||||
...validState,
|
||||
cityEquipmentContribution: validCityEquipmentContribution
|
||||
};
|
||||
const normalizedWrongBattleCityEquipment =
|
||||
normalizeBattleSaveState(
|
||||
wrongBattleCityEquipment,
|
||||
options
|
||||
);
|
||||
assert(
|
||||
normalizedWrongBattleCityEquipment?.cityEquipmentContribution ===
|
||||
undefined &&
|
||||
!isValidBattleSaveState(
|
||||
wrongBattleCityEquipment,
|
||||
options
|
||||
),
|
||||
'Expected city-equipment progress attached to another battle to be discarded and rejected by direct validation.'
|
||||
);
|
||||
|
||||
const mismatchedEquipmentCityState = {
|
||||
...validCityEquipmentState,
|
||||
units: validState.units.map((unit) => ({
|
||||
...unit,
|
||||
equipment: {
|
||||
weapon: { ...unit.equipment.weapon },
|
||||
armor: { ...unit.equipment.armor },
|
||||
accessory: { ...unit.equipment.accessory }
|
||||
}
|
||||
}))
|
||||
};
|
||||
const normalizedMismatchedEquipment = normalizeBattleSaveState(
|
||||
mismatchedEquipmentCityState,
|
||||
eighthBattleOptions
|
||||
);
|
||||
assert(
|
||||
normalizedMismatchedEquipment?.cityEquipmentContribution ===
|
||||
undefined &&
|
||||
!isValidBattleSaveState(
|
||||
mismatchedEquipmentCityState,
|
||||
eighthBattleOptions
|
||||
),
|
||||
'Expected a save whose recorded unit no longer equips the purchased item to discard and reject the contribution.'
|
||||
);
|
||||
|
||||
const corruptedCityEquipmentCases = [
|
||||
{
|
||||
label: 'forged record id',
|
||||
contribution: {
|
||||
...validCityEquipmentContribution,
|
||||
selectionRecordId: 'forged-city-equipment'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'wrong-slot previous item',
|
||||
contribution: {
|
||||
...validCityEquipmentContribution,
|
||||
previousItemId: 'cloth-armor'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'undeployed runtime',
|
||||
contribution: {
|
||||
...validCityEquipmentContribution,
|
||||
deployed: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'unknown prepared unit',
|
||||
contribution: {
|
||||
...validCityEquipmentContribution,
|
||||
unitId: 'missing-unit'
|
||||
}
|
||||
}
|
||||
];
|
||||
corruptedCityEquipmentCases.forEach(({ label, contribution }) => {
|
||||
const candidate = {
|
||||
...eighthBattleBaseState,
|
||||
cityEquipmentContribution: contribution
|
||||
};
|
||||
const normalized = normalizeBattleSaveState(
|
||||
candidate,
|
||||
eighthBattleOptions
|
||||
);
|
||||
assert(
|
||||
normalized?.cityEquipmentContribution === undefined &&
|
||||
!isValidBattleSaveState(candidate, eighthBattleOptions),
|
||||
`Expected ${label} city-equipment progress to be discarded and rejected.`
|
||||
);
|
||||
});
|
||||
|
||||
assert(
|
||||
isValidBattleSaveState(
|
||||
eighthBattleBaseState,
|
||||
eighthBattleOptions
|
||||
) &&
|
||||
parseBattleSaveState(
|
||||
JSON.stringify(eighthBattleBaseState),
|
||||
eighthBattleOptions
|
||||
)?.cityEquipmentContribution === undefined,
|
||||
'Expected legacy eighth-battle saves without city-equipment contribution progress to remain valid.'
|
||||
);
|
||||
|
||||
const validSortieStatsState = {
|
||||
...validState,
|
||||
battleStats: {
|
||||
|
||||
Reference in New Issue
Block a user