feat: add resonance follow-up attacks
This commit is contained in:
@@ -35,6 +35,23 @@ try {
|
||||
assert(normalizeBattleSaveSlot(Number.NaN, 3) === 1, 'Expected NaN slot to normalize to slot 1.');
|
||||
assert(normalizeBattleSaveSlot(99, 3) === 3, 'Expected overflowing slot to clamp to slot count.');
|
||||
assert(isValidBattleSaveState(validState, options), 'Expected valid battle save to pass validation.');
|
||||
const validCompletedAttackHistoryState = {
|
||||
...validState,
|
||||
actedUnitIds: ['liu-bei', 'guan-yu'],
|
||||
attackIntents: [
|
||||
{ attackerId: 'liu-bei', targetId: 'rebel-1' },
|
||||
{ attackerId: 'guan-yu', targetId: 'rebel-1' }
|
||||
]
|
||||
};
|
||||
assert(
|
||||
isValidBattleSaveState(validCompletedAttackHistoryState, options),
|
||||
'Expected unique completed allied attacks against a living enemy to pass validation.'
|
||||
);
|
||||
assert(
|
||||
JSON.stringify(parseBattleSaveState(JSON.stringify(validCompletedAttackHistoryState), options)?.attackIntents) ===
|
||||
JSON.stringify(validCompletedAttackHistoryState.attackIntents),
|
||||
'Expected completed allied attack history to round-trip through battle-save parsing.'
|
||||
);
|
||||
assert(
|
||||
['elite', 'mobile', 'siege'].every((sortieOrderId) => isValidBattleSaveState({ ...validState, sortieOrderId }, options)),
|
||||
'Expected every sortie order id to pass battle-save validation.'
|
||||
@@ -218,6 +235,7 @@ try {
|
||||
['unknown attack intent unit id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'ghost-unit' }] }],
|
||||
['self-targeting attack intent', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'liu-bei' }] }],
|
||||
['duplicate attack intent attacker id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'rebel-1' }, { attackerId: 'liu-bei', targetId: 'rebel-1' }] }],
|
||||
['unacted attack intent attacker', { actedUnitIds: [] }],
|
||||
['enemy attack intent attacker', { attackIntents: [{ attackerId: 'rebel-1', targetId: 'liu-bei' }] }],
|
||||
['ally attack intent target', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'guan-yu' }] }],
|
||||
['defeated attack intent attacker', { units: patchUnit(0, { hp: 0 }) }],
|
||||
|
||||
@@ -85,6 +85,80 @@ try {
|
||||
!firstBattleProbe.sideTexts.some((text) => text.includes('...')),
|
||||
`Expected sortie doctrine opening message without ASCII truncation: ${JSON.stringify(firstBattleProbe.sideTexts)}`
|
||||
);
|
||||
await setDebugQueryParam(page, 'debugForceBondChain', '1');
|
||||
const firstBattleBondChain = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
const state = window.__HEROS_DEBUG__?.battle();
|
||||
const mechanics = state?.combatMechanicsProbe;
|
||||
const attacker = mechanics?.attackerId ? scene?.debugUnitById(mechanics.attackerId) : undefined;
|
||||
const partner = mechanics?.partnerId ? scene?.debugUnitById(mechanics.partnerId) : undefined;
|
||||
const enemy = mechanics?.enemyId ? scene?.debugUnitById(mechanics.enemyId) : undefined;
|
||||
if (!scene || !attacker || !partner || !enemy) {
|
||||
return { chain: mechanics?.chain ?? null, forcedRoll: null, preview: null };
|
||||
}
|
||||
|
||||
const originalIntents = scene.attackIntents.map((intent) => ({ ...intent }));
|
||||
const originalActedUnitIds = new Set(scene.actedUnitIds);
|
||||
scene.attackIntents = [{ attackerId: partner.id, targetId: enemy.id }];
|
||||
scene.actedUnitIds = new Set([...originalActedUnitIds, partner.id]);
|
||||
const preview = scene.combatPreview(attacker, enemy, 'attack');
|
||||
scene.attackIntents = originalIntents;
|
||||
scene.actedUnitIds = originalActedUnitIds;
|
||||
|
||||
return {
|
||||
chain: mechanics.chain ?? null,
|
||||
forcedRoll: scene.debugBondChainRollOverride(),
|
||||
preview: {
|
||||
rate: preview.bondChainRate,
|
||||
damage: preview.bondChainDamage,
|
||||
bondId: preview.bondChainBondId ?? null,
|
||||
partnerId: preview.bondChainPartnerId ?? null,
|
||||
partnerName: preview.bondChainPartnerName ?? null,
|
||||
label: preview.bondChainLabel ?? null
|
||||
}
|
||||
};
|
||||
});
|
||||
await setDebugQueryParam(page, 'debugForceBondChain', '0');
|
||||
const firstBattleBondChainForcedFailure = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
return scene?.debugBondChainRollOverride() ?? null;
|
||||
});
|
||||
await setDebugQueryParam(page, 'debugForceBondChain', null);
|
||||
assert(
|
||||
firstBattleBondChain.preview?.rate === 18 &&
|
||||
firstBattleBondChain.preview.damage > 0 &&
|
||||
firstBattleBondChain.preview.bondId &&
|
||||
firstBattleBondChain.preview.partnerId === firstBattleBondChain.chain?.expectedSupporterId &&
|
||||
firstBattleBondChain.preview.partnerName &&
|
||||
firstBattleBondChain.preview.label,
|
||||
`Expected a complete level-72 resonance-pursuit preview contract: ${JSON.stringify(firstBattleBondChain)}`
|
||||
);
|
||||
assert(
|
||||
firstBattleBondChain.forcedRoll === true &&
|
||||
firstBattleBondChainForcedFailure === false &&
|
||||
firstBattleBondChain.chain?.rate === 18 &&
|
||||
firstBattleBondChain.chain.supportDamage === firstBattleBondChain.preview.damage &&
|
||||
firstBattleBondChain.chain.ineligibleWithoutPriorIntent === true &&
|
||||
firstBattleBondChain.chain.eligibleWithMatchingPriorIntent === true &&
|
||||
firstBattleBondChain.chain.strategyIgnored === true &&
|
||||
firstBattleBondChain.chain.counterIgnored === true &&
|
||||
firstBattleBondChain.chain.missedBaseBlocked === true &&
|
||||
firstBattleBondChain.chain.lethalBaseBlocked === true &&
|
||||
firstBattleBondChain.chain.survivingBaseAllowed === true &&
|
||||
firstBattleBondChain.chain.forcedSuccessTriggers === true &&
|
||||
firstBattleBondChain.chain.forcedFailureBlocked === true,
|
||||
`Expected pursuit eligibility, hit/survival gates, and deterministic roll overrides: ${JSON.stringify(firstBattleBondChain)}`
|
||||
);
|
||||
assert(
|
||||
firstBattleBondChain.chain?.statCredit?.partnerDamage === firstBattleBondChain.chain.supportDamage &&
|
||||
firstBattleBondChain.chain.statCredit.partnerDefeats === 1 &&
|
||||
firstBattleBondChain.chain.statCredit.partnerActions === 0 &&
|
||||
firstBattleBondChain.chain.statCredit.defenderDamageTaken === firstBattleBondChain.chain.supportDamage &&
|
||||
firstBattleBondChain.chain.followUpKillCancelsCounter === true &&
|
||||
firstBattleBondChain.chain.intentClearedAtTurnReset === true,
|
||||
`Expected pursuit damage and defeat credit to belong to the supporter, cancel counters on defeat, and reset next turn: ${JSON.stringify(firstBattleBondChain.chain)}`
|
||||
);
|
||||
|
||||
const firstBattleOrderHud = await assertLiveSortieOrderHud(page, {
|
||||
battleId: 'first-battle-zhuo-commandery',
|
||||
orderId: 'elite',
|
||||
|
||||
Reference in New Issue
Block a user