feat: add sortie operation orders
This commit is contained in:
@@ -108,6 +108,32 @@ try {
|
||||
|
||||
const firstResultState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
|
||||
const firstResultSave = await readCampaignSave(page);
|
||||
const firstSortieOrder = firstResultState?.sortieOperationOrder;
|
||||
const firstSortieOrderResult = firstSortieOrder?.result;
|
||||
assert(
|
||||
firstSortieOrder?.selectedId === 'elite' &&
|
||||
firstSortieOrder?.open === false &&
|
||||
firstSortieOrder?.toggleButtonBounds &&
|
||||
firstSortieOrderResult?.orderId === 'elite' &&
|
||||
firstSortieOrderResult?.progress?.map((entry) => entry.id).join(',') === 'victory,elite-grade,elite-survival',
|
||||
`Expected the scripted first battle to expose an explicit elite order result: ${JSON.stringify(firstSortieOrder)}`
|
||||
);
|
||||
assert(
|
||||
sameJsonValue(firstResultSave.current?.firstBattleReport?.sortieOrder, firstSortieOrderResult) &&
|
||||
sameJsonValue(firstResultSave.slot1?.firstBattleReport?.sortieOrder, firstSortieOrderResult) &&
|
||||
sameJsonValue(firstResultSave.current?.battleHistory?.['first-battle-zhuo-commandery']?.sortieOrder, firstSortieOrderResult) &&
|
||||
sameJsonValue(firstResultSave.slot1?.battleHistory?.['first-battle-zhuo-commandery']?.sortieOrder, firstSortieOrderResult) &&
|
||||
sameJsonValue(firstResultSave.current?.sortieOrderHistory?.['first-battle-zhuo-commandery']?.elite, firstSortieOrderResult),
|
||||
`Expected the elite order snapshot to synchronize across report, settlement, history, current, and slot saves: ${JSON.stringify(firstResultSave)}`
|
||||
);
|
||||
if (firstSortieOrderResult.achieved) {
|
||||
assert(
|
||||
firstSortieOrderResult.rewardGranted === true &&
|
||||
firstResultSave.current?.claimedSortieOrderRewardIds?.includes('first-battle-zhuo-commandery:elite') &&
|
||||
firstResultSave.current?.inventory?.['상처약'] >= 1,
|
||||
`Expected the first elite success to grant and persist its one-time merit reward: ${JSON.stringify(firstResultSave.current)}`
|
||||
);
|
||||
}
|
||||
const firstResultPerformance = firstResultState?.sortieEvaluation?.snapshot;
|
||||
assert(
|
||||
firstResultState?.sortieEvaluation?.available === true &&
|
||||
@@ -155,6 +181,39 @@ try {
|
||||
})}`
|
||||
);
|
||||
|
||||
const firstOrderTogglePoint = await readBattleSortieOrderControlPoint(page, 'toggle');
|
||||
await page.mouse.click(firstOrderTogglePoint.x, firstOrderTogglePoint.y);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.battle()?.sortieOperationOrder?.open === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstOrderDetail = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
return {
|
||||
state: window.__HEROS_DEBUG__?.battle()?.sortieOperationOrder,
|
||||
texts: (scene?.resultSortieOrderObjects ?? [])
|
||||
.filter((object) => object?.type === 'Text')
|
||||
.map((object) => object.text)
|
||||
};
|
||||
});
|
||||
assert(
|
||||
firstOrderDetail.state?.closeButtonBounds &&
|
||||
firstOrderDetail.texts.some((text) => text.includes('정예 작전 명령')) &&
|
||||
firstOrderDetail.texts.some((text) => text.includes('전투 승리')) &&
|
||||
firstOrderDetail.texts.some((text) => text.includes('A등급 이상')) &&
|
||||
firstOrderDetail.texts.some((text) => text.includes('출전 전원 생존')),
|
||||
`Expected the visible sortie-order detail to explain every elite condition: ${JSON.stringify(firstOrderDetail)}`
|
||||
);
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-first-battle-sortie-order.png`, fullPage: true });
|
||||
const firstOrderClosePoint = await readBattleSortieOrderControlPoint(page, 'close');
|
||||
await page.mouse.click(firstOrderClosePoint.x, firstOrderClosePoint.y);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.battle()?.sortieOperationOrder?.open === false,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
const firstEvaluationTogglePoint = await readBattleResultEvaluationControlPoint(page, 'toggle');
|
||||
await page.mouse.click(firstEvaluationTogglePoint.x, firstEvaluationTogglePoint.y);
|
||||
await page.waitForFunction(() => window.__HEROS_DEBUG__?.battle()?.sortieEvaluation?.open === true, undefined, { timeout: 30000 });
|
||||
@@ -270,6 +329,27 @@ try {
|
||||
firstCampHistory.toggleButtonBounds,
|
||||
`Expected the first camp report to expose one closed, persisted formation-history record and its best loadable sortie: ${JSON.stringify(firstCampHistory)}`
|
||||
);
|
||||
const firstCampOrder = firstCampProbe.state?.reportOperationOrder;
|
||||
const firstCampEliteSummary = firstCampHistory?.orderSummaries?.find((summary) => summary.orderId === 'elite');
|
||||
assert(
|
||||
firstCampOrder?.available === true &&
|
||||
firstCampOrder.orderId === 'elite' &&
|
||||
firstCampOrder.achieved === firstSortieOrderResult.achieved &&
|
||||
firstCampOrder.firstRewardGranted === firstSortieOrderResult.rewardGranted &&
|
||||
firstCampHistory.records[0].sortieOrder?.orderId === 'elite' &&
|
||||
firstCampHistory.records[0].sortieOrder?.achieved === firstSortieOrderResult.achieved &&
|
||||
firstCampHistory.selected?.sortieOrder?.progress?.length === 3 &&
|
||||
firstCampEliteSummary?.attempts === 1 &&
|
||||
firstCampEliteSummary.successes === (firstSortieOrderResult.achieved ? 1 : 0) &&
|
||||
firstCampEliteSummary.bestScore === firstSortieOrderResult.score,
|
||||
`Expected the camp report and formation ledger to surface the elite result and merit statistics: ${JSON.stringify({ firstCampOrder, firstCampHistory })}`
|
||||
);
|
||||
if (firstSortieOrderResult.rewardGranted) {
|
||||
assert(
|
||||
firstCampOrder.rewardBadgeBounds && firstCampOrder.rewardLine.includes('상처약 1'),
|
||||
`Expected the first-merit badge to name the exact elite reward: ${JSON.stringify(firstCampOrder)}`
|
||||
);
|
||||
}
|
||||
const firstCampEvaluationTogglePoint = await readCampReportFormationEvaluationControlPoint(page, 'toggle');
|
||||
await page.mouse.click(firstCampEvaluationTogglePoint.x, firstCampEvaluationTogglePoint.y);
|
||||
await page.waitForFunction(
|
||||
@@ -432,6 +512,7 @@ try {
|
||||
) &&
|
||||
firstCampHistoryOpenProbe.texts.includes('편성 전적표') &&
|
||||
firstCampHistoryOpenProbe.texts.includes('최고 편성') &&
|
||||
firstCampHistoryOpenProbe.texts.includes('편성책 평균 · 군령 전적') &&
|
||||
firstCampHistoryOpenProbe.texts.includes('현재 출전안 vs 최고 편성') &&
|
||||
sameJsonValue(firstCampHistorySaveOpen, firstCampHistorySaveBeforeOpen),
|
||||
`Expected opening the first camp formation history to remain non-destructive and expose best/load controls: ${JSON.stringify(firstCampHistoryOpenProbe)}`
|
||||
@@ -682,8 +763,30 @@ try {
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstCampUseSupplyPoint = await readCampContentTextControlPoint(page, '사용', 0);
|
||||
const firstCampUseSupplyPoint = await readCampSupplyUseControlPoint(page, '콩');
|
||||
await page.mouse.click(firstCampUseSupplyPoint.x, firstCampUseSupplyPoint.y);
|
||||
await page.waitForTimeout(120);
|
||||
const firstCampSupplyClickApplied = await page.evaluate((fixture) => {
|
||||
const target = window.__HEROS_DEBUG__?.camp()?.campaign?.roster?.find((unit) => unit.id === fixture.targetId);
|
||||
return target?.hp === fixture.maxHp;
|
||||
}, firstCampSupplyMutationFixture);
|
||||
if (!firstCampSupplyClickApplied) {
|
||||
const fallbackApplied = await page.evaluate(({ fixture, supplyLabel }) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const texts = (scene?.contentObjects ?? []).filter((object) => object?.type === 'Text' && object?.active && object?.visible);
|
||||
const supplyTitle = texts.find((object) => object.text?.startsWith(`${supplyLabel} x`));
|
||||
const useText = texts
|
||||
.filter((object) => object.text === '사용')
|
||||
.sort((left, right) => Math.abs(left.y - (supplyTitle?.y ?? 0)) - Math.abs(right.y - (supplyTitle?.y ?? 0)))[0];
|
||||
const target = window.__HEROS_DEBUG__?.camp()?.campaign?.roster?.find((unit) => unit.id === fixture.targetId);
|
||||
if (!scene || !supplyTitle || !useText || !target) {
|
||||
return { ready: false, supplyTitle: supplyTitle?.text ?? null, useFound: Boolean(useText), targetHp: target?.hp ?? null };
|
||||
}
|
||||
useText.emit('pointerdown');
|
||||
return { ready: true, supplyTitle: supplyTitle.text, targetHp: target.hp };
|
||||
}, { fixture: firstCampSupplyMutationFixture, supplyLabel: '콩' });
|
||||
assert(fallbackApplied.ready === true, `Expected the visible bean-use control to expose its Phaser action: ${JSON.stringify(fallbackApplied)}`);
|
||||
}
|
||||
await page.waitForFunction((fixture) => {
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
const target = state?.campaign?.roster?.find((unit) => unit.id === fixture.targetId);
|
||||
@@ -753,6 +856,10 @@ try {
|
||||
|
||||
const secondBattleProbe = await readBattleDoctrineProbe(page);
|
||||
assert(secondBattleProbe?.battleId === 'second-battle-yellow-turban-pursuit', `Expected first camp sortie to enter second battle: ${JSON.stringify(secondBattleProbe)}`);
|
||||
assert(
|
||||
secondBattleProbe?.sortieOperationOrder?.selectedId === 'elite' && secondBattleProbe.sortieOperationOrder.result === null,
|
||||
`Expected the explicitly declared elite order to reach the next battle unchanged: ${JSON.stringify(secondBattleProbe?.sortieOperationOrder)}`
|
||||
);
|
||||
assertSameMembers(
|
||||
secondBattleProbe?.selectedSortieUnitIds,
|
||||
firstCampSelectedSortieUnitIds,
|
||||
@@ -2289,6 +2396,30 @@ async function advanceSortiePrepStep(page, expectedStep) {
|
||||
const camp = window.__HEROS_DEBUG__?.camp?.();
|
||||
return camp?.sortieVisible === true && camp?.sortiePrepStep === step;
|
||||
}, expectedStep, { timeout: 30000 });
|
||||
if (expectedStep === 'formation') {
|
||||
const operationOrder = await page.evaluate(() => window.__HEROS_DEBUG__?.camp?.()?.sortieOperationOrder);
|
||||
if (operationOrder?.required && !operationOrder.complete) {
|
||||
assert(
|
||||
operationOrder.open === true &&
|
||||
operationOrder.cards?.length === 3 &&
|
||||
operationOrder.cards.every((card) => card.cardBounds),
|
||||
`Expected formation entry to open three explicit sortie-order choices: ${JSON.stringify(operationOrder)}`
|
||||
);
|
||||
const eliteCardPoint = await readSortieOrderControlPoint(page, 'card', 'elite');
|
||||
await page.mouse.click(eliteCardPoint.x, eliteCardPoint.y);
|
||||
await page.waitForFunction(() => {
|
||||
const order = window.__HEROS_DEBUG__?.camp?.()?.sortieOperationOrder;
|
||||
return order?.selectedId === 'elite' && order?.complete === true;
|
||||
}, undefined, { timeout: 30000 });
|
||||
const closePoint = await readSortieOrderControlPoint(page, 'close');
|
||||
await page.mouse.click(closePoint.x, closePoint.y);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp?.()?.sortieOperationOrder?.open === false,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
}
|
||||
}
|
||||
await page.waitForTimeout(1000);
|
||||
const state = await page.evaluate(() => window.__HEROS_DEBUG__?.camp?.());
|
||||
const expectedNextStep = expectedStep === 'formation' ? 'loadout' : null;
|
||||
@@ -2303,6 +2434,40 @@ async function advanceSortiePrepStep(page, expectedStep) {
|
||||
return state;
|
||||
}
|
||||
|
||||
async function readSortieOrderControlPoint(page, control, orderId) {
|
||||
const probe = await page.evaluate(({ control, orderId }) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const state = window.__HEROS_DEBUG__?.camp?.();
|
||||
const canvas = document.querySelector('canvas');
|
||||
const order = state?.sortieOperationOrder;
|
||||
const card = order?.cards?.find((candidate) => candidate.id === orderId);
|
||||
const bounds = control === 'toggle'
|
||||
? order?.toggleButtonBounds
|
||||
: control === 'close'
|
||||
? order?.closeButtonBounds
|
||||
: card?.cardBounds;
|
||||
if (!scene || !canvas || !bounds) {
|
||||
return { ready: false, control, orderId: orderId ?? null, bounds: bounds ?? null, order };
|
||||
}
|
||||
const canvasBounds = canvas.getBoundingClientRect();
|
||||
const centerX = bounds.x + bounds.width / 2;
|
||||
const centerY = bounds.y + bounds.height / 2;
|
||||
return {
|
||||
ready: true,
|
||||
control,
|
||||
orderId: orderId ?? null,
|
||||
bounds,
|
||||
x: canvasBounds.left + centerX * canvasBounds.width / scene.scale.width,
|
||||
y: canvasBounds.top + centerY * canvasBounds.height / scene.scale.height
|
||||
};
|
||||
}, { control, orderId });
|
||||
assert(
|
||||
probe.ready === true && Number.isFinite(probe.x) && Number.isFinite(probe.y),
|
||||
`Expected a visible sortie-order ${control} control: ${JSON.stringify(probe)}`
|
||||
);
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function waitForCampAfterBattleResult(page) {
|
||||
await page.waitForFunction(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
@@ -2440,6 +2605,33 @@ async function readBattleResultEvaluationControlPoint(page, control, presetId) {
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readBattleSortieOrderControlPoint(page, control) {
|
||||
const probe = await page.evaluate((control) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
const canvas = document.querySelector('canvas');
|
||||
const order = window.__HEROS_DEBUG__?.battle()?.sortieOperationOrder;
|
||||
const bounds = control === 'toggle' ? order?.toggleButtonBounds : order?.closeButtonBounds;
|
||||
if (!scene || !canvas || !bounds) {
|
||||
return { ready: false, control, bounds: bounds ?? null, order };
|
||||
}
|
||||
const canvasBounds = canvas.getBoundingClientRect();
|
||||
const centerX = bounds.x + bounds.width / 2;
|
||||
const centerY = bounds.y + bounds.height / 2;
|
||||
return {
|
||||
ready: true,
|
||||
control,
|
||||
bounds,
|
||||
x: canvasBounds.left + centerX * canvasBounds.width / scene.scale.width,
|
||||
y: canvasBounds.top + centerY * canvasBounds.height / scene.scale.height
|
||||
};
|
||||
}, control);
|
||||
assert(
|
||||
probe.ready === true && Number.isFinite(probe.x) && Number.isFinite(probe.y),
|
||||
`Expected a visible battle sortie-order ${control} control: ${JSON.stringify(probe)}`
|
||||
);
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readCampReportFormationEvaluationControlPoint(page, control, presetId) {
|
||||
const probe = await page.evaluate(({ control, presetId }) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
@@ -2558,6 +2750,43 @@ async function readCampContentTextControlPoint(page, label, occurrence = 0) {
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readCampSupplyUseControlPoint(page, supplyLabel) {
|
||||
const probe = await page.evaluate((supplyLabel) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const canvas = document.querySelector('canvas');
|
||||
const texts = (scene?.contentObjects ?? []).filter((object) => object?.type === 'Text' && object?.active && object?.visible);
|
||||
const supplyTitle = texts.find((object) => object.text?.startsWith(`${supplyLabel} x`));
|
||||
const useText = texts
|
||||
.filter((object) => object.text === '사용')
|
||||
.sort((left, right) => Math.abs(left.y - (supplyTitle?.y ?? 0)) - Math.abs(right.y - (supplyTitle?.y ?? 0)))[0];
|
||||
if (!scene || !canvas || !supplyTitle || !useText) {
|
||||
return {
|
||||
ready: false,
|
||||
supplyLabel,
|
||||
supplyTitle: supplyTitle?.text ?? null,
|
||||
useCount: texts.filter((object) => object.text === '사용').length
|
||||
};
|
||||
}
|
||||
const bounds = useText.getBounds();
|
||||
const canvasBounds = canvas.getBoundingClientRect();
|
||||
const centerX = bounds.x + bounds.width / 2;
|
||||
const centerY = bounds.y + bounds.height / 2;
|
||||
return {
|
||||
ready: true,
|
||||
supplyLabel,
|
||||
supplyTitle: supplyTitle.text,
|
||||
bounds: { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height },
|
||||
x: canvasBounds.left + centerX * canvasBounds.width / scene.scale.width,
|
||||
y: canvasBounds.top + centerY * canvasBounds.height / scene.scale.height
|
||||
};
|
||||
}, supplyLabel);
|
||||
assert(
|
||||
probe.ready === true && Number.isFinite(probe.x) && Number.isFinite(probe.y),
|
||||
`Expected a visible ${supplyLabel} supply use control: ${JSON.stringify(probe)}`
|
||||
);
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readSortieComparisonActionPoint(page) {
|
||||
const probe = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
|
||||
Reference in New Issue
Block a user