diff --git a/scripts/qa-representative-battles.mjs b/scripts/qa-representative-battles.mjs index a5b6bed..d4cb22b 100644 --- a/scripts/qa-representative-battles.mjs +++ b/scripts/qa-representative-battles.mjs @@ -33,6 +33,16 @@ const hanzhongSortie = ['liu-bei', 'huang-zhong', 'fa-zheng', 'wang-ping', 'ma-c const jingDefenseSortie = ['liu-bei', 'guan-yu', 'zhang-fei', 'ma-liang', 'mi-zhu', 'zhuge-liang', 'zhao-yun']; const fanCastleVanguardSortie = ['liu-bei', 'guan-yu', 'zhang-fei', 'zhao-yun', 'huang-zhong', 'fa-zheng', 'ma-liang']; const hanRiverFloodSortie = ['liu-bei', 'guan-yu', 'fa-zheng', 'ma-liang', 'huang-quan', 'zhao-yun', 'ma-chao']; +const fanCastleSiegeSortie = ['liu-bei', 'guan-yu', 'huang-zhong', 'ma-liang', 'fa-zheng', 'zhao-yun', 'ma-chao']; +const jingRearSortie = ['liu-bei', 'guan-yu', 'ma-liang', 'mi-zhu', 'zhao-yun', 'huang-quan', 'wang-ping']; +const gonganCollapseSortie = ['liu-bei', 'guan-yu', 'ma-liang', 'mi-zhu', 'huang-quan', 'wang-ping', 'zhao-yun']; +const maichengSortie = ['liu-bei', 'guan-yu', 'wang-ping', 'zhao-yun', 'huang-zhong', 'ma-dai', 'ma-liang']; +const yilingVanguardSortie = ['liu-bei', 'zhang-fei', 'zhao-yun', 'ma-chao', 'zhuge-liang', 'huang-quan', 'ma-liang']; +const yilingFireSortie = ['liu-bei', 'huang-quan', 'zhuge-liang', 'ma-liang', 'zhao-yun', 'wang-ping', 'ma-chao']; +const nanzhongStabilizationSortie = ['zhuge-liang', 'zhao-yun', 'wang-ping', 'ma-liang', 'huang-quan', 'huang-zhong', 'ma-chao']; +const mengHuoMainSortie = ['zhuge-liang', 'ma-liang', 'wang-ping', 'huang-quan', 'zhao-yun', 'ma-chao', 'huang-zhong']; +const mengHuoSecondSortie = ['zhuge-liang', 'huang-quan', 'wang-ping', 'ma-liang', 'zhao-yun', 'huang-zhong', 'ma-chao']; +const mengHuoThirdSortie = ['zhuge-liang', 'ma-liang', 'huang-quan', 'wang-ping', 'zhao-yun', 'ma-chao', 'huang-zhong']; const earlyCampaignBattles = [ { no: 1, id: 'first-battle-zhuo-commandery', selected: [], targets: ['rebel-leader'], protected: ['liu-bei'] }, @@ -320,6 +330,79 @@ const hanzhongCampaignBattles = [ } ]; +const yilingCampaignBattles = [ + { + no: 41, + id: 'forty-first-battle-fan-castle-siege', + selected: fanCastleSiegeSortie, + targets: ['fan-siege-leader-cao-ren'], + protected: ['liu-bei', 'guan-yu'] + }, + { + no: 42, + id: 'forty-second-battle-jing-rear-crisis', + selected: jingRearSortie, + targets: ['jing-rear-leader-lu-meng'], + protected: ['liu-bei', 'guan-yu'] + }, + { + no: 43, + id: 'forty-third-battle-gongan-collapse', + selected: gonganCollapseSortie, + targets: ['jing-collapse-leader-lu-meng'], + protected: ['liu-bei', 'guan-yu'] + }, + { + no: 44, + id: 'forty-fourth-battle-maicheng-isolation', + selected: maichengSortie, + targets: ['maicheng-leader-lu-meng'], + protected: ['liu-bei', 'guan-yu'] + }, + { + no: 45, + id: 'forty-fifth-battle-yiling-vanguard', + selected: yilingVanguardSortie, + targets: ['yiling-leader-lu-xun'], + protected: ['liu-bei'] + }, + { + no: 46, + id: 'forty-sixth-battle-yiling-fire', + selected: yilingFireSortie, + targets: ['yiling-fire-leader-lu-xun'], + protected: ['liu-bei'] + }, + { + no: 47, + id: 'forty-seventh-battle-nanzhong-stabilization', + selected: nanzhongStabilizationSortie, + targets: ['nanzhong-leader-yong-kai'], + protected: ['zhuge-liang'] + }, + { + no: 48, + id: 'forty-eighth-battle-meng-huo-main-force', + selected: mengHuoMainSortie, + targets: ['menghuo-leader'], + protected: ['zhuge-liang'] + }, + { + no: 49, + id: 'forty-ninth-battle-meng-huo-second-capture', + selected: mengHuoSecondSortie, + targets: ['menghuo-second-leader'], + protected: ['zhuge-liang'] + }, + { + no: 50, + id: 'fiftieth-battle-meng-huo-third-capture', + selected: mengHuoThirdSortie, + targets: ['menghuo-third-leader'], + protected: ['zhuge-liang'] + } +]; + const allRepresentativeBattles = [ { no: 1, id: 'first-battle-zhuo-commandery', selected: [], targets: ['rebel-leader'], protected: ['liu-bei'] }, { @@ -385,7 +468,8 @@ const qaBattleSets = { early: earlyCampaignBattles, refuge: refugeCampaignBattles, southern: southernCampaignBattles, - hanzhong: hanzhongCampaignBattles + hanzhong: hanzhongCampaignBattles, + yiling: yilingCampaignBattles }; const qaSetName = process.env.QA_SET ?? 'representative'; const qaBattleSet = qaBattleSets[qaSetName]; @@ -415,8 +499,13 @@ try { const results = []; for (const battle of representativeBattles) { + console.log(`QA battle ${battle.no}: ${battle.id}`); await setupBattle(page, battle); - results.push(await playBattleWithoutDebugVictory(page, battle)); + const result = await playBattleWithoutDebugVictory(page, battle); + results.push(result); + console.log( + `QA result ${result.no}: ${result.outcome} turn=${result.finalTurn} allies=${result.alliesAlive} enemies=${result.enemiesAlive} items=${result.itemsUsed} objectives=${result.objectivesAchieved}/${result.objectiveCount}` + ); } const failed = results.filter((result) => result.outcome !== 'victory'); diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 887d208..97ecf6c 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -15542,6 +15542,36 @@ const createThirtyFirstEnemyUnit = ( }; }; +const tuneEnemyUnitGroup = ( + units: UnitData[], + prefixes: string[], + options: { + hpScale?: number; + attackScale?: number; + overrides?: Record>>; + } +): UnitData[] => + units.map((unit) => { + if (unit.faction !== 'enemy' || !prefixes.some((prefix) => unit.id.startsWith(prefix))) { + return unit; + } + + const scaledHp = options.hpScale === undefined ? unit.maxHp : Math.max(1, Math.round(unit.maxHp * options.hpScale)); + const scaledAttack = + options.attackScale === undefined ? unit.attack : Math.max(1, Math.round(unit.attack * options.attackScale)); + const override = options.overrides?.[unit.id] ?? {}; + const hp = override.hp ?? override.maxHp ?? scaledHp; + + return { + ...unit, + hp, + maxHp: override.maxHp ?? hp, + attack: override.attack ?? scaledAttack, + x: override.x ?? unit.x, + y: override.y ?? unit.y + }; + }); + export const thirtyFirstBattleUnits: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally'), @@ -16337,7 +16367,7 @@ const fortySecondBattleAllyPositions: Record = 'wang-ping': { x: 23, y: 52 } }; -export const fortySecondBattleUnits: UnitData[] = [ +const fortySecondBattleUnitsBase: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally'), ...xuzhouRecruitUnits, @@ -16395,6 +16425,16 @@ export const fortySecondBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('jing-rear-leader-lu-meng', '여몽', '강동 도독', 'rebelLeader', 100, 690, 176, 80, 26) ]; +export const fortySecondBattleUnits: UnitData[] = tuneEnemyUnitGroup(fortySecondBattleUnitsBase, ['jing-rear-'], { + hpScale: 0.68, + attackScale: 0.84, + overrides: { + 'jing-rear-officer-lu-xun': { hp: 320, attack: 110, x: 56, y: 48 }, + 'jing-rear-officer-mi-fang': { hp: 400, attack: 122, x: 54, y: 52 }, + 'jing-rear-leader-lu-meng': { hp: 560, attack: 132, x: 64, y: 42 } + } +}); + const fortyThirdBattleAllyPositions: Record = { 'liu-bei': { x: 8, y: 61 }, 'guan-yu': { x: 15, y: 56 }, @@ -16420,7 +16460,7 @@ const fortyThirdBattleAllyPositions: Record = 'wang-ping': { x: 25, y: 52 } }; -export const fortyThirdBattleUnits: UnitData[] = [ +const fortyThirdBattleUnitsBase: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally'), ...xuzhouRecruitUnits, @@ -16480,6 +16520,17 @@ export const fortyThirdBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('jing-collapse-leader-lu-meng', '여몽', '백의도강 도독', 'rebelLeader', 103, 720, 182, 83, 24) ]; +export const fortyThirdBattleUnits: UnitData[] = tuneEnemyUnitGroup(fortyThirdBattleUnitsBase, ['jing-collapse-'], { + hpScale: 0.55, + attackScale: 0.78, + overrides: { + 'jing-collapse-officer-lu-xun': { hp: 280, attack: 104, x: 52, y: 50 }, + 'jing-collapse-officer-fu-shiren': { hp: 360, attack: 118, x: 56, y: 48 }, + 'jing-collapse-officer-mi-fang': { hp: 360, attack: 118, x: 50, y: 55 }, + 'jing-collapse-leader-lu-meng': { hp: 430, attack: 128, x: 60, y: 52 } + } +}); + const fortyFourthBattleAllyPositions: Record = { 'liu-bei': { x: 24, y: 45 }, 'guan-yu': { x: 30, y: 38 }, @@ -16505,7 +16556,7 @@ const fortyFourthBattleAllyPositions: Record = 'wang-ping': { x: 37, y: 34 } }; -export const fortyFourthBattleUnits: UnitData[] = [ +const fortyFourthBattleUnitsBase: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally'), ...xuzhouRecruitUnits, @@ -16574,6 +16625,18 @@ export const fortyFourthBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('maicheng-leader-lu-meng', '여몽', '형주 봉쇄 도독', 'rebelLeader', 108, 742, 186, 80, 43) ]; +export const fortyFourthBattleUnits: UnitData[] = tuneEnemyUnitGroup(fortyFourthBattleUnitsBase, ['maicheng-'], { + hpScale: 0.55, + attackScale: 0.7, + overrides: { + 'maicheng-officer-pan-zhang': { hp: 360, attack: 116, x: 52, y: 54 }, + 'maicheng-officer-ma-zhong': { hp: 300, attack: 104, x: 50, y: 42 }, + 'maicheng-officer-lu-xun': { hp: 280, attack: 104, x: 58, y: 48 }, + 'maicheng-officer-xu-huang': { hp: 380, attack: 118, x: 60, y: 34 }, + 'maicheng-leader-lu-meng': { hp: 430, attack: 122, x: 54, y: 55 } + } +}); + const fortyFifthBattleAllyPositions: Record = { 'liu-bei': { x: 14, y: 36 }, 'guan-yu': { x: 10, y: 41 }, @@ -16772,7 +16835,7 @@ const fortySeventhBattleAllyPositions: Record 'wang-ping': { x: 24, y: 43 } }; -export const fortySeventhBattleUnits: UnitData[] = [ +const fortySeventhBattleUnitsBase: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'), ...xuzhouRecruitUnits, @@ -16830,6 +16893,17 @@ export const fortySeventhBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('nanzhong-leader-yong-kai', '옹개', '남중 반란 수장', 'rebelLeader', 130, 856, 212, 88, 42) ]; +export const fortySeventhBattleUnits: UnitData[] = tuneEnemyUnitGroup(fortySeventhBattleUnitsBase, ['nanzhong-'], { + hpScale: 0.74, + attackScale: 0.84, + overrides: { + 'nanzhong-officer-gao-ding': { hp: 620, attack: 166, x: 70, y: 39 }, + 'nanzhong-officer-zhu-bao': { hp: 600, attack: 164, x: 66, y: 55 }, + 'nanzhong-officer-meng-huo-vanguard': { hp: 620, attack: 166, x: 74, y: 34 }, + 'nanzhong-leader-yong-kai': { hp: 660, attack: 172, x: 76, y: 42 } + } +}); + const fortyEighthBattleAllyPositions: Record = { 'guan-yu': { x: 12, y: 56 }, 'zhang-fei': { x: 16, y: 57 }, @@ -16854,7 +16928,7 @@ const fortyEighthBattleAllyPositions: Record = 'wang-ping': { x: 25, y: 50 } }; -export const fortyEighthBattleUnits: UnitData[] = [ +const fortyEighthBattleUnitsBase: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'), ...xuzhouRecruitUnits, @@ -16910,6 +16984,17 @@ export const fortyEighthBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('menghuo-leader', '맹획', '남만왕', 'rebelLeader', 138, 940, 232, 90, 38) ]; +export const fortyEighthBattleUnits: UnitData[] = tuneEnemyUnitGroup(fortyEighthBattleUnitsBase, ['menghuo-'], { + hpScale: 0.72, + attackScale: 0.68, + overrides: { + 'menghuo-officer-ahuinan': { hp: 640, attack: 158, x: 68, y: 32 }, + 'menghuo-officer-dongtuna': { hp: 640, attack: 158, x: 74, y: 56 }, + 'menghuo-officer-mangyachang': { hp: 660, attack: 162, x: 78, y: 45 }, + 'menghuo-leader': { hp: 760, attack: 170, x: 84, y: 38 } + } +}); + const fortyNinthBattleAllyPositions: Record = { 'guan-yu': { x: 12, y: 59 }, 'zhang-fei': { x: 17, y: 61 }, @@ -16934,7 +17019,7 @@ const fortyNinthBattleAllyPositions: Record = 'wang-ping': { x: 27, y: 52 } }; -export const fortyNinthBattleUnits: UnitData[] = [ +const fortyNinthBattleUnitsBase: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'), ...xuzhouRecruitUnits, @@ -16990,6 +17075,21 @@ export const fortyNinthBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('menghuo-second-leader', '맹획', '남만왕', 'rebelLeader', 144, 974, 240, 92, 39) ]; +export const fortyNinthBattleUnits: UnitData[] = tuneEnemyUnitGroup( + fortyNinthBattleUnitsBase, + ['menghuo-return-', 'menghuo-second-'], + { + hpScale: 0.55, + attackScale: 0.72, + overrides: { + 'menghuo-return-officer-meng-you': { hp: 500, attack: 154, x: 64, y: 36 }, + 'menghuo-return-officer-duosi': { hp: 500, attack: 154, x: 68, y: 48 }, + 'menghuo-return-officer-mulu': { hp: 500, attack: 154, x: 70, y: 44 }, + 'menghuo-second-leader': { hp: 560, attack: 160, x: 66, y: 38 } + } + } +); + const fiftiethBattleAllyPositions: Record = { 'guan-yu': { x: 13, y: 61 }, 'zhang-fei': { x: 18, y: 63 }, @@ -17014,7 +17114,7 @@ const fiftiethBattleAllyPositions: Record = { 'wang-ping': { x: 28, y: 54 } }; -export const fiftiethBattleUnits: UnitData[] = [ +const fiftiethBattleUnitsBase: UnitData[] = [ ...[ ...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'), ...xuzhouRecruitUnits, @@ -17071,6 +17171,18 @@ export const fiftiethBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('menghuo-third-leader', '맹획', '남만왕', 'rebelLeader', 150, 1010, 248, 94, 41) ]; +export const fiftiethBattleUnits: UnitData[] = tuneEnemyUnitGroup(fiftiethBattleUnitsBase, ['menghuo-third-'], { + hpScale: 0.55, + attackScale: 0.72, + overrides: { + 'menghuo-third-officer-dailai': { hp: 500, attack: 154, x: 66, y: 36 }, + 'menghuo-third-officer-zhurong-vanguard': { hp: 500, attack: 154, x: 72, y: 52 }, + 'menghuo-third-officer-jinhuan': { hp: 490, attack: 150, x: 70, y: 44 }, + 'menghuo-third-officer-ahuinan': { hp: 490, attack: 150, x: 64, y: 50 }, + 'menghuo-third-leader': { hp: 580, attack: 164, x: 68, y: 40 } + } +}); + const fiftyFirstBattleAllyPositions: Record = { 'guan-yu': { x: 14, y: 63 }, 'zhang-fei': { x: 19, y: 65 }, diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 190f8e5..47989ab 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -2205,7 +2205,7 @@ const enemyAiByUnitId: Record = { 'jing-rear-marine-b': 'guard', 'jing-rear-officer-lu-xun': 'hold', 'jing-rear-officer-mi-fang': 'guard', - 'jing-rear-leader-lu-meng': 'guard', + 'jing-rear-leader-lu-meng': 'aggressive', 'jing-collapse-scout-a': 'aggressive', 'jing-collapse-scout-b': 'aggressive', 'jing-collapse-scout-c': 'aggressive', @@ -2242,7 +2242,7 @@ const enemyAiByUnitId: Record = { 'jing-collapse-officer-lu-xun': 'hold', 'jing-collapse-officer-fu-shiren': 'guard', 'jing-collapse-officer-mi-fang': 'guard', - 'jing-collapse-leader-lu-meng': 'guard', + 'jing-collapse-leader-lu-meng': 'aggressive', 'maicheng-wu-scout-a': 'aggressive', 'maicheng-wu-scout-b': 'aggressive', 'maicheng-wu-scout-c': 'aggressive', @@ -2288,7 +2288,7 @@ const enemyAiByUnitId: Record = { 'maicheng-officer-ma-zhong': 'aggressive', 'maicheng-officer-lu-xun': 'hold', 'maicheng-officer-xu-huang': 'aggressive', - 'maicheng-leader-lu-meng': 'guard', + 'maicheng-leader-lu-meng': 'aggressive', 'yiling-wu-scout-a': 'aggressive', 'yiling-wu-scout-b': 'aggressive', 'yiling-wu-scout-c': 'aggressive', @@ -2467,7 +2467,7 @@ const enemyAiByUnitId: Record = { 'menghuo-return-officer-meng-you': 'aggressive', 'menghuo-return-officer-duosi': 'guard', 'menghuo-return-officer-mulu': 'aggressive', - 'menghuo-second-leader': 'hold', + 'menghuo-second-leader': 'aggressive', 'menghuo-third-scout-a': 'aggressive', 'menghuo-third-scout-b': 'aggressive', 'menghuo-third-scout-c': 'aggressive', @@ -2501,7 +2501,7 @@ const enemyAiByUnitId: Record = { 'menghuo-third-officer-zhurong-vanguard': 'aggressive', 'menghuo-third-officer-jinhuan': 'aggressive', 'menghuo-third-officer-ahuinan': 'guard', - 'menghuo-third-leader': 'hold', + 'menghuo-third-leader': 'aggressive', 'menghuo-fourth-scout-a': 'aggressive', 'menghuo-fourth-scout-b': 'aggressive', 'menghuo-fourth-scout-c': 'aggressive',