diff --git a/mods/d2k/maps/harkonnen-02a/harkonnen02a-AI.lua b/mods/d2k/maps/harkonnen-02a/harkonnen02a-AI.lua new file mode 100644 index 0000000000..f2447351eb --- /dev/null +++ b/mods/d2k/maps/harkonnen-02a/harkonnen02a-AI.lua @@ -0,0 +1,126 @@ +IdlingUnits = { } + +AttackGroupSize = +{ + easy = 6, + normal = 8, + hard = 10 +} +AttackDelays = +{ + easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, + normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, + hard = { DateTime.Seconds(1), DateTime.Seconds(5) } +} + +AtreidesInfantryTypes = { "light_inf" } + +AttackOnGoing = false +HoldProduction = false +HarvesterKilled = true + +IdleHunt = function(unit) if not unit.IsDead then Trigger.OnIdle(unit, unit.Hunt) end end + +SetupAttackGroup = function() + local units = { } + + for i = 0, AttackGroupSize[Map.LobbyOption("difficulty")], 1 do + if #IdlingUnits == 0 then + return units + end + + local number = Utils.RandomInteger(1, #IdlingUnits + 1) + + if IdlingUnits[number] and not IdlingUnits[number].IsDead then + units[i] = IdlingUnits[number] + table.remove(IdlingUnits, number) + end + end + + return units +end + +SendAttack = function() + if Attacking then + return + end + Attacking = true + HoldProduction = true + + local units = SetupAttackGroup() + Utils.Do(units, function(unit) + IdleHunt(unit) + end) + + Trigger.OnAllRemovedFromWorld(units, function() + Attacking = false + HoldProduction = false + end) +end + +DefendActor = function(unit) + Trigger.OnDamaged(unit, function(self, attacker) + if AttackOnGoing then + return + end + AttackOnGoing = true + + local Guards = SetupAttackGroup() + + if #Guards <= 0 then + AttackOnGoing = false + return + end + + Utils.Do(Guards, function(unit) + if not self.IsDead then + unit.AttackMove(self.Location) + end + IdleHunt(unit) + end) + + Trigger.OnAllRemovedFromWorld(Guards, function() AttackOnGoing = false end) + end) +end + +InitAIUnits = function() + Utils.Do(AtreidesBase, function(actor) + DefendActor(actor) + Trigger.OnDamaged(actor, function(building) + if building.Health < building.MaxHealth * 3/4 then + building.StartBuildingRepairs() + end + end) + end) +end + +ProduceInfantry = function() + if ABarracks.IsDead then + return + end + + if HoldProduction then + Trigger.AfterDelay(DateTime.Minutes(1), ProduceInfantry) + return + end + + local delay = Utils.RandomInteger(AttackDelays[Map.LobbyOption("difficulty")][1], AttackDelays[Map.LobbyOption("difficulty")][2] + 1) + local toBuild = { Utils.Random(AtreidesInfantryTypes) } + atreides.Build(toBuild, function(unit) + IdlingUnits[#IdlingUnits + 1] = unit[1] + Trigger.AfterDelay(delay, ProduceInfantry) + + if #IdlingUnits >= (AttackGroupSize[Map.LobbyOption("difficulty")] * 2.5) then + SendAttack() + end + end) +end + +ActivateAI = function() + Trigger.AfterDelay(0, InitAIUnits) + + -- Finish the upgrades first before trying to build something + Trigger.AfterDelay(DateTime.Seconds(14), function() + ProduceInfantry() + end) +end diff --git a/mods/d2k/maps/harkonnen-02a/harkonnen02a.lua b/mods/d2k/maps/harkonnen-02a/harkonnen02a.lua new file mode 100644 index 0000000000..1eaf553409 --- /dev/null +++ b/mods/d2k/maps/harkonnen-02a/harkonnen02a.lua @@ -0,0 +1,125 @@ + +AtreidesBase = { AConyard, APower1, APower2, ABarracks, AOutpost } + +AtreidesReinforcements = { } +AtreidesReinforcements["easy"] = +{ + { "light_inf", "trike" }, + { "light_inf", "trike" }, + { "light_inf", "light_inf", "light_inf", "trike", "trike" } +} + +AtreidesReinforcements["normal"] = +{ + { "light_inf", "trike" }, + { "light_inf", "trike" }, + { "light_inf", "light_inf", "light_inf", "trike", "trike" }, + { "light_inf", "light_inf" }, + { "light_inf", "light_inf", "light_inf" }, + { "light_inf", "trike" }, +} + +AtreidesReinforcements["hard"] = +{ + { "trike", "trike" }, + { "light_inf", "trike" }, + { "light_inf", "trike" }, + { "light_inf", "light_inf", "light_inf", "trike", "trike" }, + { "light_inf", "light_inf" }, + { "trike", "trike" }, + { "light_inf", "light_inf", "light_inf" }, + { "light_inf", "trike" }, + { "trike", "trike" } +} + +AtreidesAttackPaths = +{ + { AtreidesEntry1.Location, AtreidesRally1.Location }, + { AtreidesEntry1.Location, AtreidesRally4.Location }, + { AtreidesEntry2.Location, AtreidesRally2.Location }, + { AtreidesEntry2.Location, AtreidesRally3.Location } +} + +AtreidesAttackDelay = { } +AtreidesAttackDelay["easy"] = DateTime.Minutes(5) +AtreidesAttackDelay["normal"] = DateTime.Minutes(2) + DateTime.Seconds(40) +AtreidesAttackDelay["hard"] = DateTime.Minutes(1) + DateTime.Seconds(20) + +AtreidesAttackWaves = { } +AtreidesAttackWaves["easy"] = 3 +AtreidesAttackWaves["normal"] = 6 +AtreidesAttackWaves["hard"] = 9 + +wave = 0 +SendAtreides = function() + Trigger.AfterDelay(AtreidesAttackDelay[Map.LobbyOption("difficulty")], function() + wave = wave + 1 + if wave > AtreidesAttackWaves[Map.LobbyOption("difficulty")] then + return + end + + local path = Utils.Random(AtreidesAttackPaths) + local units = Reinforcements.ReinforceWithTransport(atreides, "carryall.reinforce", AtreidesReinforcements[Map.LobbyOption("difficulty")][wave], path, { path[1] })[2] + Utils.Do(units, IdleHunt) + + SendAtreides() + end) +end + +IdleHunt = function(unit) + Trigger.OnIdle(unit, unit.Hunt) +end + +Tick = function() + if player.HasNoRequiredUnits() then + atreides.MarkCompletedObjective(KillHarkonnen) + end + + if atreides.HasNoRequiredUnits() and not player.IsObjectiveCompleted(KillAtreides) then + Media.DisplayMessage("The Atreides have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillAtreides) + end +end + +WorldLoaded = function() + atreides = Player.GetPlayer("Atreides") + player = Player.GetPlayer("Harkonnen") + + InitObjectives() + + Camera.Position = HConyard.CenterPosition + + Trigger.OnAllKilled(AtreidesBase, function() + Utils.Do(atreides.GetGroundAttackers(), IdleHunt) + end) + + SendAtreides() + ActivateAI() +end + +InitObjectives = function() + Trigger.OnObjectiveAdded(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective") + end) + + KillHarkonnen = atreides.AddPrimaryObjective("Kill all Harkonnen units.") + KillAtreides = player.AddPrimaryObjective("Destroy all Atreides forces.") + + Trigger.OnObjectiveCompleted(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "Objective completed") + end) + Trigger.OnObjectiveFailed(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "Objective failed") + end) + + Trigger.OnPlayerLost(player, function() + Trigger.AfterDelay(DateTime.Seconds(1), function() + Media.PlaySpeechNotification(player, "Lose") + end) + end) + Trigger.OnPlayerWon(player, function() + Trigger.AfterDelay(DateTime.Seconds(1), function() + Media.PlaySpeechNotification(player, "Win") + end) + end) +end diff --git a/mods/d2k/maps/harkonnen-02a/map.bin b/mods/d2k/maps/harkonnen-02a/map.bin new file mode 100644 index 0000000000..37f5e8252f Binary files /dev/null and b/mods/d2k/maps/harkonnen-02a/map.bin differ diff --git a/mods/d2k/maps/harkonnen-02a/map.png b/mods/d2k/maps/harkonnen-02a/map.png new file mode 100644 index 0000000000..7ebbf1b695 Binary files /dev/null and b/mods/d2k/maps/harkonnen-02a/map.png differ diff --git a/mods/d2k/maps/harkonnen-02a/map.yaml b/mods/d2k/maps/harkonnen-02a/map.yaml new file mode 100644 index 0000000000..5c22a78b4e --- /dev/null +++ b/mods/d2k/maps/harkonnen-02a/map.yaml @@ -0,0 +1,165 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Harkonnen 02a + +Author: Westwood Studios + +Tileset: ARRAKIS + +MapSize: 52,52 + +Bounds: 2,2,48,48 + +Visibility: MissionSelector + +Categories: Campaign + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Enemies: Harkonnen, Atreides + PlayerReference@Harkonnen: + Name: Harkonnen + Playable: True + LockFaction: True + Faction: harkonnen + LockColor: True + Color: FE0000 + Enemies: Atreides, Creeps + PlayerReference@Atreides: + Name: Atreides + LockFaction: True + Faction: atreides + LockColor: True + Color: 9191FF + Enemies: Harkonnen + +Actors: + Actor0: light_inf + Location: 39,2 + Owner: Atreides + APower1: wind_trap + Location: 7,3 + Owner: Atreides + Actor2: light_inf + Location: 9,3 + Owner: Atreides + AConyard: construction_yard + Location: 3,4 + Owner: Atreides + Actor4: spicebloom.spawnpoint + Location: 38,4 + Owner: Neutral + Actor5: light_inf + Location: 11,5 + Owner: Atreides + ABarracks: barracks + Location: 9,7 + Owner: Atreides + Actor7: trike + Location: 12,7 + Owner: Atreides + AOutpost: outpost + Location: 5,8 + Owner: Atreides + Actor9: trike + Location: 8,9 + Owner: Atreides + Actor10: light_inf + Location: 12,9 + Owner: Atreides + APower2: wind_trap + Location: 2,10 + Owner: Atreides + Actor12: trike + Location: 11,12 + Owner: Atreides + Actor13: light_inf + Location: 42,13 + Owner: Harkonnen + Actor14: trike + Location: 46,13 + Owner: Harkonnen + Actor15: light_inf + Location: 8,14 + Owner: Atreides + Actor16: light_inf + Location: 4,15 + Owner: Atreides + Actor17: trike + Location: 6,15 + Owner: Atreides + Actor18: light_inf + Location: 41,15 + Owner: Harkonnen + HConyard: construction_yard + Location: 44,15 + Owner: Harkonnen + Actor20: light_inf + Location: 49,15 + Owner: Harkonnen + Actor21: trike + Location: 43,18 + Owner: Harkonnen + Actor22: light_inf + Location: 47,18 + Owner: Harkonnen + Actor23: light_inf + Location: 22,22 + Owner: Atreides + Actor24: spicebloom.spawnpoint + Location: 44,22 + Owner: Neutral + Actor25: light_inf + Location: 33,23 + Owner: Atreides + Actor26: light_inf + Location: 8,32 + Owner: Atreides + Actor27: spicebloom.spawnpoint + Location: 6,33 + Owner: Neutral + Actor28: light_inf + Location: 21,33 + Owner: Atreides + Actor29: wormspawner + Location: 29,34 + Owner: Creeps + Actor30: spicebloom.spawnpoint + Location: 35,37 + Owner: Neutral + Actor31: light_inf + Location: 46,40 + Owner: Atreides + Actor32: light_inf + Location: 11,43 + Owner: Atreides + AtreidesEntry1: waypoint + Owner: Neutral + Location: 25,2 + AtreidesEntry2: waypoint + Owner: Neutral + Location: 25,49 + AtreidesRally1: waypoint + Owner: Neutral + Location: 36,12 + AtreidesRally2: waypoint + Owner: Neutral + Location: 46,21 + AtreidesRally3: waypoint + Owner: Neutral + Location: 36,22 + AtreidesRally4: waypoint + Owner: Neutral + Location: 47,4 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-02a/rules.yaml b/mods/d2k/maps/harkonnen-02a/rules.yaml new file mode 100644 index 0000000000..c5d4d9964f --- /dev/null +++ b/mods/d2k/maps/harkonnen-02a/rules.yaml @@ -0,0 +1,48 @@ +Player: + PlayerResources: + DefaultCash: 5000 + +World: + LuaScript: + Scripts: harkonnen02a.lua, harkonnen02a-AI.lua + MissionData: + Briefing: Strengthen your forces at our mining camp in the Imperial Basin. We must punish the Atreides for their insolence. Teach them the consequences of opposing House Harkonnen.\n\nOur radar will help you find your targets.\n + BriefingVideo: H_BR02_E.VQA + MapOptions: + TechLevel: low + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 + +construction_yard: + Production: + Produces: Building + +concreteb: + Buildable: + Prerequisites: ~disabled + +heavy_factory: + Buildable: + Prerequisites: ~disabled + +medium_gun_turret: + Buildable: + Prerequisites: ~disabled + +wall: + Buildable: + Prerequisites: ~disabled + +outpost: + Buildable: + Prerequisites: barracks diff --git a/mods/d2k/missions.yaml b/mods/d2k/missions.yaml index 415c9dcf80..f138cee7b3 100644 --- a/mods/d2k/missions.yaml +++ b/mods/d2k/missions.yaml @@ -14,3 +14,4 @@ Ordos Campaign: Harkonnen Campaign: ./mods/d2k/maps/harkonnen-01a ./mods/d2k/maps/harkonnen-01b + ./mods/d2k/maps/harkonnen-02a