diff --git a/OpenRA.sln b/OpenRA.sln index e70743577f..b2acf4d482 100644 --- a/OpenRA.sln +++ b/OpenRA.sln @@ -122,6 +122,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dune 2000 Lua scripts", "Du mods\d2k\maps\harkonnen-02b\harkonnen02b.lua = mods\d2k\maps\harkonnen-02b\harkonnen02b.lua mods\d2k\maps\harkonnen-03a\harkonnen03a-AI.lua = mods\d2k\maps\harkonnen-03a\harkonnen03a-AI.lua mods\d2k\maps\harkonnen-03a\harkonnen03a.lua = mods\d2k\maps\harkonnen-03a\harkonnen03a.lua + mods\d2k\maps\harkonnen-03b\harkonnen03b-AI.lua = mods\d2k\maps\harkonnen-03b\harkonnen03b-AI.lua + mods\d2k\maps\harkonnen-03b\harkonnen03b.lua = mods\d2k\maps\harkonnen-03b\harkonnen03b.lua mods\d2k\maps\ordos-01a\ordos01a.lua = mods\d2k\maps\ordos-01a\ordos01a.lua mods\d2k\maps\ordos-01b\ordos01b.lua = mods\d2k\maps\ordos-01b\ordos01b.lua mods\d2k\maps\ordos-02a\ordos02a.lua = mods\d2k\maps\ordos-02a\ordos02a.lua diff --git a/mods/d2k/maps/harkonnen-03b/harkonnen03b-AI.lua b/mods/d2k/maps/harkonnen-03b/harkonnen03b-AI.lua new file mode 100644 index 0000000000..6b142317ae --- /dev/null +++ b/mods/d2k/maps/harkonnen-03b/harkonnen03b-AI.lua @@ -0,0 +1,159 @@ +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", "light_inf", "light_inf", "trooper", "trooper" } +AtreidesVehicleTypes = { "trike", "trike", "quad" } + +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 IsAttacking then + return + end + IsAttacking = true + HoldProduction = true + + local units = SetupAttackGroup() + Utils.Do(units, function(unit) + IdleHunt(unit) + end) + + Trigger.OnAllRemovedFromWorld(units, function() + IsAttacking = false + HoldProduction = false + end) +end + +ProtectHarvester = function(unit) + DefendActor(unit) + Trigger.OnKilled(unit, function() HarvesterKilled = true 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() + IdlingUnits = Reinforcements.Reinforce(atreides, AtreidesInitialReinforcements, AtreidesInitialPath) + + 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 + +ProduceVehicles = function() + if ALightFactory.IsDead then + return + end + + if HoldProduction then + Trigger.AfterDelay(DateTime.Minutes(1), ProduceVehicles) + return + end + + local delay = Utils.RandomInteger(AttackDelays[Map.LobbyOption("difficulty")][1], AttackDelays[Map.LobbyOption("difficulty")][2] + 1) + local toBuild = { Utils.Random(AtreidesVehicleTypes) } + atreides.Build(toBuild, function(unit) + IdlingUnits[#IdlingUnits + 1] = unit[1] + Trigger.AfterDelay(delay, ProduceVehicles) + + if #IdlingUnits >= (AttackGroupSize[Map.LobbyOption("difficulty")] * 2.5) then + SendAttack() + end + end) +end + +ActivateAI = function() + Trigger.AfterDelay(0, InitAIUnits) + + AConyard.Produce(HarkonnenUpgrades[1]) + AConyard.Produce(HarkonnenUpgrades[2]) + + -- Finish the upgrades first before trying to build something + Trigger.AfterDelay(DateTime.Seconds(14), function() + ProduceInfantry() + ProduceVehicles() + end) +end diff --git a/mods/d2k/maps/harkonnen-03b/harkonnen03b.lua b/mods/d2k/maps/harkonnen-03b/harkonnen03b.lua new file mode 100644 index 0000000000..06cca7efdb --- /dev/null +++ b/mods/d2k/maps/harkonnen-03b/harkonnen03b.lua @@ -0,0 +1,197 @@ +AtreidesBase = { ABarracks, AWindTrap1, AWindTrap2, AWindTrap3, ALightFactory, AOutpost, AConyard, ARefinery, ASilo1, ASilo2, ASilo3, ASilo4 } +AtreidesBaseAreaTriggers = +{ + { CPos.New(10, 53), CPos.New(11, 53), CPos.New(12, 53), CPos.New(13, 53), CPos.New(14, 53), CPos.New(15, 53), CPos.New(16, 53), CPos.New(17, 53), CPos.New(17, 52), CPos.New(17, 51), CPos.New(17, 50), CPos.New(17, 49), CPos.New(17, 48), CPos.New(17, 47), CPos.New(17, 46), CPos.New(17, 45), CPos.New(17, 44), CPos.New(17, 43), CPos.New(17, 42), CPos.New(17, 41), CPos.New(17, 40), CPos.New(17, 39), CPos.New(17, 38), CPos.New(2, 35), CPos.New(3, 35), CPos.New(4, 35), CPos.New(5, 35), CPos.New(6, 35), CPos.New(7, 35), CPos.New(8, 35), CPos.New(9, 35), CPos.New(10, 35), CPos.New(11, 35), CPos.New(12, 35) }, + { CPos.New(49, 25), CPos.New(50, 25), CPos.New(51, 25), CPos.New(52, 25), CPos.New(53, 25), CPos.New(54, 25), CPos.New(54, 26), CPos.New(54, 27), CPos.New(54, 28), CPos.New(54, 29) }, + { CPos.New(19, 2), CPos.New(19, 3), CPos.New(19, 4), CPos.New(41, 2), CPos.New(41, 3), CPos.New(41, 4), CPos.New(41, 5), CPos.New(41, 6), CPos.New(41, 7), CPos.New(41, 8), CPos.New(41, 9), CPos.New(41, 10), CPos.New(41, 11) }, + { CPos.New(2, 16), CPos.New(3, 16), CPos.New(4, 16), CPos.New(5, 16), CPos.New(19, 2), CPos.New(19, 3), CPos.New(19, 4) } +} + +AtreidesReinforcements = +{ + easy = + { + { "light_inf", "trike", "trooper" }, + { "light_inf", "trike", "quad" }, + { "light_inf", "light_inf", "trooper", "trike", "trike", "quad" } + }, + + normal = + { + { "light_inf", "trike", "trooper" }, + { "light_inf", "trike", "trike" }, + { "light_inf", "light_inf", "trooper", "trike", "trike", "quad" }, + { "light_inf", "light_inf", "trooper", "trooper" }, + { "light_inf", "light_inf", "light_inf", "light_inf" }, + { "light_inf", "trike", "quad", "quad" } + }, + + hard = + { + { "trike", "trike", "quad" }, + { "light_inf", "trike", "trike" }, + { "trooper", "trooper", "light_inf", "trike" }, + { "light_inf", "light_inf", "light_inf", "trike", "trike" }, + { "light_inf", "light_inf", "trooper", "trooper" }, + { "trike", "trike", "quad", "quad", "quad", "trike" }, + { "light_inf", "light_inf", "light_inf", "trike", "trike" }, + { "light_inf", "trike", "light_inf", "trooper", "trooper", "quad" }, + { "trike", "trike", "quad", "quad", "quad", "trike" } + } +} + +AtreidesAttackDelay = +{ + easy = DateTime.Minutes(5), + normal = DateTime.Minutes(2) + DateTime.Seconds(40), + hard = DateTime.Minutes(1) + DateTime.Seconds(20) +} + +AtreidesAttackWaves = +{ + easy = 3, + normal = 6, + hard = 9 +} + +AtreidesHunters = +{ + { "trooper", "trooper", "trooper" }, + { "trike", "light_inf", "light_inf", "light_inf", "light_inf" }, + { "trooper", "trooper", "trooper", "trike", "trike" }, + { "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper", "trooper" } +} + +AtreidesPaths = +{ + { AtreidesEntry4.Location, AtreidesRally4.Location }, + { AtreidesEntry7.Location, AtreidesRally7.Location }, + { AtreidesEntry8.Location, AtreidesRally8.Location } +} + +AtreidesHunterPaths = +{ + { AtreidesEntry6.Location, AtreidesRally6.Location }, + { AtreidesEntry5.Location, AtreidesRally5.Location }, + { AtreidesEntry3.Location, AtreidesRally3.Location }, + { AtreidesEntry1.Location, AtreidesRally1.Location } +} + +AtreidesInitialPath = { AtreidesEntry2.Location, AtreidesRally2.Location } +AtreidesInitialReinforcements = { "light_inf", "light_inf", "quad", "quad", "trike", "trike", "trooper", "trooper" } + +HarkonnenReinforcements = { "quad", "quad" } +HarkonnenPath = { HarkonnenEntry.Location, HarkonnenRally.Location } + +HarkonnenBaseBuildings = { "barracks", "light_factory" } +HarkonnenUpgrades = { "upgrade.barracks", "upgrade.light" } + +wave = 0 +SendAtreides = function() + Trigger.AfterDelay(AtreidesAttackDelay[Map.LobbyOption("difficulty")], function() + if player.IsObjectiveCompleted(KillAtreides) then + return + end + + wave = wave + 1 + if wave > AtreidesAttackWaves[Map.LobbyOption("difficulty")] then + return + end + + local path = Utils.Random(AtreidesPaths) + local units = Reinforcements.ReinforceWithTransport(atreides, "carryall.reinforce", AtreidesReinforcements[Map.LobbyOption("difficulty")][wave], path, { path[1] })[2] + Utils.Do(units, IdleHunt) + + SendAtreides() + end) +end + +MessageCheck = function(index) + return #player.GetActorsByType(HarkonnenBaseBuildings[index]) > 0 and not player.HasPrerequisites({ HarkonnenUpgrades[index] }) +end + +SendHunters = function(areaTrigger, unit, path, check) + Trigger.OnEnteredFootprint(areaTrigger, function(a, id) + if not check and a.Owner == player then + local units = Reinforcements.ReinforceWithTransport(atreides, "carryall.reinforce", unit, path, { path[1] })[2] + Utils.Do(units, IdleHunt) + check = true + end + end) +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 + + if DateTime.GameTime % DateTime.Seconds(30) and HarvesterKilled then + local units = atreides.GetActorsByType("harvester") + + if #units > 0 then + HarvesterKilled = false + ProtectHarvester(units[1]) + end + end + + if DateTime.GameTime % DateTime.Seconds(32) == 0 and (MessageCheck(1) or MessageCheck(2)) then + Media.DisplayMessage("Upgrade barracks and light factory to produce more advanced units.", "Mentat") + 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() + + Trigger.AfterDelay(DateTime.Minutes(2) + DateTime.Seconds(30), function() + Reinforcements.ReinforceWithTransport(player, "carryall.reinforce", HarkonnenReinforcements, HarkonnenPath, { HarkonnenPath[1] }) + end) + + SendHunters(AtreidesBaseAreaTriggers[1], AtreidesHunters[1], AtreidesHunterPaths[1], HuntersSent1) + SendHunters(AtreidesBaseAreaTriggers[2], AtreidesHunters[2], AtreidesHunterPaths[2], HuntersSent2) + SendHunters(AtreidesBaseAreaTriggers[3], AtreidesHunters[3], AtreidesHunterPaths[3], HuntersSent3) + SendHunters(AtreidesBaseAreaTriggers[4], AtreidesHunters[4], AtreidesHunterPaths[4], HuntersSent4) +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("Eliminate all Atreides units and reinforcements\nin the area.") + + 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-03b/map.bin b/mods/d2k/maps/harkonnen-03b/map.bin new file mode 100644 index 0000000000..b6e6870c39 Binary files /dev/null and b/mods/d2k/maps/harkonnen-03b/map.bin differ diff --git a/mods/d2k/maps/harkonnen-03b/map.png b/mods/d2k/maps/harkonnen-03b/map.png new file mode 100644 index 0000000000..a35bfff3b2 Binary files /dev/null and b/mods/d2k/maps/harkonnen-03b/map.png differ diff --git a/mods/d2k/maps/harkonnen-03b/map.yaml b/mods/d2k/maps/harkonnen-03b/map.yaml new file mode 100644 index 0000000000..886989db8e --- /dev/null +++ b/mods/d2k/maps/harkonnen-03b/map.yaml @@ -0,0 +1,210 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Harkonnen 03b + +Author: Westwood Studios + +Tileset: ARRAKIS + +MapSize: 68,68 + +Bounds: 2,2,64,64 + +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: wall + Location: 37,2 + Owner: Atreides + AConyard: construction_yard + Location: 27,3 + Owner: Atreides + ARefinery: refinery + Location: 31,3 + Owner: Atreides + ASilo1: silo + Location: 35,3 + Owner: Atreides + Actor4: wall + Location: 37,3 + Owner: Atreides + ASilo2: silo + Location: 35,4 + Owner: Atreides + Actor6: wall + Location: 37,4 + Owner: Atreides + Actor7: wall + Location: 36,5 + Owner: Atreides + Actor8: wall + Location: 37,5 + Owner: Atreides + ASilo3: silo + Location: 28,7 + Owner: Atreides + ASilo4: silo + Location: 28,8 + Owner: Atreides + Actor11: wall + Location: 38,8 + Owner: Atreides + Actor12: wall + Location: 39,8 + Owner: Atreides + Actor13: wall + Location: 39,9 + Owner: Atreides + ALightFactory: light_factory + Location: 28,10 + Owner: Atreides + AOutpost: outpost + Location: 33,10 + Owner: Atreides + Actor16: wall + Location: 39,10 + Owner: Atreides + Actor17: wall + Location: 39,11 + Owner: Atreides + Actor18: wall + Location: 39,12 + Owner: Atreides + Actor19: wall + Location: 40,12 + Owner: Atreides + AWindTrap1: wind_trap + Location: 28,13 + Owner: Atreides + AWindTrap2: wind_trap + Location: 30,13 + Owner: Atreides + ABarracks: barracks + Location: 32,14 + Owner: Atreides + AWindTrap3: wind_trap + Location: 36,14 + Owner: Atreides + Actor24: wormspawner + Location: 3,21 + Owner: Creeps + Actor25: wormspawner + Location: 29,29 + Owner: Creeps + HConyard: construction_yard + Location: 57,39 + Owner: Harkonnen + Actor27: trike + Location: 54,40 + Owner: Harkonnen + Actor28: light_inf + Location: 54,42 + Owner: Harkonnen + Actor29: trike + Location: 56,42 + Owner: Harkonnen + Actor30: quad + Location: 62,42 + Owner: Harkonnen + Actor31: light_inf + Location: 54,43 + Owner: Harkonnen + Actor32: trooper + Location: 58,44 + Owner: Harkonnen + Actor33: quad + Location: 59,44 + Owner: Harkonnen + Actor34: trooper + Location: 62,44 + Owner: Harkonnen + Actor35: light_inf + Location: 58,46 + Owner: Harkonnen + AtreidesRally1: waypoint + Owner: Neutral + Location: 20,3 + AtreidesEntry1: waypoint + Owner: Neutral + Location: 20,2 + AtreidesRally3: waypoint + Owner: Neutral + Location: 33,8 + AtreidesRally2: waypoint + Owner: Neutral + Location: 32,7 + AtreidesEntry2: waypoint + Owner: Neutral + Location: 32,2 + AtreidesEntry3: waypoint + Owner: Neutral + Location: 65,8 + AtreidesRally4: waypoint + Owner: Neutral + Location: 42,45 + AtreidesEntry4: waypoint + Owner: Neutral + Location: 42,65 + AtreidesRally5: waypoint + Owner: Neutral + Location: 60,13 + AtreidesEntry5: waypoint + Owner: Neutral + Location: 65,13 + AtreidesRally6: waypoint + Owner: Neutral + Location: 14,43 + AtreidesEntry6: waypoint + Owner: Neutral + Location: 2,43 + AtreidesRally7: waypoint + Owner: Neutral + Location: 21,49 + AtreidesEntry7: waypoint + Owner: Neutral + Location: 21,65 + AtreidesRally8: waypoint + Owner: Neutral + Location: 64,55 + AtreidesEntry8: waypoint + Owner: Neutral + Location: 64,65 + HarkonnenRally: waypoint + Owner: Neutral + Location: 60,45 + HarkonnenEntry: waypoint + Owner: Neutral + Location: 65,45 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-03b/rules.yaml b/mods/d2k/maps/harkonnen-03b/rules.yaml new file mode 100644 index 0000000000..c8c4c01645 --- /dev/null +++ b/mods/d2k/maps/harkonnen-03b/rules.yaml @@ -0,0 +1,56 @@ +Player: + PlayerResources: + DefaultCash: 5000 + +World: + LuaScript: + Scripts: harkonnen03b.lua, harkonnen03b-AI.lua + MissionData: + Briefing: Attack and destroy the Atreides base at Sietch Tabr. Strike hard and eliminate all resistance.\n\nHeavier Quad vehicles will be made available for your attack. Upgrade your Light Factory to gain access to these vehicles.\n + BriefingVideo: H_BR03_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 + +concreteb: + Buildable: + Prerequisites: ~disabled + +heavy_factory: + Buildable: + Prerequisites: ~disabled + +medium_gun_turret: + Buildable: + Prerequisites: ~disabled + +outpost: + Buildable: + Prerequisites: barracks + +quad: + Buildable: + Prerequisites: upgrade.light + +trooper: + Buildable: + Prerequisites: upgrade.barracks + +upgrade.conyard: + Buildable: + Prerequisites: ~disabled + +upgrade.heavy: + Buildable: + Prerequisites: ~disabled diff --git a/mods/d2k/missions.yaml b/mods/d2k/missions.yaml index 9afd985b36..a16a9b0007 100644 --- a/mods/d2k/missions.yaml +++ b/mods/d2k/missions.yaml @@ -21,3 +21,4 @@ Harkonnen Campaign: ./mods/d2k/maps/harkonnen-02a ./mods/d2k/maps/harkonnen-02b ./mods/d2k/maps/harkonnen-03a + ./mods/d2k/maps/harkonnen-03b