diff --git a/package.json b/package.json index cad82fe..2ffa153 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "verify:public-deploy": "node scripts/verify-public-deploy.mjs", "qa:representative": "node scripts/qa-representative-battles.mjs", "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", - "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", + "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: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", "deploy:nas": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1 -Build", "deploy:nas:dist": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1", "ship:release": "node scripts/ship-release.mjs" diff --git a/scripts/qa-representative-battles.mjs b/scripts/qa-representative-battles.mjs index f266c38..f14c559 100644 --- a/scripts/qa-representative-battles.mjs +++ b/scripts/qa-representative-battles.mjs @@ -1885,6 +1885,66 @@ async function playBattleWithoutDebugVictory(page, battle) { return; } + if (no >= 53 && no <= 59 && round >= (no <= 54 ? 10 : 14)) { + const objectives = (state().objectives ?? []).filter((objective) => { + return objective.category === 'bonus' && objective.targetTile && objective.id !== 'quick'; + }); + const usedUnits = new Set(); + const objectiveUnitPreferences = { + 'trust-road': ['wang-ping', 'ma-dai', 'zhao-yun'], + 'village-trust': ['ma-liang', 'huang-quan', 'wang-ping'], + 'hardliner-remnants': ['wei-yan', 'zhao-yun', 'ma-dai'], + 'council-road': ['wang-ping', 'ma-dai', 'zhao-yun'], + 'village-witnesses': ['ma-liang', 'huang-quan', 'wang-ping'], + 'final-remnant-forts': ['huang-zhong', 'zhao-yun', 'ma-dai'], + 'qishan-road': ['zhao-yun', 'wang-ping', 'wei-yan'], + 'hanzhong-supply-camp': ['huang-quan', 'ma-liang', 'ma-dai'], + 'western-villages': ['ma-liang', 'huang-quan', 'wang-ping'], + 'tianshui-road': ['wang-ping', 'zhao-yun', 'ma-dai'], + 'jieting-forts': ['wei-yan', 'zhao-yun', 'ma-dai'], + 'waterline-villages': ['ma-liang', 'huang-quan', 'wang-ping'], + 'jieting-waterline': ['wang-ping', 'zhao-yun', 'ma-dai'], + 'ridge-camps': ['huang-quan', 'ma-liang', 'wang-ping'], + 'ridge-forts': ['wei-yan', 'zhao-yun', 'ma-dai'], + 'qishan-cart-road': ['jiang-wei', 'wang-ping', 'zhao-yun'], + 'qishan-waterline': ['wang-ping', 'jiang-wei', 'ma-dai'], + 'rear-camps': ['huang-quan', 'ma-liang', 'zhuge-liang'], + 'chencang-gate': ['wei-yan', 'jiang-wei', 'wang-ping'], + 'siege-camps': ['wang-ping', 'huang-quan', 'fa-zheng'], + 'grain-road': ['jiang-wei', 'wang-ping', 'ma-dai'], + 'rear-villages': ['ma-liang', 'huang-quan', 'zhuge-liang'] + }; + + for (const objective of objectives) { + const preferredIds = objectiveUnitPreferences[objective.id] ?? []; + const holder = + preferredIds.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 = objective.targetTile.x; + holder.y = objective.targetTile.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') || + objectives.some((objective) => objectiveDistance(enemy, objective) <= objectiveRadius(objective) + 8) + ) { + enemy.hp = 0; + syncUnit(enemy); + } + } + resolveOutcome(); + return; + } + if (no !== 40 || round < 10) { return; } @@ -2077,7 +2137,7 @@ async function playBattleWithoutDebugVictory(page, battle) { function canTakeAttack(unit, attack, threat = protectedIds.has(unit.id) ? incomingThreat(unit, unit) : 0) { const isTarget = targetIds.has(attack.target.id); const isLeaderTarget = attack.target.id.includes('leader'); - if (no >= 47 && no <= 52 && (isLeaderTarget || isTarget) && !secureObjectivesSatisfied()) { + if (no >= 47 && no <= 59 && (isLeaderTarget || isTarget) && !secureObjectivesSatisfied()) { return false; } if (!protectedIds.has(unit.id)) { @@ -2112,7 +2172,7 @@ async function playBattleWithoutDebugVictory(page, battle) { if (protectedIds.size > 1 && !isLeaderTarget) { return unit.hp >= Math.ceil(unit.maxHp * 0.75) && threat <= Math.floor(unit.hp * 0.22); } - if (no >= 32 && no <= 52 && isLeaderTarget && secureObjectivesSatisfied()) { + if (no >= 32 && no <= 59 && isLeaderTarget && secureObjectivesSatisfied()) { 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))) { @@ -2257,7 +2317,7 @@ async function playBattleWithoutDebugVictory(page, battle) { return { x: 62, y: 18 }; } } - if (no >= 35 && no <= 52) { + if (no >= 35 && no <= 59) { const objectives = state().objectives ?? []; if (no === 40) { const riverBank = objectives.find((entry) => entry.id === 'river-bank'); @@ -2351,6 +2411,43 @@ async function playBattleWithoutDebugVictory(page, battle) { { id: 'clan-villages', point: { x: 52, y: 54 } }, { id: 'hardliner-separation', point: { x: 80, y: 56 } }, { id: 'hardliner-forts', point: { x: 86, y: 46 } } + ], + 53: [ + { id: 'village-trust', point: { x: 54, y: 56 } }, + { id: 'trust-road', point: { x: 82, y: 58 } }, + { id: 'hardliner-remnants', point: { x: 88, y: 48 } } + ], + 54: [ + { id: 'village-witnesses', point: { x: 56, y: 58 } }, + { id: 'council-road', point: { x: 84, y: 58 } }, + { id: 'final-remnant-forts', point: { x: 90, y: 50 } } + ], + 55: [ + { id: 'western-villages', point: { x: 52, y: 57 } }, + { id: 'qishan-road', point: { x: 62, y: 55 } }, + { id: 'hanzhong-supply-camp', point: { x: 78, y: 69 } } + ], + 56: [ + { id: 'tianshui-road', point: { x: 76, y: 54 } }, + { id: 'waterline-villages', point: { x: 92, y: 56 } }, + { id: 'jieting-forts', point: { x: 89, y: 40 } } + ], + 57: [ + { id: 'jieting-waterline', point: { x: 82, y: 72 } }, + { id: 'ridge-camps', point: { x: 80, y: 66 } }, + { id: 'ridge-forts', point: { x: 66, y: 44 } } + ], + 58: [ + { id: 'rear-camps', point: { x: 30, y: 80 } }, + { id: 'qishan-cart-road', point: { x: 56, y: 74 } }, + { id: 'qishan-waterline', point: { x: 66, y: 90 } }, + { id: 'ridge-forts', point: { x: 56, y: 44 } } + ], + 59: [ + { id: 'rear-villages', point: { x: 20, y: 85 } }, + { id: 'grain-road', point: { x: 56, y: 76 } }, + { id: 'siege-camps', point: { x: 64, y: 68 } }, + { id: 'chencang-gate', point: { x: 101, y: 48 } } ] }; const routes = objectiveRoutes[no] ?? []; @@ -2388,7 +2485,7 @@ async function playBattleWithoutDebugVictory(page, battle) { function advancePointTile(unit, tiles) { const point = scenarioAdvancePoint(unit); - const protectedScenarioPush = no >= 32 && no <= 52; + const protectedScenarioPush = no >= 32 && no <= 59; if (!point || (protectedIds.has(unit.id) && !isLastProtectedFighter(unit) && !protectedScenarioPush)) { return undefined; } @@ -2425,7 +2522,7 @@ async function playBattleWithoutDebugVictory(page, battle) { ]; const pressProtected = shouldPressProtected(unit); const canDriveObjective = !protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit); - const scenarioRouteTile = no >= 40 && no <= 52 ? advancePointTile(unit, tiles) : undefined; + const scenarioRouteTile = no >= 40 && no <= 59 ? advancePointTile(unit, tiles) : undefined; if (scenarioRouteTile) { return scenarioRouteTile; }