diff --git a/mpq/lua/.gitignore b/mpq/lua/.gitignore new file mode 100644 index 00000000..9bbbeeaf --- /dev/null +++ b/mpq/lua/.gitignore @@ -0,0 +1 @@ +.luarc.json diff --git a/mpq/lua/examples/basic_ai.lua b/mpq/lua/examples/basic_ai.lua new file mode 100644 index 00000000..0033ce35 --- /dev/null +++ b/mpq/lua/examples/basic_ai.lua @@ -0,0 +1,151 @@ +-- Setup main variables +local player = Player() +if player.type ~= PlayerType.Computer then + return +end +local ai = AI() +local config = PlayerVars().ai_config or {} + +local main_town = ai:start_town() + +-- Neat way to organize stuff is to use a table +local brain = { + saw_air_units = false, +} + +---@param town Town +local function flyer_watch(town) + wait_until(function() return brain.saw_air_units end) + for i = 1, 5 do + town:build(i, UnitId.TerranWatchdog, 70-i) + wait(secs(30 + 5 * i)) + end +end + +---@param town Town +local function build_main_town(town) + town:build(20, UnitId.TerranMason, 90) + wait(secs(30)) + town:build(1, UnitId.TerranStockade, 100) + wait(secs(90)) + town:build(2, UnitId.TerranStockade, 90) + wait(secs(5)) + town:build(3, UnitId.TerranStockade, 70) + wait(mins(1)) + town:multirun(flyer_watch) + town:build(4, UnitId.TerranStockade, 70) +end + +---@param town Town +local function build_expo(town) + cprint(TextColor.Orange, "Expanding:") + print(town) + town:build(1, UnitId.TerranMinistry, 90) + town:wait_build(1, UnitId.TerranMinistry) + town:transfer_workers(4) + town:build(20, UnitId.TerranMason, 85) + town:wait_build(10, UnitId.TerranMason) + town:multirun(flyer_watch) + town:build(1, UnitId.TerranStockade, 80) + wait(secs(30)) + town:build(2, UnitId.TerranStockade, 70) + -- TODO: town:build_at(UnitId.TerranStockage, Point(69, 69), 90) + -- TODO: town:new_guard(...) +end + +local function expand_loop() + wait(secs(90)) + while true do + local town = ai:expand() + town:multirun(build_expo) -- town:multirun manages script lifetime based on town alive state + if town.is_alive then + wait(math.random(mins(3), mins(4))) + else + wait(math.random(secs(30), mins(1))) -- if town didn't create we wait for shorter + end + + -- NOTE: if we didn't need the town.is_alive check we can use nice shorthand: + -- ai:expand:multirun(build_expo) + end +end + +local function attack_waves() + main_town:wait_build(1, UnitId.TerranStockade) + while true do + -- Classic attack wave with counts + --[[ + local attack = ai:new_attack() + attack.from = Point(3800, 500) + if Time.elapsed <= mins(5) then + attack:add(5 + (Time.elapsed // mins(2)), UnitId.TerranMaverick) + else + attack:add(4 + (Time.elapsed // mins(4)), UnitId.TerranMaverick) + attack:add(2 + (Time.elapsed // mins(4)), UnitId.TerranHarakan) + end + ]] + + -- Composing attack wave from power value + local attack_budget = player:unit_count(UnitId.TerranMason) * 50 + + (player.minerals + player.gas) // 2 + local attack = ai:new_attack(attack_budget) + attack.budget = attack_budget + attack.timeout = 120 + -- attack.from = Point(3800, 500) + if player:unit_count(UnitId.TerranStockade) > 0 then + attack:compose(config.pref_maverick or 1, UnitId.TerranMaverick) + attack:compose(config.pref_harakan or 1, UnitId.TerranHarakan) + attack:compose(config.pref_cyprian or 1, UnitId.TerranCyprian) + end + + cprint(TextColor.Red, "Sending attack:") + print(attack) + attack:send() + wait(math.random(secs(5), secs(15))) + end +end + +local function dump_defenses() + cprint(TextColor.Yellow3, "Defenses:") + print("build.gg:", ai.defense.build.gg) + print("use.gg:", ai.defense.use.gg) + print("build.ag:", ai.defense.build.ag) + print("use.ag:", ai.defense.use.ag) + print("build.ga:", ai.defense.build.ga) + print("use.ga:", ai.defense.use.ga) + print("build.aa:", ai.defense.build.aa) + print("use.aa:", ai.defense.use.aa) +end + +---@param unit_id UnitId +---@param cond fun(): boolean +local function defense_watch_impl(unit_id, cond) + while true do + wait_until(cond, secs(1)) + ai.defense:auto_set(unit_id) + -- dump_defenses() + wait_while(cond, secs(1)) + ai.defense:auto_remove(unit_id) + -- dump_defenses() + end +end + +---@param factory_id UnitId +---@param unit_id UnitId +local function defense_watch(factory_id, unit_id) + defense_watch_impl(unit_id, function() + return player:unit_count(factory_id) > 0 + end) +end + +-- Run all the functions + +-- Special kind of multirun, this script will die when town dies +-- Equivalent to `multirun(function(town) town:auto_kill() ... end, town)` +main_town:multirun(build_main_town) +multirun(expand_loop) +multirun(attack_waves) +multirun(defense_watch, UnitId.TerranStockade, UnitId.TerranMaverick) +multirun(defense_watch, UnitId.TerranStockade, UnitId.TerranHarakan) + +wait(mins(8)) +brain.saw_air_units = true -- let's pretend, since we don't have api for detecting units yet diff --git a/mpq/lua/melee/melee_ai.lua b/mpq/lua/melee/melee_ai.lua new file mode 100644 index 00000000..df10dd47 --- /dev/null +++ b/mpq/lua/melee/melee_ai.lua @@ -0,0 +1,235 @@ +require("melee.melee_common") +require("melee.personalities") + +---@class AIPersonality +---@field name string +---@field unit_composition table +---@field main_build_order fun(melee_ai: MeleeAI, town: Town)[] +---@field expansion_build_order fun(melee_ai: MeleeAI, town: Town)[] +---@field build_anti_ground_defenses fun(melee_ai: MeleeAI, town: Town) +---@field build_anti_air_defenses fun(melee_ai: MeleeAI, town: Town) +---@field expand_rate? number Default is 3 minutes 30 seconds +---@field expand_wait_after_fail? number Default is 30 seconds +---@field attack_wave_budget_scaling? number Default is 1.0 +---@field attack_wave_cost_bias? number Default derived from AI:new_attack() + +---@class TownState +---@field started_anti_ground_defenses boolean +---@field started_anti_air_defenses boolean + +---@class MeleeAI +---@field debugging {enabled: boolean, last_tick: number, last_script_id: number} +---@field player Player +---@field personality AIPersonality +---@field ai AI +---@field main_town Town +---@field towns table +---@field started_expanding boolean +---@field started_attacking boolean +---@field seen_air_threats boolean +MeleeAI = {} +MeleeAI.__index = MeleeAI + +setmetatable(MeleeAI, { + __call = function(cls, ...) + return cls.new(...) + end, +}) + +---@param config? table +---@param player? Player +function MeleeAI.new(config, player) + local self = setmetatable({}, MeleeAI) + self.debugging = { + enabled = PlayerVars(player).ai_debug or false, + last_tick = -1, + last_script_id = -1, + } + self.player = player or Player() + self.personality = config or Personalities.pick_random(player) + self.ai = AI(self.player.id) + self.main_town = self.ai:start_town() + self.towns = { + [self.main_town] = { + started_anti_ground_defenses = false, + started_anti_air_defenses = false, + }, + } + self.started_expanding = false + self.started_attacking = false + self.seen_air_threats = true -- TODO: detect dynamically or after timeout + -- TODO: maybe have a dynamic unit loss threshold check that triggers a stronger defense response + + self:cdebug(TextColor.Yellow3, "Starting AI with personality: " .. self.personality.name) + + return self +end + +function MeleeAI:debug_header() + if self.debugging.last_tick ~= Time.elapsed or self.debugging.last_script_id ~= Script.id then + self.debugging.last_tick = Time.elapsed + self.debugging.last_script_id = Script.id + print() + cprint(TextColor.Yellow3, "-- " .. tostring(self.player.name) .. " (P" .. tostring(self.player.id) .. "):\n") + end +end + +function MeleeAI:debug(...) + if self.debugging.enabled then + self:debug_header() + print(...) + end +end + +function MeleeAI:cdebug(color, ...) + if self.debugging.enabled then + self:debug_header() + cprint(color, ...) + end +end + +-- How often the banked-resource snapshot is refreshed while a dynamic_wait is +-- in progress, so a long wait reacts to resources changing mid-wait instead +-- of freezing the scale from a single sample taken at the start. +local DYNAMIC_WAIT_RESAMPLE_INTERVAL = secs(2) + +---@param duration integer +function MeleeAI:dynamic_wait(duration) + local elapsed = 0 + while elapsed < duration do + local chunk = math.min(DYNAMIC_WAIT_RESAMPLE_INTERVAL, duration - elapsed) + local resources = self.player.minerals + self.player.gas + local resource_scale = math.max(0, math.min(1, (1100 - resources) / 600)) + local scaled_chunk = math.floor(chunk * resource_scale) + if scaled_chunk > 0 then + wait(scaled_chunk) + end + elapsed = elapsed + chunk + end +end + +---@protected +---@return table +function MeleeAI:get_final_unit_composition() + local unit_composition = {} + for unit_id, preference in pairs(self.personality.unit_composition) do + unit_composition[unit_id] = preference + end + return unit_composition +end + +function MeleeAI:run() + for unit_id, preference in pairs(self:get_final_unit_composition()) do + if preference > 0 then + multirun(function() + wait_until(function() return self.player:can_make(unit_id) end, secs(1)) + self:cdebug(TextColor.Teal, "Added " .. UnitId.name(unit_id) .. " to defense") + self.ai.defense:auto_set(unit_id) + wait_while(function() return self.player:can_make(unit_id) end, secs(1)) + self:cdebug(TextColor.Teal, "Added " .. UnitId.name(unit_id) .. " to defense") + self.ai.defense:auto_remove(unit_id) + end) + end + end + + multirun(function() + self.main_town:auto_kill_script() + self.main_town:build(1, MeleeCommon.default_town_building(self.player.race), 120) + self.main_town:build(self.main_town.max_workers, MeleeCommon.default_worker(self.player.race), 100) + for i, build_order_step in ipairs(self.personality.main_build_order) do + build_order_step(self, self.main_town) + if i == 1 then + -- If opener didn't enable this, we now should + self:start_attacking() + self:start_expanding() + end + end + end) +end + +function MeleeAI:start_expanding() + if self.started_expanding then + return + end + self.started_expanding = true + multirun(function() + while true do + local town = self.ai:expand() + self:cdebug(TextColor.Green, "Expanding:") + self:debug(town) + if town.is_alive then + self.towns[town] = { + started_anti_ground_defenses = false, + started_anti_air_defenses = false, + } + town:multirun(function() + for i, build_order_step in ipairs(self.personality.expansion_build_order) do + build_order_step(self, town) + if i == 1 then + self:start_anti_ground_defenses(town) + self:start_anti_air_defenses(town) + end + end + end) + wait(self.personality.expand_rate or (mins(3) + secs(30))) + else + wait(self.personality.expand_wait_after_fail or secs(30)) + end + end + end) +end + +function MeleeAI:start_attacking() + if self.started_attacking then + return + end + self.started_attacking = true + multirun(function() + while true do + local budget = self.player:unit_count(MeleeCommon.default_worker(self.player.race)) * 50 + + (self.player.minerals + self.player.gas) / 2 + budget = budget * (self.personality.attack_wave_budget_scaling or 1.0) + + local attack = self.ai:new_attack(budget, self.personality.attack_wave_cost_bias or nil) + for unit_id, preference in pairs(self:get_final_unit_composition()) do + if preference > 0 and self.player:can_make(unit_id) then + attack:compose(preference, unit_id) + end + end + + self:cdebug(TextColor.Orange, "Sending attack:") + self:debug(attack) + attack:send() + wait(secs(5)) + end + end) +end + +function MeleeAI:start_anti_ground_defenses(town) + if self.personality.build_anti_ground_defenses == nil then + return + end + if self.towns[town].started_anti_ground_defenses then + return + end + self.towns[town].started_anti_ground_defenses = true + + multirun(function() + self.personality.build_anti_ground_defenses(self, town) + end) +end + +function MeleeAI:start_anti_air_defenses(town) + if self.personality.build_anti_air_defenses == nil then + return + end + if self.towns[town].started_anti_air_defenses then + return + end + self.towns[town].started_anti_air_defenses = true + + multirun(function() + wait_until(function() return self.seen_air_threats end, secs(1)) + self.personality.build_anti_air_defenses(self, town) + end) +end diff --git a/mpq/lua/melee/melee_common.lua b/mpq/lua/melee/melee_common.lua new file mode 100644 index 00000000..5940f72f --- /dev/null +++ b/mpq/lua/melee/melee_common.lua @@ -0,0 +1,33 @@ +MeleeCommon = {} + +---@param race RaceId +---@return UnitId worker_unit_id +function MeleeCommon.default_worker(race) + if race == RaceId.Terran then + return UnitId.TerranMason + elseif race == RaceId.Zerg then + return UnitId.ZergDroleth + elseif race == RaceId.Protoss then + return UnitId.ProtossScribe + elseif race == RaceId.Askosi then + return UnitId.AskosiPilgrim + end + error("Race " .. tostring(race) .. " has no default worker set") +end + +---@param race RaceId +---@return UnitId town_building_unit_id +function MeleeCommon.default_town_building(race) + if race == RaceId.Terran then + return UnitId.TerranMinistry + elseif race == RaceId.Zerg then + return UnitId.ZergHachirosk + elseif race == RaceId.Protoss then + return UnitId.ProtossNexus + elseif race == RaceId.Askosi then + return UnitId.AskosiLandlock + end + error("Race " .. tostring(race) .. " has no default town set") +end + +return MeleeCommon diff --git a/mpq/lua/melee/personalities.lua b/mpq/lua/melee/personalities.lua new file mode 100644 index 00000000..96f1d12f --- /dev/null +++ b/mpq/lua/melee/personalities.lua @@ -0,0 +1,141 @@ +local TerranArmor = require("melee.terran.armor") +local TerranRaider = require("melee.terran.raider") +local TerranOrbital = require("melee.terran.orbital") +local TerranGeneral = require("melee.terran.general") + +local ZergAssault = require("melee.zerg.assault") +local ZergSwarm = require("melee.zerg.swarm") +local ZergSunblot = require("melee.zerg.sunblot") +local ZergGeneral = require("melee.zerg.general") + +local ProtossRanger = require("melee.protoss.ranger") +local ProtossTemplar = require("melee.protoss.templar") +local ProtossSkylord = require("melee.protoss.skylord") +local ProtossGeneral = require("melee.protoss.general") + +---@class Personalities +Personalities = { + Terran = { + ArmorTankPush = TerranArmor.tank_push, + ArmorFeFiveFulc = TerranArmor.fe_five_fulc, + ArmorTwoFulcVulture = TerranArmor.two_fulc_vulture, + ArmorWalkerPower = TerranArmor.walker_power, + ArmorIronCrusade = TerranArmor.iron_crusade, + ArmorDavidAndGoliath = TerranArmor.david_and_goliath, + RaiderEightRax = TerranRaider.eight_rax, + RaiderThreeRaxBio = TerranRaider.three_rax_bio, + RaiderFastAnvil = TerranRaider.fast_anvil, + RaiderFourRaxPressure = TerranRaider.four_rax_pressure, + RaiderUprising = TerranRaider.uprising, + RaiderHolyAckmeds = TerranRaider.holy_ackmeds, + OrbitalRapidStarpads = TerranOrbital.rapid_starpads, + OrbitalTheDon = TerranOrbital.the_don, + OrbitalWraithBio = TerranOrbital.wraith_bio, + OrbitalHotdrop = TerranOrbital.hotdrop, + OrbitalSuddenStarport = TerranOrbital.sudden_starport, + OrbitalAcesHigh = TerranOrbital.aces_high, + GeneralTwoFulcrumMech = TerranGeneral.two_fulcrum_mech, + GeneralEarlyRiser = TerranGeneral.early_riser, + GeneralTheCube = TerranGeneral.the_cube, + GeneralDoublingUp = TerranGeneral.doubling_up, + GeneralMechIntoAir = TerranGeneral.mech_into_air, + GeneralObservant = TerranGeneral.observant, + }, + Zerg = { + AssaultThreeBaseHydrith = ZergAssault.three_base_hydrith, + AssaultTwoBaseZorkiz = ZergAssault.two_base_zorkiz, + AssaultTwoBaseHydrithQuazeth = ZergAssault.two_base_hydrith_quazeth, + AssaultTwoBaseAlkag = ZergAssault.two_base_alkag, + AssaultPotency = ZergAssault.potency, + AssaultEarthenTides = ZergAssault.earthen_tides, + SwarmThreeBaseFlood = ZergSwarm.three_base_flood, + SwarmThreeBaseUltrav = ZergSwarm.three_base_ultrav, + SwarmInstantQuazeth = ZergSwarm.instant_quazeth, + SwarmThreeBaseOthstol = ZergSwarm.three_base_othstol, + SwarmGatesOfHell = ZergSwarm.gates_of_hell, + SwarmPrimitivePressure = ZergSwarm.primitive_pressure, + SunblotBigAndLittle = ZergSunblot.big_and_little, + SunblotTheTriad = ZergSunblot.the_triad, + SunblotSkitteringDuo = ZergSunblot.skittering_duo, + SunblotEarlyBirds = ZergSunblot.early_birds, + SunblotFlyingFeint = ZergSunblot.flying_feint, + SunblotDescendingHorizon = ZergSunblot.descending_horizon, + GeneralSleepingGiant = ZergGeneral.sleeping_giant, + GeneralShriekingSwarm = ZergGeneral.shrieking_swarm, + GeneralSubjugation = ZergGeneral.subjugation, + GeneralBeforeTheStorm = ZergGeneral.before_the_storm, + GeneralTimeAfterTime = ZergGeneral.time_after_time, + GeneralConstrainedCoil = ZergGeneral.constrained_coil, + }, + Protoss = { + RangerTwoGateStrider = ProtossRanger.two_gate_strider, + RangerEmissarySquared = ProtossRanger.emissary_squared, + RangerOneGateAuthority = ProtossRanger.one_gate_authority, + RangerLatticeExpo = ProtossRanger.lattice_expo, + RangerSimulantSwarm = ProtossRanger.simulant_swarm, + RangerTheRegistry = ProtossRanger.the_registry, + TemplarThreeGateStrider = ProtossTemplar.three_gate_strider, + TemplarSuffocatingShadow = ProtossTemplar.suffocating_shadow, + TemplarLegion = ProtossTemplar.legion, + TemplarInitiates = ProtossTemplar.initiates, + TemplarMartyrdom = ProtossTemplar.martyrdom, + TemplarLegionsHeralds = ProtossTemplar.legions_heralds, + SkylordSolarEscort = ProtossSkylord.solar_escort, + SkylordDeadlyOmen = ProtossSkylord.deadly_omen, + SkylordTwoGatePanoptus = ProtossSkylord.two_gate_panoptus, + SkylordExalted = ProtossSkylord.exalted, + SkylordSorrowFleet = ProtossSkylord.sorrow_fleet, + SkylordShipsAndSwords = ProtossSkylord.ships_and_swords, + GeneralLunaticLegion = ProtossGeneral.lunatic_legion, + GeneralStridentStriders = ProtossGeneral.strident_striders, + GeneralWorship = ProtossGeneral.worship, + GeneralRoboticsFe = ProtossGeneral.robotics_fe, + GeneralTheSwordsmiths = ProtossGeneral.the_swordsmiths, + GeneralMatriarchy = ProtossGeneral.matriarchy, + }, + Askosi = {}, +} + +---@class RacePersonalities + +---@param race RaceId +---@return RacePersonalities +local function by_race(race) + if race == RaceId.Terran then + return Personalities.Terran + elseif race == RaceId.Zerg then + return Personalities.Zerg + elseif race == RaceId.Protoss then + return Personalities.Protoss + elseif race == RaceId.Askosi then + return Personalities.Askosi + end + error("Race " .. tostring(race) .. " is not a valid AI race") +end + +---@param dict table +---@return AIPersonality +local function pick_random(dict) + local keys = {} + for key, _ in pairs(dict) do + table.insert(keys, key) + end + local random_key = keys[math.random(#keys)] + return dict[random_key] +end + +---@param player? Player +---@return AIPersonality +function Personalities.pick_random(player) + player = player or Player() + local race_personalities = by_race(player.race) + return pick_random(race_personalities) +end + +---@param personality AIPersonality +---@return AIPersonality +function Personalities.copy(personality) + error("not implemented yet") +end + +return Personalities diff --git a/mpq/lua/melee/protoss/build/basic.lua b/mpq/lua/melee/protoss/build/basic.lua new file mode 100644 index 00000000..7ba15d7d --- /dev/null +++ b/mpq/lua/melee/protoss/build/basic.lua @@ -0,0 +1,31 @@ +local Basic = {} + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_expansion(melee_ai, town) + town:build(1, UnitId.ProtossNexus, 120) + town:wait_build(1, UnitId.ProtossNexus) + town:build(town.max_workers, UnitId.ProtossScribe, 100) + town:transfer_workers(8) + town:wait_build(10, UnitId.ProtossScribe) +end + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_anti_ground(melee_ai, town) + for i = 1, 4 do + town:build(i, UnitId.ProtossWarden, 70 - i * 5) + wait(secs(25 + i * 5)) + end +end + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_anti_air(melee_ai, town) + for i = 1, 3 do + town:build(i, UnitId.ProtossMatrix, 80 - i * 5) + wait(secs(25 + i * 5)) + end +end + +return Basic diff --git a/mpq/lua/melee/protoss/build/openers.lua b/mpq/lua/melee/protoss/build/openers.lua new file mode 100644 index 00000000..b4d880ab --- /dev/null +++ b/mpq/lua/melee/protoss/build/openers.lua @@ -0,0 +1,424 @@ +---@class ProtossOpeners +local Openers = {} + +-- Ranger + +---@param melee_ai MeleeAI +---@param town Town +function Openers.two_gate_strider(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(30)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.emissary_squared(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ProtossPylon, 90) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(55)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.one_gate_authority(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.lattice_expo(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(50)) + town:build(2, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(3, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.simulant_swarm(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(20)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.the_registry(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(30)) + town:build(2, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() +end + +-- Templar + +---@param melee_ai MeleeAI +---@param town Town +function Openers.three_gate_strider(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.suffocating_shadow(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.legion(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(35)) + town:build(3, UnitId.ProtossGateway, 90) + wait(secs(1)) + town:build(3, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(30)) + town:build(4, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(5, UnitId.ProtossGateway, 90) + wait(secs(1)) + town:build(4, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(6, UnitId.ProtossGateway, 90) + wait(secs(1)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.initiates(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + town:wait_build(2, UnitId.ProtossGateway) + melee_ai:dynamic_wait(secs(10)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.martyrdom(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(2) + secs(30)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.legions_heralds(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() +end + +-- Skylord + +---@param melee_ai MeleeAI +---@param town Town +function Openers.solar_escort(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.deadly_omen(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossStargate, 90) + town:wait_build(1, UnitId.ProtossStargate) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) + town:build(2, UnitId.ProtossPylon, 90) + town:wait_build(2, UnitId.ProtossPylon) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.two_gate_panoptus(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(secs(50)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(5)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.exalted(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ProtossStargate, 90) + + town:build(1, UnitId.ProtossWarden, 79) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossWarden, 79) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.sorrow_fleet(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.ships_and_swords(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(2, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_attacking() +end + +-- General + +---@param melee_ai MeleeAI +---@param town Town +function Openers.lunatic_legion(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossPylon, 90) + wait(secs(3)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.matriarchy(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(5)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.robotics_fe(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ProtossPylon, 90) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.strident_striders(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.the_swordsmiths(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossLattice, 90) + town:wait_build(1, UnitId.ProtossGateway) + town:wait_build(1, UnitId.ProtossLattice) + + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.worship(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossPylon, 110) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() +end + +return Openers diff --git a/mpq/lua/melee/protoss/build/tier1.lua b/mpq/lua/melee/protoss/build/tier1.lua new file mode 100644 index 00000000..631173dd --- /dev/null +++ b/mpq/lua/melee/protoss/build/tier1.lua @@ -0,0 +1,199 @@ +---@class ProtossTier1 +local Tier1 = {} + +-- Ranger + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.two_gate_strider(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + town:build(4, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.one_gate_authority(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(45)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.simulant_swarm(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(5, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(20)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.the_registry(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(40)) + town:wait_build(2, UnitId.ProtossPylon) + town:build(4, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(30)) +end + +-- Templar + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.three_gate_strider(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(mins(1)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(45)) + town:build(5, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.suffocating_shadow(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.legion(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(9, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.initiates(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(4, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.legions_heralds(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(20)) +end + +-- Skylord + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.solar_escort(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.deadly_omen(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(mins(1)) + town:build(4, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(50)) + town:build(5, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.two_gate_panoptus(melee_ai, town) + melee_ai:dynamic_wait(mins(1) + secs(35)) + town:build(3, UnitId.ProtossGateway, 90) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.exalted(melee_ai, town) + melee_ai:dynamic_wait(mins(1)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(mins(1)) + town:build(3, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.ships_and_swords(melee_ai, town) + melee_ai:dynamic_wait(mins(1)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(secs(55)) + town:build(3, UnitId.ProtossStargate, 90) + melee_ai:dynamic_wait(secs(50)) +end + +-- General + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.matriarchy(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.robotics_fe(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(1, UnitId.ProtossPylon) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.strident_striders(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(50)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(mins(1) + secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.the_swordsmiths(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossPylon, 90) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.worship(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.ProtossGateway, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(15)) +end + +return Tier1 diff --git a/mpq/lua/melee/protoss/build/tier2.lua b/mpq/lua/melee/protoss/build/tier2.lua new file mode 100644 index 00000000..2956a647 --- /dev/null +++ b/mpq/lua/melee/protoss/build/tier2.lua @@ -0,0 +1,179 @@ +---@class ProtossTier2 +local Tier2 = {} + +--- Requests a Lattice and an extra Gateway. The generic tier-2 scale-up, for +--- personalities without bespoke tier-2 content. +---@param melee_ai MeleeAI +---@param town Town +function Tier2.acquire(melee_ai, town) + town:build(1, UnitId.ProtossLattice, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossGateway, 70) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.strider_lattice_library_gateway(melee_ai, town) + town:build(1, UnitId.ProtossLattice, 70) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(5, UnitId.ProtossGateway, 70) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.emissary_ardent_lattice_gateway(melee_ai, town) + town:build(1, UnitId.ProtossArdentAuthority, 70) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossLattice, 70) + wait(secs(3)) + town:build(2, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossLattice, 70) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.authority_ardent_gateway_expand_lattice(melee_ai, town) + town:build(1, UnitId.ProtossArdentAuthority, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.ProtossGateway, 70) + wait(secs(1)) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ProtossLattice, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossLattice, 70) + melee_ai:dynamic_wait(secs(5)) + town:build(4, UnitId.ProtossLattice, 70) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.expo_ardent_authority(melee_ai, town) + town:build(1, UnitId.ProtossArdentAuthority, 70) + melee_ai:dynamic_wait(secs(50)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.strider3_library_expand(melee_ai, town) + town:build(1, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(secs(20) + secs(40)) + town:build(2, UnitId.ProtossGrandLibrary, 70) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.shadow_roguegallery_expand_library(melee_ai, town) + town:build(1, UnitId.ProtossRogueGallery, 70) + town:build(2, UnitId.ProtossRogueGallery, 70) + melee_ai:dynamic_wait(secs(30) + secs(40)) + town:build(1, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(mins(1) + secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.legion_library_x2(melee_ai, town) + town:build(1, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossGrandLibrary, 70) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.martyrdom_pylon_roguegallery(melee_ai, town) + town:build(4, UnitId.ProtossPylon, 70) + wait(secs(3)) + town:build(2, UnitId.ProtossRogueGallery, 70) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.heralds_library_gateway_buildup(melee_ai, town) + town:build(1, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.ProtossPylon, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(secs(40) + mins(1)) + town:build(5, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(mins(1)) + town:build(9, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(mins(1) + secs(55)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.escort_stargate_buildup(melee_ai, town) + town:build(1, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(5)) + town:build(2, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(45)) + town:build(3, UnitId.ProtossStargate, 70) + wait(secs(1)) + melee_ai:dynamic_wait(mins(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.legion_stargate_gateway_library(melee_ai, town) + town:build(1, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(40) + secs(5)) + town:build(3, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(20)) + town:build(4, UnitId.ProtossGateway, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.matriarchy_library_lattice(melee_ai, town) + town:build(1, UnitId.ProtossGrandLibrary, 70) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ProtossLattice, 70) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.robotics_ardent_x2(melee_ai, town) + town:build(1, UnitId.ProtossArdentAuthority, 70) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossArdentAuthority, 70) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.swordsmiths_ardent_stargate(melee_ai, town) + town:build(1, UnitId.ProtossArdentAuthority, 70) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossStargate, 70) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossArdentAuthority, 70) + melee_ai:dynamic_wait(secs(25)) +end + +return Tier2 diff --git a/mpq/lua/melee/protoss/build/tier3.lua b/mpq/lua/melee/protoss/build/tier3.lua new file mode 100644 index 00000000..33b14a84 --- /dev/null +++ b/mpq/lua/melee/protoss/build/tier3.lua @@ -0,0 +1,86 @@ +---@class ProtossTier3 +local Tier3 = {} + +--- Requests a Prostration Stage and waits for it. The generic tier-3 tech +--- transition, for personalities without bespoke tier-3 content. +---@param melee_ai MeleeAI +---@param town Town +function Tier3.acquire(melee_ai, town) + town:build(1, UnitId.ProtossProstrationStage, 65) + melee_ai:dynamic_wait(secs(50)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.authority_prostration_ardent2(melee_ai, town) + town:build(1, UnitId.ProtossProstrationStage, 65) + melee_ai:dynamic_wait(secs(50)) + town:build(2, UnitId.ProtossArdentAuthority, 50) + melee_ai:dynamic_wait(mins(1) + secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.martyrdom_prostration_crucible(melee_ai, town) + town:build(1, UnitId.ProtossProstrationStage, 65) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ProtossCrucible, 50) + melee_ai:dynamic_wait(secs(50)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.escort_argosy_buildup(melee_ai, town) + town:build(1, UnitId.ProtossArgosy, 50) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossArgosy, 50) + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.ProtossArgosy, 50) + + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.exalted_argosy_crucible(melee_ai, town) + town:build(1, UnitId.ProtossArgosy, 50) + town:wait_build(1, UnitId.ProtossArgosy, false) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ProtossCrucible, 50) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ProtossArgosy, 50) + melee_ai:dynamic_wait(mins(1) + secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.fleet_argosy_buildup(melee_ai, town) + town:build(1, UnitId.ProtossArgosy, 50) + melee_ai:dynamic_wait(mins(1)) + town:build(2, UnitId.ProtossArgosy, 50) + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.ProtossArgosy, 50) + melee_ai:dynamic_wait(mins(1) + secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.legion_argosy_only(melee_ai, town) + town:build(1, UnitId.ProtossArgosy, 50) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.matriarchy_prostration(melee_ai, town) + town:build(1, UnitId.ProtossProstrationStage, 65) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.robotics_prostration(melee_ai, town) + town:build(1, UnitId.ProtossProstrationStage, 65) + melee_ai:dynamic_wait(mins(2) + secs(20)) +end + +return Tier3 diff --git a/mpq/lua/melee/protoss/build/tier4.lua b/mpq/lua/melee/protoss/build/tier4.lua new file mode 100644 index 00000000..6203bc8b --- /dev/null +++ b/mpq/lua/melee/protoss/build/tier4.lua @@ -0,0 +1,174 @@ +---@class ProtossTier4 +local Tier4 = {} + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.emissary_synthetic_synod(melee_ai, town) + town:build(1, UnitId.ProtossSyntheticSynod, 45) +end + +--- Generic tier-4 capstone for the Templar category, for personalities +--- without bespoke tier-4 content. +---@param melee_ai MeleeAI +---@param town Town +function Tier4.templar_archives_acquire(melee_ai, town) + town:build(1, UnitId.ProtossAncestralArchives, 45) + melee_ai:dynamic_wait(secs(15)) +end + +--- Generic tier-4 capstone for the Skylord category, for personalities +--- without bespoke tier-4 content. +---@param melee_ai MeleeAI +---@param town Town +function Tier4.skylord_monument_acquire(melee_ai, town) + town:build(1, UnitId.ProtossMonumentOfSin, 45) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.authority_synthetic_synod(melee_ai, town) + town:build(1, UnitId.ProtossSyntheticSynod, 45) + wait(secs(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.ranger_expansion(melee_ai, town) + town:build(1, UnitId.ProtossLattice, 90, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossSyntheticSynod, 45, true) + wait(secs(1)) + town:build(1, UnitId.ProtossGateway, 90, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossArdentAuthority, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ProtossEmbassy, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossStargate, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ProtossArgosy, 50, true) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ProtossGrandLibrary, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossAncestralArchives, 45, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ProtossMonumentOfSin, 45, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.martyrdom_ancestral_archives_scaling(melee_ai, town) + town:build(2, UnitId.ProtossAncestralArchives, 45) + melee_ai:dynamic_wait(mins(1)) + town:wait_build(2, UnitId.ProtossAncestralArchives, false) + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.ProtossAncestralArchives, 45) + melee_ai:dynamic_wait(secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.templar_expansion(melee_ai, town) + town:build(1, UnitId.ProtossGateway, 90, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossGrandLibrary, 70, true) + town:build(1, UnitId.ProtossAncestralArchives, 45, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossLattice, 90, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossEmbassy, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ProtossArdentAuthority, 70, true) + town:build(1, UnitId.ProtossSyntheticSynod, 45, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossStargate, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossArgosy, 50, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ProtossMonumentOfSin, 45, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.exalted_monument_of_sin(melee_ai, town) + town:build(1, UnitId.ProtossMonumentOfSin, 45) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.fleet_monument_of_sin(melee_ai, town) + town:build(1, UnitId.ProtossMonumentOfSin, 45) + melee_ai:dynamic_wait(mins(2) + secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.skylord_expansion(melee_ai, town) + town:build(1, UnitId.ProtossGateway, 90, true) + town:wait_build(1, UnitId.ProtossGateway) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ProtossStargate, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossEmbassy, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossArgosy, 50, true) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(1, UnitId.ProtossStargate) + melee_ai:dynamic_wait(secs(50)) + town:wait_build(1, UnitId.ProtossArgosy) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ProtossLattice, 90) + melee_ai:dynamic_wait(secs(40)) + town:wait_build(1, UnitId.ProtossArgosy) + town:build(1, UnitId.ProtossMonumentOfSin, 45, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossGrandLibrary, 70) + town:wait_build(1, UnitId.ProtossGrandLibrary) + town:build(1, UnitId.ProtossAncestralArchives, 45, true) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(1, UnitId.ProtossLattice) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossArdentAuthority, 70) + town:wait_build(1, UnitId.ProtossArdentAuthority) + town:build(1, UnitId.ProtossGrandLibrary, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossUnitaryUnion, 50, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ProtossSyntheticSynod, 45, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossProstrationStage, 65, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.matriarchy_synthetic_synod_x2(melee_ai, town) + town:build(2, UnitId.ProtossSyntheticSynod, 45) + melee_ai:dynamic_wait(mins(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.robotics_synthetic_synod(melee_ai, town) + town:build(1, UnitId.ProtossSyntheticSynod, 45) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier4.general_expansion(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossGateway, 90, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossEmbassy, 70, true) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ProtossLattice, 90, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ProtossStargate, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ProtossGrandLibrary, 70, true) + town:build(1, UnitId.ProtossAncestralArchives, 45, true) +end + +return Tier4 diff --git a/mpq/lua/melee/protoss/general.lua b/mpq/lua/melee/protoss/general.lua new file mode 100644 index 00000000..043cda59 --- /dev/null +++ b/mpq/lua/melee/protoss/general.lua @@ -0,0 +1,164 @@ +local Basic = require("melee.protoss.build.basic") +local Openers = require("melee.protoss.build.openers") +local Tier1 = require("melee.protoss.build.tier1") +local Tier2 = require("melee.protoss.build.tier2") +local Tier3 = require("melee.protoss.build.tier3") +local Tier4 = require("melee.protoss.build.tier4") + +local expansion_build_order = { + Basic.build_expansion, + Tier4.general_expansion, +} +local attack_wave_cost_bias = 0.5 + +return { + ---@type AIPersonality + lunatic_legion = { + name = "General: Lunatic Legion", + unit_composition = { + -- signature units + [UnitId.ProtossLegionnaire] = 2, + [UnitId.ProtossSimulacrum] = 2, + -- filler units + [UnitId.ProtossEcclesiast] = 1, + [UnitId.ProtossAurora] = 1, + [UnitId.ProtossMagister] = 1, + [UnitId.ProtossDidact] = 1, + }, + main_build_order = { + Openers.lunatic_legion, + Tier2.legion_stargate_gateway_library, + Tier3.legion_argosy_only, + Tier4.robotics_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + matriarchy = { + name = "General: Matriarchy", + unit_composition = { + -- signature units (Prostration Stage / Synthetic Synod) + [UnitId.ProtossZealot] = 2, + [UnitId.ProtossAmaranth] = 2, + [UnitId.ProtossVassal] = 2, + -- filler units + [UnitId.ProtossEmpress] = 1, + [UnitId.ProtossAnalogue] = 1, + [UnitId.ProtossPariah] = 1, + [UnitId.ProtossMindTyrant] = 1, + }, + main_build_order = { + Openers.matriarchy, + Tier1.matriarchy, + Tier2.matriarchy_library_lattice, + Tier3.matriarchy_prostration, + Tier4.matriarchy_synthetic_synod_x2, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + robotics_fe = { + name = "General: Robotics FE", + unit_composition = { + -- signature units (Ardent Authority x2) + [UnitId.ProtossZealot] = 2, + [UnitId.ProtossVassal] = 2, + -- filler units + [UnitId.ProtossServitor] = 1, + [UnitId.ProtossPositron] = 1, + [UnitId.ProtossAccantor] = 1, + [UnitId.ProtossClarion] = 1, + }, + main_build_order = { + Openers.robotics_fe, + Tier1.robotics_fe, + Tier2.robotics_ardent_x2, + Tier3.robotics_prostration, + Tier4.robotics_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + strident_striders = { + name = "General: Strident Striders", + unit_composition = { + -- signature unit + [UnitId.ProtossDracadin] = 2, + -- filler units + [UnitId.ProtossAtreus] = 1, + }, + main_build_order = { + Openers.strident_striders, + Tier1.strident_striders, + Tier2.acquire, + Tier3.acquire, + Tier4.robotics_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + the_swordsmiths = { + name = "General: The Swordsmiths", + unit_composition = { + -- signature units (Ardent Authority x2) + [UnitId.ProtossZealot] = 2, + [UnitId.ProtossManifold] = 2, + -- filler units + [UnitId.ProtossServitor] = 1, + [UnitId.ProtossPositron] = 1, + [UnitId.ProtossAccantor] = 1, + [UnitId.ProtossClarion] = 1, + }, + main_build_order = { + Openers.the_swordsmiths, + Tier1.the_swordsmiths, + Tier2.swordsmiths_ardent_stargate, + Tier3.acquire, + Tier4.robotics_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + worship = { + name = "General: Worship", + unit_composition = { + -- signature units + [UnitId.ProtossAmaranth] = 2, + [UnitId.ProtossSimulacrum] = 2, + -- filler units + [UnitId.ProtossGolem] = 1, + [UnitId.ProtossDracadin] = 1, + }, + main_build_order = { + Openers.worship, + Tier1.worship, + Tier2.acquire, + Tier3.acquire, + Tier4.robotics_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/protoss/ranger.lua b/mpq/lua/melee/protoss/ranger.lua new file mode 100644 index 00000000..b1c2f00b --- /dev/null +++ b/mpq/lua/melee/protoss/ranger.lua @@ -0,0 +1,159 @@ +local Basic = require("melee.protoss.build.basic") +local Openers = require("melee.protoss.build.openers") +local Tier1 = require("melee.protoss.build.tier1") +local Tier2 = require("melee.protoss.build.tier2") +local Tier3 = require("melee.protoss.build.tier3") +local Tier4 = require("melee.protoss.build.tier4") + +local expansion_build_order = { + Basic.build_expansion, + Tier4.ranger_expansion, +} +local attack_wave_cost_bias = 0.5 + +return { + ---@type AIPersonality + two_gate_strider = { + name = "Ranger: 2 Gate Strider", + unit_composition = { + -- signature units + [UnitId.ProtossVassal] = 2, + [UnitId.ProtossDracadin] = 2, + -- filler units + [UnitId.ProtossIdol] = 1, + [UnitId.ProtossAccantor] = 1, + }, + main_build_order = { + Openers.two_gate_strider, + Tier1.two_gate_strider, + Tier2.strider_lattice_library_gateway, + Tier3.acquire, + Tier4.emissary_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + emissary_squared = { + name = "Ranger: Emissary Squared", + unit_composition = { + -- signature units (Synthetic Synod) + [UnitId.ProtossAnalogue] = 2, + [UnitId.ProtossDemiurge] = 2, + [UnitId.ProtossEmpress] = 2, + -- filler units + [UnitId.ProtossVassal] = 1, + [UnitId.ProtossGolem] = 1, + [UnitId.ProtossAccantor] = 1, + [UnitId.ProtossDracadin] = 1, + }, + main_build_order = { + Openers.emissary_squared, + Tier2.emissary_ardent_lattice_gateway, + Tier3.acquire, + Tier4.emissary_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + one_gate_authority = { + name = "Ranger: 1 Gate Authority", + unit_composition = { + -- signature unit + [UnitId.ProtossManifold] = 2, + -- filler units (Prostration Stage) + [UnitId.ProtossPariah] = 1, + [UnitId.ProtossPotentate] = 1, + [UnitId.ProtossSycophant] = 1, + [UnitId.ProtossMindTyrant] = 1, + [UnitId.ProtossIdol] = 1, + [UnitId.ProtossAccantor] = 1, + }, + main_build_order = { + Openers.one_gate_authority, + Tier1.one_gate_authority, + Tier2.authority_ardent_gateway_expand_lattice, + Tier3.authority_prostration_ardent2, + Tier4.authority_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + lattice_expo = { + name = "Ranger: Lattice Expo", + unit_composition = { + -- signature unit + [UnitId.ProtossSimulacrum] = 2, + -- filler units + [UnitId.ProtossIdol] = 1, + [UnitId.ProtossDracadin] = 1, + }, + main_build_order = { + Openers.lattice_expo, + Tier2.expo_ardent_authority, + Tier3.acquire, + Tier4.emissary_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + simulant_swarm = { + name = "Ranger: Simulant Swarm", + unit_composition = { + -- signature unit + [UnitId.ProtossSimulacrum] = 2, + -- filler units + [UnitId.ProtossIdol] = 1, + [UnitId.ProtossEcclesiast] = 1, + }, + main_build_order = { + Openers.simulant_swarm, + Tier1.simulant_swarm, + Tier2.acquire, + Tier3.acquire, + Tier4.emissary_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + the_registry = { + name = "Ranger: The Registry", + unit_composition = { + -- signature unit + [UnitId.ProtossManifold] = 2, + -- filler units + [UnitId.ProtossIdol] = 1, + [UnitId.ProtossAccantor] = 1, + }, + main_build_order = { + Openers.the_registry, + Tier1.the_registry, + Tier2.acquire, + Tier3.acquire, + Tier4.emissary_synthetic_synod, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/protoss/skylord.lua b/mpq/lua/melee/protoss/skylord.lua new file mode 100644 index 00000000..9b9d7729 --- /dev/null +++ b/mpq/lua/melee/protoss/skylord.lua @@ -0,0 +1,164 @@ +local Basic = require("melee.protoss.build.basic") +local Openers = require("melee.protoss.build.openers") +local Tier1 = require("melee.protoss.build.tier1") +local Tier2 = require("melee.protoss.build.tier2") +local Tier3 = require("melee.protoss.build.tier3") +local Tier4 = require("melee.protoss.build.tier4") + +local expansion_build_order = { + Basic.build_expansion, + Tier4.skylord_expansion, +} +local attack_wave_cost_bias = 0.6 + +return { + ---@type AIPersonality + solar_escort = { + name = "Skylord: Solar Escort", + unit_composition = { + -- signature units + [UnitId.ProtossSolarion] = 2, + [UnitId.ProtossLegionnaire] = 2, + [UnitId.ProtossVassal] = 2, + -- filler units + [UnitId.ProtossEcclesiast] = 1, + [UnitId.ProtossPanoptus] = 1, + [UnitId.ProtossGladius] = 1, + [UnitId.ProtossEmpyrean] = 1, + }, + main_build_order = { + Openers.solar_escort, + Tier1.solar_escort, + Tier2.escort_stargate_buildup, + Tier3.escort_argosy_buildup, + Tier4.skylord_monument_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + deadly_omen = { + name = "Skylord: Deadly Omen", + unit_composition = { + -- signature units + [UnitId.ProtossZealot] = 2, + [UnitId.ProtossVassal] = 2, + -- filler units + [UnitId.ProtossPanoptus] = 1, + [UnitId.ProtossExemplar] = 1, + }, + main_build_order = { + Openers.deadly_omen, + Tier1.deadly_omen, + Tier2.acquire, + Tier3.acquire, + Tier4.skylord_monument_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + two_gate_panoptus = { + name = "Skylord: 2 Gate Panoptus", + unit_composition = { + -- signature units + [UnitId.ProtossPanoptus] = 3, + [UnitId.ProtossVassal] = 2, + -- filler units + [UnitId.ProtossHierophant] = 1, + }, + main_build_order = { + Openers.two_gate_panoptus, + Tier1.two_gate_panoptus, + Tier2.acquire, + Tier3.acquire, + Tier4.skylord_monument_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + exalted = { + name = "Skylord: Exalted", + unit_composition = { + -- signature units (Monument of Sin) + [UnitId.ProtossVassal] = 2, + [UnitId.ProtossStarSovereign] = 2, + [UnitId.ProtossAnthelion] = 2, + -- filler units + [UnitId.ProtossPanoptus] = 1, + [UnitId.ProtossExemplar] = 1, + [UnitId.ProtossDidact] = 1, + }, + main_build_order = { + Openers.exalted, + Tier1.exalted, + Tier2.acquire, + Tier3.exalted_argosy_crucible, + Tier4.exalted_monument_of_sin, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + sorrow_fleet = { + name = "Skylord: Sorrow Fleet", + unit_composition = { + -- signature units (Monument of Sin) + [UnitId.ProtossVassal] = 2, + [UnitId.ProtossStarSovereign] = 2, + [UnitId.ProtossAnthelion] = 1.5, + -- filler units + [UnitId.ProtossPanoptus] = 1, + [UnitId.ProtossGladius] = 1, + [UnitId.ProtossEmpyrean] = 1, + }, + main_build_order = { + Openers.sorrow_fleet, + Tier2.acquire, + Tier3.fleet_argosy_buildup, + Tier4.fleet_monument_of_sin, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + ships_and_swords = { + name = "Skylord: Ships and Swords", + unit_composition = { + -- signature units + [UnitId.ProtossVassal] = 2, + [UnitId.ProtossAurora] = 1.5, + [UnitId.ProtossMagister] = 1.5, + -- filler units + [UnitId.ProtossGladius] = 1, + [UnitId.ProtossStarSovereign] = 1, + }, + main_build_order = { + Openers.ships_and_swords, + Tier1.ships_and_swords, + Tier2.acquire, + Tier3.acquire, + Tier4.skylord_monument_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/protoss/templar.lua b/mpq/lua/melee/protoss/templar.lua new file mode 100644 index 00000000..efe3e4df --- /dev/null +++ b/mpq/lua/melee/protoss/templar.lua @@ -0,0 +1,163 @@ +local Basic = require("melee.protoss.build.basic") +local Openers = require("melee.protoss.build.openers") +local Tier1 = require("melee.protoss.build.tier1") +local Tier2 = require("melee.protoss.build.tier2") +local Tier3 = require("melee.protoss.build.tier3") +local Tier4 = require("melee.protoss.build.tier4") + +local expansion_build_order = { + Basic.build_expansion, + Tier4.templar_expansion, +} +local attack_wave_cost_bias = 0.4 + +return { + ---@type AIPersonality + three_gate_strider = { + name = "Templar: 3 Gate Strider", + unit_composition = { + -- signature unit + [UnitId.ProtossLegionnaire] = 2, + -- filler units + [UnitId.ProtossAccantor] = 1, + [UnitId.ProtossDracadin] = 1, + [UnitId.ProtossArchon] = 1, + }, + main_build_order = { + Openers.three_gate_strider, + Tier1.three_gate_strider, + Tier2.strider3_library_expand, + Tier3.acquire, + Tier4.templar_archives_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + suffocating_shadow = { + name = "Templar: Suffocating Shadow", + unit_composition = { + -- signature units (Rogue Gallery) + [UnitId.ProtossVagrant] = 2, + [UnitId.ProtossCabalist] = 2, + [UnitId.ProtossCharlatan] = 2, + [UnitId.ProtossBarghest] = 2, + [UnitId.ProtossZealot] = 2, + -- filler units + [UnitId.ProtossAtreus] = 1, + [UnitId.ProtossEcclesiast] = 1, + }, + main_build_order = { + Openers.suffocating_shadow, + Tier1.suffocating_shadow, + Tier2.shadow_roguegallery_expand_library, + Tier3.acquire, + Tier4.templar_archives_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + legion = { + name = "Templar: Legion", + unit_composition = { + -- signature unit + [UnitId.ProtossLegionnaire] = 2, + -- filler units + [UnitId.ProtossEcclesiast] = 1, + [UnitId.ProtossArchon] = 1, + }, + main_build_order = { + Openers.legion, + Tier1.legion, + Tier2.legion_library_x2, + Tier3.acquire, + Tier4.templar_archives_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + initiates = { + name = "Templar: Initiates", + unit_composition = { + -- signature unit + [UnitId.ProtossZealot] = 2, + -- filler units + [UnitId.ProtossEcclesiast] = 1, + [UnitId.ProtossCantavis] = 1, + [UnitId.ProtossAccantor] = 1, + [UnitId.ProtossArchon] = 1, + }, + main_build_order = { + Openers.initiates, + Tier1.initiates, + Tier2.acquire, + Tier3.acquire, + Tier4.templar_archives_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + martyrdom = { + name = "Templar: Martyrdom", + unit_composition = { + -- signature units (Ancestral Archives x4) + [UnitId.ProtossArchon] = 2, + [UnitId.ProtossLegionnaire] = 2, + -- filler units + [UnitId.ProtossAtreus] = 1, + [UnitId.ProtossMindTyrant] = 1, + [UnitId.ProtossOptecton] = 1, + [UnitId.ProtossPatriarch] = 1, + }, + main_build_order = { + Openers.martyrdom, + Tier2.martyrdom_pylon_roguegallery, + Tier3.martyrdom_prostration_crucible, + Tier4.martyrdom_ancestral_archives_scaling, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + legions_heralds = { + name = "Templar: Legion's Heralds", + unit_composition = { + -- signature unit + [UnitId.ProtossLegionnaire] = 2, + -- filler units + [UnitId.ProtossEcclesiast] = 1, + [UnitId.ProtossAtreus] = 1, + [UnitId.ProtossHerald] = 1, + [UnitId.ProtossPatriarch] = 1, + }, + main_build_order = { + Openers.legions_heralds, + Tier1.legions_heralds, + Tier2.heralds_library_gateway_buildup, + Tier3.acquire, + Tier4.templar_archives_acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/terran/armor.lua b/mpq/lua/melee/terran/armor.lua new file mode 100644 index 00000000..d24303f2 --- /dev/null +++ b/mpq/lua/melee/terran/armor.lua @@ -0,0 +1,164 @@ +local Basic = require("melee.terran.build.basic") +local Openers = require("melee.terran.build.openers") +local Tier1 = require("melee.terran.build.tier1") +local Tier2 = require("melee.terran.build.tier2") +local Tier3 = require("melee.terran.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier2.acquire, + Tier2.fulcrum_buildup, + Tier3.acquire, +} +local attack_wave_cost_bias = 0.6 + +return { + ---@type AIPersonality + tank_push = { + name = "Armor: Tank Push", + unit_composition = { + -- signature units + [UnitId.TerranMatadorTankMode] = 2, + [UnitId.TerranPhalanxTankMode] = 2, + -- healers + [UnitId.TerranCleric] = 1, + -- filler units + [UnitId.TerranCyprian] = 1, + [UnitId.TerranBlackjack] = 1, + [UnitId.TerranMadrigalImperiosoMode] = 1, + }, + main_build_order = { + Openers.tank_push, + Tier2.acquire, + Tier2.stockade_requeue, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + two_fulc_vulture = { + name = "Armor: 2Fulc Vulture", + unit_composition = { + -- signature unit + [UnitId.TerranVulture] = 3, + -- filler units + [UnitId.TerranMaverick] = 1, + [UnitId.TerranCyclops] = 1, + }, + main_build_order = { + Openers.two_fulc_vulture, + Tier1.two_fulc_vulture, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + fe_five_fulc = { + name = "Armor: FE 5Fulc", + unit_composition = { + -- signature units + [UnitId.TerranVulture] = 2, + [UnitId.TerranBlackjack] = 2, + [UnitId.TerranPhalanxTankMode] = 2, + -- filler units + [UnitId.TerranMaverick] = 1, + [UnitId.TerranAzazel] = 1, + [UnitId.TerranSeraph] = 1, + }, + main_build_order = { + Openers.fe_five_fulc, + Tier1.fe_five_fulc, + Tier2.starport_commandment_buildup, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + david_and_goliath = { + name = "Armor: David and Goliath", + unit_composition = { + -- signature units + [UnitId.TerranGoliath] = 3, + [UnitId.TerranMaverick] = 2, + -- filler units + [UnitId.TerranMatadorTankMode] = 1, + [UnitId.TerranPhalanxTankMode] = 1, + [UnitId.TerranWraith] = 1, + [UnitId.TerranBlackjack] = 1, + }, + main_build_order = { + Openers.david_and_goliath, + Tier1.david_and_goliath, + Tier2.acquire, + Tier2.fulcrum_scaling, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + iron_crusade = { + name = "Armor: Iron Crusade", + unit_composition = { + -- signature units (Iron Foundry x4 is the real investment of this build) + [UnitId.TerranPaladin] = 3, + [UnitId.TerranCuirassTankMode] = 2, + [UnitId.TerranPazuzu] = 2, + -- filler units + [UnitId.TerranVulture] = 1, + [UnitId.TerranWraith] = 1, + }, + main_build_order = { + Openers.expand_first, + Tier1.iron_crusade, + Tier2.acquire, + Tier2.fulcrum_buildup, + Tier3.acquire, + Tier3.iron_foundries, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + walker_power = { + name = "Armor: Walker Power", + unit_composition = { + -- signature units (walkers) + [UnitId.TerranGoliath] = 3, + [UnitId.TerranCyclops] = 2, + [UnitId.TerranBlackjack] = 2, + -- filler units + [UnitId.TerranPhalanxTankMode] = 1, + [UnitId.TerranMaverick] = 1, + }, + main_build_order = { + Openers.walker_power, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/terran/build/basic.lua b/mpq/lua/melee/terran/build/basic.lua new file mode 100644 index 00000000..84f3ddd5 --- /dev/null +++ b/mpq/lua/melee/terran/build/basic.lua @@ -0,0 +1,31 @@ +local Basic = {} + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_expansion(melee_ai, town) + town:build(1, UnitId.TerranMinistry, 120) + town:wait_build(1, UnitId.TerranMinistry) + town:build(town.max_workers, UnitId.TerranMason, 100) + town:transfer_workers(8) + town:wait_build(10, UnitId.TerranMason) +end + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_anti_ground(melee_ai, town) + for i = 1, 2 do + town:build(i, UnitId.TerranAnchor, 70 - i * 5) + wait(secs(25 + i * 5)) + end +end + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_anti_air(melee_ai, town) + for i = 1, 4 do + town:build(i, UnitId.TerranWatchdog, 80 - i * 5) + wait(secs(25 + i * 5)) + end +end + +return Basic diff --git a/mpq/lua/melee/terran/build/openers.lua b/mpq/lua/melee/terran/build/openers.lua new file mode 100644 index 00000000..d425f9ad --- /dev/null +++ b/mpq/lua/melee/terran/build/openers.lua @@ -0,0 +1,416 @@ +---@class TerranOpeners +local Openers = {} + +-- Shared short openers (still used as-is by personalities whose own build +-- order never repositions an attack/expand trigger within this phase). + +---@param melee_ai MeleeAI +---@param town Town +function Openers.expand_first(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.expand_first_slow(melee_ai, town) + melee_ai:dynamic_wait(secs(50)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.expand_first_very_slow(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.fulcrum_start_medium(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranFulcrum, 110) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.stockade_quick_start(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 110) +end + +-- Armor + +---@param melee_ai MeleeAI +---@param town Town +function Openers.tank_push(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranFulcrum, 110) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(1, UnitId.TerranFulcrum) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.TerranScrapyard, 90) + town:multirun(function() + wait(secs(18)) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 89) + town:wait_build(3, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranPalladium, 89) + town:wait_build(4, UnitId.TerranFulcrum) + town:build(3, UnitId.TerranPalladium, 89) + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 89) + end) + town:wait_build(1, UnitId.TerranScrapyard, false) + town:build(2, UnitId.TerranFulcrum, 90) + town:wait_build(2, UnitId.TerranFulcrum, false) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.two_fulc_vulture(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.TerranFulcrum, 110) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(mins(1) + secs(15)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(50)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.fe_five_fulc(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(35)) + town:build(1, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.david_and_goliath(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranFulcrum, 110) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.TerranStockade, 90) + town:wait_build(2, UnitId.TerranStockade, false) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(30)) + town:build(2, UnitId.TerranFulcrum, 90) + town:wait_build(2, UnitId.TerranFulcrum, false) + town:multirun(function() + town:wait_build(2, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 89) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 89) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(3, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranPalladium, 89) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(5, UnitId.TerranFulcrum) + town:build(3, UnitId.TerranPalladium, 89) + melee_ai:dynamic_wait(secs(15)) + end) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.walker_power(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranFulcrum, 110) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(35)) + town:multirun(function() + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 89) + melee_ai:dynamic_wait(secs(15)) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 89) + end) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(5)) + town:wait_build(1, UnitId.TerranFulcrum) + town:wait_build(2, UnitId.TerranFulcrum, false) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(mins(1) + secs(10)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(mins(1)) +end + +-- Raider + +---@param melee_ai MeleeAI +---@param town Town +function Openers.eight_rax(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(5, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(6, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(15)) + town:wait_build(6, UnitId.TerranStockade, false) + melee_ai:dynamic_wait(mins(1) + secs(15)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.three_rax_bio(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(mins(1) + secs(5)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 89) + town:wait_build(2, UnitId.TerranStockade) + town:build(2, UnitId.TerranVestry, 89) + end) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(10)) + town:wait_build(3, UnitId.TerranStockade, false) + melee_ai:start_expanding() + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.fast_anvil(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.four_rax_pressure(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.holy_ackmeds(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(45)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) + town:wait_build(4, UnitId.TerranStockade, false) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +-- Orbital + +---@param melee_ai MeleeAI +---@param town Town +function Openers.rapid_starpads(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranStarpad, 110) + melee_ai:dynamic_wait(secs(50)) + town:build(2, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(mins(1) + secs(5)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1) + secs(15)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.wraith_bio(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStarpad, 90) + + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(5)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(5)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.hotdrop(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(35)) + town:build(2, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(45)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(35)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) + town:wait_build(3, UnitId.TerranStarpad) + town:wait_build(2, UnitId.TerranStockade, false) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.sudden_starport(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(50)) + town:wait_build(3, UnitId.TerranStockade, false) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.aces_high(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranFulcrum, 110) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(2, UnitId.TerranFulcrum, false) + town:multirun(function() + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 89) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranScrapyard, 89) + town:wait_build(3, UnitId.TerranFulcrum) + town:build(3, UnitId.TerranScrapyard, 89) + end) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() +end + +-- General + +---@param melee_ai MeleeAI +---@param town Town +function Openers.two_fulcrum_mech(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.TerranFulcrum, 110) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.doubling_up(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(5)) + town:build(2, UnitId.TerranStockade, 90) + wait(secs(1)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(mins(1) + secs(30)) + town:build(1, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(15)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 89) + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 89) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranScrapyard, 89) + town:wait_build(2, UnitId.TerranStockade) + town:build(1, UnitId.TerranCovenant, 89) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(3, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 89) + end) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.mech_into_air(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranFulcrum, 110) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.observant(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStockade, 110) + melee_ai:dynamic_wait(secs(5)) +end + +return Openers diff --git a/mpq/lua/melee/terran/build/tier1.lua b/mpq/lua/melee/terran/build/tier1.lua new file mode 100644 index 00000000..1d13a59d --- /dev/null +++ b/mpq/lua/melee/terran/build/tier1.lua @@ -0,0 +1,238 @@ +---@class TerranTier1 +local Tier1 = {} + +-- Shared expansion-order helpers (unrelated to the opener/tier1 split). + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.vestry_request(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranVestry, 89) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.covenant_request(melee_ai, town) + town:build(1, UnitId.TerranCovenant, 89) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +-- Armor (scale-up after the opener; unlisted personalities fold entirely +-- into their opener and skip tier1) + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.two_fulc_vulture(melee_ai, town) + town:build(3, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.TerranStockade, 90) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.fe_five_fulc(melee_ai, town) + melee_ai:dynamic_wait(secs(50)) + town:build(3, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.david_and_goliath(melee_ai, town) + melee_ai:dynamic_wait(secs(50)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(mins(1) + secs(20)) + town:wait_build(1, UnitId.TerranFulcrum) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.iron_crusade(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(mins(1)) + town:wait_build(1, UnitId.TerranFulcrum) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(mins(1) + secs(20)) +end + +-- Raider + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.eight_rax(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(8, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(50)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.fast_anvil(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(40)) + town:wait_build(3, UnitId.TerranStockade, false) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 89) + town:wait_build(2, UnitId.TerranStockade) + town:build(1, UnitId.TerranCovenant, 89) + town:wait_build(1, UnitId.TerranCovenant, false) + town:build(1, UnitId.TerranAnvil, 89) + town:wait_build(3, UnitId.TerranStockade) + town:wait_build(1, UnitId.TerranAnvil, false) + town:build(2, UnitId.TerranCovenant, 89) + town:wait_build(4, UnitId.TerranStockade) + town:build(3, UnitId.TerranCovenant, 89) + end) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.four_rax_pressure(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 89) + town:wait_build(2, UnitId.TerranStockade) + town:build(2, UnitId.TerranVestry, 89) + town:wait_build(3, UnitId.TerranStockade) + town:build(1, UnitId.TerranCovenant, 89) + town:wait_build(4, UnitId.TerranStockade) + town:build(2, UnitId.TerranCovenant, 89) + end) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(4, UnitId.TerranStockade) + melee_ai:dynamic_wait(mins(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.uprising(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 89) + town:wait_build(4, UnitId.TerranStockade) + town:build(2, UnitId.TerranVestry, 89) + end) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + town:wait_build(4, UnitId.TerranStockade) + town:wait_build(2, UnitId.TerranVestry) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +-- Orbital + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.rapid_starpads(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(2, UnitId.TerranStarpad) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(mins(2) + secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.orbital_stockade_settle(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(mins(1)) + town:wait_build(1, UnitId.TerranStockade) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.wraith_bio(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:wait_build(3, UnitId.TerranStockade, false) + town:build(1, UnitId.TerranVestry, 89) + melee_ai:dynamic_wait(mins(1) + secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.aces_high(melee_ai, town) + melee_ai:dynamic_wait(secs(50)) + town:build(3, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(mins(1) + secs(20)) +end + +-- General + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.two_fulcrum_mech(melee_ai, town) + melee_ai:dynamic_wait(mins(1)) + town:build(3, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.general_stockade_fulcrum_vestry_palladium_starpad(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.TerranFulcrum, 90) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 89) + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 89) + end) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(5)) + town:build(2, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(mins(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.general_stockade_starpad(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStockade, 90) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.doubling_up(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + town:build(1, UnitId.TerranStarpad, 90) + melee_ai:dynamic_wait(secs(25)) +end + +return Tier1 diff --git a/mpq/lua/melee/terran/build/tier2.lua b/mpq/lua/melee/terran/build/tier2.lua new file mode 100644 index 00000000..423afa83 --- /dev/null +++ b/mpq/lua/melee/terran/build/tier2.lua @@ -0,0 +1,322 @@ +---@class TerranTier2 +local Tier2 = {} + +--- Requests Atlas and waits for it. The generic tier-2 tech transition. +---@param melee_ai MeleeAI +---@param town Town +function Tier2.acquire(melee_ai, town) + town:multirun(function() + wait(secs(1)) + town:build(1, UnitId.TerranAtlas, 70, true) + end) +end + +--- Requests a bare Starport once Atlas exists, for base air units. +---@param melee_ai MeleeAI +---@param town Town +function Tier2.starport(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.TerranStarport, 69, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.stockade_requeue(melee_ai, town) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.TerranStockade, 70) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.starport_commandment_buildup(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 69) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 69) + town:wait_build(3, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranPalladium, 69) + wait(secs(2)) + town:wait_build(1, UnitId.TerranAtlas) + town:build(1, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport) + town:build(1, UnitId.TerranCommandment, 69) + end) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(2, UnitId.TerranFulcrum, false) + melee_ai:dynamic_wait(secs(70) + secs(25)) + town:build(5, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(mins(2) + secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.fulcrum_scaling(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(4, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(25)) + town:build(5, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.fulcrum_buildup(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(3, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(2, UnitId.TerranFulcrum) + town:wait_build(3, UnitId.TerranFulcrum, false) + wait(secs(1)) + town:wait_build(1, UnitId.TerranAtlas) + melee_ai:dynamic_wait(secs(20)) + town:build(4, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(20)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.raider_vestry_covenant_buildup(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 69) + town:wait_build(2, UnitId.TerranStockade) + town:build(2, UnitId.TerranVestry, 69) + town:wait_build(3, UnitId.TerranStockade) + town:build(1, UnitId.TerranCovenant, 69) + town:wait_build(4, UnitId.TerranStockade) + town:build(2, UnitId.TerranCovenant, 69) + end) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.raider_stockade_requeue(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.TerranStockade, 70) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.raider_vestry_starport_rotary(melee_ai, town) + melee_ai:dynamic_wait(secs(5)) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 69) + town:wait_build(1, UnitId.TerranAtlas) + town:build(1, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport) + town:build(1, UnitId.TerranRotary, 69) + end) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.orbital_starport_rotary_commandment(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.TerranAtlas) + town:build(1, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport) + town:build(1, UnitId.TerranRotary, 69) + town:build(2, UnitId.TerranStarport, 70) + town:wait_build(2, UnitId.TerranStarport) + town:build(2, UnitId.TerranRotary, 69) + town:build(3, UnitId.TerranStarport, 70) + town:wait_build(3, UnitId.TerranStarport) + town:build(1, UnitId.TerranCommandment, 69) + end) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.orbital_covenant_anvil_starport_rotary(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:multirun(function() + town:build(1, UnitId.TerranCovenant, 69) + town:wait_build(1, UnitId.TerranCovenant) + town:build(1, UnitId.TerranAnvil, 69) + wait(secs(1)) + town:wait_build(1, UnitId.TerranAtlas) + town:build(1, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport) + town:build(1, UnitId.TerranRotary, 69) + town:build(2, UnitId.TerranStarport, 70) + town:wait_build(2, UnitId.TerranStarport) + town:build(2, UnitId.TerranRotary, 69) + melee_ai:dynamic_wait(secs(50)) + end) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.orbital_vestry_starport_rotary_x4(melee_ai, town) + town:multirun(function() + town:wait_build(2, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 69) + town:wait_build(1, UnitId.TerranAtlas) + town:build(1, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport, false) + town:build(4, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport) + town:build(1, UnitId.TerranRotary, 69) + town:wait_build(2, UnitId.TerranStarport) + town:build(2, UnitId.TerranRotary, 69) + town:wait_build(3, UnitId.TerranStarport) + town:build(3, UnitId.TerranRotary, 69) + town:wait_build(4, UnitId.TerranStarport) + town:build(4, UnitId.TerranRotary, 69) + melee_ai:dynamic_wait(secs(15)) + end) + melee_ai:dynamic_wait(mins(1) + secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.general_expansion_tail(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.TerranStarpad, 69, true) + melee_ai:dynamic_wait(secs(15)) + Tier2.acquire(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranStockade, 69, true) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.TerranCovenant, 69, true) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.TerranStarport, 69, true) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:multirun(function() + wait(secs(1)) + town:wait_build(1, UnitId.TerranAtlas) + town:build(1, UnitId.TerranDaedala, 65, true) + end) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.general_scrapyard_palladium_mantle(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 69) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranScrapyard, 69) + town:wait_build(3, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 69) + town:wait_build(4, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranPalladium, 69) + end) + melee_ai:dynamic_wait(secs(40) + secs(40)) + town:build(2, UnitId.TerranMantle, 70) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.general_vestry_palladium_starport_rotary(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 69) + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 69) + town:wait_build(1, UnitId.TerranAtlas) + wait(secs(1)) + town:build(1, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport) + town:build(1, UnitId.TerranRotary, 69) + end) + melee_ai:dynamic_wait(secs(40) + secs(25) + secs(50)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.general_fulcrum_follow_up(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.general_mega_tech_buildup(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 69) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranScrapyard, 69) + town:build(1, UnitId.TerranQuarry, 69) + town:wait_build(3, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranPalladium, 69) + town:wait_build(4, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranPalladium, 69) + town:wait_build(1, UnitId.TerranAtlas) + town:build(2, UnitId.TerranStarport, 70) + town:wait_build(1, UnitId.TerranStarport) + town:build(1, UnitId.TerranRotary, 69) + town:wait_build(2, UnitId.TerranStarport) + town:build(2, UnitId.TerranRotary, 69) + town:wait_build(5, UnitId.TerranFulcrum) + town:build(3, UnitId.TerranPalladium, 69) + town:wait_build(6, UnitId.TerranFulcrum) + town:build(4, UnitId.TerranPalladium, 69) + town:wait_build(3, UnitId.TerranStarport) + town:build(3, UnitId.TerranRotary, 69) + town:wait_build(4, UnitId.TerranStarport) + town:build(4, UnitId.TerranRotary, 69) + town:wait_build(5, UnitId.TerranStarport) + town:build(5, UnitId.TerranRotary, 69) + town:wait_build(1, UnitId.TerranDaedala) + town:wait_build(6, UnitId.TerranStarport) + town:build(1, UnitId.TerranCommandment, 69) + town:wait_build(7, UnitId.TerranStarport) + town:build(2, UnitId.TerranCommandment, 69) + town:wait_build(8, UnitId.TerranStarport) + town:build(3, UnitId.TerranCommandment, 69) + town:build(2, UnitId.TerranNaniteAssembly, 69) + town:wait_build(1, UnitId.TerranNaniteAssembly) + town:build(1, UnitId.TerranHadron, 69) + end) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(1, UnitId.TerranStarpad, 70) + melee_ai:dynamic_wait(secs(15) + mins(1) + secs(15)) + town:build(6, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.general_vestry_scrapyard_palladium_starport_commandment(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.TerranStockade) + town:build(1, UnitId.TerranVestry, 69) + town:wait_build(1, UnitId.TerranFulcrum) + town:build(1, UnitId.TerranScrapyard, 69) + town:wait_build(2, UnitId.TerranFulcrum) + town:build(2, UnitId.TerranPalladium, 69) + town:wait_build(3, UnitId.TerranFulcrum) + wait(secs(1)) + town:wait_build(1, UnitId.TerranAtlas) + town:build(2, UnitId.TerranStarport, 70) + town:wait_build(2, UnitId.TerranStarport) + town:build(2, UnitId.TerranCommandment, 69) + town:wait_build(1, UnitId.TerranCommandment) + melee_ai:dynamic_wait(secs(15)) + end) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(5) + secs(35)) + town:build(1, UnitId.TerranStarpad, 70) + melee_ai:dynamic_wait(secs(30)) + town:build(2, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(25) + secs(50)) + Tier2.acquire(melee_ai, town) + town:build(3, UnitId.TerranFulcrum, 70) + melee_ai:dynamic_wait(secs(50)) +end + +return Tier2 diff --git a/mpq/lua/melee/terran/build/tier3.lua b/mpq/lua/melee/terran/build/tier3.lua new file mode 100644 index 00000000..d3a4daf8 --- /dev/null +++ b/mpq/lua/melee/terran/build/tier3.lua @@ -0,0 +1,49 @@ +---@class TerranTier3 +local Tier3 = {} + +--- Requests Daedala and waits for it. The generic tier-3 tech transition. +---@param melee_ai MeleeAI +---@param town Town +function Tier3.acquire(melee_ai, town) + town:multirun(function() + wait(secs(1)) + town:wait_build(1, UnitId.TerranAtlas) + town:build(1, UnitId.TerranDaedala, 65, true) + end) + wait(secs(1)) + town:wait_build(1, UnitId.TerranDaedala) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.iron_foundries(melee_ai, town) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.TerranIronFoundry, 50) + melee_ai:dynamic_wait(secs(5)) + town:build(2, UnitId.TerranIronFoundry, 50) + melee_ai:dynamic_wait(secs(5)) + town:build(3, UnitId.TerranIronFoundry, 50) + melee_ai:dynamic_wait(secs(5)) + town:build(4, UnitId.TerranIronFoundry, 50) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.nanite_assembly_buildup(melee_ai, town) + town:build(2, UnitId.TerranNaniteAssembly, 50) + melee_ai:dynamic_wait(secs(20)) + town:build(3, UnitId.TerranNaniteAssembly, 50) + melee_ai:dynamic_wait(secs(20)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.general_starport_scaling(melee_ai, town) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(4, UnitId.TerranStarport, 50) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(8, UnitId.TerranStarport, 50) + melee_ai:dynamic_wait(mins(1) + secs(15)) +end + +return Tier3 diff --git a/mpq/lua/melee/terran/general.lua b/mpq/lua/melee/terran/general.lua new file mode 100644 index 00000000..ac7e92b2 --- /dev/null +++ b/mpq/lua/melee/terran/general.lua @@ -0,0 +1,161 @@ +local Basic = require("melee.terran.build.basic") +local Openers = require("melee.terran.build.openers") +local Tier1 = require("melee.terran.build.tier1") +local Tier2 = require("melee.terran.build.tier2") +local Tier3 = require("melee.terran.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier2.general_expansion_tail, +} +local attack_wave_cost_bias = 0.5 + +return { + ---@type AIPersonality + two_fulcrum_mech = { + name = "General: 2 Fulcrum Mech", + unit_composition = { + -- signature units (Mantle x2) + [UnitId.TerranPhalanxTankMode] = 2, + [UnitId.TerranRamesses] = 2, + [UnitId.TerranDurendal] = 2, + [UnitId.TerranCataphract] = 2, + -- filler units + [UnitId.TerranBlackjack] = 1, + [UnitId.TerranMadrigalImperiosoMode] = 1, + }, + main_build_order = { + Openers.two_fulcrum_mech, + Tier1.two_fulcrum_mech, + Tier2.acquire, + Tier2.general_scrapyard_palladium_mantle, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + early_riser = { + name = "General: Early Riser", + unit_composition = { + -- signature unit + [UnitId.TerranMaverick] = 2, + -- healers + [UnitId.TerranCleric] = 1, + -- filler units + [UnitId.TerranCyclops] = 1, + }, + main_build_order = { + Openers.expand_first_very_slow, + Tier1.general_stockade_fulcrum_vestry_palladium_starpad, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + the_cube = { + name = "General: The Cube", + unit_composition = { + -- signature unit + [UnitId.TerranMaverick] = 2, + -- filler units + [UnitId.TerranVulture] = 1, + [UnitId.TerranPhalanxTankMode] = 1, + [UnitId.TerranSalamander] = 1, + }, + main_build_order = { + Openers.fulcrum_start_medium, + Tier1.general_stockade_starpad, + Tier2.general_vestry_palladium_starport_rotary, + Tier2.acquire, + Tier2.general_fulcrum_follow_up, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + doubling_up = { + name = "General: Doubling Up", + unit_composition = { + -- signature units + [UnitId.TerranMaverick] = 2, + [UnitId.TerranGoliath] = 2, + -- healers + [UnitId.TerranShamanSupportMode] = 1, + -- filler units + [UnitId.TerranCyclops] = 1, + [UnitId.TerranBlackjack] = 1, + }, + main_build_order = { + Openers.doubling_up, + Tier1.doubling_up, + Tier2.acquire, + Tier2.general_fulcrum_follow_up, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + mech_into_air = { + name = "General: Mech into Air", + unit_composition = { + -- signature units (the mech-into-air transition) + [UnitId.TerranPhalanxTankMode] = 2, + [UnitId.TerranMinotaur] = 2, + -- filler units + [UnitId.TerranMatadorTankMode] = 1, + [UnitId.TerranSalamander] = 1, + [UnitId.TerranSeraph] = 1, + [UnitId.TerranHippogriff] = 1, + [UnitId.TerranPhobos] = 1, + }, + main_build_order = { + Openers.mech_into_air, + Tier2.acquire, + Tier2.general_mega_tech_buildup, + Tier3.acquire, + Tier3.general_starport_scaling, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + observant = { + name = "General: Observant", + unit_composition = { + -- signature unit + [UnitId.TerranCyprian] = 2, + -- filler units + [UnitId.TerranSeraph] = 1, + [UnitId.TerranBlackjack] = 1, + }, + main_build_order = { + Openers.observant, + Tier2.general_vestry_scrapyard_palladium_starport_commandment, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/terran/orbital.lua b/mpq/lua/melee/terran/orbital.lua new file mode 100644 index 00000000..846d9ea7 --- /dev/null +++ b/mpq/lua/melee/terran/orbital.lua @@ -0,0 +1,153 @@ +local Basic = require("melee.terran.build.basic") +local Openers = require("melee.terran.build.openers") +local Tier1 = require("melee.terran.build.tier1") +local Tier2 = require("melee.terran.build.tier2") +local Tier3 = require("melee.terran.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier2.acquire, + Tier3.acquire, + Tier2.starport, +} +local attack_wave_cost_bias = 0.6 + +return { + ---@type AIPersonality + rapid_starpads = { + name = "Orbital: 2 Starpad Wraith", + unit_composition = { + -- signature unit + [UnitId.TerranWraith] = 3, + -- filler units + [UnitId.TerranSalamander] = 1, + [UnitId.TerranAzazel] = 1, + [UnitId.TerranSeraph] = 1, + }, + main_build_order = { + Openers.rapid_starpads, + Tier1.rapid_starpads, + Tier2.acquire, + Tier2.orbital_starport_rotary_commandment, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + the_don = { + name = "Orbital: The Don", + unit_composition = { + -- signature units (Nanite Assembly x3) + [UnitId.TerranMinotaur] = 2, + [UnitId.TerranCentaur] = 2, + [UnitId.TerranMagnetar] = 2, + -- filler units + [UnitId.TerranGorgon] = 1, + }, + main_build_order = { + Openers.expand_first_slow, + Tier1.orbital_stockade_settle, + Tier2.acquire, + Tier3.acquire, + Tier3.nanite_assembly_buildup, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + wraith_bio = { + name = "Orbital: Wraith Bio", + unit_composition = { + -- signature units + [UnitId.TerranMaverick] = 3, + [UnitId.TerranWraith] = 3, + -- filler units + [UnitId.TerranValkyrie] = 1, + }, + main_build_order = { + Openers.wraith_bio, + Tier1.wraith_bio, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + hotdrop = { + name = "Orbital: Hotdrop", + unit_composition = { + -- signature unit + [UnitId.TerranHarakan] = 3, + -- filler units + [UnitId.TerranWraith] = 1, + [UnitId.TerranSalamander] = 1, + }, + main_build_order = { + Openers.hotdrop, + Tier2.acquire, + Tier2.orbital_covenant_anvil_starport_rotary, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + sudden_starport = { + name = "Orbital: Sudden Starport", + unit_composition = { + -- signature units + [UnitId.TerranMaverick] = 2, + [UnitId.TerranSundog] = 2, + [UnitId.TerranSalamander] = 2, + -- healers + [UnitId.TerranCleric] = 1.5, + }, + main_build_order = { + Openers.sudden_starport, + Tier2.orbital_vestry_starport_rotary_x4, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + aces_high = { + name = "Orbital: Aces High", + unit_composition = { + -- signature units + [UnitId.TerranVulture] = 3, + [UnitId.TerranWraith] = 3, + -- filler units + [UnitId.TerranBlackjack] = 1, + [UnitId.TerranCyclops] = 1, + }, + main_build_order = { + Openers.aces_high, + Tier1.aces_high, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/terran/raider.lua b/mpq/lua/melee/terran/raider.lua new file mode 100644 index 00000000..4bb09fa0 --- /dev/null +++ b/mpq/lua/melee/terran/raider.lua @@ -0,0 +1,153 @@ +local Basic = require("melee.terran.build.basic") +local Openers = require("melee.terran.build.openers") +local Tier1 = require("melee.terran.build.tier1") +local Tier2 = require("melee.terran.build.tier2") +local Tier3 = require("melee.terran.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier1.vestry_request, + Tier2.acquire, + Tier1.covenant_request, + Tier3.acquire, +} +local attack_wave_cost_bias = 0.4 + +return { + ---@type AIPersonality + eight_rax = { + name = "Raider: 8Rax", + unit_composition = { + -- signature unit + [UnitId.TerranMaverick] = 3, + -- healers + [UnitId.TerranCleric] = 1.5, + -- filler units + [UnitId.TerranHarakan] = 1, + [UnitId.TerranCyprian] = 1, + }, + main_build_order = { + Openers.eight_rax, + Tier1.eight_rax, + Tier2.acquire, + Tier2.raider_vestry_covenant_buildup, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + three_rax_bio = { + name = "Raider: 3Rax Maverick & Cleric", + unit_composition = { + -- signature units + [UnitId.TerranMaverick] = 3, + -- healers + [UnitId.TerranCleric] = 2, + -- filler units + [UnitId.TerranEidolon] = 1, + }, + main_build_order = { + Openers.three_rax_bio, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + fast_anvil = { + name = "Raider: Fast Anvil", + unit_composition = { + -- signature units + [UnitId.TerranCyprian] = 2, + [UnitId.TerranEidolon] = 2, + -- filler units + [UnitId.TerranMaverick] = 1, + }, + main_build_order = { + Openers.fast_anvil, + Tier1.fast_anvil, + Tier2.acquire, + Tier2.raider_stockade_requeue, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + four_rax_pressure = { + name = "Raider: 4Rax Pressure", + unit_composition = { + -- healers + [UnitId.TerranShamanSupportMode] = 2, + -- filler units + [UnitId.TerranSavantEntropyGauntlets] = 1, + [UnitId.TerranCyprian] = 1, + }, + main_build_order = { + Openers.four_rax_pressure, + Tier1.four_rax_pressure, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + uprising = { + name = "Raider: Uprising", + unit_composition = { + -- signature units + [UnitId.TerranCyprian] = 3, + -- healers + [UnitId.TerranCleric] = 1.5, + -- filler units + [UnitId.TerranEidolon] = 1, + }, + main_build_order = { + Openers.stockade_quick_start, + Tier1.uprising, + Tier2.acquire, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + holy_ackmeds = { + name = "Raider: Holy Ackmeds!", + unit_composition = { + -- signature unit + [UnitId.TerranHarakan] = 3, + -- filler units + [UnitId.TerranSundog] = 1, + [UnitId.TerranSalamander] = 1, + }, + main_build_order = { + Openers.holy_ackmeds, + Tier2.acquire, + Tier2.raider_vestry_starport_rotary, + Tier3.acquire, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/zerg/assault.lua b/mpq/lua/melee/zerg/assault.lua new file mode 100644 index 00000000..9a63b0e8 --- /dev/null +++ b/mpq/lua/melee/zerg/assault.lua @@ -0,0 +1,177 @@ +local Basic = require("melee.zerg.build.basic") +local Openers = require("melee.zerg.build.openers") +local Tier1 = require("melee.zerg.build.tier1") +local Tier2 = require("melee.zerg.build.tier2") +local Tier3 = require("melee.zerg.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier1.saturated_production, + Tier3.assault_expansion, +} +local attack_wave_cost_bias = 0.6 + +return { + ---@type AIPersonality + three_base_hydrith = { + name = "Assault: 3 Base Hydrith", + unit_composition = { + -- signature units + [UnitId.ZergHydralisk] = 3, + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergUltrokor] = 2, + -- filler units + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergSovroleth] = 1, + [UnitId.ZergSkithrokor] = 1, + [UnitId.ZergBactalisk] = 1, + }, + main_build_order = { + Openers.three_base_hydrith, + Tier1.three_base_hydrith, + Tier2.hydrith_iroliris_hachirosk_zorkiz, + Tier3.hydrith_othstol_hachirosk, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + two_base_zorkiz = { + name = "Assault: 2 Base Hydrith-Zorkiz", + unit_composition = { + -- signature units + [UnitId.ZergHydralisk] = 3, + [UnitId.ZergUltrokor] = 2, + [UnitId.ZergZoryusthaleth] = 2, + -- filler units + [UnitId.ZergIzirokor] = 1, + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergSovroleth] = 1, + }, + main_build_order = { + Openers.two_base_zorkiz, + Tier1.two_base_zorkiz, + Tier2.zorkiz_iroliris_tethvith, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + two_base_hydrith_quazeth = { + name = "Assault: 2 Base Hydrith-Quazeth", + unit_composition = { + -- signature units + [UnitId.ZergHydralisk] = 3, + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergUltrokor] = 2, + -- filler units + [UnitId.ZergQuazilisk] = 1, + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.two_base_hydrith_quazeth, + Tier1.two_base_hydrith_quazeth, + Tier2.hydrith_quazeth_iroliris_vornath_kalkiir, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + two_base_alkag = { + name = "Assault: 2 Base Alkag", + unit_composition = { + -- signature units (Alkag Iteth) + [UnitId.ZergKagralisk] = 2, + [UnitId.ZergEvigrilisk] = 2, + [UnitId.ZergAlmaksalisk] = 2, + -- filler units + [UnitId.ZergSkithrokor] = 1, + [UnitId.ZergBactalisk] = 1, + }, + main_build_order = { + Openers.two_base_alkag, + Tier2.alkag_iroliris_hachirosk, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + potency = { + name = "Assault: Potency", + unit_composition = { + -- signature units + [UnitId.ZergZoryusthaleth] = 2, + [UnitId.ZergHydralisk] = 2, + [UnitId.ZergKalkalisk] = 2, + -- filler units + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergKagralisk] = 1, + [UnitId.ZergSovroleth] = 1, + [UnitId.ZergEvigrilisk] = 1, + [UnitId.ZergAlmaksalisk] = 1, + [UnitId.ZergSkithrokor] = 1, + [UnitId.ZergBactalisk] = 1, + }, + main_build_order = { + Openers.hydrith_den_start, + Tier1.hachirosk_zorkiz_skibact_quazeth_scaleup, + Tier2.potency_iroliris_hachirosk_alkag, + Tier3.potency_othstol_settle, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + earthen_tides = { + name = "Assault: Earthen Tides", + unit_composition = { + -- signature units + [UnitId.ZergIzirokor] = 2, + [UnitId.ZergLakizilisk] = 2, + [UnitId.ZergProtathalor] = 2, + -- filler units + [UnitId.ZergSovroleth] = 1, + [UnitId.ZergKeskathalor] = 1, + [UnitId.ZergSkithrokor] = 1, + [UnitId.ZergBactalisk] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.hydrith_den_start, + Tier1.hachirosk_zorkiz_skibact_quazeth_scaleup, + Tier2.earthen_iroliris_hachirosk_vornath_mutath_matravil, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/zerg/build/basic.lua b/mpq/lua/melee/zerg/build/basic.lua new file mode 100644 index 00000000..dd9bb8e1 --- /dev/null +++ b/mpq/lua/melee/zerg/build/basic.lua @@ -0,0 +1,31 @@ +local Basic = {} + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_expansion(melee_ai, town) + town:build(1, UnitId.ZergHachirosk, 120) + town:wait_build(1, UnitId.ZergHachirosk) + town:build(town.max_workers, UnitId.ZergDroleth, 100) + town:transfer_workers(8) + town:wait_build(10, UnitId.ZergDroleth) +end + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_anti_ground(melee_ai, town) + for i = 1, 3 do + town:build(i, UnitId.ZergSurkithCesiant, 80 - i * 5) + wait(secs(25 + i * 5)) + end +end + +---@param melee_ai MeleeAI +---@param town Town +function Basic.build_anti_air(melee_ai, town) + for i = 1, 3 do + town:build(i, UnitId.ZergSpraithCesiant, 80 - i * 5) + wait(secs(25 + i * 5)) + end +end + +return Basic diff --git a/mpq/lua/melee/zerg/build/openers.lua b/mpq/lua/melee/zerg/build/openers.lua new file mode 100644 index 00000000..0a246487 --- /dev/null +++ b/mpq/lua/melee/zerg/build/openers.lua @@ -0,0 +1,298 @@ +---@class ZergOpeners +local Openers = {} + +-- Shared opener still used unmodified by a personality whose own build order +-- never repositions an attack/expand trigger within this phase. + +---@param melee_ai MeleeAI +---@param town Town +function Openers.quazeth_pool_check_then_expand(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergQuazethPool, 110, true) + town:wait_build(1, UnitId.ZergQuazethPool, false) + melee_ai:start_expanding() +end + +-- Assault + +---@param melee_ai MeleeAI +---@param town Town +function Openers.three_base_hydrith(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergHydrithDen, 90, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.two_base_zorkiz(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergHydrithDen, 90, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.two_base_hydrith_quazeth(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.two_base_alkag(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.ZergHydrithDen) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergSkibactScab, 90, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTethvithSwamp, 90, true) + town:wait_build(1, UnitId.ZergIrolIris) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergAlkagIteth, 90, true) + town:build(1, UnitId.ZergOthstolOviform, 90, true) + end) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() +end + +--- Shared by Potency and Earthen Tides (identical build up to this point). +---@param melee_ai MeleeAI +---@param town Town +function Openers.hydrith_den_start(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergHydrithDen, 110, true) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() +end + +-- Swarm + +---@param melee_ai MeleeAI +---@param town Town +function Openers.three_base_flood(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergQuazethPool, 110, true) + melee_ai:dynamic_wait(secs(50)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.three_base_ultrav(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(2, UnitId.ZergHachirosk, 115) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(50)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.instant_quazeth(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergQuazethPool, 110, true) + melee_ai:dynamic_wait(secs(5)) + town:wait_build(1, UnitId.ZergQuazethPool, false) + melee_ai:dynamic_wait(secs(5)) + town:build(2, UnitId.ZergHachirosk, 115) + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:start_attacking() + wait(secs(4)) + town:build(4, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(mins(1) + secs(20)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.three_base_othstol(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(mins(1) + secs(55)) + town:build(1, UnitId.ZergQuazethPool, 90, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.gates_of_hell(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(mins(1) + secs(55)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergZorkizShroud, 90, true) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.primitive_pressure(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() +end + +-- Sunblot + +---@param melee_ai MeleeAI +---@param town Town +function Openers.big_and_little(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.the_triad(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergQuazethPool, 90, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.skittering_duo(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(50)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.early_birds(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(mins(1) + secs(25)) + town:build(1, UnitId.ZergQuazethPool, 90, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.flying_feint(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergQuazethPool, 110, true) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(secs(30)) + melee_ai:start_expanding() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.descending_horizon(melee_ai, town) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergQuazethPool, 110, true) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(mins(1) + secs(50)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(25)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(1)) +end + +-- General + +---@param melee_ai MeleeAI +---@param town Town +function Openers.sleeping_giant(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ZergQuazethPool, 90, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.shrieking_swarm(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.subjugation(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ZergDroleth, 100) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergHydrithDen, 90, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.before_the_storm(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergQuazethPool, 110, true) + town:wait_build(1, UnitId.ZergQuazethPool, false) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergDroleth, 100) + melee_ai:dynamic_wait(secs(50)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergZorkizShroud, 90, true) + melee_ai:dynamic_wait(secs(15)) + melee_ai:start_attacking() + melee_ai:dynamic_wait(mins(2) + secs(30)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Openers.time_after_time(melee_ai, town) + melee_ai:dynamic_wait(secs(45)) + melee_ai:start_expanding() + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(40)) + melee_ai:start_attacking() +end + +return Openers diff --git a/mpq/lua/melee/zerg/build/tier1.lua b/mpq/lua/melee/zerg/build/tier1.lua new file mode 100644 index 00000000..7094d36c --- /dev/null +++ b/mpq/lua/melee/zerg/build/tier1.lua @@ -0,0 +1,229 @@ +---@class ZergTier1 +local Tier1 = {} + +--- Saturated hatchery/larva production ramp, reused by every category's main build order. +---@param melee_ai MeleeAI +---@param town Town +function Tier1.saturated_production(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.ZergIrolIris) + town:build(5, UnitId.ZergLarvoskCesiant, 90) + melee_ai:dynamic_wait(secs(45)) + town:build(6, UnitId.ZergHachirosk, 90) + melee_ai:dynamic_wait(secs(45)) + town:build(6, UnitId.ZergCaphrolosk, 90) + town:wait_build(1, UnitId.ZergOthstolOviform) + melee_ai:dynamic_wait(secs(45)) + town:build(6, UnitId.ZergGathtelosk, 50) + end) +end + +-- Assault (scale-up after the opener; unlisted personalities fold entirely +-- into their opener and skip tier1) + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.three_base_hydrith(melee_ai, town) + melee_ai:dynamic_wait(mins(1) + secs(30)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergSkibactScab, 90, true) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.two_base_zorkiz(melee_ai, town) + melee_ai:dynamic_wait(secs(55)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.two_base_hydrith_quazeth(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(50)) +end + +--- Shared by Potency and Earthen Tides (identical build up to this point). +---@param melee_ai MeleeAI +---@param town Town +function Tier1.hachirosk_zorkiz_skibact_quazeth_scaleup(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergZorkizShroud, 90, true) + melee_ai:dynamic_wait(mins(1) + secs(30)) + town:build(1, UnitId.ZergSkibactScab, 90, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(50)) +end + +-- Swarm + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.three_base_flood(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.instant_quazeth(melee_ai, town) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(mins(1) + secs(55)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(mins(2) + secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.three_base_othstol(melee_ai, town) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.gates_of_hell(melee_ai, town) + melee_ai:dynamic_wait(secs(55)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(20)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.primitive_pressure(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(mins(1) + secs(30)) + town:build(1, UnitId.ZergZorkizShroud, 90, true) + melee_ai:dynamic_wait(secs(50)) +end + +-- Sunblot + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.big_and_little(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ZergZorkizShroud, 90, true) + melee_ai:dynamic_wait(mins(1) + secs(55)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.the_triad(melee_ai, town) + melee_ai:dynamic_wait(mins(1) + secs(10)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(45)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergZorkizShroud, 90, true) + melee_ai:dynamic_wait(mins(1)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.skittering_duo(melee_ai, town) + melee_ai:dynamic_wait(secs(75)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergSkibactScab, 90, true) + melee_ai:dynamic_wait(mins(2) + secs(20)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.early_birds(melee_ai, town) + melee_ai:dynamic_wait(secs(55)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.flying_feint(melee_ai, town) + melee_ai:dynamic_wait(mins(1)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(40)) +end + +-- General + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.sleeping_giant(melee_ai, town) + melee_ai:dynamic_wait(mins(1)) + town:wait_build(15, UnitId.ZergDroleth) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergVornathPond, 90, true) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.shrieking_swarm(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.subjugation(melee_ai, town) + melee_ai:dynamic_wait(secs(15)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergSkibactScab, 90, true) + melee_ai:dynamic_wait(secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.time_after_time(melee_ai, town) + melee_ai:dynamic_wait(secs(75)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(secs(75)) + town:build(1, UnitId.ZergSkibactScab, 90, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergZorkizShroud, 90, true) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(50)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier1.coil_buildup(melee_ai, town) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergDroleth, 100) + wait(secs(1)) + town:build(1, UnitId.ZergQuazethPool, 90, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergHydrithDen, 90, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergVornathPond, 90, true) + melee_ai:dynamic_wait(mins(1)) +end + +return Tier1 diff --git a/mpq/lua/melee/zerg/build/tier2.lua b/mpq/lua/melee/zerg/build/tier2.lua new file mode 100644 index 00000000..9f8283b7 --- /dev/null +++ b/mpq/lua/melee/zerg/build/tier2.lua @@ -0,0 +1,512 @@ +---@class ZergTier2 +local Tier2 = {} + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.hydrith_iroliris_hachirosk_zorkiz(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.zorkiz_iroliris_tethvith(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(secs(20) + secs(15)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + town:build(1, UnitId.ZergCaphrolosk, 70) + melee_ai:dynamic_wait(secs(45)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.hydrith_quazeth_iroliris_vornath_kalkiir(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + end) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.alkag_iroliris_hachirosk(melee_ai, town) + melee_ai:dynamic_wait(secs(55)) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(mins(1) + secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.potency_iroliris_hachirosk_alkag(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.ZergHydrithDen) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergAlkagIteth, 70, true) + end) + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(mins(1) + secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.earthen_iroliris_hachirosk_vornath_mutath_matravil(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + town:build(1, UnitId.ZergMutathSpire, 70, true) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + end) + melee_ai:dynamic_wait(mins(2) + secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.flood_vornath_kalkiir_mutath_tosvrol_then_iroliris_caphrolosk_hachirosk(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + town:wait_build(3, UnitId.ZergHachirosk, false) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergMutathSpire, 70, true) + town:wait_build(1, UnitId.ZergMutathSpire) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + end) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + wait(secs(1)) + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergCaphrolosk, 70) + melee_ai:dynamic_wait(mins(1) + secs(20)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.ultrav_iroliris_caphrolosk_buildup(melee_ai, town) + town:multirun(function() + wait(secs(1)) + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(55)) + town:build(1, UnitId.ZergCaphrolosk, 70) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + town:wait_build(1, UnitId.ZergZorkizShroud) + town:wait_build(2, UnitId.ZergHachirosk, false) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:wait_build(1, UnitId.ZergHydrithDen) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + town:wait_build(1, UnitId.ZergOthstolOviform) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergGathtelosk, 50) + end) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(45)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergHydrithDen, 70, true) + melee_ai:dynamic_wait(secs(50)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.instant_iroliris_vornath_kalkiir(melee_ai, town) + town:multirun(function() + wait(secs(1)) + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(3) + secs(20)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + end) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.othstol_iroliris_buildup(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:wait_build(1, UnitId.ZergHydrithDen) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:wait_build(1, UnitId.ZergZorkizShroud) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergZozarcGorge, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + town:wait_build(1, UnitId.ZergOthstolOviform) + town:build(1, UnitId.ZergGathtelosk, 50) + town:wait_build(1, UnitId.ZergGathtelosk, false) + town:wait_build(2, UnitId.ZergHachirosk, false) + end) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergHydrithDen, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(5)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.gates_buildup_vornath_hachirosk_iroliris(melee_ai, town) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:wait_build(1, UnitId.ZergHydrithDen) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:wait_build(1, UnitId.ZergZorkizShroud) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergZozarcGorge, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + melee_ai:dynamic_wait(mins(8) + secs(20)) + town:multirun(function() + wait(secs(1)) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + end) + end) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(15)) + town:multirun(function() + wait(secs(1)) + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(30)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.pressure_iroliris_vornath_skibact_tethvith(melee_ai, town) + town:multirun(function() + wait(secs(1)) + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(1, UnitId.ZergHydrithDen) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + end) + melee_ai:dynamic_wait(secs(40)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.big_little_iroliris_buildup_caphrolosk_hachirosk(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + wait(secs(1)) + town:multirun(function() + wait(secs(1)) + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + town:wait_build(1, UnitId.ZergZorkizShroud) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + town:build(1, UnitId.ZergMutathSpire, 70, true) + town:wait_build(1, UnitId.ZergMutathSpire) + melee_ai:dynamic_wait(mins(3) + secs(20)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + end) + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(1, UnitId.ZergCaphrolosk, 70) + melee_ai:dynamic_wait(mins(1) + secs(45)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(30)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.birds_iroliris_alkag(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergMutathSpire, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergAlkagIteth, 70, true) + end) + melee_ai:dynamic_wait(mins(1) + secs(30)) + town:build(2, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(mins(1) + secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.feint_iroliris_buildup_vornath_hydrith_skibact(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(3) + secs(20)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(1, UnitId.ZergHydrithDen) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergMutathSpire, 70, true) + end) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergHydrithDen, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergSkibactScab, 70, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.horizon_iroliris_buildup(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + town:multirun(function() + town:wait_build(1, UnitId.ZergHydrithDen) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(1, UnitId.ZergQuazethPool) + town:build(1, UnitId.ZergVornathPond, 70, true) + town:wait_build(1, UnitId.ZergVornathPond) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:wait_build(1, UnitId.ZergIrolIris) + town:build(1, UnitId.ZergMutathSpire, 70, true) + town:wait_build(1, UnitId.ZergMutathSpire) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + end) + melee_ai:dynamic_wait(mins(2)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.giant_iroliris_caphrolosk_hachirosk_zorkiz_buildup(melee_ai, town) + town:multirun(function() + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(15) + secs(5)) + town:wait_build(1, UnitId.ZergIrolIris) + town:build(1, UnitId.ZergCaphrolosk, 70) + melee_ai:dynamic_wait(secs(30)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + town:build(1, UnitId.ZergMatravilNest, 70, true) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergMutathSpire, 70, true) + melee_ai:dynamic_wait(mins(2) + secs(45)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + end) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.swarm_iroliris_vornathkalkiir_zorkiz_caphrolosk_hachirosk(melee_ai, town) + town:multirun(function() + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + end) + melee_ai:dynamic_wait(mins(1)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergCaphrolosk, 70) + melee_ai:dynamic_wait(secs(30)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(15)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.subjugation_quazeth_iroliris_buildup(melee_ai, town) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ZergQuazethPool, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:multirun(function() + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(50)) + town:multirun(function() + town:wait_build(1, UnitId.ZergHydrithDen) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergSkibactScab, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + town:wait_build(1, UnitId.ZergIrolIris) + town:build(1, UnitId.ZergMatravilNest, 70, true) + end) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergCaphrolosk, 70) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(45)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.storm_iroliris_hachirosk_vornathkalkiir(melee_ai, town) + town:multirun(function() + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(15)) + town:build(3, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(15)) + town:build(4, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(15)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(secs(30)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + end) + melee_ai:dynamic_wait(secs(30)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.time_iroliris_vornathkalkiir(melee_ai, town) + town:multirun(function() + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(15)) + town:multirun(function() + town:wait_build(1, UnitId.ZergQuazethPool) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergVornathPond, 70, true) + melee_ai:dynamic_wait(mins(1) + secs(40)) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + end) + melee_ai:dynamic_wait(secs(30)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier2.coil_iroliris_hachirosk(melee_ai, town) + town:multirun(function() + town:build(1, UnitId.ZergIrolIris, 70, true) + end) + melee_ai:dynamic_wait(secs(30) + mins(1) + secs(5)) + town:build(2, UnitId.ZergHachirosk, 115) +end + +return Tier2 diff --git a/mpq/lua/melee/zerg/build/tier3.lua b/mpq/lua/melee/zerg/build/tier3.lua new file mode 100644 index 00000000..4282ad42 --- /dev/null +++ b/mpq/lua/melee/zerg/build/tier3.lua @@ -0,0 +1,168 @@ +---@class ZergTier3 +local Tier3 = {} + +--- Requests Othstol Oviform and waits for it. The generic tier-3 tech +--- transition, for personalities without bespoke tier-3 content. +---@param melee_ai MeleeAI +---@param town Town +function Tier3.acquire(melee_ai, town) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.hydrith_othstol_hachirosk(melee_ai, town) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + melee_ai:dynamic_wait(secs(25)) + town:build(4, UnitId.ZergHachirosk, 115) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.potency_othstol_settle(melee_ai, town) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.horizon_othstol(melee_ai, town) + town:multirun(function() + melee_ai:dynamic_wait(mins(1) + secs(15)) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + end) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.giant_othstol_gathtelosk(melee_ai, town) + town:multirun(function() + town:build(1, UnitId.ZergOthstolOviform, 65, true) + end) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergGathtelosk, 50) + melee_ai:dynamic_wait(secs(15)) +end + +-- Category expansion tails: reuse the same shared node-acquisition pattern per +-- category, run once a new town is expanded into. + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.assault_expansion(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergCaphrolosk, 70) + town:build(1, UnitId.ZergQuazethPool, 70, true) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergHachirosk, 70) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergHydrithDen, 70, true) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergMutathSpire, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergGathtelosk, 50) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergAlmakisAntre, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergGeszkathGrotto, 50, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.swarm_expansion(melee_ai, town) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergIrolIris, 70, true) + town:build(1, UnitId.ZergMutathSpire, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergHydrithDen, 70, true) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + town:build(1, UnitId.ZergGathtelosk, 50) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergAlmakisAntre, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergGeszkathGrotto, 50, true) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.sunblot_expansion(melee_ai, town) + town:build(1, UnitId.ZergIrolIris, 70, true) + town:build(1, UnitId.ZergQuazethPool, 70, true) + town:build(1, UnitId.ZergMutathSpire, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergHydrithDen, 70, true) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergAlmakisAntre, 70, true) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergGathtelosk, 50) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergGeszkathGrotto, 50, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + melee_ai:dynamic_wait(secs(25)) +end + +---@param melee_ai MeleeAI +---@param town Town +function Tier3.general_expansion(melee_ai, town) + town:build(1, UnitId.ZergHydrithDen, 70, true) + melee_ai:dynamic_wait(secs(20)) + town:build(1, UnitId.ZergQuazethPool, 70, true) + town:build(1, UnitId.ZergKalkiirLake, 70, true) + town:build(1, UnitId.ZergIrolIris, 70, true) + melee_ai:dynamic_wait(secs(5)) + town:build(1, UnitId.ZergMutathSpire, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergZorkizShroud, 70, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergTethvithSwamp, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergOthstolOviform, 65, true) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergGathtelosk, 50) + melee_ai:dynamic_wait(secs(15)) + town:build(1, UnitId.ZergUlgorgCavern, 70, true) + melee_ai:dynamic_wait(secs(40)) + town:build(1, UnitId.ZergTosvrolHaunt, 70, true) + melee_ai:dynamic_wait(secs(25)) + town:build(1, UnitId.ZergMatravilNest, 70, true) + town:build(1, UnitId.ZergAlmakisAntre, 70, true) + melee_ai:dynamic_wait(secs(50)) + town:build(1, UnitId.ZergGeszkathGrotto, 50, true) +end + +return Tier3 diff --git a/mpq/lua/melee/zerg/general.lua b/mpq/lua/melee/zerg/general.lua new file mode 100644 index 00000000..24e17891 --- /dev/null +++ b/mpq/lua/melee/zerg/general.lua @@ -0,0 +1,176 @@ +local Basic = require("melee.zerg.build.basic") +local Openers = require("melee.zerg.build.openers") +local Tier1 = require("melee.zerg.build.tier1") +local Tier2 = require("melee.zerg.build.tier2") +local Tier3 = require("melee.zerg.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier1.saturated_production, + Tier3.general_expansion, +} +local attack_wave_cost_bias = 0.5 + +return { + ---@type AIPersonality + sleeping_giant = { + name = "General: Sleeping Giant", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergUltrokor] = 2, + [UnitId.ZergHydralisk] = 2, + -- filler units + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.sleeping_giant, + Tier1.sleeping_giant, + Tier2.giant_iroliris_caphrolosk_hachirosk_zorkiz_buildup, + Tier3.giant_othstol_gathtelosk, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + shrieking_swarm = { + name = "General: Shrieking Swarm", + unit_composition = { + -- signature units (Vornath Pond) + [UnitId.ZergVorvrokor] = 2, + [UnitId.ZergNathrokor] = 2, + -- filler units + [UnitId.ZergUltrokor] = 1, + [UnitId.ZergHydralisk] = 1, + [UnitId.ZergLakizilisk] = 1, + [UnitId.ZergSovroleth] = 1, + }, + main_build_order = { + Openers.shrieking_swarm, + Tier1.shrieking_swarm, + Tier2.swarm_iroliris_vornathkalkiir_zorkiz_caphrolosk_hachirosk, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + subjugation = { + name = "General: Subjugation", + unit_composition = { + -- signature units (Skibact Scab) + [UnitId.ZergSkithrokor] = 2, + [UnitId.ZergBactalisk] = 2, + [UnitId.ZergZethrokor] = 2, + -- filler units + [UnitId.ZergKeskathalor] = 1, + [UnitId.ZergHydralisk] = 1, + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergVithrilisk] = 1, + }, + main_build_order = { + Openers.subjugation, + Tier1.subjugation, + Tier2.subjugation_quazeth_iroliris_buildup, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + before_the_storm = { + name = "General: Before the Storm", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergKalkalisk] = 2, + -- filler units + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.before_the_storm, + Tier2.storm_iroliris_hachirosk_vornathkalkiir, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + time_after_time = { + name = "General: Time After Time", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergSkithrokor] = 2, + [UnitId.ZergLakizilisk] = 2, + -- filler units + [UnitId.ZergLiiralisk] = 1, + [UnitId.ZergCikralisk] = 1, + [UnitId.ZergBactalisk] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.time_after_time, + Tier1.time_after_time, + Tier2.time_iroliris_vornathkalkiir, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + constrained_coil = { + name = "General: Constrained Coil", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergKalkalisk] = 2, + -- filler units + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.quazeth_pool_check_then_expand, + Tier1.coil_buildup, + Tier2.coil_iroliris_hachirosk, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/zerg/sunblot.lua b/mpq/lua/melee/zerg/sunblot.lua new file mode 100644 index 00000000..6aa99082 --- /dev/null +++ b/mpq/lua/melee/zerg/sunblot.lua @@ -0,0 +1,174 @@ +local Basic = require("melee.zerg.build.basic") +local Openers = require("melee.zerg.build.openers") +local Tier1 = require("melee.zerg.build.tier1") +local Tier2 = require("melee.zerg.build.tier2") +local Tier3 = require("melee.zerg.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier1.saturated_production, + Tier3.sunblot_expansion, +} +local attack_wave_cost_bias = 0.6 + +return { + ---@type AIPersonality + big_and_little = { + name = "Sunblot: Big and Little", + unit_composition = { + -- signature units + [UnitId.ZergNathrokor] = 2, + [UnitId.ZergMutalisk] = 2, + [UnitId.ZergGorgrokor] = 2, + -- filler units + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergVilgorokor] = 1, + [UnitId.ZergSovroleth] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.big_and_little, + Tier1.big_and_little, + Tier2.big_little_iroliris_buildup_caphrolosk_hachirosk, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + the_triad = { + name = "Sunblot: The Triad", + unit_composition = { + -- signature units + [UnitId.ZergNathrokor] = 2, + [UnitId.ZergGorgrokor] = 2, + [UnitId.ZergKalkalisk] = 2, + -- filler units + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergIsthrathaleth] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.the_triad, + Tier1.the_triad, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + skittering_duo = { + name = "Sunblot: Skittering Duo", + unit_composition = { + -- signature units (Skibact Scab) + [UnitId.ZergSkithrokor] = 2, + [UnitId.ZergBactalisk] = 2, + [UnitId.ZergNathrokor] = 2, + -- filler units + [UnitId.ZergGorgrokor] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.skittering_duo, + Tier1.skittering_duo, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + early_birds = { + name = "Sunblot: Early Birds", + unit_composition = { + -- signature units (Alkag Iteth) + [UnitId.ZergKagralisk] = 2, + [UnitId.ZergEvigrilisk] = 2, + [UnitId.ZergAlmaksalisk] = 2, + -- filler units + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergGorgrokor] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.early_birds, + Tier1.early_birds, + Tier2.birds_iroliris_alkag, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + flying_feint = { + name = "Sunblot: Flying Feint", + unit_composition = { + -- signature units + [UnitId.ZergNathrokor] = 2, + [UnitId.ZergSkithrokor] = 2, + -- filler units + [UnitId.ZergGorgrokor] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergBactalisk] = 1, + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.flying_feint, + Tier1.flying_feint, + Tier2.feint_iroliris_buildup_vornath_hydrith_skibact, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + descending_horizon = { + name = "Sunblot: Descending Horizon", + unit_composition = { + -- signature units (Skibact Scab) + [UnitId.ZergSkithrokor] = 2, + [UnitId.ZergBactalisk] = 2, + -- filler units + [UnitId.ZergGorgrokor] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergGeszithalor] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.descending_horizon, + Tier2.horizon_iroliris_buildup, + Tier3.horizon_othstol, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/melee/zerg/swarm.lua b/mpq/lua/melee/zerg/swarm.lua new file mode 100644 index 00000000..dd651845 --- /dev/null +++ b/mpq/lua/melee/zerg/swarm.lua @@ -0,0 +1,185 @@ +local Basic = require("melee.zerg.build.basic") +local Openers = require("melee.zerg.build.openers") +local Tier1 = require("melee.zerg.build.tier1") +local Tier2 = require("melee.zerg.build.tier2") +local Tier3 = require("melee.zerg.build.tier3") + +local expansion_build_order = { + Basic.build_expansion, + Tier1.saturated_production, + Tier3.swarm_expansion, +} +local attack_wave_cost_bias = 0.4 + +return { + ---@type AIPersonality + three_base_flood = { + name = "Swarm: 3 Base Flood", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergUltrokor] = 2, + [UnitId.ZergHydralisk] = 2, + -- filler units + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.three_base_flood, + Tier1.three_base_flood, + Tier2.flood_vornath_kalkiir_mutath_tosvrol_then_iroliris_caphrolosk_hachirosk, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + three_base_ultrav = { + name = "Swarm: 3 Base Ultrav", + unit_composition = { + -- signature units + [UnitId.ZergUltrokor] = 3, + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergHydralisk] = 2, + -- filler units + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergMutalisk] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + [UnitId.ZergSkithrokor] = 1, + [UnitId.ZergBactalisk] = 1, + }, + main_build_order = { + Openers.three_base_ultrav, + Tier2.ultrav_iroliris_caphrolosk_buildup, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + instant_quazeth = { + name = "Swarm: Instant Quazeth", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 3, + [UnitId.ZergUltrokor] = 2, + [UnitId.ZergHydralisk] = 2, + -- filler units + [UnitId.ZergQuazilisk] = 1, + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergNathrokor] = 1, + [UnitId.ZergVorvrokor] = 1, + }, + main_build_order = { + Openers.instant_quazeth, + Tier1.instant_quazeth, + Tier2.instant_iroliris_vornath_kalkiir, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + three_base_othstol = { + name = "Swarm: 3 Base Othstol", + unit_composition = { + -- signature units + [UnitId.ZergIzirokor] = 2, + [UnitId.ZergUltrokor] = 2, + [UnitId.ZergHydralisk] = 2, + -- filler units + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergZoryusthaleth] = 1, + [UnitId.ZergGorgrokor] = 1, + [UnitId.ZergSkithrokor] = 1, + [UnitId.ZergBactalisk] = 1, + [UnitId.ZergZobriolisk] = 1, + [UnitId.ZergZarcavrokor] = 1, + }, + main_build_order = { + Openers.three_base_othstol, + Tier1.three_base_othstol, + Tier2.othstol_iroliris_buildup, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + gates_of_hell = { + name = "Swarm: Gates of Hell", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergKeskathalor] = 2, + [UnitId.ZergHydralisk] = 2, + -- filler units + [UnitId.ZergKalkalisk] = 1, + [UnitId.ZergSovroleth] = 1, + [UnitId.ZergGorgrokor] = 1, + [UnitId.ZergSkithrokor] = 1, + [UnitId.ZergBactalisk] = 1, + }, + main_build_order = { + Openers.gates_of_hell, + Tier1.gates_of_hell, + Tier2.gates_buildup_vornath_hachirosk_iroliris, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, + + ---@type AIPersonality + primitive_pressure = { + name = "Swarm: Primitive Pressure", + unit_composition = { + -- signature units + [UnitId.ZergZethrokor] = 2, + [UnitId.ZergHydralisk] = 2, + [UnitId.ZergZoryusthaleth] = 2, + -- filler units + [UnitId.ZergLiiralisk] = 1, + [UnitId.ZergProtathalor] = 1, + [UnitId.ZergMatraleth] = 1, + [UnitId.ZergSovroleth] = 1, + [UnitId.ZergKeskathalor] = 1, + }, + main_build_order = { + Openers.primitive_pressure, + Tier1.primitive_pressure, + Tier2.pressure_iroliris_vornath_skibact_tethvith, + Tier3.acquire, + Tier1.saturated_production, + }, + expansion_build_order = expansion_build_order, + build_anti_ground_defenses = Basic.build_anti_ground, + build_anti_air_defenses = Basic.build_anti_air, + attack_wave_cost_bias = attack_wave_cost_bias, + }, +} diff --git a/mpq/lua/start_melee_ai.lua b/mpq/lua/start_melee_ai.lua new file mode 100644 index 00000000..61858569 --- /dev/null +++ b/mpq/lua/start_melee_ai.lua @@ -0,0 +1,10 @@ +require "melee.melee_ai" + +local player = Player() +if player.type == PlayerType.Computer then + if player.race == RaceId.Askosi then + cprint(TextColor.Orange, "Player " .. player.name .. "(P" .. player.id .. "): Askosi AI is not supported!") + return + end + MeleeAI():run() +end diff --git a/mpq/scripts/aiscript.bin b/mpq/scripts/aiscript.bin index c98730f0..8d268770 100644 Binary files a/mpq/scripts/aiscript.bin and b/mpq/scripts/aiscript.bin differ diff --git a/patchnotes.md b/patchnotes.md index d7216e88..b098fb5c 100644 --- a/patchnotes.md +++ b/patchnotes.md @@ -10,6 +10,13 @@ - **Triggers:** - New custom trigger subcommand for **set_unit_stat** - **transport_capacity** (b. Lucifirius) -- enables transporting via any unit, modifies transport capacity on any transport +- **User-defined resource areas:** (c. DF) + - Our methodology for user-defined resource areas has changed from the previous system! + - **The location-based system has been removed.** + - To use the new system: + - Select the resource nodes you wish to assign to a specific resarea + - Tick **burrowed** (bottom-left-most status flag) in the unit property panel + - Set the **HP** field to the intended resarea id (a number between 1 and 255) ## Bugfixes - **Gameplay:** diff --git a/plugins/gptp.qdp b/plugins/gptp.qdp index 34c96b51..1e6ab526 100644 Binary files a/plugins/gptp.qdp and b/plugins/gptp.qdp differ