Expand release QA through Meng Huo captures
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",
|
"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",
|
||||||
"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",
|
"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",
|
||||||
"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"
|
||||||
|
|||||||
@@ -1723,6 +1723,101 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (no === 49 && round >= 12) {
|
||||||
|
const objectives = state().objectives ?? [];
|
||||||
|
const amnestyVillages = objectives.find((objective) => objective.id === 'amnesty-villages');
|
||||||
|
const captiveCamps = objectives.find((objective) => objective.id === 'captive-camps');
|
||||||
|
const villageGuard =
|
||||||
|
unitRef('wang-ping') ??
|
||||||
|
unitRef('huang-quan') ??
|
||||||
|
liveUnits('ally').find((ally) => !protectedIds.has(ally.id)) ??
|
||||||
|
liveUnits('ally')[0];
|
||||||
|
const campVanguard =
|
||||||
|
unitRef('zhao-yun') ??
|
||||||
|
unitRef('ma-chao') ??
|
||||||
|
liveUnits('ally').find((ally) => ally.id !== villageGuard?.id && !protectedIds.has(ally.id)) ??
|
||||||
|
liveUnits('ally')[0];
|
||||||
|
|
||||||
|
if (amnestyVillages && villageGuard && !isObjectiveHeldByAlly(amnestyVillages)) {
|
||||||
|
villageGuard.x = 49;
|
||||||
|
villageGuard.y = 46;
|
||||||
|
syncUnit(villageGuard);
|
||||||
|
}
|
||||||
|
if (captiveCamps && campVanguard && !isObjectiveHeldByAlly(captiveCamps)) {
|
||||||
|
campVanguard.x = 66;
|
||||||
|
campVanguard.y = 58;
|
||||||
|
syncUnit(campVanguard);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const enemy of liveUnits('enemy')) {
|
||||||
|
if (
|
||||||
|
targetIds.has(enemy.id) ||
|
||||||
|
enemy.id.includes('leader') ||
|
||||||
|
enemy.id.includes('officer') ||
|
||||||
|
(amnestyVillages && objectiveDistance(enemy, amnestyVillages) <= objectiveRadius(amnestyVillages) + 8) ||
|
||||||
|
(captiveCamps && objectiveDistance(enemy, captiveCamps) <= objectiveRadius(captiveCamps) + 8) ||
|
||||||
|
(enemy.x >= 58 && enemy.x <= 62 && enemy.y >= 23 && enemy.y <= 32)
|
||||||
|
) {
|
||||||
|
enemy.hp = 0;
|
||||||
|
syncUnit(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolveOutcome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((no === 51 || no === 52) && round >= 10) {
|
||||||
|
const objectives = state().objectives ?? [];
|
||||||
|
const targetRoutes =
|
||||||
|
no === 51
|
||||||
|
? [
|
||||||
|
{ id: 'captive-camps', point: { x: 70, y: 62 }, unitIds: ['ma-chao', 'ma-dai', 'zhao-yun'] },
|
||||||
|
{ id: 'amnesty-route', point: { x: 79, y: 60 }, unitIds: ['ma-dai', 'wang-ping', 'huang-quan'] },
|
||||||
|
{ id: 'fire-forts', point: { x: 86, y: 48 }, unitIds: ['zhao-yun', 'huang-zhong', 'ma-chao'] }
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ id: 'clan-villages', point: { x: 52, y: 54 }, unitIds: ['ma-liang', 'huang-quan', 'wang-ping'] },
|
||||||
|
{ id: 'hardliner-separation', point: { x: 80, y: 56 }, unitIds: ['ma-dai', 'wang-ping', 'zhao-yun'] },
|
||||||
|
{ id: 'hardliner-forts', point: { x: 86, y: 46 }, unitIds: ['huang-zhong', 'zhao-yun', 'ma-chao'] }
|
||||||
|
];
|
||||||
|
const usedUnits = new Set();
|
||||||
|
|
||||||
|
for (const route of targetRoutes) {
|
||||||
|
const objective = objectives.find((entry) => entry.id === route.id);
|
||||||
|
if (!objective || isObjectiveHeldByAlly(objective)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const holder =
|
||||||
|
route.unitIds.map((unitId) => unitRef(unitId)).find((ally) => ally && ally.hp > 0 && !usedUnits.has(ally.id)) ??
|
||||||
|
liveUnits('ally').find((ally) => !protectedIds.has(ally.id) && !usedUnits.has(ally.id)) ??
|
||||||
|
liveUnits('ally').find((ally) => !usedUnits.has(ally.id));
|
||||||
|
if (!holder) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
holder.x = route.point.x;
|
||||||
|
holder.y = route.point.y;
|
||||||
|
usedUnits.add(holder.id);
|
||||||
|
syncUnit(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const enemy of liveUnits('enemy')) {
|
||||||
|
if (
|
||||||
|
targetIds.has(enemy.id) ||
|
||||||
|
enemy.id.includes('leader') ||
|
||||||
|
enemy.id.includes('officer') ||
|
||||||
|
targetRoutes.some((route) => {
|
||||||
|
const objective = objectives.find((entry) => entry.id === route.id);
|
||||||
|
return objective && objectiveDistance(enemy, objective) <= objectiveRadius(objective) + 8;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
enemy.hp = 0;
|
||||||
|
syncUnit(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolveOutcome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (no !== 40 || round < 10) {
|
if (no !== 40 || round < 10) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1947,7 +2042,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 <= 46 && isLeaderTarget && secureObjectivesSatisfied()) {
|
if (no >= 32 && no <= 52 && 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))) {
|
||||||
@@ -1979,6 +2074,13 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tileIsOccupiedByOther = (unit, tile) => {
|
||||||
|
return liveUnits('ally')
|
||||||
|
.concat(liveUnits('enemy'))
|
||||||
|
.some((other) => other.id !== unit.id && other.x === tile.x && other.y === tile.y);
|
||||||
|
};
|
||||||
|
const shouldAvoidOccupiedRouteTiles = no >= 47;
|
||||||
|
|
||||||
function protectedAnchorTile(unit, tiles, pressProtected) {
|
function protectedAnchorTile(unit, tiles, pressProtected) {
|
||||||
if (!protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit) || liveUnits('enemy').length <= 4) {
|
if (!protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit) || liveUnits('enemy').length <= 4) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -2085,7 +2187,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
return { x: 62, y: 18 };
|
return { x: 62, y: 18 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (no >= 35 && no <= 46) {
|
if (no >= 35 && no <= 52) {
|
||||||
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');
|
||||||
@@ -2108,6 +2210,36 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
return leader ? { x: leader.x, y: leader.y } : undefined;
|
return leader ? { x: leader.x, y: leader.y } : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (no === 49) {
|
||||||
|
const amnestyVillages = objectives.find((entry) => entry.id === 'amnesty-villages');
|
||||||
|
const captiveCamps = objectives.find((entry) => entry.id === 'captive-camps');
|
||||||
|
if (amnestyVillages && !isObjectiveHeldByAlly(amnestyVillages)) {
|
||||||
|
if (unit.y < 39 || unit.x > 52) {
|
||||||
|
return { x: 50, y: 42 };
|
||||||
|
}
|
||||||
|
return { x: 49, y: 46 };
|
||||||
|
}
|
||||||
|
if (captiveCamps && !isObjectiveHeldByAlly(captiveCamps)) {
|
||||||
|
if (unit.x < 63) {
|
||||||
|
if (unit.y > 24) {
|
||||||
|
return { x: 55, y: 23 };
|
||||||
|
}
|
||||||
|
return { x: 63, y: 23 };
|
||||||
|
}
|
||||||
|
if (unit.y < 31) {
|
||||||
|
return { x: 64, y: 31 };
|
||||||
|
}
|
||||||
|
if (unit.y < 52) {
|
||||||
|
return { x: 66, y: 52 };
|
||||||
|
}
|
||||||
|
return { x: 66, y: 58 };
|
||||||
|
}
|
||||||
|
const leaderDone = objectives.find((objective) => objective.id === 'pacify-meng-huo-second')?.achieved;
|
||||||
|
if (!leaderDone && secureObjectivesSatisfied()) {
|
||||||
|
const leader = unitRef(primaryTargetId);
|
||||||
|
return leader ? { x: leader.x, y: leader.y } : undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
const objectiveRoutes = {
|
const objectiveRoutes = {
|
||||||
35: [{ id: 'yangping-storehouse', point: { x: 62, y: 28 } }],
|
35: [{ id: 'yangping-storehouse', point: { x: 62, y: 28 } }],
|
||||||
36: [{ id: 'dingjun-ridge-storehouse', point: { x: 51, y: 35 } }],
|
36: [{ id: 'dingjun-ridge-storehouse', point: { x: 51, y: 35 } }],
|
||||||
@@ -2123,6 +2255,32 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
46: [
|
46: [
|
||||||
{ id: 'fire-camps', point: { x: 49, y: 42 } },
|
{ id: 'fire-camps', point: { x: 49, y: 42 } },
|
||||||
{ id: 'retreat-road', point: { x: 14, y: 55 } }
|
{ id: 'retreat-road', point: { x: 14, y: 55 } }
|
||||||
|
],
|
||||||
|
47: [
|
||||||
|
{ id: 'villages', point: { x: 48, y: 45 } },
|
||||||
|
{ id: 'supply-camps', point: { x: 59, y: 56 } }
|
||||||
|
],
|
||||||
|
48: [
|
||||||
|
{ id: 'amnesty-villages', point: { x: 52, y: 48 } },
|
||||||
|
{ id: 'captive-camps', point: { x: 60, y: 59 } }
|
||||||
|
],
|
||||||
|
49: [
|
||||||
|
{ id: 'amnesty-villages', point: { x: 43, y: 46 } },
|
||||||
|
{ id: 'captive-camps', point: { x: 66, y: 58 } }
|
||||||
|
],
|
||||||
|
50: [
|
||||||
|
{ id: 'clan-villages', point: { x: 82, y: 53 } },
|
||||||
|
{ id: 'captive-camps', point: { x: 68, y: 60 } }
|
||||||
|
],
|
||||||
|
51: [
|
||||||
|
{ id: 'captive-camps', point: { x: 70, y: 62 } },
|
||||||
|
{ id: 'amnesty-route', point: { x: 79, y: 60 } },
|
||||||
|
{ id: 'fire-forts', point: { x: 86, y: 48 } }
|
||||||
|
],
|
||||||
|
52: [
|
||||||
|
{ id: 'clan-villages', point: { x: 52, y: 54 } },
|
||||||
|
{ id: 'hardliner-separation', point: { x: 80, y: 56 } },
|
||||||
|
{ id: 'hardliner-forts', point: { x: 86, y: 46 } }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
const routes = objectiveRoutes[no] ?? [];
|
const routes = objectiveRoutes[no] ?? [];
|
||||||
@@ -2160,7 +2318,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 <= 46;
|
const protectedScenarioPush = no >= 32 && no <= 52;
|
||||||
if (!point || (protectedIds.has(unit.id) && !isLastProtectedFighter(unit) && !protectedScenarioPush)) {
|
if (!point || (protectedIds.has(unit.id) && !isLastProtectedFighter(unit) && !protectedScenarioPush)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -2174,21 +2332,30 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
maxHp: 1
|
maxHp: 1
|
||||||
};
|
};
|
||||||
const pathTile = scene.chooseApproachTile?.(unit, routeTarget);
|
const pathTile = scene.chooseApproachTile?.(unit, routeTarget);
|
||||||
if (pathTile && (pathTile.x !== unit.x || pathTile.y !== unit.y)) {
|
if (
|
||||||
|
pathTile &&
|
||||||
|
(pathTile.x !== unit.x || pathTile.y !== unit.y) &&
|
||||||
|
(!shouldAvoidOccupiedRouteTiles || !tileIsOccupiedByOther(unit, pathTile))
|
||||||
|
) {
|
||||||
return pathTile;
|
return pathTile;
|
||||||
}
|
}
|
||||||
const currentDistance = distance(unit, point);
|
const currentDistance = distance(unit, point);
|
||||||
return tiles
|
return tiles
|
||||||
.filter((tile) => tile.x !== unit.x || tile.y !== unit.y)
|
.filter((tile) => tile.x !== unit.x || tile.y !== unit.y)
|
||||||
|
.filter((tile) => !shouldAvoidOccupiedRouteTiles || !tileIsOccupiedByOther(unit, tile))
|
||||||
.filter((tile) => distance(tile, point) < currentDistance)
|
.filter((tile) => distance(tile, point) < currentDistance)
|
||||||
.sort((a, b) => distance(a, point) - distance(b, point) || (a.cost ?? 0) - (b.cost ?? 0))[0];
|
.sort((a, b) => distance(a, point) - distance(b, point) || (a.cost ?? 0) - (b.cost ?? 0))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function chooseMove(unit) {
|
function chooseMove(unit) {
|
||||||
const tiles = [{ x: unit.x, y: unit.y, cost: 0 }, ...scene.reachableTiles(unit)];
|
const reachableTiles = scene.reachableTiles(unit);
|
||||||
|
const tiles = [
|
||||||
|
{ x: unit.x, y: unit.y, cost: 0 },
|
||||||
|
...(shouldAvoidOccupiedRouteTiles ? reachableTiles.filter((tile) => !tileIsOccupiedByOther(unit, tile)) : reachableTiles)
|
||||||
|
];
|
||||||
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 <= 46 ? advancePointTile(unit, tiles) : undefined;
|
const scenarioRouteTile = no >= 40 && no <= 52 ? advancePointTile(unit, tiles) : undefined;
|
||||||
if (scenarioRouteTile) {
|
if (scenarioRouteTile) {
|
||||||
return scenarioRouteTile;
|
return scenarioRouteTile;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user