Extend release QA through full campaign
This commit is contained in:
@@ -33,8 +33,8 @@
|
|||||||
"verify:public-deploy": "node scripts/verify-public-deploy.mjs",
|
"verify:public-deploy": "node scripts/verify-public-deploy.mjs",
|
||||||
"qa:representative": "node scripts/qa-representative-battles.mjs",
|
"qa:representative": "node scripts/qa-representative-battles.mjs",
|
||||||
"qa:smoke": "node scripts/qa-representative-battles.mjs --set=smoke",
|
"qa:smoke": "node scripts/qa-representative-battles.mjs --set=smoke",
|
||||||
"qa:early": "node scripts/qa-representative-battles.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59",
|
"qa:early": "node scripts/qa-representative-battles.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66",
|
||||||
"qa:early:repeat": "node scripts/qa-repeat.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59",
|
"qa:early:repeat": "node scripts/qa-repeat.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66",
|
||||||
"deploy:nas": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1 -Build",
|
"deploy:nas": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1 -Build",
|
||||||
"deploy:nas:dist": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1",
|
"deploy:nas:dist": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1",
|
||||||
"ship:release": "node scripts/ship-release.mjs"
|
"ship:release": "node scripts/ship-release.mjs"
|
||||||
|
|||||||
@@ -1690,6 +1690,16 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
});
|
});
|
||||||
return objectives.length > 0 && objectives.every((objective) => objective.achieved || isObjectiveHeldByAlly(objective));
|
return objectives.length > 0 && objectives.every((objective) => objective.achieved || isObjectiveHeldByAlly(objective));
|
||||||
};
|
};
|
||||||
|
const requiredTargetUnits = () => targets.map((targetId) => unitRef(targetId)).filter((target) => target && target.hp > 0);
|
||||||
|
const pendingNonLeaderTargets = () => requiredTargetUnits().filter((target) => !target.id.includes('leader'));
|
||||||
|
const shouldHoldLeaderForRequiredTargets = (enemy) => {
|
||||||
|
return no >= 60 && no <= 66 && enemy.id.includes('leader') && pendingNonLeaderTargets().some((target) => target.id !== enemy.id);
|
||||||
|
};
|
||||||
|
const nextRequiredTarget = (unit) => {
|
||||||
|
const preferredTargets = pendingNonLeaderTargets();
|
||||||
|
const candidates = preferredTargets.length > 0 ? preferredTargets : requiredTargetUnits();
|
||||||
|
return candidates.sort((a, b) => distance(unit, a) - distance(unit, b))[0];
|
||||||
|
};
|
||||||
const stabilizeRepresentativeBattleIfNeeded = (round) => {
|
const stabilizeRepresentativeBattleIfNeeded = (round) => {
|
||||||
if (no === 13 && round >= 18) {
|
if (no === 13 && round >= 18) {
|
||||||
const objectives = state().objectives ?? [];
|
const objectives = state().objectives ?? [];
|
||||||
@@ -1885,7 +1895,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (no >= 53 && no <= 59 && round >= (no <= 54 ? 10 : 14)) {
|
if (no >= 53 && no <= 66 && round >= (no <= 54 ? 10 : 14)) {
|
||||||
const objectives = (state().objectives ?? []).filter((objective) => {
|
const objectives = (state().objectives ?? []).filter((objective) => {
|
||||||
return objective.category === 'bonus' && objective.targetTile && objective.id !== 'quick';
|
return objective.category === 'bonus' && objective.targetTile && objective.id !== 'quick';
|
||||||
});
|
});
|
||||||
@@ -1912,7 +1922,23 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
'chencang-gate': ['wei-yan', 'jiang-wei', 'wang-ping'],
|
'chencang-gate': ['wei-yan', 'jiang-wei', 'wang-ping'],
|
||||||
'siege-camps': ['wang-ping', 'huang-quan', 'fa-zheng'],
|
'siege-camps': ['wang-ping', 'huang-quan', 'fa-zheng'],
|
||||||
'grain-road': ['jiang-wei', 'wang-ping', 'ma-dai'],
|
'grain-road': ['jiang-wei', 'wang-ping', 'ma-dai'],
|
||||||
'rear-villages': ['ma-liang', 'huang-quan', 'zhuge-liang']
|
'rear-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'],
|
||||||
|
'two-commandery-road': ['wang-ping', 'jiang-wei', 'zhao-yun'],
|
||||||
|
'wudu-yinping-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'],
|
||||||
|
'mountain-camps': ['ma-dai', 'wei-yan', 'zhao-yun'],
|
||||||
|
'hanzhong-granary-camps': ['huang-quan', 'li-yan', 'wang-ping'],
|
||||||
|
'rain-pass-roads': ['jiang-wei', 'wang-ping', 'zhao-yun'],
|
||||||
|
'front-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'],
|
||||||
|
'qishan-supply-camps': ['huang-quan', 'li-yan', 'ma-liang'],
|
||||||
|
'lucheng-front-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'],
|
||||||
|
'hold-return-supply-camps': ['huang-quan', 'li-yan', 'wang-ping'],
|
||||||
|
'reassure-lucheng-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'],
|
||||||
|
'hold-weishui-supply-camps': ['huang-quan', 'li-yan', 'wang-ping'],
|
||||||
|
'reassure-river-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'],
|
||||||
|
'hold-northbank-forward-camps': ['huang-quan', 'li-yan', 'jiang-wei'],
|
||||||
|
'reassure-northbank-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'],
|
||||||
|
'preserve-wuzhang-camps': ['jiang-wei', 'wang-ping', 'zhuge-liang'],
|
||||||
|
'protect-returning-villages': ['huang-quan', 'li-yan', 'ma-liang']
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const objective of objectives) {
|
for (const objective of objectives) {
|
||||||
@@ -1995,6 +2021,9 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
if (shouldHoldBattleEndingTarget(enemy, objective)) {
|
if (shouldHoldBattleEndingTarget(enemy, objective)) {
|
||||||
return 420 + objectivePriority;
|
return 420 + objectivePriority;
|
||||||
}
|
}
|
||||||
|
if (shouldHoldLeaderForRequiredTargets(enemy)) {
|
||||||
|
return 380 + objectivePriority;
|
||||||
|
}
|
||||||
if (enemy.id === primaryTargetId) {
|
if (enemy.id === primaryTargetId) {
|
||||||
return (shouldDelayPrimaryTarget() ? 800 : 5200) + objectivePriority;
|
return (shouldDelayPrimaryTarget() ? 800 : 5200) + objectivePriority;
|
||||||
}
|
}
|
||||||
@@ -2032,7 +2061,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const aliveTargets = targets.map((targetId) => unitRef(targetId)).filter((target) => target && target.hp > 0);
|
const aliveTargets = no >= 60 && no <= 66 ? requiredTargetUnits().filter((target) => !shouldHoldLeaderForRequiredTargets(target)) : requiredTargetUnits();
|
||||||
if (aliveTargets.length > 0) {
|
if (aliveTargets.length > 0) {
|
||||||
const reachableTargets = aliveTargets.filter(canProgressTo);
|
const reachableTargets = aliveTargets.filter(canProgressTo);
|
||||||
const nearestTarget = (reachableTargets.length > 0 ? reachableTargets : aliveTargets).sort((a, b) => distance(unit, a) - distance(unit, b))[0];
|
const nearestTarget = (reachableTargets.length > 0 ? reachableTargets : aliveTargets).sort((a, b) => distance(unit, a) - distance(unit, b))[0];
|
||||||
@@ -2054,6 +2083,9 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
const candidates = liveUnits('enemy')
|
const candidates = liveUnits('enemy')
|
||||||
.filter((enemy) => scene.canAttack(unit, enemy))
|
.filter((enemy) => scene.canAttack(unit, enemy))
|
||||||
.filter((enemy) => {
|
.filter((enemy) => {
|
||||||
|
if (shouldHoldLeaderForRequiredTargets(enemy)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (endingHoldObjective && shouldHoldBattleEndingTarget(enemy, endingHoldObjective)) {
|
if (endingHoldObjective && shouldHoldBattleEndingTarget(enemy, endingHoldObjective)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2137,7 +2169,10 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
function canTakeAttack(unit, attack, threat = protectedIds.has(unit.id) ? incomingThreat(unit, unit) : 0) {
|
function canTakeAttack(unit, attack, threat = protectedIds.has(unit.id) ? incomingThreat(unit, unit) : 0) {
|
||||||
const isTarget = targetIds.has(attack.target.id);
|
const isTarget = targetIds.has(attack.target.id);
|
||||||
const isLeaderTarget = attack.target.id.includes('leader');
|
const isLeaderTarget = attack.target.id.includes('leader');
|
||||||
if (no >= 47 && no <= 59 && (isLeaderTarget || isTarget) && !secureObjectivesSatisfied()) {
|
if (shouldHoldLeaderForRequiredTargets(attack.target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (no >= 47 && no <= 66 && (isLeaderTarget || isTarget) && !secureObjectivesSatisfied()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!protectedIds.has(unit.id)) {
|
if (!protectedIds.has(unit.id)) {
|
||||||
@@ -2172,7 +2207,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
if (protectedIds.size > 1 && !isLeaderTarget) {
|
if (protectedIds.size > 1 && !isLeaderTarget) {
|
||||||
return unit.hp >= Math.ceil(unit.maxHp * 0.75) && threat <= Math.floor(unit.hp * 0.22);
|
return unit.hp >= Math.ceil(unit.maxHp * 0.75) && threat <= Math.floor(unit.hp * 0.22);
|
||||||
}
|
}
|
||||||
if (no >= 32 && no <= 59 && isLeaderTarget && secureObjectivesSatisfied()) {
|
if (no >= 32 && no <= 66 && isLeaderTarget && secureObjectivesSatisfied()) {
|
||||||
return unit.hp >= Math.ceil(unit.maxHp * 0.45) && threat <= Math.floor(unit.hp * 0.7);
|
return unit.hp >= Math.ceil(unit.maxHp * 0.45) && threat <= Math.floor(unit.hp * 0.7);
|
||||||
}
|
}
|
||||||
if (isLeaderTarget && (pressProtected || kill) && threat <= Math.floor(unit.hp * (pressProtected ? 0.7 : 0.42))) {
|
if (isLeaderTarget && (pressProtected || kill) && threat <= Math.floor(unit.hp * (pressProtected ? 0.7 : 0.42))) {
|
||||||
@@ -2317,7 +2352,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
return { x: 62, y: 18 };
|
return { x: 62, y: 18 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (no >= 35 && no <= 59) {
|
if (no >= 35 && no <= 66) {
|
||||||
const objectives = state().objectives ?? [];
|
const objectives = state().objectives ?? [];
|
||||||
if (no === 40) {
|
if (no === 40) {
|
||||||
const riverBank = objectives.find((entry) => entry.id === 'river-bank');
|
const riverBank = objectives.find((entry) => entry.id === 'river-bank');
|
||||||
@@ -2448,6 +2483,36 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
{ id: 'grain-road', point: { x: 56, y: 76 } },
|
{ id: 'grain-road', point: { x: 56, y: 76 } },
|
||||||
{ id: 'siege-camps', point: { x: 64, y: 68 } },
|
{ id: 'siege-camps', point: { x: 64, y: 68 } },
|
||||||
{ id: 'chencang-gate', point: { x: 101, y: 48 } }
|
{ id: 'chencang-gate', point: { x: 101, y: 48 } }
|
||||||
|
],
|
||||||
|
60: [
|
||||||
|
{ id: 'two-commandery-road', point: { x: 84, y: 58 } },
|
||||||
|
{ id: 'wudu-yinping-villages', point: { x: 87, y: 62 } },
|
||||||
|
{ id: 'mountain-camps', point: { x: 97, y: 62 } }
|
||||||
|
],
|
||||||
|
61: [
|
||||||
|
{ id: 'hanzhong-granary-camps', point: { x: 67, y: 72 } },
|
||||||
|
{ id: 'rain-pass-roads', point: { x: 88, y: 58 } },
|
||||||
|
{ id: 'front-villages', point: { x: 109, y: 49 } }
|
||||||
|
],
|
||||||
|
62: [
|
||||||
|
{ id: 'qishan-supply-camps', point: { x: 68, y: 72 } },
|
||||||
|
{ id: 'lucheng-front-villages', point: { x: 111, y: 50 } }
|
||||||
|
],
|
||||||
|
63: [
|
||||||
|
{ id: 'hold-return-supply-camps', point: { x: 70, y: 76 } },
|
||||||
|
{ id: 'reassure-lucheng-villages', point: { x: 113, y: 52 } }
|
||||||
|
],
|
||||||
|
64: [
|
||||||
|
{ id: 'hold-weishui-supply-camps', point: { x: 70, y: 78 } },
|
||||||
|
{ id: 'reassure-river-villages', point: { x: 96, y: 70 } }
|
||||||
|
],
|
||||||
|
65: [
|
||||||
|
{ id: 'hold-northbank-forward-camps', point: { x: 112, y: 66 } },
|
||||||
|
{ id: 'reassure-northbank-villages', point: { x: 97, y: 72 } }
|
||||||
|
],
|
||||||
|
66: [
|
||||||
|
{ id: 'preserve-wuzhang-camps', point: { x: 38, y: 96 } },
|
||||||
|
{ id: 'protect-returning-villages', point: { x: 25, y: 101 } }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
const routes = objectiveRoutes[no] ?? [];
|
const routes = objectiveRoutes[no] ?? [];
|
||||||
@@ -2459,7 +2524,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
}
|
}
|
||||||
const leaderDone = objectives.find((objective) => objective.id === 'leader')?.achieved;
|
const leaderDone = objectives.find((objective) => objective.id === 'leader')?.achieved;
|
||||||
if (!leaderDone && secureObjectivesSatisfied()) {
|
if (!leaderDone && secureObjectivesSatisfied()) {
|
||||||
const leader = unitRef(primaryTargetId);
|
const leader = no >= 60 && no <= 66 ? nextRequiredTarget(unit) : unitRef(primaryTargetId);
|
||||||
return leader ? { x: leader.x, y: leader.y } : undefined;
|
return leader ? { x: leader.x, y: leader.y } : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2485,7 +2550,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
|
|
||||||
function advancePointTile(unit, tiles) {
|
function advancePointTile(unit, tiles) {
|
||||||
const point = scenarioAdvancePoint(unit);
|
const point = scenarioAdvancePoint(unit);
|
||||||
const protectedScenarioPush = no >= 32 && no <= 59;
|
const protectedScenarioPush = no >= 32 && no <= 66;
|
||||||
if (!point || (protectedIds.has(unit.id) && !isLastProtectedFighter(unit) && !protectedScenarioPush)) {
|
if (!point || (protectedIds.has(unit.id) && !isLastProtectedFighter(unit) && !protectedScenarioPush)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -2522,7 +2587,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
];
|
];
|
||||||
const pressProtected = shouldPressProtected(unit);
|
const pressProtected = shouldPressProtected(unit);
|
||||||
const canDriveObjective = !protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit);
|
const canDriveObjective = !protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit);
|
||||||
const scenarioRouteTile = no >= 40 && no <= 59 ? advancePointTile(unit, tiles) : undefined;
|
const scenarioRouteTile = no >= 40 && no <= 66 ? advancePointTile(unit, tiles) : undefined;
|
||||||
if (scenarioRouteTile) {
|
if (scenarioRouteTile) {
|
||||||
return scenarioRouteTile;
|
return scenarioRouteTile;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user