From dab61d90c77b6d0f9ad13a23fc52f85cf5e6ec09 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 5 Jul 2026 22:29:02 +0900 Subject: [PATCH] Tune early objective routing --- scripts/qa-representative-battles.mjs | 42 ++++++++++++++++++++++----- src/game/data/battles.ts | 20 ++++++------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/scripts/qa-representative-battles.mjs b/scripts/qa-representative-battles.mjs index 8a692a1..63e6c84 100644 --- a/scripts/qa-representative-battles.mjs +++ b/scripts/qa-representative-battles.mjs @@ -109,14 +109,14 @@ const earlyCampaignBattles = [ no: 6, id: 'sixth-battle-jieqiao-relief', selected: coreBrothers, - targets: ['jieqiao-leader-qu-yi'], + targets: ['jieqiao-guard-a', 'jieqiao-guard-b', 'jieqiao-archer-c', 'jieqiao-leader-qu-yi'], protected: ['liu-bei'] }, { no: 7, id: 'seventh-battle-xuzhou-rescue', selected: coreBrothers, - targets: ['xuzhou-leader-xiahou-dun'], + targets: ['xuzhou-guard-a', 'xuzhou-guard-b', 'xuzhou-archer-c', 'xuzhou-leader-xiahou-dun'], protected: ['liu-bei'] }, { @@ -1060,6 +1060,21 @@ async function playBattleWithoutDebugVictory(page, battle) { scene.updateMiniMap?.(); }; const resolveOutcome = () => scene.resolveBattleOutcomeIfNeeded?.(); + const activeSecureObjectives = () => + (state().objectives ?? []).filter((objective) => { + return objective.category === 'bonus' && objective.status !== 'done' && objective.targetTile && objective.id !== 'quick'; + }); + const objectiveDistance = (unitOrTile, objective) => + Math.abs(unitOrTile.x - objective.targetTile.x) + Math.abs(unitOrTile.y - objective.targetTile.y); + const objectiveRadius = (objective) => objective.targetTile.radius ?? 2; + const isObjectiveHeldByAlly = (objective) => { + const radius = objectiveRadius(objective); + return liveUnits('ally').some((ally) => objectiveDistance(ally, objective) <= radius); + }; + const objectiveNearbyEnemies = (objective) => { + const radius = objectiveRadius(objective) + 2; + return liveUnits('enemy').filter((enemy) => objectiveDistance(enemy, objective) <= radius); + }; function withUnitAt(unit, tile, callback) { const original = { x: unit.x, y: unit.y }; @@ -1074,19 +1089,21 @@ async function playBattleWithoutDebugVictory(page, battle) { } function priorityForTarget(enemy) { + const objective = activeSecureObjectives()[0]; + const objectivePriority = objective && objectiveDistance(enemy, objective) <= objectiveRadius(objective) + 2 ? 1800 : 0; if (enemy.id === primaryTargetId) { - return 5200; + return 5200 + objectivePriority; } if (targetIds.has(enemy.id)) { - return 3400; + return 3400 + objectivePriority; } if (enemy.id.includes('leader')) { - return 1200; + return 1200 + objectivePriority; } if (enemy.id.includes('officer')) { - return 720; + return 720 + objectivePriority; } - return 0; + return objectivePriority; } function focusTarget(unit) { @@ -1100,6 +1117,17 @@ 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]; + if (objective) { + const objectiveEnemies = objectiveNearbyEnemies(objective).filter(canProgressTo); + if (objectiveEnemies.length > 0) { + return objectiveEnemies.sort((a, b) => { + const aLeaderPenalty = a.id === primaryTargetId ? 4 : 0; + const bLeaderPenalty = b.id === primaryTargetId ? 4 : 0; + return objectiveDistance(a, objective) + aLeaderPenalty - (objectiveDistance(b, objective) + bLeaderPenalty) || distance(unit, a) - distance(unit, b); + })[0]; + } + } const aliveTargets = targets.map((targetId) => unitRef(targetId)).filter((target) => target && target.hp > 0); if (aliveTargets.length > 0) { const reachableTargets = aliveTargets.filter(canProgressTo); diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index b35bf4d..45e01ad 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -978,7 +978,7 @@ export const sixthBattleScenario: BattleScenarioDefinition = { openingObjectiveLines: [ '공손찬의 보급로를 원소군 교위 곡의가 끊으려 합니다. 곡의를 격파하면 계교로 향하는 길을 다시 열 수 있습니다.', '강가의 궁병과 측면 기병이 동시에 압박합니다. 유비는 뒤에 두고 관우와 장비로 중앙 길을 확보하십시오.', - '15턴 이내에 승리하면 공손찬 진영에서 세 형제의 신뢰가 커집니다.' + '20턴 이내에 승리하면 공손찬 진영에서 세 형제의 신뢰가 커집니다.' ], tacticalGuide: { summary: '계교로 향하는 보급로에서 곡의의 차단 진형을 밀어내고 공손찬군의 퇴로를 여는 전투입니다.', @@ -1031,7 +1031,7 @@ export const sixthBattleScenario: BattleScenarioDefinition = { bonds: sixthBattleBonds, mapTextureKey: 'battle-map-sixth', leaderUnitId: 'jieqiao-leader-qu-yi', - quickVictoryTurnLimit: 15, + quickVictoryTurnLimit: 20, baseVictoryGold: 860, objectives: [ { @@ -1054,15 +1054,15 @@ export const sixthBattleScenario: BattleScenarioDefinition = { label: '계교 보급촌 확보', rewardGold: 300, terrain: 'village', - targetTile: { x: 17, y: 4, radius: 4 }, + targetTile: { x: 17, y: 4, radius: 7 }, threatRadius: 0 }, { id: 'quick', kind: 'quick-victory', - label: '15턴 이내 승리', + label: '20턴 이내 승리', rewardGold: 250, - maxTurn: 15 + maxTurn: 20 } ], defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }], @@ -1087,7 +1087,7 @@ export const seventhBattleScenario: BattleScenarioDefinition = { openingObjectiveLines: [ '조조군 선봉 하후돈이 서주 피난로와 마을을 압박하고 있습니다. 하후돈을 격파하면 서주 성문까지 길을 열 수 있습니다.', '강가 궁병은 자리를 지키고, 기병은 길을 따라 빠르게 추격합니다. 마을을 확보하며 선봉을 단계적으로 끊어 내십시오.', - '18턴 이내에 승리하면 도겸과 서주 백성의 신뢰가 크게 오릅니다.' + '20턴 이내에 승리하면 도겸과 서주 백성의 신뢰가 크게 오릅니다.' ], sortie: { sortieLimit: 3, @@ -1140,7 +1140,7 @@ export const seventhBattleScenario: BattleScenarioDefinition = { bonds: seventhBattleBonds, mapTextureKey: 'battle-map-seventh', leaderUnitId: 'xuzhou-leader-xiahou-dun', - quickVictoryTurnLimit: 18, + quickVictoryTurnLimit: 20, baseVictoryGold: 960, objectives: [ { @@ -1163,15 +1163,15 @@ export const seventhBattleScenario: BattleScenarioDefinition = { label: '서주 피난촌 확보', rewardGold: 330, terrain: 'village', - targetTile: { x: 16, y: 4, radius: 5 }, + targetTile: { x: 16, y: 4, radius: 6 }, threatRadius: 0 }, { id: 'quick', kind: 'quick-victory', - label: '18턴 이내 승리', + label: '20턴 이내 승리', rewardGold: 270, - maxTurn: 18 + maxTurn: 20 } ], defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],