diff --git a/OpenRA.sln b/OpenRA.sln index 83c12e4078..26d6996939 100644 --- a/OpenRA.sln +++ b/OpenRA.sln @@ -106,6 +106,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dune 2000 Lua scripts", "Du mods\d2k\maps\atreides-02b\atreides02b.lua = mods\d2k\maps\atreides-02b\atreides02b.lua mods\d2k\maps\atreides-03a\atreides03a-AI.lua = mods\d2k\maps\atreides-03a\atreides03a-AI.lua mods\d2k\maps\atreides-03a\atreides03a.lua = mods\d2k\maps\atreides-03a\atreides03a.lua + mods\d2k\maps\atreides-03b\atreides03b-AI.lua = mods\d2k\maps\atreides-03b\atreides03b-AI.lua + mods\d2k\maps\atreides-03b\atreides03b.lua = mods\d2k\maps\atreides-03b\atreides03b.lua EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "System Lua scripts", "System Lua scripts", "{A4D6AEA4-8009-4256-903B-8D227E50452B}" diff --git a/mods/d2k/maps/atreides-03b/atreides03b-AI.lua b/mods/d2k/maps/atreides-03b/atreides03b-AI.lua new file mode 100644 index 0000000000..6f462cddb1 --- /dev/null +++ b/mods/d2k/maps/atreides-03b/atreides03b-AI.lua @@ -0,0 +1,132 @@ +IdlingUnits = { } + +AttackGroupSize = +{ + easy = 6, + normal = 8, + hard = 10 +} + +AttackDelays = +{ + easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, + normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, + hard = { DateTime.Seconds(1), DateTime.Seconds(3) } +} + +OrdosInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + +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[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, IdleHunt) + + Trigger.OnAllRemovedFromWorld(units, function() + Attacking = 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(ordos, InitialOrdosReinforcements, OrdosPaths[2]) + + Utils.Do(OrdosBase, 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 OBarracks.IsDead then + return + end + + if HoldProduction then + Trigger.AfterDelay(DateTime.Seconds(30), ProduceInfantry) + return + end + + local delay = Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) + local toBuild = { Utils.Random(OrdosInfantryTypes) } + ordos.Build(toBuild, function(unit) + IdlingUnits[#IdlingUnits + 1] = unit[1] + Trigger.AfterDelay(delay, ProduceInfantry) + + if #IdlingUnits >= (AttackGroupSize[Difficulty] * 2.5) then + SendAttack() + end + end) +end + +ActivateAI = function() + Trigger.AfterDelay(0, InitAIUnits) + + OConyard.Produce(AtreidesUpgrades[1]) + + -- Finish the upgrades first before trying to build something + Trigger.AfterDelay(DateTime.Seconds(14), ProduceInfantry) +end diff --git a/mods/d2k/maps/atreides-03b/atreides03b.lua b/mods/d2k/maps/atreides-03b/atreides03b.lua new file mode 100644 index 0000000000..aa9e8f1d32 --- /dev/null +++ b/mods/d2k/maps/atreides-03b/atreides03b.lua @@ -0,0 +1,173 @@ +OrdosBase = { OBarracks, OWindTrap1, OWindTrap2, OOutpost, OConyard, ORefinery, OSilo } + +OrdosReinforcements = +{ + easy = + { + { "light_inf", "raider", "trooper" }, + { "light_inf", "raider", "quad" }, + { "light_inf", "light_inf", "trooper", "raider", "raider", "quad" } + }, + + normal = + { + { "light_inf", "raider", "trooper" }, + { "light_inf", "raider", "raider" }, + { "light_inf", "light_inf", "trooper", "raider", "raider", "quad" }, + { "light_inf", "light_inf", "trooper", "trooper" }, + { "light_inf", "light_inf", "light_inf", "light_inf" }, + { "light_inf", "raider", "quad", "quad" } + }, + + hard = + { + { "raider", "raider", "quad" }, + { "light_inf", "raider", "raider" }, + { "trooper", "trooper", "light_inf", "raider" }, + { "light_inf", "light_inf", "light_inf", "raider", "raider" }, + { "light_inf", "light_inf", "trooper", "trooper" }, + { "raider", "raider", "quad", "quad", "quad", "raider" }, + { "light_inf", "light_inf", "light_inf", "raider", "raider" }, + { "light_inf", "raider", "light_inf", "trooper", "trooper", "quad" }, + { "raider", "raider", "quad", "quad", "quad", "raider" } + } +} + +OrdosAttackDelay = +{ + easy = DateTime.Minutes(5), + normal = DateTime.Minutes(2) + DateTime.Seconds(40), + hard = DateTime.Minutes(1) + DateTime.Seconds(20) +} + +OrdosAttackWaves = +{ + easy = 3, + normal = 6, + hard = 9 +} + +ToHarvest = +{ + easy = 5000, + normal = 6000, + hard = 7000 +} + +InitialOrdosReinforcements = { "light_inf", "light_inf", "quad", "quad", "raider", "raider", "trooper", "trooper" } + +OrdosPaths = +{ + { OrdosEntry1.Location, OrdosRally1.Location }, + { OrdosEntry2.Location, OrdosRally2.Location } +} + +AtreidesReinforcements = { "quad", "quad", "trike", "trike" } +AtreidesPath = { AtreidesEntry.Location, AtreidesRally.Location } + +AtreidesBaseBuildings = { "barracks", "light_factory" } +AtreidesUpgrades = { "upgrade.barracks", "upgrade.light" } + +wave = 0 +SendOrdos = function() + Trigger.AfterDelay(OrdosAttackDelay[Difficulty], function() + if player.IsObjectiveCompleted(KillOrdos) then + return + end + + wave = wave + 1 + if wave > OrdosAttackWaves[Difficulty] then + return + end + + local units = Reinforcements.ReinforceWithTransport(ordos, "carryall.reinforce", OrdosReinforcements[Difficulty][wave], OrdosPaths[1], { OrdosPaths[1][1] })[2] + Utils.Do(units, IdleHunt) + + SendOrdos() + end) +end + +MessageCheck = function(index) + return #player.GetActorsByType(AtreidesBaseBuildings[index]) > 0 and not player.HasPrerequisites({ AtreidesUpgrades[index] }) +end + +Tick = function() + if player.HasNoRequiredUnits() then + ordos.MarkCompletedObjective(KillAtreides) + end + + if ordos.HasNoRequiredUnits() and not player.IsObjectiveCompleted(KillOrdos) then + Media.DisplayMessage("The Ordos have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillOrdos) + end + + if DateTime.GameTime % DateTime.Seconds(30) and HarvesterKilled then + local units = ordos.GetActorsByType("harvester") + + if #units > 0 then + HarvesterKilled = false + ProtectHarvester(units[1]) + end + end + + if player.Resources > SpiceToHarvest - 1 then + player.MarkCompletedObjective(GatherSpice) + 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 + + UserInterface.SetMissionText("Harvested resources: " .. player.Resources .. "/" .. SpiceToHarvest, player.Color) +end + +WorldLoaded = function() + ordos = Player.GetPlayer("Ordos") + player = Player.GetPlayer("Atreides") + + Difficulty = Map.LobbyOption("difficulty") + SpiceToHarvest = ToHarvest[Difficulty] + + InitObjectives() + + Camera.Position = AConyard.CenterPosition + + Trigger.OnAllKilled(OrdosBase, function() + Utils.Do(ordos.GetGroundAttackers(), IdleHunt) + end) + + SendOrdos() + ActivateAI() + + Trigger.AfterDelay(DateTime.Minutes(2) + DateTime.Seconds(30), function() + Reinforcements.ReinforceWithTransport(player, "carryall.reinforce", AtreidesReinforcements, AtreidesPath, { AtreidesPath[1] }) + end) +end + +InitObjectives = function() + Trigger.OnObjectiveAdded(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective") + end) + + KillAtreides = ordos.AddPrimaryObjective("Kill all Atreides units.") + GatherSpice = player.AddPrimaryObjective("Harvest " .. tostring(SpiceToHarvest) .. " Solaris worth of Spice.") + KillOrdos = player.AddSecondaryObjective("Eliminate all Ordos 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/atreides-03b/map.bin b/mods/d2k/maps/atreides-03b/map.bin new file mode 100644 index 0000000000..af8864c036 Binary files /dev/null and b/mods/d2k/maps/atreides-03b/map.bin differ diff --git a/mods/d2k/maps/atreides-03b/map.png b/mods/d2k/maps/atreides-03b/map.png new file mode 100644 index 0000000000..02d300b3e4 Binary files /dev/null and b/mods/d2k/maps/atreides-03b/map.png differ diff --git a/mods/d2k/maps/atreides-03b/map.yaml b/mods/d2k/maps/atreides-03b/map.yaml new file mode 100644 index 0000000000..825dc30763 --- /dev/null +++ b/mods/d2k/maps/atreides-03b/map.yaml @@ -0,0 +1,135 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Atreides 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: Atreides, Ordos + PlayerReference@Atreides: + Name: Atreides + Playable: True + LockFaction: True + Faction: atreides + LockColor: True + Color: 9191FF + Enemies: Ordos, Creeps + PlayerReference@Ordos: + Name: Ordos + LockFaction: True + Faction: ordos + LockColor: True + Color: B3EAA5 + Enemies: Atreides, Creeps + +Actors: + Actor3: wormspawner + Location: 27,14 + Owner: Creeps + Actor8: light_inf + Location: 48,24 + Owner: Ordos + Actor9: light_inf + Location: 19,34 + Owner: Ordos + Actor10: light_inf + Location: 35,34 + Owner: Ordos + Actor11: trooper + Location: 18,35 + Owner: Ordos + Actor12: light_inf + Location: 18,37 + Owner: Ordos + Actor13: trooper + Location: 19,39 + Owner: Ordos + Actor14: light_inf + Location: 34,50 + Owner: Ordos + Actor20: light_inf + Location: 34,54 + Owner: Ordos + Actor15: light_inf + Location: 50,50 + Owner: Atreides + Actor16: quad + Location: 47,51 + Owner: Atreides + Actor18: trike + Location: 46,53 + Owner: Atreides + Actor19: quad + Location: 53,53 + Owner: Atreides + Actor21: trooper + Location: 50,55 + Owner: Atreides + Actor22: trike + Location: 52,55 + Owner: Atreides + OWindTrap1: wind_trap + Location: 12,9 + Owner: Ordos + OWindTrap2: wind_trap + Location: 18,9 + Owner: Ordos + OConyard: construction_yard + Location: 15,12 + Owner: Ordos + OBarracks: barracks + Location: 13,15 + Owner: Ordos + ORefinery: refinery + Location: 14,18 + Owner: Ordos + OSilo: silo + Location: 18,20 + Owner: Ordos + OOutpost: outpost + Location: 18,23 + Owner: Ordos + AConyard: construction_yard + Location: 49,52 + Owner: Atreides + AtreidesEntry: waypoint + Owner: Neutral + Location: 65,53 + AtreidesRally: waypoint + Owner: Neutral + Location: 45,55 + OrdosEntry1: waypoint + Owner: Neutral + Location: 41,65 + OrdosRally1: waypoint + Owner: Neutral + Location: 46,62 + OrdosEntry2: waypoint + Owner: Neutral + Location: 6,2 + OrdosRally2: waypoint + Owner: Neutral + Location: 15,34 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mods/d2k/maps/atreides-03b/rules.yaml b/mods/d2k/maps/atreides-03b/rules.yaml new file mode 100644 index 0000000000..7f2fd86c4d --- /dev/null +++ b/mods/d2k/maps/atreides-03b/rules.yaml @@ -0,0 +1,57 @@ + +Player: + PlayerResources: + DefaultCash: 3000 + +World: + LuaScript: + Scripts: atreides03b.lua, atreides03b-AI.lua + MissionData: + Briefing: Bring the Atreides forces up to combat strength - quickly. Guard against surprise attacks and ensure Spice production.\n\nThe Ordos forces are light but numerous. To combat the Ordos, you have been granted license to produce Quads, which have a greater anti-vehicle capability than Trikes. Upgrade your Light Factories to allow production of these units.\n\nMeet any agression with overwhelming force.\n\nGood luck.\n + BriefingVideo: A_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 4784ea512a..f4724468cb 100644 --- a/mods/d2k/missions.yaml +++ b/mods/d2k/missions.yaml @@ -4,3 +4,4 @@ Atreides Campaign: ./mods/d2k/maps/atreides-02a ./mods/d2k/maps/atreides-02b ./mods/d2k/maps/atreides-03a + ./mods/d2k/maps/atreides-03b