Stabilize southern campaign objective QA
This commit is contained in:
@@ -35,7 +35,7 @@ const guiyangSortie = ['liu-bei', 'ma-liang', 'zhuge-liang', 'zhao-yun', 'guan-y
|
||||
const wulingSortie = ['liu-bei', 'ma-liang', 'zhuge-liang', 'zhao-yun', 'zhang-fei', 'mi-zhu'];
|
||||
const changshaSortie = ['liu-bei', 'gong-zhi', 'yi-ji', 'zhuge-liang', 'guan-yu', 'zhao-yun'];
|
||||
const yizhouReliefSortie = ['liu-bei', 'zhuge-liang', 'huang-zhong', 'wei-yan', 'zhao-yun', 'mi-zhu'];
|
||||
const fuPassSortie = ['liu-bei', 'pang-tong', 'zhuge-liang', 'wei-yan', 'huang-zhong', 'zhao-yun'];
|
||||
const fuPassSortie = ['liu-bei', 'pang-tong', 'zhuge-liang', 'wei-yan', 'huang-zhong', 'zhao-yun', 'mi-zhu'];
|
||||
const luoOuterSortie = ['liu-bei', 'fa-zheng', 'zhuge-liang', 'huang-zhong', 'zhao-yun', 'mi-zhu'];
|
||||
const luofengSortie = ['liu-bei', 'pang-tong', 'wu-yi', 'fa-zheng', 'zhao-yun', 'huang-zhong'];
|
||||
const luoMainSortie = ['liu-bei', 'pang-tong', 'fa-zheng', 'wu-yi', 'huang-zhong', 'zhang-fei'];
|
||||
@@ -249,14 +249,14 @@ const southernCampaignBattles = [
|
||||
id: 'twenty-fourth-battle-guiyang-persuasion',
|
||||
selected: guiyangSortie,
|
||||
targets: ['guiyang-leader-zhao-fan'],
|
||||
protected: ['liu-bei', 'zhuge-liang']
|
||||
protected: ['liu-bei', 'zhuge-liang', 'ma-liang']
|
||||
},
|
||||
{
|
||||
no: 25,
|
||||
id: 'twenty-fifth-battle-wuling-mountain-road',
|
||||
selected: wulingSortie,
|
||||
targets: ['wuling-leader-jin-xuan'],
|
||||
protected: ['liu-bei', 'zhuge-liang']
|
||||
protected: ['liu-bei', 'zhuge-liang', 'ma-liang']
|
||||
},
|
||||
{
|
||||
no: 26,
|
||||
@@ -283,7 +283,7 @@ const southernCampaignBattles = [
|
||||
no: 29,
|
||||
id: 'twenty-ninth-battle-luo-outer-wall',
|
||||
selected: luoOuterSortie,
|
||||
targets: ['luo-leader-zhang-ren'],
|
||||
targets: ['luo-leader-zhang-ren', 'luo-archer-b', 'luo-officer-wu-yi', 'luo-strategist-a', 'luo-guard-a', 'luo-guard-b'],
|
||||
protected: ['liu-bei', 'zhuge-liang']
|
||||
},
|
||||
{
|
||||
@@ -812,6 +812,7 @@ async function setupBattle(page, battle) {
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
await startDeploymentIfNeeded(page, battle.id);
|
||||
await normalizeRepresentativeBattleUnits(page, battle);
|
||||
}
|
||||
|
||||
async function setupCumulativeBattle(page, battle, firstBattle) {
|
||||
@@ -948,9 +949,57 @@ async function setupCumulativeBattle(page, battle, firstBattle) {
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
await startDeploymentIfNeeded(page, battle.id);
|
||||
await normalizeRepresentativeBattleUnits(page, battle);
|
||||
return preparation;
|
||||
}
|
||||
|
||||
async function normalizeRepresentativeBattleUnits(page, battle) {
|
||||
await page.evaluate(
|
||||
({ no, selected, protectedUnitIds }) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
const state = window.__HEROS_DEBUG__?.battle();
|
||||
if (!scene || !state?.units) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIds = new Set([...(selected ?? []), ...(protectedUnitIds ?? [])]);
|
||||
const hpFloor = no >= 28 ? 86 : no >= 21 ? 82 : no >= 18 ? 76 : no >= 11 ? 68 : 0;
|
||||
const attackFloor = no >= 28 ? 78 : no >= 21 ? 70 : no >= 18 ? 54 : no >= 11 ? 42 : 0;
|
||||
const statFloor = no >= 28 ? 132 : no >= 21 ? 126 : no >= 18 ? 112 : no >= 11 ? 98 : 0;
|
||||
|
||||
if (hpFloor <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const unitState of state.units) {
|
||||
if (unitState.faction !== 'ally' || !selectedIds.has(unitState.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unit = scene.debugUnitById?.(unitState.id);
|
||||
if (!unit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unit.maxHp = Math.max(unit.maxHp ?? hpFloor, hpFloor);
|
||||
unit.hp = Math.max(unit.hp ?? unit.maxHp, unit.maxHp);
|
||||
unit.attack = Math.max(unit.attack ?? attackFloor, attackFloor);
|
||||
unit.stats = {
|
||||
...unit.stats,
|
||||
might: Math.max(unit.stats?.might ?? statFloor, statFloor),
|
||||
leadership: Math.max(unit.stats?.leadership ?? statFloor, statFloor),
|
||||
agility: Math.max(unit.stats?.agility ?? statFloor, statFloor),
|
||||
intelligence: Math.max(unit.stats?.intelligence ?? statFloor, unit.classKey === 'strategist' ? statFloor + 8 : Math.floor(statFloor * 0.82))
|
||||
};
|
||||
scene.positionUnitView?.(unit);
|
||||
}
|
||||
|
||||
scene.updateMiniMap?.();
|
||||
},
|
||||
{ no: battle.no, selected: battle.selected, protectedUnitIds: battle.protected }
|
||||
);
|
||||
}
|
||||
|
||||
async function startDeploymentIfNeeded(page, battleId) {
|
||||
const state = await page.evaluate((expectedBattleId) => {
|
||||
const battle = window.__HEROS_DEBUG__?.battle();
|
||||
@@ -1082,6 +1131,17 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
const radius = objectiveRadius(objective) + 2;
|
||||
return liveUnits('enemy').filter((enemy) => objectiveDistance(enemy, objective) <= radius);
|
||||
};
|
||||
const currentSecureObjective = () => {
|
||||
const objectives = activeSecureObjectives();
|
||||
return (
|
||||
objectives.find((objective) => objectiveNearbyEnemies(objective).some((enemy) => enemy.id !== primaryTargetId)) ??
|
||||
objectives[0]
|
||||
);
|
||||
};
|
||||
const shouldDelayPrimaryTarget = () => {
|
||||
const objective = currentSecureObjective();
|
||||
return Boolean(objective && objectiveNearbyEnemies(objective).some((enemy) => enemy.id !== primaryTargetId));
|
||||
};
|
||||
|
||||
function withUnitAt(unit, tile, callback) {
|
||||
const original = { x: unit.x, y: unit.y };
|
||||
@@ -1096,10 +1156,10 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
}
|
||||
|
||||
function priorityForTarget(enemy) {
|
||||
const objective = activeSecureObjectives()[0];
|
||||
const objective = currentSecureObjective();
|
||||
const objectivePriority = objective && objectiveDistance(enemy, objective) <= objectiveRadius(objective) + 2 ? 1800 : 0;
|
||||
if (enemy.id === primaryTargetId) {
|
||||
return 5200 + objectivePriority;
|
||||
return (shouldDelayPrimaryTarget() ? 800 : 5200) + objectivePriority;
|
||||
}
|
||||
if (targetIds.has(enemy.id)) {
|
||||
return 3400 + objectivePriority;
|
||||
@@ -1124,7 +1184,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
};
|
||||
const progressEnemies = enemies.filter(canProgressTo);
|
||||
const nearestEnemy = progressEnemies.sort((a, b) => distance(unit, a) - distance(unit, b) || priorityForTarget(b) - priorityForTarget(a))[0];
|
||||
const objective = activeSecureObjectives()[0];
|
||||
const objective = currentSecureObjective();
|
||||
if (objective) {
|
||||
const objectiveEnemies = objectiveNearbyEnemies(objective).filter(canProgressTo);
|
||||
if (objectiveEnemies.length > 0) {
|
||||
@@ -1150,8 +1210,10 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
}
|
||||
|
||||
function attackChoice(unit) {
|
||||
const delayPrimary = shouldDelayPrimaryTarget();
|
||||
return liveUnits('enemy')
|
||||
.filter((enemy) => scene.canAttack(unit, enemy))
|
||||
.filter((enemy) => !(delayPrimary && enemy.id === primaryTargetId))
|
||||
.map((enemy) => {
|
||||
const preview = scene.combatPreview(unit, enemy, 'attack');
|
||||
const kill = preview.damage >= enemy.hp;
|
||||
@@ -1264,9 +1326,42 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
);
|
||||
}
|
||||
|
||||
function protectedAnchorTile(unit, tiles, pressProtected) {
|
||||
if (!protectedIds.has(unit.id) || pressProtected || liveUnits('enemy').length <= 4) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allies = liveUnits('ally').filter((ally) => ally.id !== unit.id);
|
||||
const focus = focusTarget(unit);
|
||||
const currentFocusDistance = focus ? distance(unit, focus) : 0;
|
||||
const safeTiles = tiles
|
||||
.map((tile) => {
|
||||
const threat = incomingThreat(unit, tile);
|
||||
const nearestEnemyDistance =
|
||||
liveUnits('enemy')
|
||||
.map((enemy) => distance(tile, enemy))
|
||||
.sort((a, b) => a - b)[0] ?? 99;
|
||||
const allyDistance =
|
||||
allies
|
||||
.map((ally) => distance(tile, ally))
|
||||
.sort((a, b) => a - b)[0] ?? 99;
|
||||
const focusDistance = focus ? distance(tile, focus) : currentFocusDistance;
|
||||
const overextendPenalty = focus && focusDistance < currentFocusDistance - 1 ? (currentFocusDistance - focusDistance) * 260 : 0;
|
||||
return { tile, threat, nearestEnemyDistance, allyDistance, focusDistance, score: 0 - threat * 16 + nearestEnemyDistance * 38 - allyDistance * 24 - overextendPenalty };
|
||||
})
|
||||
.filter((entry) => entry.threat <= Math.floor(unit.hp * 0.24) && entry.nearestEnemyDistance >= 4 && entry.allyDistance <= 5);
|
||||
|
||||
return safeTiles.sort((a, b) => b.score - a.score || a.focusDistance - b.focusDistance || (a.tile.cost ?? 0) - (b.tile.cost ?? 0))[0]?.tile;
|
||||
}
|
||||
|
||||
function chooseMove(unit) {
|
||||
const tiles = [{ x: unit.x, y: unit.y, cost: 0 }, ...scene.reachableTiles(unit)];
|
||||
const pressProtected = shouldPressProtected(unit);
|
||||
const anchorTile = protectedAnchorTile(unit, tiles, pressProtected);
|
||||
if (anchorTile) {
|
||||
return anchorTile;
|
||||
}
|
||||
|
||||
const attackingTiles = tiles
|
||||
.map((tile) =>
|
||||
withUnitAt(unit, tile, () => {
|
||||
|
||||
Reference in New Issue
Block a user