feat: preview resonance pursuit pairs in formation
This commit is contained in:
@@ -993,6 +993,273 @@ try {
|
||||
firstSortieSynergy?.firstPursuitTrinityConfigured === true,
|
||||
`Expected first sortie to preview three bonds, the strongest effect, and trinity: ${JSON.stringify(firstSortieSynergy)}`
|
||||
);
|
||||
|
||||
const expectedFirstSortiePursuitPairs = [
|
||||
{
|
||||
id: 'liu-bei__guan-yu',
|
||||
unitIds: ['liu-bei', 'guan-yu'],
|
||||
unitNames: ['유비', '관우'],
|
||||
title: '도원결의',
|
||||
level: 72,
|
||||
chainRate: 18,
|
||||
damageBonus: 9
|
||||
},
|
||||
{
|
||||
id: 'liu-bei__zhang-fei',
|
||||
unitIds: ['liu-bei', 'zhang-fei'],
|
||||
unitNames: ['유비', '장비'],
|
||||
title: '의형제',
|
||||
level: 68,
|
||||
chainRate: 8,
|
||||
damageBonus: 9
|
||||
},
|
||||
{
|
||||
id: 'guan-yu__zhang-fei',
|
||||
unitIds: ['guan-yu', 'zhang-fei'],
|
||||
unitNames: ['관우', '장비'],
|
||||
title: '맹장 공명',
|
||||
level: 64,
|
||||
chainRate: 8,
|
||||
damageBonus: 9
|
||||
}
|
||||
];
|
||||
const firstSortiePursuit = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview);
|
||||
const logicalViewportBounds = { x: 0, y: 0, width: 1280, height: 720 };
|
||||
assert(
|
||||
sameJsonValue(firstSortiePursuit?.activePairs, expectedFirstSortiePursuitPairs) &&
|
||||
firstSortiePursuit?.selectableOpportunities?.length === 0,
|
||||
`Expected the selected founding trio to expose ordered 18/8/8 resonance-pursuit pairs without reserve opportunities: ${JSON.stringify(firstSortiePursuit)}`
|
||||
);
|
||||
assert(
|
||||
firstSortiePursuit?.summaryText === '조합 시너지 · 추격 후보 3조 · 역할 3/3' &&
|
||||
firstSortiePursuit?.ruleText === '같은 적을 먼저 친 공명 장수가 지원 거리 안에서 표시 확률로 추격합니다.' &&
|
||||
isFiniteBounds(firstSortiePursuit.panelBounds) &&
|
||||
isFiniteBounds(firstSortiePursuit.ruleBounds) &&
|
||||
boundsInside(firstSortiePursuit.panelBounds, logicalViewportBounds) &&
|
||||
boundsInside(firstSortiePursuit.ruleBounds, firstSortiePursuit.panelBounds) &&
|
||||
sameJsonValue(firstSortiePursuit.rows?.map((row) => ({ state: row.state, text: row.text })), [
|
||||
{ state: 'candidate', text: '18% 유비↔관우' },
|
||||
{ state: 'candidate', text: '8% 유비↔장비' },
|
||||
{ state: 'candidate', text: '8% 관우↔장비' }
|
||||
]) &&
|
||||
firstSortiePursuit.rows.every(
|
||||
(row) =>
|
||||
isFiniteBounds(row.bounds) &&
|
||||
isFiniteBounds(row.textBounds) &&
|
||||
boundsInside(row.bounds, firstSortiePursuit.panelBounds) &&
|
||||
boundsInside(row.textBounds, row.bounds, 2)
|
||||
),
|
||||
`Expected all three pursuit chips and their labels to remain visible inside the first-sortie panel: ${JSON.stringify(firstSortiePursuit)}`
|
||||
);
|
||||
|
||||
const firstSortiePursuitSaveBefore = await readCampaignSave(page);
|
||||
const firstSortiePursuitMutation = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
if (
|
||||
!scene ||
|
||||
typeof scene.persistSortieSelection !== 'function' ||
|
||||
typeof scene.showSortiePrep !== 'function'
|
||||
) {
|
||||
return { ready: false };
|
||||
}
|
||||
|
||||
const before = {
|
||||
selectedUnitIds: [...scene.selectedSortieUnitIds],
|
||||
formationAssignments: { ...scene.sortieFormationAssignments },
|
||||
itemAssignments: structuredClone(scene.sortieItemAssignments ?? {}),
|
||||
focusedUnitId: scene.sortieFocusedUnitId ?? null,
|
||||
planFeedback: scene.sortiePlanFeedback,
|
||||
rosterScroll: scene.sortieRosterScroll
|
||||
};
|
||||
scene.selectedSortieUnitIds = scene.selectedSortieUnitIds.filter((unitId) => unitId !== 'guan-yu');
|
||||
const formationAssignments = { ...scene.sortieFormationAssignments };
|
||||
delete formationAssignments['guan-yu'];
|
||||
scene.sortieFormationAssignments = formationAssignments;
|
||||
const itemAssignments = structuredClone(scene.sortieItemAssignments ?? {});
|
||||
delete itemAssignments['guan-yu'];
|
||||
scene.sortieItemAssignments = itemAssignments;
|
||||
scene.sortieFocusedUnitId = 'guan-yu';
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
return {
|
||||
ready: true,
|
||||
before,
|
||||
after: {
|
||||
selectedUnitIds: state?.selectedSortieUnitIds,
|
||||
formationAssignments: state?.sortieFormationAssignments,
|
||||
itemAssignments: state?.sortieItemAssignments,
|
||||
focusedUnitId: state?.sortieFocusedUnitId,
|
||||
pursuit: state?.sortiePursuitPreview,
|
||||
focusedSynergy: state?.sortieFocusedSynergyPreview
|
||||
}
|
||||
};
|
||||
});
|
||||
const expectedGuanYuPursuitOpportunities = [
|
||||
{
|
||||
...expectedFirstSortiePursuitPairs[0],
|
||||
selectedPartnerId: 'liu-bei',
|
||||
selectedPartnerName: '유비',
|
||||
candidateUnitId: 'guan-yu',
|
||||
candidateUnitName: '관우'
|
||||
},
|
||||
{
|
||||
...expectedFirstSortiePursuitPairs[2],
|
||||
selectedPartnerId: 'zhang-fei',
|
||||
selectedPartnerName: '장비',
|
||||
candidateUnitId: 'guan-yu',
|
||||
candidateUnitName: '관우'
|
||||
}
|
||||
];
|
||||
assert(
|
||||
firstSortiePursuitMutation.ready === true &&
|
||||
!firstSortiePursuitMutation.after?.selectedUnitIds?.includes('guan-yu') &&
|
||||
firstSortiePursuitMutation.after?.focusedUnitId === 'guan-yu' &&
|
||||
!Object.prototype.hasOwnProperty.call(firstSortiePursuitMutation.after?.formationAssignments ?? {}, 'guan-yu') &&
|
||||
!Object.prototype.hasOwnProperty.call(firstSortiePursuitMutation.after?.itemAssignments ?? {}, 'guan-yu'),
|
||||
`Expected the RC fixture to bench Guan Yu and clear only his live role and supply assignments: ${JSON.stringify(firstSortiePursuitMutation)}`
|
||||
);
|
||||
assert(
|
||||
sameJsonValue(firstSortiePursuitMutation.after?.pursuit?.activePairs, [expectedFirstSortiePursuitPairs[1]]) &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitMutation.after?.pursuit?.selectableOpportunities,
|
||||
expectedGuanYuPursuitOpportunities
|
||||
),
|
||||
`Expected benching Guan Yu to leave one active 8% pursuit and expose his 18%/8% partner opportunities: ${JSON.stringify(firstSortiePursuitMutation.after?.pursuit)}`
|
||||
);
|
||||
assert(
|
||||
firstSortiePursuitMutation.after?.focusedSynergy?.mode === 'add' &&
|
||||
firstSortiePursuitMutation.after.focusedSynergy.comparison?.activeBondDelta === 2 &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitMutation.after.focusedSynergy.comparison.gainedBonds?.map((bond) => bond.id),
|
||||
['liu-bei__guan-yu', 'guan-yu__zhang-fei']
|
||||
) &&
|
||||
firstSortiePursuitMutation.after.focusedSynergy.comparison.lostBonds?.length === 0 &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitMutation.after.focusedSynergy.projected?.activeBonds?.map((bond) => bond.id),
|
||||
expectedFirstSortiePursuitPairs.map((pair) => pair.id)
|
||||
),
|
||||
`Expected Guan Yu's focused add projection to restore both pursuit bonds and the full trio: ${JSON.stringify(firstSortiePursuitMutation.after?.focusedSynergy)}`
|
||||
);
|
||||
|
||||
const firstSortiePursuitRemoveProjection = await page.evaluate((before) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
if (!scene || typeof scene.persistSortieSelection !== 'function' || typeof scene.showSortiePrep !== 'function') {
|
||||
return { ready: false };
|
||||
}
|
||||
const scenario = scene.nextSortieScenario();
|
||||
const current = scene.sortieSynergySnapshot(before.selectedUnitIds, scenario, before.formationAssignments);
|
||||
const projectedUnitIds = before.selectedUnitIds.filter((unitId) => unitId !== 'guan-yu');
|
||||
const projectedFormationAssignments = { ...before.formationAssignments };
|
||||
delete projectedFormationAssignments['guan-yu'];
|
||||
const projected = scene.sortieSynergySnapshot(projectedUnitIds, scenario, projectedFormationAssignments);
|
||||
const pursuitChanges = scene.changedSortiePursuitPairs(current, projected);
|
||||
|
||||
scene.selectedSortieUnitIds = [...before.selectedUnitIds];
|
||||
scene.sortieFormationAssignments = { ...before.formationAssignments };
|
||||
scene.sortieItemAssignments = structuredClone(before.itemAssignments);
|
||||
scene.sortieFocusedUnitId = before.focusedUnitId ?? undefined;
|
||||
scene.sortiePlanFeedback = before.planFeedback;
|
||||
scene.sortieRosterScroll = before.rosterScroll;
|
||||
scene.persistSortieSelection();
|
||||
scene.showSortiePrep();
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
return {
|
||||
ready: true,
|
||||
selectedUnitIds: state?.selectedSortieUnitIds,
|
||||
pursuit: state?.sortiePursuitPreview,
|
||||
removeProjection: {
|
||||
currentPairIds: pursuitChanges.currentPairs.map((pair) => pair.id),
|
||||
projectedPairIds: pursuitChanges.projectedPairs.map((pair) => pair.id),
|
||||
gainedPairIds: pursuitChanges.gainedPairs.map((pair) => pair.id),
|
||||
lostPairIds: pursuitChanges.lostPairs.map((pair) => pair.id),
|
||||
activePairDelta: pursuitChanges.projectedPairs.length - pursuitChanges.currentPairs.length
|
||||
}
|
||||
};
|
||||
}, firstSortiePursuitMutation.before);
|
||||
assert(
|
||||
firstSortiePursuitRemoveProjection.ready === true &&
|
||||
sameJsonValue(firstSortiePursuitRemoveProjection.selectedUnitIds, firstSortiePursuitMutation.before?.selectedUnitIds) &&
|
||||
sameJsonValue(firstSortiePursuitRemoveProjection.pursuit?.activePairs, expectedFirstSortiePursuitPairs) &&
|
||||
firstSortiePursuitRemoveProjection.pursuit?.selectableOpportunities?.length === 0 &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitRemoveProjection.removeProjection?.currentPairIds,
|
||||
expectedFirstSortiePursuitPairs.map((pair) => pair.id)
|
||||
) &&
|
||||
sameJsonValue(firstSortiePursuitRemoveProjection.removeProjection?.projectedPairIds, ['liu-bei__zhang-fei']) &&
|
||||
firstSortiePursuitRemoveProjection.removeProjection?.activePairDelta === -2 &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitRemoveProjection.removeProjection?.lostPairIds,
|
||||
['liu-bei__guan-yu', 'guan-yu__zhang-fei']
|
||||
) &&
|
||||
firstSortiePursuitRemoveProjection.removeProjection?.gainedPairIds?.length === 0,
|
||||
`Expected the restored trio to preview exactly the two pursuit pairs lost by removing Guan Yu: ${JSON.stringify(firstSortiePursuitRemoveProjection)}`
|
||||
);
|
||||
|
||||
const firstSortiePursuitRestored = await page.evaluate((before) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
if (!scene || typeof scene.persistSortieSelection !== 'function' || typeof scene.showSortiePrep !== 'function') {
|
||||
return { ready: false };
|
||||
}
|
||||
scene.selectedSortieUnitIds = [...before.selectedUnitIds];
|
||||
scene.sortieFormationAssignments = { ...before.formationAssignments };
|
||||
scene.sortieItemAssignments = structuredClone(before.itemAssignments);
|
||||
scene.sortieFocusedUnitId = before.focusedUnitId ?? undefined;
|
||||
scene.sortiePlanFeedback = before.planFeedback;
|
||||
scene.sortieRosterScroll = before.rosterScroll;
|
||||
scene.persistSortieSelection();
|
||||
scene.showSortiePrep();
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
return {
|
||||
ready: true,
|
||||
selectedUnitIds: state?.selectedSortieUnitIds,
|
||||
formationAssignments: state?.sortieFormationAssignments,
|
||||
itemAssignments: state?.sortieItemAssignments,
|
||||
focusedUnitId: state?.sortieFocusedUnitId ?? null,
|
||||
planFeedback: state?.sortiePlanFeedback,
|
||||
rosterScroll: scene.sortieRosterScroll,
|
||||
deploymentPreview: state?.sortieDeploymentPreview,
|
||||
pursuit: state?.sortiePursuitPreview
|
||||
};
|
||||
}, firstSortiePursuitMutation.before);
|
||||
const firstSortiePursuitSaveRestored = await readCampaignSave(page);
|
||||
assert(
|
||||
firstSortiePursuitRestored.ready === true &&
|
||||
sameJsonValue(firstSortiePursuitRestored.selectedUnitIds, firstSortiePursuitMutation.before?.selectedUnitIds) &&
|
||||
sameJsonValue(firstSortiePursuitRestored.formationAssignments, firstSortiePursuitMutation.before?.formationAssignments) &&
|
||||
sameJsonValue(firstSortiePursuitRestored.itemAssignments, firstSortiePursuitMutation.before?.itemAssignments) &&
|
||||
firstSortiePursuitRestored.focusedUnitId === firstSortiePursuitMutation.before?.focusedUnitId &&
|
||||
firstSortiePursuitRestored.planFeedback === firstSortiePursuitMutation.before?.planFeedback &&
|
||||
firstSortiePursuitRestored.rosterScroll === firstSortiePursuitMutation.before?.rosterScroll &&
|
||||
sameJsonValue(firstSortiePursuitRestored.deploymentPreview, firstCampDeploymentPreview) &&
|
||||
sameJsonValue(firstSortiePursuitRestored.pursuit?.activePairs, expectedFirstSortiePursuitPairs),
|
||||
`Expected the pursuit RC fixture to restore the exact live formation, roles, supplies, focus, feedback, scroll, and deployment: ${JSON.stringify(firstSortiePursuitRestored)}`
|
||||
);
|
||||
assert(
|
||||
sameJsonValue(
|
||||
firstSortiePursuitSaveRestored.current?.selectedSortieUnitIds,
|
||||
firstSortiePursuitSaveBefore.current?.selectedSortieUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitSaveRestored.current?.sortieFormationAssignments,
|
||||
firstSortiePursuitSaveBefore.current?.sortieFormationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitSaveRestored.current?.sortieItemAssignments,
|
||||
firstSortiePursuitSaveBefore.current?.sortieItemAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitSaveRestored.slot1?.selectedSortieUnitIds,
|
||||
firstSortiePursuitSaveBefore.slot1?.selectedSortieUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitSaveRestored.slot1?.sortieFormationAssignments,
|
||||
firstSortiePursuitSaveBefore.slot1?.sortieFormationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuitSaveRestored.slot1?.sortieItemAssignments,
|
||||
firstSortiePursuitSaveBefore.slot1?.sortieItemAssignments
|
||||
),
|
||||
`Expected the pursuit RC fixture to restore current and slot-1 sortie saves before launch: ${JSON.stringify(firstSortiePursuitSaveRestored)}`
|
||||
);
|
||||
await advanceSortiePrepStep(page, 'loadout');
|
||||
await page.mouse.click(1116, 656);
|
||||
await advanceUntilBattle(page, 'second-battle-yellow-turban-pursuit');
|
||||
@@ -1321,9 +1588,10 @@ try {
|
||||
midFormationSwapPreviewProbe.panel.title === '가상 교체 · 마대 → 관우' &&
|
||||
midFormationSwapPreviewProbe.panel.headline?.includes('OUT 마대') &&
|
||||
midFormationSwapPreviewProbe.panel.headline?.includes('IN 관우') &&
|
||||
midFormationSwapPreviewProbe.panel.impact?.includes('추격') &&
|
||||
midFormationSwapPreviewProbe.panel.impact?.includes('공백 돌파') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('+맥성 북문') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('-산길과 말발굽') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('추격 해제') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('8%') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('실제 편성 미변경'),
|
||||
`Expected the visible comparison panel to render the swap preview: ${JSON.stringify(midFormationSwapPreviewProbe.panel)}`
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user