diff --git a/mtrsd2k/maps/atreides-02a/atreides02a-AI.lua b/mtrsd2k/maps/atreides-02a/atreides02a-AI.lua new file mode 100644 index 0000000..d7557e3 --- /dev/null +++ b/mtrsd2k/maps/atreides-02a/atreides02a-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) } +} + +HarkonnenInfantryTypes = { "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(HarkonnenBase, 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 HBarracks.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(HarkonnenInfantryTypes) } + harkonnen.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/mtrsd2k/maps/atreides-02a/atreides02a.lua b/mtrsd2k/maps/atreides-02a/atreides02a.lua index 606cf7a..fc68e43 100644 --- a/mtrsd2k/maps/atreides-02a/atreides02a.lua +++ b/mtrsd2k/maps/atreides-02a/atreides02a.lua @@ -94,6 +94,7 @@ WorldLoaded = function() end) SendHarkonnen() + ActivateAI() end InitObjectives = function() diff --git a/mtrsd2k/maps/atreides-02a/map.bin b/mtrsd2k/maps/atreides-02a/map.bin index 08ad5fc..d91b94b 100644 Binary files a/mtrsd2k/maps/atreides-02a/map.bin and b/mtrsd2k/maps/atreides-02a/map.bin differ diff --git a/mtrsd2k/maps/atreides-02a/rules.yaml b/mtrsd2k/maps/atreides-02a/rules.yaml index 6926fb6..257e261 100644 --- a/mtrsd2k/maps/atreides-02a/rules.yaml +++ b/mtrsd2k/maps/atreides-02a/rules.yaml @@ -4,7 +4,7 @@ Player: World: LuaScript: - Scripts: atreides02a.lua + Scripts: atreides02a.lua, atreides02a-AI.lua MissionData: Briefing: Infiltrate the Imperial Basin and build up our forces until they are strong enough to eradicate the local Harkonnen presence.\n\nThe Harkonnen are reinforcing their troops by air, so be on your guard. Use the Outpost's radar to detect attacks from unexpected quarters.\n\nBe careful when mining the Spice. Spice mounds grow out of the sand. While a vital source of Spice, Spice mounds can damage or destroy any unit that blunders into them.\n\nGood luck.\n BriefingVideo: A_BR02_E.VQA diff --git a/mtrsd2k/maps/atreides-02b/atreides02b-AI.lua b/mtrsd2k/maps/atreides-02b/atreides02b-AI.lua new file mode 100644 index 0000000..d7557e3 --- /dev/null +++ b/mtrsd2k/maps/atreides-02b/atreides02b-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) } +} + +HarkonnenInfantryTypes = { "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(HarkonnenBase, 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 HBarracks.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(HarkonnenInfantryTypes) } + harkonnen.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/mtrsd2k/maps/atreides-02b/atreides02b.lua b/mtrsd2k/maps/atreides-02b/atreides02b.lua index bb29c1f..40d2f4a 100644 --- a/mtrsd2k/maps/atreides-02b/atreides02b.lua +++ b/mtrsd2k/maps/atreides-02b/atreides02b.lua @@ -98,6 +98,7 @@ WorldLoaded = function() end) SendHarkonnen() + ActivateAI() end InitObjectives = function() diff --git a/mtrsd2k/maps/atreides-02b/map.bin b/mtrsd2k/maps/atreides-02b/map.bin index 0068202..2005604 100644 Binary files a/mtrsd2k/maps/atreides-02b/map.bin and b/mtrsd2k/maps/atreides-02b/map.bin differ diff --git a/mtrsd2k/maps/atreides-02b/rules.yaml b/mtrsd2k/maps/atreides-02b/rules.yaml index d3505c0..83a3fcc 100644 --- a/mtrsd2k/maps/atreides-02b/rules.yaml +++ b/mtrsd2k/maps/atreides-02b/rules.yaml @@ -4,7 +4,7 @@ Player: World: LuaScript: - Scripts: atreides02b.lua + Scripts: atreides02b.lua, atreides02b-AI.lua MissionData: Briefing: Infiltrate the Imperial Basin and build up our forces until they are strong enough to eradicate the local Harkonnen presence.\n\nThe Harkonnen are reinforcing their troops by air, so be on your guard. Use the Outpost's radar to detect attacks from unexpected quarters.\n\nBe careful when mining the Spice. Spice mounds grow out of the sand. While a vital source of Spice, Spice mounds can damage or destroy any unit that blunders into them.\n\nGood luck.\n BriefingVideo: A_BR02_E.VQA diff --git a/mtrsd2k/maps/atreides-03a/map.bin b/mtrsd2k/maps/atreides-03a/map.bin index 866f870..8b68728 100644 Binary files a/mtrsd2k/maps/atreides-03a/map.bin and b/mtrsd2k/maps/atreides-03a/map.bin differ diff --git a/mtrsd2k/maps/atreides-03a/map.yaml b/mtrsd2k/maps/atreides-03a/map.yaml index d529d91..095fbc7 100644 --- a/mtrsd2k/maps/atreides-03a/map.yaml +++ b/mtrsd2k/maps/atreides-03a/map.yaml @@ -1,6 +1,6 @@ MapFormat: 11 -RequiresMod: d2k +RequiresMod: mtrsd2k Title: Atreides 03a @@ -134,5 +134,15 @@ Actors: OrdosRally2: waypoint Owner: Neutral Location: 39,30 + Actor30: wall + Owner: Ordos + Location: 53,24 + Actor31: wall + Owner: Ordos + Location: 54,25 + Actor32: artillery_platform + Owner: Ordos + Location: 53,25 + TurretFacing: 92 Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/atreides-03b/map.bin b/mtrsd2k/maps/atreides-03b/map.bin index af8864c..b3cd052 100644 Binary files a/mtrsd2k/maps/atreides-03b/map.bin and b/mtrsd2k/maps/atreides-03b/map.bin differ diff --git a/mtrsd2k/maps/atreides-03b/map.yaml b/mtrsd2k/maps/atreides-03b/map.yaml index 825dc30..b949225 100644 --- a/mtrsd2k/maps/atreides-03b/map.yaml +++ b/mtrsd2k/maps/atreides-03b/map.yaml @@ -1,6 +1,6 @@ MapFormat: 11 -RequiresMod: d2k +RequiresMod: mtrsd2k Title: Atreides 03b @@ -131,5 +131,15 @@ Actors: OrdosRally2: waypoint Owner: Neutral Location: 15,34 + Actor29: wall + Owner: Ordos + Location: 15,11 + Actor30: wall + Owner: Ordos + Location: 17,11 + Actor31: artillery_platform + Owner: Ordos + Location: 16,11 + TurretFacing: 92 Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/atreides-04/atreides04-AI.lua b/mtrsd2k/maps/atreides-04/atreides04-AI.lua new file mode 100644 index 0000000..fe50bc5 --- /dev/null +++ b/mtrsd2k/maps/atreides-04/atreides04-AI.lua @@ -0,0 +1,163 @@ +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) } +} + +HarkonnenInfantryTypes = { "light_inf", "light_inf", "trooper", "trooper", "trooper" } +HarkonnenVehicleType = { "combat_tank_h" } + +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] 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) + + if #units > 0 then + Media.DisplayMessage("Harkonnen units approaching!", "Fremen Leader") + end + + 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 + + -- Don't try to attack spiceblooms + if attacker and attacker.Type == "spicebloom" then + return + end + + 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(harkonnen, InitialHarkonnenReinforcements, HarkonnenPaths[1]) + + Utils.Do(HarkonnenBase, 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 HarkonnenBarracks.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(HarkonnenInfantryTypes) } + harkonnen.Build(toBuild, function(unit) + IdlingUnits[#IdlingUnits + 1] = unit[1] + Trigger.AfterDelay(delay, ProduceInfantry) + + if #IdlingUnits >= (AttackGroupSize[Difficulty] * 2.5) then + SendAttack() + end + end) +end + + +ProduceVehicles = function() + if HarkonnenHeavyFact.IsDead then + return + end + + if HoldProduction then + Trigger.AfterDelay(DateTime.Seconds(30), ProduceVehicles) + return + end + + local delay = Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) + harkonnen.Build(HarkonnenVehicleType, function(unit) + IdlingUnits[#IdlingUnits + 1] = unit[1] + Trigger.AfterDelay(delay, ProduceVehicles) + + if #IdlingUnits >= (AttackGroupSize[Difficulty] * 2.5) then + SendAttack() + end + end) +end + +ActivateAI = function() + InitAIUnits() + FremenProduction() + + ProduceInfantry() + ProduceVehicles() +end diff --git a/mtrsd2k/maps/atreides-04/atreides04.lua b/mtrsd2k/maps/atreides-04/atreides04.lua new file mode 100644 index 0000000..591039e --- /dev/null +++ b/mtrsd2k/maps/atreides-04/atreides04.lua @@ -0,0 +1,240 @@ +HarkonnenBase = { HarkonnenOutpost, HarkonnenRefinery, HarkonnenHeavyFact, HarkonnenTurret1, HarkonnenTurret2, HarkonnenBarracks, HarkonnenSilo1, HarkonnenSilo2, HarkonnenWindTrap1, HarkonnenWindTrap2, HarkonnenWindTrap3, HarkonnenWindTrap4, HarkonnenWindTrap5 } + +HarkonnenReinforcements = +{ + easy = + { + { "combat_tank_h", "trooper", "trooper", "trooper", "trooper" }, + { "trooper", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper" } + }, + + normal = + { + { "combat_tank_h", "trooper", "trooper", "trooper", "trooper" }, + { "trooper", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "combat_tank_h", "light_inf", "trooper", "trooper", "quad" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" } + }, + + hard = + { + { "combat_tank_h", "trooper", "trooper", "trooper", "trooper" }, + { "trooper", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "combat_tank_h", "light_inf", "trooper", "trooper", "quad" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" }, + { "combat_tank_h", "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" }, + { "combat_tank_h", "combat_tank_h", "combat_tank_h", "combat_tank_h", "combat_tank_h", "combat_tank_h" } + } +} + +HarkonnenAttackDelay = +{ + easy = DateTime.Minutes(3), + normal = DateTime.Minutes(2) + DateTime.Seconds(20), + hard = DateTime.Minutes(1) +} + +HarkonnenAttackWaves = +{ + easy = 5, + normal = 7, + hard = 9 +} + +InitialHarkonnenReinforcements = { "trooper", "trooper", "trooper", "trooper", "trooper", "trooper" } + +HarkonnenPaths = +{ + { HarkonnenEntry1.Location, HarkonnenRally3.Location }, + { HarkonnenEntry2.Location, HarkonnenRally2.Location }, + { HarkonnenEntry3.Location, HarkonnenRally4.Location }, + { HarkonnenEntry4.Location, HarkonnenRally4.Location } +} + +AtreidesReinforcements = +{ + { "trike", "combat_tank_a", "combat_tank_a" }, + { "quad", "combat_tank_a", "combat_tank_a" } +} +AtreidesPath = { AtreidesEntry.Location, AtreidesRally.Location } + +FremenInterval = +{ + easy = { DateTime.Minutes(1) + DateTime.Seconds(30), DateTime.Minutes(2) }, + normal = { DateTime.Minutes(2) + DateTime.Seconds(20), DateTime.Minutes(2) + DateTime.Seconds(40) }, + hard = { DateTime.Minutes(3) + DateTime.Seconds(40), DateTime.Minutes(4) } +} + +IntegrityLevel = +{ + easy = 50, + normal = 75, + hard = 100 +} + +wave = 0 +SendHarkonnen = function() + Trigger.AfterDelay(HarkonnenAttackDelay[Difficulty], function() + if player.IsObjectiveCompleted(KillHarkonnen) then + return + end + + wave = wave + 1 + if wave > HarkonnenAttackWaves[Difficulty] then + return + end + + local entryPath = Utils.Random(HarkonnenPaths) + local units = Reinforcements.ReinforceWithTransport(harkonnen, "carryall.reinforce", HarkonnenReinforcements[Difficulty][wave], entryPath, { entryPath[1] })[2] + Utils.Do(units, function(unit) + unit.AttackMove(HarkonnenAttackLocation) + IdleHunt(unit) + end) + + SendHarkonnen() + end) +end + +FremenProduction = function() + if Sietch.IsDead then + return + end + + local delay = Utils.RandomInteger(FremenInterval[Difficulty][1], FremenInterval[Difficulty][2] + 1) + fremen.Build({ "nsfremen" }, function() + Trigger.AfterDelay(delay, ProduceInfantry) + end) +end + +AttackNotifier = 0 +Tick = function() + if player.HasNoRequiredUnits() then + harkonnen.MarkCompletedObjective(KillAtreides) + end + + if harkonnen.HasNoRequiredUnits() and not player.IsObjectiveCompleted(KillHarkonnen) then + Media.DisplayMessage("The Harkonnen have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillHarkonnen) + player.MarkCompletedObjective(ProtectFremen) + player.MarkCompletedObjective(KeepIntegrity) + end + + if DateTime.GameTime % DateTime.Seconds(30) and HarvesterKilled then + local units = harkonnen.GetActorsByType("harvester") + + if #units > 0 then + HarvesterKilled = false + ProtectHarvester(units[1]) + end + end + + if not Sietch.IsDead then + AttackNotifier = AttackNotifier - 1 + local integrity = math.floor((Sietch.Health * 100) / Sietch.MaxHealth) + UserInterface.SetMissionText("Sietch structural integrity: " .. integrity .. "%", player.Color) + + if integrity < IntegrityLevel[Difficulty] then + player.MarkFailedObjective(KeepIntegrity) + end + end +end + +WorldLoaded = function() + harkonnen = Player.GetPlayer("Harkonnen") + fremen = Player.GetPlayer("Fremen") + player = Player.GetPlayer("Atreides") + + Difficulty = Map.LobbyOption("difficulty") + + InitObjectives() + + Camera.Position = AConyard.CenterPosition + HarkonnenAttackLocation = AConyard.Location + + Trigger.AfterDelay(DateTime.Seconds(2), function() + Beacon.New(player, Sietch.CenterPosition + WVec.New(0, 1024, 0)) + Media.DisplayMessage("Fremen Sietch detected to the southeast.", "Mentat") + end) + + Trigger.OnAllKilledOrCaptured(HarkonnenBase, function() + Utils.Do(harkonnen.GetGroundAttackers(), IdleHunt) + end) + + Trigger.OnKilled(Sietch, function() + UserInterface.SetMissionText("Sietch destroyed!", player.Color) + player.MarkFailedObjective(ProtectFremen) + end) + Trigger.OnDamaged(Sietch, function() + if AttackNotifier <= 0 then + AttackNotifier = DateTime.Seconds(10) + Beacon.New(player, Sietch.CenterPosition + WVec.New(0, 1024, 0), DateTime.Seconds(7)) + Media.DisplayMessage("The Fremen Sietch is under attack!", "Mentat") + + local defenders = fremen.GetGroundAttackers() + if #defenders > 0 then + Utils.Do(defenders, function(unit) + unit.Guard(Sietch) + end) + end + end + end) + + SendHarkonnen() + Actor.Create("upgrade.barracks", true, { Owner = harkonnen }) + Trigger.AfterDelay(0, ActivateAI) + + Trigger.AfterDelay(DateTime.Seconds(50), function() + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.Reinforce(player, AtreidesReinforcements[1], AtreidesPath) + end) + Trigger.AfterDelay(DateTime.Minutes(1) + DateTime.Seconds(40), function() + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.ReinforceWithTransport(player, "carryall.reinforce", AtreidesReinforcements[2], AtreidesPath, { AtreidesPath[1] }) + end) + + Trigger.OnEnteredProximityTrigger(HarkonnenRally1.CenterPosition, WDist.New(6 * 1024), function(a, id) + if a.Owner == player then + Trigger.RemoveProximityTrigger(id) + local units = Reinforcements.Reinforce(harkonnen, { "light_inf", "combat_tank_h", "trike" }, HarkonnenPaths[1]) + Utils.Do(units, IdleHunt) + end + end) +end + +InitObjectives = function() + Trigger.OnObjectiveAdded(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective") + end) + + KillAtreides = harkonnen.AddPrimaryObjective("Kill all Atreides units.") + ProtectFremen = player.AddPrimaryObjective("Protect the Fremen Sietch.") + KillHarkonnen = player.AddPrimaryObjective("Destroy the Harkonnen.") + KeepIntegrity = player.AddSecondaryObjective("Keep the Sietch " .. IntegrityLevel[Difficulty] .. "% intact!") + + 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/mtrsd2k/maps/atreides-04/map.bin b/mtrsd2k/maps/atreides-04/map.bin new file mode 100644 index 0000000..c2d08db Binary files /dev/null and b/mtrsd2k/maps/atreides-04/map.bin differ diff --git a/mtrsd2k/maps/atreides-04/map.png b/mtrsd2k/maps/atreides-04/map.png new file mode 100644 index 0000000..f25f595 Binary files /dev/null and b/mtrsd2k/maps/atreides-04/map.png differ diff --git a/mtrsd2k/maps/atreides-04/map.yaml b/mtrsd2k/maps/atreides-04/map.yaml new file mode 100644 index 0000000..5bf09ef --- /dev/null +++ b/mtrsd2k/maps/atreides-04/map.yaml @@ -0,0 +1,417 @@ +MapFormat: 11 + +RequiresMod: mtrsd2k + +Title: Atreides 04 + +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 + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + Enemies: Atreides, Harkonnen, Fremen + PlayerReference@Atreides: + Name: Atreides + Playable: True + LockFaction: True + Faction: atreides + LockColor: True + Color: 9191FF + Allies: Fremen + Enemies: Harkonnen, Creeps + PlayerReference@Harkonnen: + Name: Harkonnen + LockFaction: True + Faction: harkonnen + LockColor: True + Color: FE0000 + Enemies: Atreides, Creeps, Fremen + PlayerReference@Fremen: + Name: Fremen + LockFaction: True + Faction: fremen + LockColor: True + Color: 9191FF + Allies: Atreides + Enemies: Harkonnen, Creeps + +Actors: + Actor0: wormspawner + Location: 63,7 + Owner: Creeps + Actor1: trike + Location: 17,11 + Owner: Harkonnen + Actor2: spicebloom + Location: 20,12 + Owner: Neutral + Actor3: spicebloom + Location: 24,14 + Owner: Neutral + Actor4: spicebloom + Location: 40,22 + Owner: Neutral + Actor5: spicebloom + Location: 57,24 + Owner: Neutral + Actor6: trike + Location: 6,25 + Owner: Atreides + Actor7: trike + Location: 10,26 + Owner: Atreides + Actor9: combat_tank_a + Location: 10,29 + Owner: Atreides + Actor10: combat_tank_a + Location: 12,29 + Owner: Atreides + Actor11: spicebloom + Location: 46,29 + Owner: Neutral + Actor12: light_inf + Location: 6,30 + Owner: Atreides + Actor13: quad + Location: 8,30 + Owner: Atreides + Actor14: light_inf + Location: 10,30 + Owner: Atreides + Actor15: light_inf + Location: 4,31 + Owner: Atreides + Actor16: combat_tank_a + Location: 11,31 + Owner: Atreides + Actor17: combat_tank_a + Location: 9,32 + Owner: Atreides + Actor18: combat_tank_a + Location: 10,32 + Owner: Atreides + Actor19: combat_tank_a + Location: 8,33 + Owner: Atreides + Actor20: light_inf + Location: 35,33 + Owner: Harkonnen + Actor21: light_inf + Location: 33,34 + Owner: Harkonnen + Actor22: spicebloom + Location: 59,34 + Owner: Neutral + Actor23: wall + Location: 42,35 + Owner: Harkonnen + Actor24: wall + Location: 43,35 + Owner: Harkonnen + Actor25: wall + Location: 44,35 + Owner: Harkonnen + Actor26: wall + Location: 45,35 + Owner: Harkonnen + Actor28: wall + Location: 46,36 + Owner: Harkonnen + Actor29: wall + Location: 46,37 + Owner: Harkonnen + Actor30: trooper + Location: 63,39 + Owner: Harkonnen + Actor31: trooper + Location: 51,40 + Owner: Harkonnen + Actor32: trooper + Location: 60,41 + Owner: Harkonnen + Actor35: light_inf + Location: 56,42 + Owner: Harkonnen + Actor37: light_inf + Location: 54,43 + Owner: Harkonnen + Actor38: wall + Location: 57,44 + Owner: Harkonnen + Actor39: wall + Location: 58,44 + Owner: Harkonnen + Actor40: wall + Location: 59,44 + Owner: Harkonnen + Actor41: wall + Location: 60,44 + Owner: Harkonnen + Actor42: wall + Location: 61,44 + Owner: Harkonnen + Actor43: wall + Location: 62,44 + Owner: Harkonnen + Actor44: wall + Location: 63,44 + Owner: Harkonnen + Actor45: wall + Location: 64,44 + Owner: Harkonnen + Actor46: wall + Location: 65,44 + Owner: Harkonnen + Actor48: wall + Location: 53,45 + Owner: Harkonnen + Actor49: wall + Location: 54,45 + Owner: Harkonnen + Actor50: wall + Location: 55,45 + Owner: Harkonnen + Actor51: wall + Location: 56,45 + Owner: Harkonnen + Actor52: wall + Location: 57,45 + Owner: Harkonnen + Actor55: wall + Location: 35,48 + Owner: Harkonnen + Actor56: wall + Location: 35,49 + Owner: Harkonnen + Actor58: wall + Location: 35,50 + Owner: Harkonnen + Actor64: wall + Location: 46,51 + Owner: Harkonnen + Actor65: nsfremen + Location: 59,51 + Owner: Fremen + Actor66: nsfremen + Location: 64,51 + Owner: Fremen + Actor67: trike + Location: 23,52 + Owner: Harkonnen + Actor68: wall + Location: 46,52 + Owner: Harkonnen + Actor69: nsfremen + Location: 60,52 + Owner: Fremen + Actor70: nsfremen + Location: 62,52 + Owner: Fremen + Actor71: wall + Location: 46,53 + Owner: Harkonnen + Actor72: nsfremen + Location: 63,53 + Owner: Fremen + Actor73: wall + Location: 46,54 + Owner: Harkonnen + Actor74: nsfremen + Location: 56,54 + Owner: Fremen + Actor75: nsfremen + Location: 57,54 + Owner: Fremen + Actor76: nsfremen + Location: 64,54 + Owner: Fremen + Actor77: wind_trap + Location: 40,55 + Owner: Harkonnen + Actor80: wall + Location: 46,55 + Owner: Harkonnen + Actor81: nsfremen + Location: 55,55 + Owner: Fremen + Actor82: wall + Location: 36,56 + Owner: Harkonnen + Actor83: wall + Location: 46,56 + Owner: Harkonnen + Actor84: nsfremen + Location: 55,56 + Owner: Fremen + Actor85: wall + Location: 36,57 + Owner: Harkonnen + Actor86: wall + Location: 46,57 + Owner: Harkonnen + Actor87: nsfremen + Location: 57,57 + Owner: Fremen + Actor88: nsfremen + Location: 60,57 + Owner: Fremen + Actor89: nsfremen + Location: 64,57 + Owner: Fremen + Actor90: wall + Location: 36,58 + Owner: Harkonnen + Actor91: wall + Location: 37,58 + Owner: Harkonnen + Actor92: wall + Location: 38,58 + Owner: Harkonnen + Actor93: wall + Location: 39,58 + Owner: Harkonnen + Actor94: wall + Location: 40,58 + Owner: Harkonnen + Actor95: wall + Location: 41,58 + Owner: Harkonnen + Actor96: wall + Location: 42,58 + Owner: Harkonnen + Actor97: wall + Location: 43,58 + Owner: Harkonnen + Actor98: wall + Location: 44,58 + Owner: Harkonnen + Actor99: wall + Location: 45,58 + Owner: Harkonnen + Actor100: wall + Location: 46,58 + Owner: Harkonnen + Actor101: nsfremen + Location: 55,59 + Owner: Fremen + Actor103: spicebloom + Location: 9,60 + Owner: Neutral + Actor104: nsfremen + Location: 55,61 + Owner: Fremen + Actor105: nsfremen + Location: 58,62 + Owner: Fremen + Actor106: nsfremen + Location: 65,63 + Owner: Fremen + HarkonnenOutpost: outpost + Location: 62,41 + Owner: Harkonnen + HarkonnenRefinery: refinery + Location: 43,43 + Owner: Harkonnen + HarkonnenHeavyFact: heavy_factory + Location: 43,46 + Owner: Harkonnen + HarkonnenTurret1: medium_gun_turret + Location: 46,35 + Owner: Harkonnen + HarkonnenTurret2: medium_gun_turret + Location: 35,47 + Owner: Harkonnen + HarkonnenBarracks: barracks + Location: 42,50 + Owner: Harkonnen + HarkonnenSilo1: silo + Location: 39,50 + Owner: Harkonnen + HarkonnenSilo2: silo + Location: 44,51 + Owner: Harkonnen + HarkonnenWindTrap1: wind_trap + Location: 37,49 + Owner: Harkonnen + HarkonnenWindTrap2: wind_trap + Location: 39,51 + Owner: Harkonnen + HarkonnenWindTrap3: wind_trap + Location: 42,55 + Owner: Harkonnen + HarkonnenWindTrap4: wind_trap + Location: 44,55 + Owner: Harkonnen + HarkonnenWindTrap5: wind_trap + Location: 52,42 + Owner: Harkonnen + AConyard: construction_yard + Location: 5,27 + Owner: Atreides + Sietch: sietch + Location: 62,59 + Owner: Fremen + HackyTile: tile475 + Location: 62,59 + Owner: Neutral + HarkonnenEntry1: waypoint + Owner: Neutral + Location: 65,35 + HarkonnenEntry2: waypoint + Owner: Neutral + Location: 14,65 + HarkonnenEntry3: waypoint + Owner: Neutral + Location: 31,2 + HarkonnenEntry4: waypoint + Owner: Neutral + Location: 10,2 + HarkonnenRally1: waypoint + Owner: Neutral + Location: 48,46 + HarkonnenRally2: waypoint + Owner: Neutral + Location: 16,39 + HarkonnenRally3: waypoint + Owner: Neutral + Location: 52,40 + HarkonnenRally4: waypoint + Owner: Neutral + Location: 8,6 + AtreidesEntry: waypoint + Owner: Neutral + Location: 2,18 + AtreidesRally: waypoint + Owner: Neutral + Location: 5,23 + Actor117: flame_tower + Owner: Harkonnen + Location: 52,45 + TurretFacing: 92 + Actor118: flame_tower + Owner: Harkonnen + Location: 46,50 + TurretFacing: 92 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml + +Sequences: sequences.yaml diff --git a/mtrsd2k/maps/atreides-04/rules.yaml b/mtrsd2k/maps/atreides-04/rules.yaml new file mode 100644 index 0000000..1f1ff33 --- /dev/null +++ b/mtrsd2k/maps/atreides-04/rules.yaml @@ -0,0 +1,48 @@ +Player: + PlayerResources: + DefaultCash: 6000 + +World: + LuaScript: + Scripts: atreides04.lua, atreides04-AI.lua + MissionData: + Briefing: Our scouts have discovered the hidden Fremen base. The Harkonnen blockade of the Fremen must be broken.\n\nPowerful Harkonnen forces to the South are massing for an assault on the Fremen. Heavy forces have been allocated to you to smash through the Harkonnen fortifications and come to the aid of the Fremen.\n\nGood luck.\n + BriefingVideo: A_BR04_E.VQA + MapOptions: + TechLevel: 3 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 + +sietch: + Exit: + ExitCell: 0,2 + Production: + Produces: Infantry + +nsfremen: + Buildable: + Prerequisites: ~sietch + AutoTarget: + InitialStanceAI: AttackAnything + +# HACK: AI units can't attack the sietch if it was on a real rock +tile475: + AlwaysVisible: + Immobile: + OccupiesSpace: false + RenderSprites: + Palette: d2k + WithSpriteBody: + BodyOrientation: + QuantizedFacings: 1 + AutoSelectionSize: diff --git a/mtrsd2k/maps/atreides-04/sequences.yaml b/mtrsd2k/maps/atreides-04/sequences.yaml new file mode 100644 index 0000000..59b1e2b --- /dev/null +++ b/mtrsd2k/maps/atreides-04/sequences.yaml @@ -0,0 +1,3 @@ +tile475: + idle: tile475.shp + Offset: 16,16 \ No newline at end of file diff --git a/mtrsd2k/maps/atreides-04/tile475.shp b/mtrsd2k/maps/atreides-04/tile475.shp new file mode 100644 index 0000000..c541ec7 Binary files /dev/null and b/mtrsd2k/maps/atreides-04/tile475.shp differ diff --git a/mtrsd2k/maps/atreides-05/atreides05-AI.lua b/mtrsd2k/maps/atreides-05/atreides05-AI.lua new file mode 100644 index 0000000..fcb1b36 --- /dev/null +++ b/mtrsd2k/maps/atreides-05/atreides05-AI.lua @@ -0,0 +1,159 @@ +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) } +} + +HarkonnenVehicleTypes = { "trike", "trike", "trike", "quad", "quad" } +HarkonnenTankType = { "combat_tank_h" } + +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] 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) + unit.AttackMove(HarkonnenAttackLocation) + IdleHunt(unit) + end) + + 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 Defending then + return + end + Defending = true + + -- Don't try to attack spiceblooms + if attacker and attacker.Type == "spicebloom" then + return + end + + local Guards = SetupAttackGroup() + + if #Guards <= 0 then + Defending = 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() Defending = false end) + end) +end + +InitAIUnits = function() + IdlingUnits = Reinforcements.ReinforceWithTransport(harkonnen, "carryall.reinforce", InitialHarkonnenReinforcements, HarkonnenPaths[1], { HarkonnenPaths[1][1] })[2] + + Utils.Do(HarkonnenBase, function(actor) + DefendActor(actor) + Trigger.OnDamaged(actor, function(building) + if building.Owner == harkonnen and building.Health < building.MaxHealth * 3/4 then + building.StartBuildingRepairs() + end + end) + end) +end + +ProduceVehicles = function() + if HarkonnenLightFactory.IsDead then + return + end + + if HoldProduction then + Trigger.AfterDelay(DateTime.Seconds(30), ProduceVehicles) + return + end + + local delay = Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) + local toBuild = { Utils.Random(HarkonnenVehicleTypes) } + harkonnen.Build(toBuild, function(unit) + IdlingUnits[#IdlingUnits + 1] = unit[1] + Trigger.AfterDelay(delay, ProduceVehicles) + + if #IdlingUnits >= (AttackGroupSize[Difficulty] * 2.5) then + SendAttack() + end + end) +end + +ProduceTanks = function() + if HarkonnenHeavyFactory.IsDead then + return + end + + if HoldProduction then + Trigger.AfterDelay(DateTime.Seconds(30), ProduceTanks) + return + end + + local delay = Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) + harkonnen.Build(HarkonnenTankType, function(unit) + IdlingUnits[#IdlingUnits + 1] = unit[1] + Trigger.AfterDelay(delay, ProduceTanks) + + if #IdlingUnits >= (AttackGroupSize[Difficulty] * 2.5) then + SendAttack() + end + end) +end + +ActivateAI = function() + harkonnen.Cash = 15000 + InitAIUnits() + + ProduceVehicles() + ProduceTanks() +end diff --git a/mtrsd2k/maps/atreides-05/atreides05.lua b/mtrsd2k/maps/atreides-05/atreides05.lua new file mode 100644 index 0000000..9eda732 --- /dev/null +++ b/mtrsd2k/maps/atreides-05/atreides05.lua @@ -0,0 +1,418 @@ +HarkonnenBase = { HarkonnenConstructionYard, HarkonnenBarracks, HarkonnenWindTrap1, HarkonnenWindTrap2, HarkonnenWindTrap3, HarkonnenWindTrap4, HarkonnenWindTrap5, HarkonnenWindTrap6, HarkonnenWindTrap7, HarkonnenWindTrap8, HarkonnenSilo1, HarkonnenSilo2, HarkonnenSilo3, HarkonnenSilo4, HarkonnenGunTurret1, HarkonnenGunTurret2, HarkonnenGunTurret3, HarkonnenGunTurret4, HarkonnenGunTurret5, HarkonnenGunTurret6, HarkonnenGunTurret7, HarkonnenHeavyFactory, HarkonnenRefinery, HarkonnenOutpost, HarkonnenLightFactory } +SmugglerBase = { SmugglerWindTrap1, SmugglerWindTrap2 } + +HarkonnenReinforcements = +{ + easy = + { + { "combat_tank_h", "trooper", "trooper", "trooper", "trooper" }, + { "trooper", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper" } + }, + + normal = + { + { "combat_tank_h", "trooper", "trooper", "trooper", "trooper" }, + { "trooper", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "combat_tank_h", "light_inf", "trooper", "trooper", "quad" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" } + }, + + hard = + { + { "combat_tank_h", "trooper", "trooper", "trooper", "trooper" }, + { "trooper", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "light_inf", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper" }, + { "combat_tank_h", "trike", "combat_tank_h", "light_inf", "trooper", "trooper", "quad" }, + { "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" }, + { "combat_tank_h", "combat_tank_h", "trike", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" }, + { "combat_tank_h", "combat_tank_h", "combat_tank_h", "combat_tank_h", "combat_tank_h", "combat_tank_h" } + } +} + +HarkonnenInfantryReinforcements = +{ + normal = + { + { "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper", "trooper" } + }, + + hard = + { + { "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper", "trooper", "trooper", "trooper", "trooper" }, + { "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper", "trooper" } + } +} +InfantryPath = { HarkonnenEntry3.Location } + +HarkonnenAttackDelay = +{ + easy = DateTime.Minutes(3), + normal = DateTime.Minutes(2) + DateTime.Seconds(20), + hard = DateTime.Minutes(1) +} + +HarkonnenAttackWaves = +{ + easy = 5, + normal = 7, + hard = 9 +} + +MercenaryReinforcements = +{ + easy = + { + { "combat_tank_o", "combat_tank_o", "quad", "quad", "light_inf", "light_inf", "light_inf", "light_inf" }, + { "trike", "trike", "quad", "quad", "quad", "trike", "trike", "trike" }, + { "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper" } + }, + + normal = + { + { "trike", "trike", "quad", "quad", "quad", "trike", "trike", "trike" }, + { "combat_tank_o", "combat_tank_o", "quad", "quad", "light_inf", "light_inf", "light_inf", "light_inf" }, + { "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_o", "combat_tank_o", "quad", "quad", "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper" }, + { "trike", "trike", "quad", "quad", "quad", "trike", "trike", "trike", "trike", "trike", "trike" }, + { "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper" } + }, + + hard = + { + { "combat_tank_o", "combat_tank_o", "quad", "quad", "light_inf", "light_inf", "light_inf", "light_inf" }, + { "trike", "trike", "quad", "quad", "quad", "trike", "trike", "trike" }, + { "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper" }, + { "trike", "trike", "quad", "quad", "quad", "trike", "trike", "trike", "trike", "trike", "trike" }, + { "combat_tank_o", "combat_tank_o", "quad", "quad", "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper" }, + { "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper" }, + { "combat_tank_o", "combat_tank_o", "quad", "quad", "trike", "trike", "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper" }, + { "trike", "trike", "quad", "quad", "quad", "trike", "trike", "trike", "trike", "trike", "trike", "quad", "quad" }, + { "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "combat_tank_o", "trike", "trike", "quad", "quad", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "trooper" } + } +} + +MercenaryAttackDelay = +{ + easy = DateTime.Minutes(2) + DateTime.Seconds(40), + normal = DateTime.Minutes(1) + DateTime.Seconds(50), + hard = DateTime.Minutes(1) + DateTime.Seconds(10) +} + +MercenaryAttackWaves = +{ + easy = 3, + normal = 6, + hard = 9 +} + +MercenarySpawn = { HarkonnenRally4.Location + CVec.New(2, -2) } + +-- Ordos tanks because those were intended for the Smugglers not the Atreides +ContrabandReinforcements = { "mcv", "quad", "quad", "combat_tank_o", "combat_tank_o", "combat_tank_o" } +SmugglerReinforcements = { "quad", "quad", "trike", "trike" } +InitialHarkonnenReinforcements = { "trooper", "trooper", "quad", "quad", "trike", "trike", "trike", "light_inf" } + +HarkonnenPaths = +{ + { HarkonnenEntry1.Location, HarkonnenRally1.Location }, + { HarkonnenEntry1.Location, HarkonnenRally3.Location }, + { HarkonnenEntry1.Location, HarkonnenRally2.Location }, + { HarkonnenEntry1.Location, HarkonnenRally4.Location }, + { HarkonnenEntry2.Location } +} + +AtreidesReinforcements = { "trike", "combat_tank_a" } +AtreidesPath = { AtreidesEntry.Location, AtreidesRally.Location } + +ContrabandTimes = +{ + easy = DateTime.Minutes(10), + normal = DateTime.Minutes(5), + hard = DateTime.Minutes(2) + DateTime.Seconds(30) +} + +wave = 0 +SendHarkonnen = function() + Trigger.AfterDelay(HarkonnenAttackDelay[Difficulty], function() + if player.IsObjectiveCompleted(KillHarkonnen) then + return + end + + wave = wave + 1 + + if InfantryReinforcements and wave % 4 == 0 then + local inf = Reinforcements.Reinforce(harkonnen, HarkonnenInfantryReinforcements[Difficulty][wave/4], InfantryPath) + Utils.Do(inf, function(unit) + unit.AttackMove(HarkonnenAttackLocation) + IdleHunt(unit) + end) + end + + local entryPath = Utils.Random(HarkonnenPaths) + local units = Reinforcements.ReinforceWithTransport(harkonnen, "carryall.reinforce", HarkonnenReinforcements[Difficulty][wave], entryPath, { entryPath[1] })[2] + Utils.Do(units, function(unit) + unit.AttackMove(HarkonnenAttackLocation) + IdleHunt(unit) + end) + + if wave < HarkonnenAttackWaves[Difficulty] then + SendHarkonnen() + end + end) +end + +mercWave = 0 +SendMercenaries = function() + Trigger.AfterDelay(MercenaryAttackDelay[Difficulty], function() + mercWave = mercWave + 1 + + Media.DisplayMessage("Incoming hostile Mercenary force detected.", "Mentat") + + local units = Reinforcements.Reinforce(mercenary, MercenaryReinforcements[Difficulty][mercWave], MercenarySpawn) + Utils.Do(units, function(unit) + unit.AttackMove(MercenaryAttackLocation1) + unit.AttackMove(MercenaryAttackLocation2) + IdleHunt(unit) + end) + + if mercWave < MercenaryAttackWaves[Difficulty] then + SendMercenaries() + return + end + + Trigger.AfterDelay(DateTime.Seconds(3), function() LastMercenariesArrived = true end) + end) +end + +SendContraband = function(owner) + ContrabandArrived = true + UserInterface.SetMissionText("The Contraband has arrived!", player.Color) + + local units = SmugglerReinforcements + if owner == player then + units = ContrabandReinforcements + end + + Reinforcements.ReinforceWithTransport(owner, "frigate", units, { ContrabandEntry.Location, Starport.Location + CVec.New(1, 1) }, { ContrabandExit.Location }) + + Trigger.AfterDelay(DateTime.Seconds(3), function() + if owner == player then + player.MarkCompletedObjective(CaptureStarport) + Media.DisplayMessage("Contraband has arrived and been confiscated.", "Mentat") + else + player.MarkFailedObjective(CaptureStarport) + Media.DisplayMessage("Smuggler contraband has arrived. It is too late to confiscate.", "Mentat") + end + end) + + UserInterface.SetMissionText("", player.Color) +end + +SmugglersAttack = function() + Utils.Do(SmugglerBase, function(building) + if not building.IsDead and building.Owner == smuggler then + building.Sell() + end + end) + + Trigger.AfterDelay(DateTime.Seconds(1), function() + Utils.Do(smuggler.GetGroundAttackers(), IdleHunt) + end) + + Trigger.AfterDelay(DateTime.Seconds(3), function() + KillSmuggler = player.AddSecondaryObjective("Destroy the Smugglers and their Mercenaries.") + SendMercenaries() + end) +end + +AttackNotifier = 0 +Tick = function() + if player.HasNoRequiredUnits() then + harkonnen.MarkCompletedObjective(KillAtreides) + end + + if LastMercenariesArrived and not player.IsObjectiveCompleted(KillSmuggler) and smuggler.HasNoRequiredUnits() and mercenary.HasNoRequiredUnits() then + Media.DisplayMessage("The Smugglers have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillSmuggler) + end + + if HarvesterKilled and DateTime.GameTime % DateTime.Seconds(30) then + local units = harkonnen.GetActorsByType("harvester") + + if #units > 0 then + HarvesterKilled = false + ProtectHarvester(units[1]) + end + end + + AttackNotifier = AttackNotifier - 1 + + if TimerTicks and not ContrabandArrived then + TimerTicks = TimerTicks - 1 + UserInterface.SetMissionText("The Contraband will arrive in " .. Utils.FormatTime(TimerTicks), player.Color) + + if TimerTicks <= 0 then + SendContraband(smuggler) + end + end +end + +WorldLoaded = function() + harkonnen = Player.GetPlayer("Harkonnen") + smuggler = Player.GetPlayer("Smugglers") + mercenary = Player.GetPlayer("Mercenaries") + player = Player.GetPlayer("Atreides") + + Difficulty = Map.LobbyOption("difficulty") + InfantryReinforcements = Difficulty ~= "easy" + + InitObjectives() + + Camera.Position = ARefinery.CenterPosition + HarkonnenAttackLocation = AtreidesRally.Location + MercenaryAttackLocation1 = Starport.Location + CVec.New(-16, 0) + MercenaryAttackLocation2 = Starport.Location + + Trigger.AfterDelay(DateTime.Seconds(2), function() + TimerTicks = ContrabandTimes[Difficulty] + Media.DisplayMessage("The Contraband is approaching the Starport to the north in " .. Utils.FormatTime(TimerTicks) .. ".", "Mentat") + end) + + Trigger.OnAllKilledOrCaptured(HarkonnenBase, function() + Utils.Do(harkonnen.GetGroundAttackers(), IdleHunt) + end) + + Trigger.OnKilled(Starport, function() + if not player.IsObjectiveCompleted(CaptureStarport) then + UserInterface.SetMissionText("Starport destroyed! Contraband can't land.", player.Color) + player.MarkFailedObjective(CaptureStarport) + SmugglersAttack() + end + + if DefendStarport then + player.MarkFailedObjective(DefendStarport) + end + end) + Trigger.OnDamaged(Starport, function() + if Starport.Owner ~= smuggler then + return + end + + if AttackNotifier <= 0 then + AttackNotifier = DateTime.Seconds(10) + Media.DisplayMessage("Don't destroy the Starport!", "Mentat") + + local defenders = smuggler.GetGroundAttackers() + if #defenders > 0 then + Utils.Do(defenders, function(unit) + unit.Guard(Starport) + end) + end + end + end) + Trigger.OnCapture(Starport, function() + DefendStarport = player.AddSecondaryObjective("Defend the captured Starport.") + SendContraband(player) + SmugglersAttack() + end) + + Trigger.OnKilled(HarkonnenBarracks, function() + player.MarkFailedObjective(CaptureBarracks) + end) + Trigger.OnDamaged(HarkonnenBarracks, function() + if AttackNotifier <= 0 and HarkonnenBarracks.Health < HarkonnenBarracks.MaxHealth * 3/4 then + AttackNotifier = DateTime.Seconds(10) + Media.DisplayMessage("Don't destroy the Barracks!", "Mentat") + end + end) + Trigger.OnCapture(HarkonnenBarracks, function() + Media.DisplayMessage("Hostages Released!", "Mentat") + + if DefendStarport then + player.MarkCompletedObjective(DefendStarport) + end + + Trigger.AfterDelay(DateTime.Seconds(1), function() + if harkonnen.HasNoRequiredUnits() then + Media.DisplayMessage("The Harkonnen have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillHarkonnen) + end + end) + + Trigger.AfterDelay(DateTime.Seconds(3), function() + player.MarkCompletedObjective(CaptureBarracks) + end) + end) + + SendHarkonnen() + Actor.Create("upgrade.light", true, { Owner = harkonnen }) + Actor.Create("upgrade.heavy", true, { Owner = harkonnen }) + Trigger.AfterDelay(0, ActivateAI) + + Trigger.AfterDelay(DateTime.Minutes(1), function() + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.ReinforceWithTransport(player, "carryall.reinforce", AtreidesReinforcements, AtreidesPath, { AtreidesPath[1] }) + end) + + local smugglerWaypoint = SmugglerWaypoint1.Location + Trigger.OnEnteredFootprint({ smugglerWaypoint + CVec.New(-2, 0), smugglerWaypoint + CVec.New(-1, 0), smugglerWaypoint, smugglerWaypoint + CVec.New(1, -1), smugglerWaypoint + CVec.New(2, -1), SmugglerWaypoint3.Location }, function(a, id) + if a.Owner == player and a.Type ~= "carryall" then + Trigger.RemoveFootprintTrigger(id) + Media.DisplayMessage("Stay away from our Starport.", "Smuggler Leader") + end + end) + + Trigger.OnEnteredFootprint({ SmugglerWaypoint2.Location }, function(a, id) + if a.Owner == player and a.Type ~= "carryall" then + Trigger.RemoveFootprintTrigger(id) + Media.DisplayMessage("You were warned. Now you will pay.", "Smuggler Leader") + Utils.Do(smuggler.GetGroundAttackers(), function(unit) + unit.AttackMove(SmugglerWaypoint2.Location) + end) + end + end) + + Trigger.OnEnteredProximityTrigger(HarkonnenBarracks.CenterPosition, WDist.New(5 * 1024), function(a, id) + if a.Owner == player and a.Type ~= "carryall" then + Trigger.RemoveProximityTrigger(id) + Media.DisplayMessage("Capture the Harkonnen barracks to release the hostages.", "Mentat") + end + end) +end + +InitObjectives = function() + Trigger.OnObjectiveAdded(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective") + end) + + KillAtreides = harkonnen.AddPrimaryObjective("Kill all Atreides units.") + CaptureBarracks = player.AddPrimaryObjective("Capture the Barracks at Sietch Tabr.") + KillHarkonnen = player.AddSecondaryObjective("Annihilate all Harkonnen units and reinforcements.") + CaptureStarport = player.AddSecondaryObjective("Capture the Smuggler Starport and\nconfiscate the Contraband.") + + 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/mtrsd2k/maps/atreides-05/map.bin b/mtrsd2k/maps/atreides-05/map.bin new file mode 100644 index 0000000..0d0bd8d Binary files /dev/null and b/mtrsd2k/maps/atreides-05/map.bin differ diff --git a/mtrsd2k/maps/atreides-05/map.png b/mtrsd2k/maps/atreides-05/map.png new file mode 100644 index 0000000..1457d60 Binary files /dev/null and b/mtrsd2k/maps/atreides-05/map.png differ diff --git a/mtrsd2k/maps/atreides-05/map.yaml b/mtrsd2k/maps/atreides-05/map.yaml new file mode 100644 index 0000000..1282300 --- /dev/null +++ b/mtrsd2k/maps/atreides-05/map.yaml @@ -0,0 +1,366 @@ +MapFormat: 11 + +RequiresMod: mtrsd2k + +Title: Atreides 05 + +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 + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + Enemies: Harkonnen, Atreides, Mercenaries, Smugglers + PlayerReference@Atreides: + Name: Atreides + Playable: True + LockFaction: True + Faction: atreides + LockColor: True + Color: 9191FF + Enemies: Harkonnen, Smugglers, Mercenaries, Creeps + PlayerReference@Harkonnen: + Name: Harkonnen + LockFaction: True + Faction: harkonnen + LockColor: True + Color: FE0000 + Enemies: Atreides + PlayerReference@Smugglers: + Name: Smugglers + LockFaction: True + Faction: smuggler + LockColor: True + Color: 542209 + Allies: Mercenaries + Enemies: Atreides + PlayerReference@Mercenaries: + Name: Mercenaries + LockFaction: True + Faction: mercenary + LockColor: True + Color: DDDD00 + Allies: Smugglers + Enemies: Atreides + +Actors: + Actor4: trike + Location: 44,3 + Owner: Harkonnen + Actor5: wormspawner + Location: 58,5 + Owner: Creeps + Actor6: wall + Location: 4,7 + Owner: Harkonnen + Actor7: wall + Location: 2,8 + Owner: Harkonnen + Actor8: wall + Location: 3,8 + Owner: Harkonnen + Actor10: spicebloom + Location: 15,8 + Owner: Neutral + Actor12: wall + Location: 26,11 + Owner: Harkonnen + Actor15: wall + Location: 27,12 + Owner: Harkonnen + Actor16: spicebloom + Location: 38,12 + Owner: Neutral + Actor20: spicebloom + Location: 4,15 + Owner: Neutral + Actor28: carryall + Location: 26,18 + Owner: Harkonnen + Actor29: spicebloom + Location: 49,18 + Owner: Neutral + Actor33: wall + Location: 31,19 + Owner: Harkonnen + Actor34: wall + Location: 32,19 + Owner: Harkonnen + Actor35: wall + Location: 33,19 + Owner: Harkonnen + Actor38: wall + Location: 34,20 + Owner: Harkonnen + Actor41: wall + Location: 8,23 + Owner: Harkonnen + Actor43: wall + Location: 10,23 + Owner: Harkonnen + Actor45: wall + Location: 24,23 + Owner: Harkonnen + Actor46: wall + Location: 22,24 + Owner: Harkonnen + Actor47: wall + Location: 23,24 + Owner: Harkonnen + Actor49: light_inf + Location: 59,24 + Owner: Smugglers + Actor50: wall + Location: 20,25 + Owner: Harkonnen + Actor51: wall + Location: 21,25 + Owner: Harkonnen + Actor52: wall + Location: 22,25 + Owner: Harkonnen + Actor53: light_inf + Location: 60,25 + Owner: Smugglers + Actor54: trooper + Location: 62,25 + Owner: Smugglers + Actor56: wall + Location: 16,26 + Owner: Harkonnen + Actor57: wall + Location: 17,26 + Owner: Harkonnen + Actor58: wall + Location: 18,26 + Owner: Harkonnen + Actor59: wall + Location: 19,26 + Owner: Harkonnen + Actor60: wall + Location: 20,26 + Owner: Harkonnen + Actor61: light_inf + Location: 58,26 + Owner: Smugglers + Actor62: light_inf + Location: 60,27 + Owner: Smugglers + Actor63: trooper + Location: 47,30 + Owner: Harkonnen + Actor64: trooper + Location: 42,31 + Owner: Harkonnen + Actor65: trooper + Location: 37,32 + Owner: Harkonnen + Actor66: trooper + Location: 27,38 + Owner: Harkonnen + Actor67: spicebloom + Location: 21,41 + Owner: Neutral + Actor68: trooper + Location: 26,41 + Owner: Harkonnen + Actor69: spicebloom + Location: 46,42 + Owner: Neutral + Actor70: quad + Location: 58,43 + Owner: Harkonnen + Actor71: spicebloom + Location: 23,46 + Owner: Neutral + Actor72: light_factory + Location: 48,50 + Owner: Atreides + Actor73: spicebloom + Location: 39,51 + Owner: Neutral + Actor74: barracks + Location: 52,52 + Owner: Atreides + Actor75: trike + Location: 54,52 + Owner: Atreides + Actor76: quad + Location: 51,53 + Owner: Atreides + Actor77: light_inf + Location: 57,53 + Owner: Atreides + Actor79: wind_trap + Location: 59,54 + Owner: Atreides + Actor80: wind_trap + Location: 62,54 + Owner: Atreides + Actor81: trooper + Location: 51,55 + Owner: Atreides + Actor84: light_inf + Location: 57,57 + Owner: Atreides + Actor85: light_inf + Location: 52,58 + Owner: Atreides + Actor86: spicebloom + Location: 17,62 + Owner: Neutral + HarkonnenConstructionYard: construction_yard + Location: 9,11 + Owner: Harkonnen + HarkonnenBarracks: barracks + Location: 3,3 + Owner: Harkonnen + HarkonnenWindTrap1: wind_trap + Location: 5,3 + Owner: Harkonnen + HarkonnenWindTrap2: wind_trap + Location: 7,3 + Owner: Harkonnen + HarkonnenWindTrap3: wind_trap + Location: 23,12 + Owner: Harkonnen + HarkonnenWindTrap4: wind_trap + Location: 26,14 + Owner: Harkonnen + HarkonnenWindTrap5: wind_trap + Location: 13,17 + Owner: Harkonnen + HarkonnenWindTrap6: wind_trap + Location: 10,19 + Owner: Harkonnen + HarkonnenWindTrap7: wind_trap + Location: 20,19 + Owner: Harkonnen + HarkonnenWindTrap8: wind_trap + Location: 18,23 + Owner: Harkonnen + HarkonnenSilo1: silo + Location: 9,3 + Owner: Harkonnen + HarkonnenSilo2: silo + Location: 18,15 + Owner: Harkonnen + HarkonnenSilo3: silo + Location: 21,18 + Owner: Harkonnen + HarkonnenSilo4: silo + Location: 26,13 + Owner: Harkonnen + HarkonnenGunTurret1: medium_gun_turret + Location: 4,8 + Owner: Harkonnen + HarkonnenGunTurret2: medium_gun_turret + Location: 27,11 + Owner: Harkonnen + HarkonnenGunTurret3: medium_gun_turret + Location: 33,20 + Owner: Harkonnen + HarkonnenGunTurret4: medium_gun_turret + Location: 35,20 + Owner: Harkonnen + HarkonnenGunTurret5: medium_gun_turret + Location: 9,23 + Owner: Harkonnen + HarkonnenGunTurret6: medium_gun_turret + Location: 24,24 + Owner: Harkonnen + HarkonnenGunTurret7: medium_gun_turret + Location: 15,26 + Owner: Harkonnen + HarkonnenHeavyFactory: heavy_factory + Location: 19,14 + Owner: Harkonnen + HarkonnenRefinery: refinery + Location: 23,15 + Owner: Harkonnen + HarkonnenOutpost: outpost + Location: 16,17 + Owner: Harkonnen + HarkonnenLightFactory: light_factory + Location: 17,20 + Owner: Harkonnen + Starport: starport + Location: 61,21 + Owner: Smugglers + SmugglerWindTrap1: wind_trap + Location: 61,17 + Owner: Smugglers + SmugglerWindTrap2: wind_trap + Location: 63,17 + Owner: Smugglers + ARefinery: refinery + Location: 54,57 + Owner: Atreides + Engineer1: engineer + Location: 56,54 + Owner: Atreides + Engineer2: engineer + Location: 52,56 + Owner: Atreides + ContrabandEntry: waypoint + Owner: Neutral + Location: 65,22 + ContrabandExit: waypoint + Owner: Neutral + Location: 2,22 + SmugglerWaypoint1: waypoint + Owner: Neutral + Location: 51,28 + SmugglerWaypoint2: waypoint + Owner: Neutral + Location: 57,29 + SmugglerWaypoint3: waypoint + Owner: Neutral + Location: 64,36 + AtreidesEntry: waypoint + Owner: Neutral + Location: 65,58 + AtreidesRally: waypoint + Owner: Neutral + Location: 54,56 + HarkonnenEntry1: waypoint + Owner: Neutral + Location: 25,2 + HarkonnenEntry2: waypoint + Owner: Neutral + Location: 38,65 + HarkonnenEntry3: waypoint + Owner: Neutral + Location: 65,32 + HarkonnenRally1: waypoint + Owner: Neutral + Location: 12,9 + HarkonnenRally2: waypoint + Owner: Neutral + Location: 39,41 + HarkonnenRally3: waypoint + Owner: Neutral + Location: 36,46 + HarkonnenRally4: waypoint + Owner: Neutral + Location: 49,4 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/atreides-05/rules.yaml b/mtrsd2k/maps/atreides-05/rules.yaml new file mode 100644 index 0000000..fdfef99 --- /dev/null +++ b/mtrsd2k/maps/atreides-05/rules.yaml @@ -0,0 +1,33 @@ +Player: + PlayerResources: + DefaultCash: 7000 + +World: + LuaScript: + Scripts: atreides05.lua, atreides05-AI.lua + MissionData: + Briefing: According to our spies, the Fremen are being held at the far Northwest corner of Sietch Tabr. Push your way through the Harkonnen ranks to rescue the hostages.\n\nScout the terrain before you launch the main assault. Our Engineers must reach the Barracks and capture it intact. The rest of the base can be razed to the ground.\n\nAdditionally, there are rumors of an illegal Smuggling operation in the area. A large shipment of contraband is expected at the Smuggler's Starport. If you can the Starport before the contraband arrives, you will be able to confiscate it for the Atreides war effort.\n\nBe warned, the Smugglers have Mercenary allies who may assist them if you interfere.\n\nGood luck.\n + BriefingVideo: A_BR05_E.VQA + MapOptions: + TechLevel: 4 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 + +frigate: + Aircraft: + LandableTerrainTypes: Sand, Rock, Transition, Spice, SpiceSand, Dune, Concrete + CanHover: true # The firgate would teleport to land otherwise + +combat_tank_o.starport: + Buildable: + Prerequisites: starport diff --git a/mtrsd2k/maps/harkonnen-01a/harkonnen01a.lua b/mtrsd2k/maps/harkonnen-01a/harkonnen01a.lua new file mode 100644 index 0000000..d0214bd --- /dev/null +++ b/mtrsd2k/maps/harkonnen-01a/harkonnen01a.lua @@ -0,0 +1,157 @@ + +AtreidesReinforcements = { } +AtreidesReinforcements["easy"] = +{ + { "light_inf", "light_inf" } +} + +AtreidesReinforcements["normal"] = +{ + { "light_inf", "light_inf" }, + { "light_inf", "light_inf", "light_inf" }, + { "light_inf", "trike" }, +} + +AtreidesReinforcements["hard"] = +{ + { "light_inf", "light_inf" }, + { "trike", "trike" }, + { "light_inf", "light_inf", "light_inf" }, + { "light_inf", "trike" }, + { "trike", "trike" } +} + +AtreidesEntryWaypoints = { AtreidesWaypoint1.Location, AtreidesWaypoint2.Location, AtreidesWaypoint3.Location, AtreidesWaypoint4.Location } +AtreidesAttackDelay = DateTime.Seconds(30) + +AtreidesAttackWaves = { } +AtreidesAttackWaves["easy"] = 1 +AtreidesAttackWaves["normal"] = 5 +AtreidesAttackWaves["hard"] = 12 + +ToHarvest = { } +ToHarvest["easy"] = 2500 +ToHarvest["normal"] = 3000 +ToHarvest["hard"] = 3500 + +HarkonnenReinforcements = { "light_inf", "light_inf", "light_inf", "trike" } +HarkonnenEntryPath = { HarkonnenWaypoint.Location, HarkonnenRally.Location } + +Messages = +{ + "Build a concrete foundation before placing your buildings.", + "Build a Wind Trap for power.", + "Build a Refinery to collect Spice.", + "Build a Silo to store additional Spice." +} + + +IdleHunt = function(actor) + if not actor.IsDead then + Trigger.OnIdle(actor, actor.Hunt) + end +end + +Tick = function() + if AtreidesArrived and atreides.HasNoRequiredUnits() then + player.MarkCompletedObjective(KillAtreides) + end + + if player.Resources > ToHarvest[Map.LobbyOption("difficulty")] - 1 then + player.MarkCompletedObjective(GatherSpice) + end + + -- player has no Wind Trap + if (player.PowerProvided <= 20 or player.PowerState ~= "Normal") and DateTime.GameTime % DateTime.Seconds(32) == 0 then + HasPower = false + Media.DisplayMessage(Messages[2], "Mentat") + else + HasPower = true + end + + -- player has no Refinery and no Silos + if HasPower and player.ResourceCapacity == 0 and DateTime.GameTime % DateTime.Seconds(32) == 0 then + Media.DisplayMessage(Messages[3], "Mentat") + end + + if HasPower and player.Resources > player.ResourceCapacity * 0.8 and DateTime.GameTime % DateTime.Seconds(32) == 0 then + Media.DisplayMessage(Messages[4], "Mentat") + end + + UserInterface.SetMissionText("Harvested resources: " .. player.Resources .. "/" .. ToHarvest[Map.LobbyOption("difficulty")], player.Color) +end + +WorldLoaded = function() + player = Player.GetPlayer("Harkonnen") + atreides = Player.GetPlayer("Atreides") + + InitObjectives() + + Trigger.OnRemovedFromWorld(HarkonnenConyard, function() + local refs = Utils.Where(Map.ActorsInWorld, function(actor) return actor.Type == "refinery" end) + + if #refs == 0 then + atreides.MarkCompletedObjective(KillHarkonnen) + else + Trigger.OnAllRemovedFromWorld(refs, function() + atreides.MarkCompletedObjective(KillHarkonnen) + end) + end + end) + + Media.DisplayMessage(Messages[1], "Mentat") + + Trigger.AfterDelay(DateTime.Seconds(25), function() + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.Reinforce(player, HarkonnenReinforcements, HarkonnenEntryPath) + end) + + WavesLeft = AtreidesAttackWaves[Map.LobbyOption("difficulty")] + SendReinforcements() +end + +SendReinforcements = function() + local units = AtreidesReinforcements[Map.LobbyOption("difficulty")] + local delay = Utils.RandomInteger(AtreidesAttackDelay - DateTime.Seconds(2), AtreidesAttackDelay) + AtreidesAttackDelay = AtreidesAttackDelay - (#units * 3 - 3 - WavesLeft) * DateTime.Seconds(1) + if AtreidesAttackDelay < 0 then AtreidesAttackDelay = 0 end + + Trigger.AfterDelay(delay, function() + Reinforcements.Reinforce(atreides, Utils.Random(units), { Utils.Random(AtreidesEntryWaypoints) }, 10, IdleHunt) + + WavesLeft = WavesLeft - 1 + if WavesLeft == 0 then + Trigger.AfterDelay(DateTime.Seconds(1), function() AtreidesArrived = true end) + else + SendReinforcements() + end + end) +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.") + GatherSpice = player.AddPrimaryObjective("Harvest " .. tostring(ToHarvest[Map.LobbyOption("difficulty")]) .. " Solaris worth of Spice.") + KillAtreides = player.AddSecondaryObjective("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/mtrsd2k/maps/harkonnen-01a/map.bin b/mtrsd2k/maps/harkonnen-01a/map.bin new file mode 100644 index 0000000..a673ff9 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-01a/map.bin differ diff --git a/mtrsd2k/maps/harkonnen-01a/map.png b/mtrsd2k/maps/harkonnen-01a/map.png new file mode 100644 index 0000000..e1747f1 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-01a/map.png differ diff --git a/mtrsd2k/maps/harkonnen-01a/map.yaml b/mtrsd2k/maps/harkonnen-01a/map.yaml new file mode 100644 index 0000000..8590dba --- /dev/null +++ b/mtrsd2k/maps/harkonnen-01a/map.yaml @@ -0,0 +1,109 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Harkonnen 01a + +Author: Westwood Studios + +Tileset: ARRAKIS + +MapSize: 32,28 + +Bounds: 2,2,28,24 + +Visibility: MissionSelector + +Categories: Campaign + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + 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: 7,2 + Owner: Atreides + Actor1: light_inf + Location: 14,3 + Owner: Atreides + Actor2: light_inf + Location: 21,3 + Owner: Atreides + Actor3: wormspawner + Location: 18,4 + Owner: Creeps + Actor4: light_inf + Location: 21,6 + Owner: Atreides + Actor5: light_inf + Location: 5,10 + Owner: Harkonnen + Actor6: light_inf + Location: 8,12 + Owner: Harkonnen + HarkonnenConyard: construction_yard + Location: 7,14 + Owner: Harkonnen + Actor8: light_inf + Location: 13,14 + Owner: Harkonnen + Actor9: trike + Location: 4,16 + Owner: Harkonnen + Actor10: light_inf + Location: 12,17 + Owner: Harkonnen + Actor11: light_inf + Location: 7,18 + Owner: Harkonnen + Actor12: trike + Location: 11,19 + Owner: Harkonnen + Actor13: light_inf + Location: 28,19 + Owner: Atreides + HarkonnenRally: waypoint + Owner: Neutral + Location: 12,12 + HarkonnenWaypoint: waypoint + Owner: Neutral + Location: 2,12 + AtreidesWaypoint1: waypoint + Owner: Neutral + Location: 20,2 + AtreidesWaypoint2: waypoint + Owner: Neutral + Location: 18,25 + AtreidesWaypoint3: waypoint + Owner: Neutral + Location: 29,21 + AtreidesWaypoint4: waypoint + Owner: Neutral + Location: 29,7 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/harkonnen-01a/rules.yaml b/mtrsd2k/maps/harkonnen-01a/rules.yaml new file mode 100644 index 0000000..b92677c --- /dev/null +++ b/mtrsd2k/maps/harkonnen-01a/rules.yaml @@ -0,0 +1,32 @@ +Player: + PlayerResources: + DefaultCash: 2300 + +World: + LuaScript: + Scripts: harkonnen01a.lua + MissionData: + Briefing: Mine the Spice from the Imperial Basin, before the Atreides try to seize the area. Execute anyone who tries to stop you.\n\nBuild Silos to store the Spice. Remember to place buildings on concrete foundations to prevent erosion and gradual decline. To build without concrete would be inefficient. Inefficiency will not be tolerated.\n + BriefingVideo: H_BR01_E.VQA + MapOptions: + TechLevel: 1 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +barracks: + Buildable: + Prerequisites: ~disabled + +light_factory: + Buildable: + Prerequisites: ~disabled + +outpost: + Buildable: + Prerequisites: ~disabled diff --git a/mtrsd2k/maps/harkonnen-01b/harkonnen01b.lua b/mtrsd2k/maps/harkonnen-01b/harkonnen01b.lua new file mode 100644 index 0000000..d0214bd --- /dev/null +++ b/mtrsd2k/maps/harkonnen-01b/harkonnen01b.lua @@ -0,0 +1,157 @@ + +AtreidesReinforcements = { } +AtreidesReinforcements["easy"] = +{ + { "light_inf", "light_inf" } +} + +AtreidesReinforcements["normal"] = +{ + { "light_inf", "light_inf" }, + { "light_inf", "light_inf", "light_inf" }, + { "light_inf", "trike" }, +} + +AtreidesReinforcements["hard"] = +{ + { "light_inf", "light_inf" }, + { "trike", "trike" }, + { "light_inf", "light_inf", "light_inf" }, + { "light_inf", "trike" }, + { "trike", "trike" } +} + +AtreidesEntryWaypoints = { AtreidesWaypoint1.Location, AtreidesWaypoint2.Location, AtreidesWaypoint3.Location, AtreidesWaypoint4.Location } +AtreidesAttackDelay = DateTime.Seconds(30) + +AtreidesAttackWaves = { } +AtreidesAttackWaves["easy"] = 1 +AtreidesAttackWaves["normal"] = 5 +AtreidesAttackWaves["hard"] = 12 + +ToHarvest = { } +ToHarvest["easy"] = 2500 +ToHarvest["normal"] = 3000 +ToHarvest["hard"] = 3500 + +HarkonnenReinforcements = { "light_inf", "light_inf", "light_inf", "trike" } +HarkonnenEntryPath = { HarkonnenWaypoint.Location, HarkonnenRally.Location } + +Messages = +{ + "Build a concrete foundation before placing your buildings.", + "Build a Wind Trap for power.", + "Build a Refinery to collect Spice.", + "Build a Silo to store additional Spice." +} + + +IdleHunt = function(actor) + if not actor.IsDead then + Trigger.OnIdle(actor, actor.Hunt) + end +end + +Tick = function() + if AtreidesArrived and atreides.HasNoRequiredUnits() then + player.MarkCompletedObjective(KillAtreides) + end + + if player.Resources > ToHarvest[Map.LobbyOption("difficulty")] - 1 then + player.MarkCompletedObjective(GatherSpice) + end + + -- player has no Wind Trap + if (player.PowerProvided <= 20 or player.PowerState ~= "Normal") and DateTime.GameTime % DateTime.Seconds(32) == 0 then + HasPower = false + Media.DisplayMessage(Messages[2], "Mentat") + else + HasPower = true + end + + -- player has no Refinery and no Silos + if HasPower and player.ResourceCapacity == 0 and DateTime.GameTime % DateTime.Seconds(32) == 0 then + Media.DisplayMessage(Messages[3], "Mentat") + end + + if HasPower and player.Resources > player.ResourceCapacity * 0.8 and DateTime.GameTime % DateTime.Seconds(32) == 0 then + Media.DisplayMessage(Messages[4], "Mentat") + end + + UserInterface.SetMissionText("Harvested resources: " .. player.Resources .. "/" .. ToHarvest[Map.LobbyOption("difficulty")], player.Color) +end + +WorldLoaded = function() + player = Player.GetPlayer("Harkonnen") + atreides = Player.GetPlayer("Atreides") + + InitObjectives() + + Trigger.OnRemovedFromWorld(HarkonnenConyard, function() + local refs = Utils.Where(Map.ActorsInWorld, function(actor) return actor.Type == "refinery" end) + + if #refs == 0 then + atreides.MarkCompletedObjective(KillHarkonnen) + else + Trigger.OnAllRemovedFromWorld(refs, function() + atreides.MarkCompletedObjective(KillHarkonnen) + end) + end + end) + + Media.DisplayMessage(Messages[1], "Mentat") + + Trigger.AfterDelay(DateTime.Seconds(25), function() + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.Reinforce(player, HarkonnenReinforcements, HarkonnenEntryPath) + end) + + WavesLeft = AtreidesAttackWaves[Map.LobbyOption("difficulty")] + SendReinforcements() +end + +SendReinforcements = function() + local units = AtreidesReinforcements[Map.LobbyOption("difficulty")] + local delay = Utils.RandomInteger(AtreidesAttackDelay - DateTime.Seconds(2), AtreidesAttackDelay) + AtreidesAttackDelay = AtreidesAttackDelay - (#units * 3 - 3 - WavesLeft) * DateTime.Seconds(1) + if AtreidesAttackDelay < 0 then AtreidesAttackDelay = 0 end + + Trigger.AfterDelay(delay, function() + Reinforcements.Reinforce(atreides, Utils.Random(units), { Utils.Random(AtreidesEntryWaypoints) }, 10, IdleHunt) + + WavesLeft = WavesLeft - 1 + if WavesLeft == 0 then + Trigger.AfterDelay(DateTime.Seconds(1), function() AtreidesArrived = true end) + else + SendReinforcements() + end + end) +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.") + GatherSpice = player.AddPrimaryObjective("Harvest " .. tostring(ToHarvest[Map.LobbyOption("difficulty")]) .. " Solaris worth of Spice.") + KillAtreides = player.AddSecondaryObjective("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/mtrsd2k/maps/harkonnen-01b/map.bin b/mtrsd2k/maps/harkonnen-01b/map.bin new file mode 100644 index 0000000..41d86ce Binary files /dev/null and b/mtrsd2k/maps/harkonnen-01b/map.bin differ diff --git a/mtrsd2k/maps/harkonnen-01b/map.png b/mtrsd2k/maps/harkonnen-01b/map.png new file mode 100644 index 0000000..58fec79 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-01b/map.png differ diff --git a/mtrsd2k/maps/harkonnen-01b/map.yaml b/mtrsd2k/maps/harkonnen-01b/map.yaml new file mode 100644 index 0000000..3e5b07b --- /dev/null +++ b/mtrsd2k/maps/harkonnen-01b/map.yaml @@ -0,0 +1,109 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Harkonnen 01b + +Author: Westwood Studios + +Tileset: ARRAKIS + +MapSize: 32,28 + +Bounds: 2,2,28,24 + +Visibility: MissionSelector + +Categories: Campaign + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + 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: 3,2 + Owner: Atreides + Actor1: light_inf + Location: 19,2 + Owner: Atreides + Actor2: wormspawner + Location: 21,2 + Owner: Creeps + Actor3: spicebloom.spawnpoint + Location: 3,12 + Owner: Neutral + Actor4: light_inf + Location: 12,12 + Owner: Harkonnen + Actor5: light_inf + Location: 14,12 + Owner: Harkonnen + Actor6: light_inf + Location: 16,12 + Owner: Harkonnen + HarkonnenConyard: construction_yard + Location: 13,13 + Owner: Harkonnen + Actor8: trike + Location: 12,14 + Owner: Harkonnen + Actor9: trike + Location: 16,14 + Owner: Harkonnen + Actor10: light_inf + Location: 12,16 + Owner: Harkonnen + Actor11: light_inf + Location: 14,16 + Owner: Harkonnen + Actor12: light_inf + Location: 16,16 + Owner: Harkonnen + Actor13: light_inf + Location: 26,25 + Owner: Atreides + HarkonnenRally: waypoint + Owner: Neutral + Location: 15,8 + HarkonnenWaypoint: waypoint + Owner: Neutral + Location: 15,2 + AtreidesWaypoint1: waypoint + Owner: Neutral + Location: 29,24 + AtreidesWaypoint2: waypoint + Owner: Neutral + Location: 5,25 + AtreidesWaypoint3: waypoint + Owner: Neutral + Location: 29,19 + AtreidesWaypoint4: waypoint + Owner: Neutral + Location: 28,2 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/harkonnen-01b/rules.yaml b/mtrsd2k/maps/harkonnen-01b/rules.yaml new file mode 100644 index 0000000..0df6b80 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-01b/rules.yaml @@ -0,0 +1,32 @@ +Player: + PlayerResources: + DefaultCash: 2300 + +World: + LuaScript: + Scripts: harkonnen01b.lua + MissionData: + Briefing: Mine the Spice from the Imperial Basin, before the Atreides try to seize the area. Execute anyone who tries to stop you.\n\nBuild Silos to store the Spice. Remember to place buildings on concrete foundations to prevent erosion and gradual decline. To build without concrete would be inefficient. Inefficiency will not be tolerated.\n + BriefingVideo: H_BR01_E.VQA + MapOptions: + TechLevel: 1 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +barracks: + Buildable: + Prerequisites: ~disabled + +light_factory: + Buildable: + Prerequisites: ~disabled + +outpost: + Buildable: + Prerequisites: ~disabled diff --git a/mtrsd2k/maps/harkonnen-02a/harkonnen02a-AI.lua b/mtrsd2k/maps/harkonnen-02a/harkonnen02a-AI.lua new file mode 100644 index 0000000..f244735 --- /dev/null +++ b/mtrsd2k/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/mtrsd2k/maps/harkonnen-02a/harkonnen02a.lua b/mtrsd2k/maps/harkonnen-02a/harkonnen02a.lua new file mode 100644 index 0000000..1eaf553 --- /dev/null +++ b/mtrsd2k/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/mtrsd2k/maps/harkonnen-02a/map.bin b/mtrsd2k/maps/harkonnen-02a/map.bin new file mode 100644 index 0000000..37f5e82 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-02a/map.bin differ diff --git a/mtrsd2k/maps/harkonnen-02a/map.png b/mtrsd2k/maps/harkonnen-02a/map.png new file mode 100644 index 0000000..7ebbf1b Binary files /dev/null and b/mtrsd2k/maps/harkonnen-02a/map.png differ diff --git a/mtrsd2k/maps/harkonnen-02a/map.yaml b/mtrsd2k/maps/harkonnen-02a/map.yaml new file mode 100644 index 0000000..5c22a78 --- /dev/null +++ b/mtrsd2k/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/mtrsd2k/maps/harkonnen-02a/rules.yaml b/mtrsd2k/maps/harkonnen-02a/rules.yaml new file mode 100644 index 0000000..03697c2 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-02a/rules.yaml @@ -0,0 +1,24 @@ +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: 1 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 diff --git a/mtrsd2k/maps/harkonnen-02b/harkonnen02b-AI.lua b/mtrsd2k/maps/harkonnen-02b/harkonnen02b-AI.lua new file mode 100644 index 0000000..8c701fb --- /dev/null +++ b/mtrsd2k/maps/harkonnen-02b/harkonnen02b-AI.lua @@ -0,0 +1,150 @@ +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" } +AtreidesVehicleTypes = { "trike" } + +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 + +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) + + -- Finish the upgrades first before trying to build something + Trigger.AfterDelay(DateTime.Seconds(14), function() + ProduceInfantry() + ProduceVehicles() + end) +end diff --git a/mtrsd2k/maps/harkonnen-02b/harkonnen02b.lua b/mtrsd2k/maps/harkonnen-02b/harkonnen02b.lua new file mode 100644 index 0000000..adbe859 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-02b/harkonnen02b.lua @@ -0,0 +1,125 @@ + +AtreidesBase = { AConyard, APower1, APower2, ABarracks, ALightFactory } + +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/mtrsd2k/maps/harkonnen-02b/map.bin b/mtrsd2k/maps/harkonnen-02b/map.bin new file mode 100644 index 0000000..93972c7 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-02b/map.bin differ diff --git a/mtrsd2k/maps/harkonnen-02b/map.png b/mtrsd2k/maps/harkonnen-02b/map.png new file mode 100644 index 0000000..d8fbb64 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-02b/map.png differ diff --git a/mtrsd2k/maps/harkonnen-02b/map.yaml b/mtrsd2k/maps/harkonnen-02b/map.yaml new file mode 100644 index 0000000..86be216 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-02b/map.yaml @@ -0,0 +1,147 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Harkonnen 02b + +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: + HConyard: construction_yard + Location: 3,3 + Owner: Harkonnen + Actor1: light_inf + Location: 8,3 + Owner: Harkonnen + Actor2: trike + Location: 11,4 + Owner: Harkonnen + Actor3: trike + Location: 9,5 + Owner: Harkonnen + Actor4: light_inf + Location: 6,7 + Owner: Harkonnen + Actor5: light_inf + Location: 8,7 + Owner: Harkonnen + Actor6: trike + Location: 2,8 + Owner: Harkonnen + Actor7: trike + Location: 23,8 + Owner: Atreides + Actor8: light_inf + Location: 5,9 + Owner: Harkonnen + Actor9: trike + Location: 33,17 + Owner: Atreides + Actor10: wormspawner + Location: 24,24 + Owner: Creeps + Actor11: wormspawner + Location: 39,30 + Owner: Creeps + Actor12: light_inf + Location: 44,35 + Owner: Atreides + Actor13: trike + Location: 44,37 + Owner: Atreides + Actor14: trike + Location: 46,37 + Owner: Atreides + Actor15: light_inf + Location: 44,39 + Owner: Atreides + Actor16: light_inf + Location: 39,40 + Owner: Atreides + Actor17: trike + Location: 41,40 + Owner: Atreides + ALightFactory: light_factory + Location: 38,42 + Owner: Atreides + APower1: wind_trap + Location: 47,42 + Owner: Atreides + Actor20: trike + Location: 43,43 + Owner: Atreides + Actor21: light_inf + Location: 45,43 + Owner: Atreides + Actor22: light_inf + Location: 41,44 + Owner: Atreides + ABarracks: barracks + Location: 38,46 + Owner: Atreides + APower2: wind_trap + Location: 43,46 + Owner: Atreides + AConyard: construction_yard + Location: 46,46 + Owner: Atreides + Actor26: light_inf + Location: 40,48 + Owner: Atreides + AtreidesEntry1: waypoint + Owner: Neutral + Location: 19,49 + AtreidesEntry2: waypoint + Owner: Neutral + Location: 49,16 + AtreidesRally1: waypoint + Owner: Neutral + Location: 5,19 + AtreidesRally2: waypoint + Owner: Neutral + Location: 25,7 + AtreidesRally3: waypoint + Owner: Neutral + Location: 14,10 + AtreidesRally4: waypoint + Owner: Neutral + Location: 23,16 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/harkonnen-02b/rules.yaml b/mtrsd2k/maps/harkonnen-02b/rules.yaml new file mode 100644 index 0000000..95e5fa4 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-02b/rules.yaml @@ -0,0 +1,24 @@ +Player: + PlayerResources: + DefaultCash: 5000 + +World: + LuaScript: + Scripts: harkonnen02b.lua, harkonnen02b-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: 1 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 diff --git a/mtrsd2k/maps/harkonnen-03a/harkonnen03a-AI.lua b/mtrsd2k/maps/harkonnen-03a/harkonnen03a-AI.lua new file mode 100644 index 0000000..a92a678 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-03a/harkonnen03a-AI.lua @@ -0,0 +1,157 @@ +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() + 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/mtrsd2k/maps/harkonnen-03a/harkonnen03a.lua b/mtrsd2k/maps/harkonnen-03a/harkonnen03a.lua new file mode 100644 index 0000000..aa224f5 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-03a/harkonnen03a.lua @@ -0,0 +1,196 @@ +AtreidesBase = { ABarracks, AWindTrap1, AWindTrap2, ALightFactory, AOutpost, AConyard, ARefinery, ASilo } +AtreidesBaseAreaTriggers = +{ + { CPos.New(34, 50), CPos.New(35, 50), CPos.New(36, 50), CPos.New(37, 50), CPos.New(38, 50), CPos.New(39, 50), CPos.New(40, 50), CPos.New(41, 50), CPos.New(14, 57), CPos.New(14, 58), CPos.New(14, 59), CPos.New(14, 60), CPos.New(14, 61), CPos.New(14, 62), CPos.New(14, 63), CPos.New(14, 64), CPos.New(14, 65)}, + { CPos.New(29, 51), CPos.New(29, 52), CPos.New(29, 53), CPos.New(29, 54), CPos.New(44, 50), CPos.New(44, 51), CPos.New(44, 52), CPos.New(44, 53), CPos.New(44, 54), CPos.New(43, 54), CPos.New(42, 54), CPos.New(41, 54), CPos.New(40, 54), CPos.New(39, 54), CPos.New(38, 54), CPos.New(37, 54), CPos.New(36, 54), CPos.New(35, 54), CPos.New(34, 54), CPos.New(33, 54), CPos.New(32, 54), CPos.New(31, 54), CPos.New(30, 54) }, + { CPos.New(46, 18), CPos.New(46, 19), CPos.New(46, 20), CPos.New(46, 21), CPos.New(46, 22), CPos.New(46, 23) } +} + +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 +} + +InitialAtreidesReinforcements = +{ + { "trooper", "trooper", "trooper", "trooper" }, + { "trike", "trike", "trike" }, + { "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "trike", "trike" }, + { "trooper", "trooper", "trooper", "trooper", "trooper", "quad", "quad" }, + { "quad", "trooper", "trooper", "trooper" } +} + +AtreidesPaths = +{ + { AtreidesEntry1.Location, AtreidesRally1.Location }, + { AtreidesEntry2.Location, AtreidesRally2.Location }, + { AtreidesEntry3.Location, AtreidesRally3.Location } +} + +AtreidesInitialPaths = +{ + { AtreidesEntry4.Location, AtreidesRally4.Location }, + { AtreidesEntry5.Location, AtreidesRally5.Location }, + { AtreidesEntry6.Location, AtreidesRally6.Location }, + { AtreidesEntry7.Location, AtreidesRally7.Location }, + { AtreidesEntry8.Location, AtreidesRally8.Location } +} + +HarkonnenReinforcements = { "trike", "trike", "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 + +SendInitialUnits = 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) + + SendInitialUnits(AtreidesBaseAreaTriggers[1], InitialAtreidesReinforcements[1], AtreidesInitialPaths[1], InitialReinforcementsSent1) + SendInitialUnits(AtreidesBaseAreaTriggers[1], InitialAtreidesReinforcements[2], AtreidesInitialPaths[2], InitialReinforcementsSent2) + SendInitialUnits(AtreidesBaseAreaTriggers[2], InitialAtreidesReinforcements[3], AtreidesInitialPaths[3], InitialReinforcementsSent3) + SendInitialUnits(AtreidesBaseAreaTriggers[2], InitialAtreidesReinforcements[4], AtreidesInitialPaths[4], InitialReinforcementsSent4) + SendInitialUnits(AtreidesBaseAreaTriggers[3], InitialAtreidesReinforcements[5], AtreidesInitialPaths[5], InitialReinforcementsSent5) +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.AddSecondaryObjective("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/mtrsd2k/maps/harkonnen-03a/map.bin b/mtrsd2k/maps/harkonnen-03a/map.bin new file mode 100644 index 0000000..6b3ce00 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-03a/map.bin differ diff --git a/mtrsd2k/maps/harkonnen-03a/map.png b/mtrsd2k/maps/harkonnen-03a/map.png new file mode 100644 index 0000000..6985920 Binary files /dev/null and b/mtrsd2k/maps/harkonnen-03a/map.png differ diff --git a/mtrsd2k/maps/harkonnen-03a/map.yaml b/mtrsd2k/maps/harkonnen-03a/map.yaml new file mode 100644 index 0000000..55a0cc8 --- /dev/null +++ b/mtrsd2k/maps/harkonnen-03a/map.yaml @@ -0,0 +1,180 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Harkonnen 03a + +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, Creeps + +Actors: + Actor0: light_inf + Location: 30,9 + Owner: Harkonnen + Actor1: trooper + Location: 34,9 + Owner: Harkonnen + HConyard: construction_yard + Location: 31,11 + Owner: Harkonnen + Actor3: quad + Location: 37,12 + Owner: Harkonnen + Actor4: wormspawner + Location: 6,14 + Owner: Creeps + Actor5: trike + Location: 27,16 + Owner: Harkonnen + Actor6: light_inf + Location: 31,16 + Owner: Harkonnen + Actor7: trooper + Location: 33,16 + Owner: Harkonnen + Actor8: wall + Location: 33,51 + Owner: Atreides + Actor9: wall + Location: 42,51 + Owner: Atreides + Actor10: wall + Location: 33,52 + Owner: Atreides + Actor11: wall + Location: 34,52 + Owner: Atreides + Actor12: wall + Location: 35,52 + Owner: Atreides + Actor13: wall + Location: 40,52 + Owner: Atreides + Actor14: wall + Location: 41,52 + Owner: Atreides + Actor15: wall + Location: 42,52 + Owner: Atreides + Actor16: quad + Location: 34,53 + Owner: Atreides + Actor17: quad + Location: 40,53 + Owner: Atreides + ARefinery: refinery + Location: 23,57 + Owner: Atreides + AWindTrap1: wind_trap + Location: 20,59 + Owner: Atreides + ASilo: silo + Location: 27,59 + Owner: Atreides + AConyard: construction_yard + Location: 22,61 + Owner: Atreides + AWindTrap2: wind_trap + Location: 27,62 + Owner: Atreides + AOutpost: outpost + Location: 29,62 + Owner: Atreides + ALightFactory: light_factory + Location: 37,62 + Owner: Atreides + ABarracks: barracks + Location: 34,63 + Owner: Atreides + AtreidesRally4: waypoint + Owner: Neutral + Location: 21,11 + AtreidesEntry4: waypoint + Owner: Neutral + Location: 21,2 + AtreidesRally2: waypoint + Owner: Neutral + Location: 13,19 + AtreidesEntry3: waypoint + Owner: Neutral + Location: 29,60 + AtreidesRally3: waypoint + Owner: Neutral + Location: 29,65 + AtreidesRally6: waypoint + Owner: Neutral + Location: 19,57 + AtreidesEntry6: waypoint + Owner: Neutral + Location: 19,65 + AtreidesRally7: waypoint + Owner: Neutral + Location: 45,61 + AtreidesEntry7: waypoint + Owner: Neutral + Location: 45,65 + AtreidesRally5: waypoint + Owner: Neutral + Location: 33,45 + AtreidesEntry5: waypoint + Owner: Neutral + Location: 33,65 + AtreidesEntry2: waypoint + Owner: Neutral + Location: 2,19 + AtreidesRally1: waypoint + Owner: Neutral + Location: 42,18 + AtreidesEntry1: waypoint + Owner: Neutral + Location: 65,18 + AtreidesRally8: waypoint + Owner: Neutral + Location: 47,27 + AtreidesEntry8: waypoint + Owner: Neutral + Location: 65,27 + HarkonnenRally: waypoint + Owner: Neutral + Location: 29,12 + HarkonnenEntry: waypoint + Owner: Neutral + Location: 29,2 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/harkonnen-03a/rules.yaml b/mtrsd2k/maps/harkonnen-03a/rules.yaml new file mode 100644 index 0000000..d05b90b --- /dev/null +++ b/mtrsd2k/maps/harkonnen-03a/rules.yaml @@ -0,0 +1,24 @@ +Player: + PlayerResources: + DefaultCash: 5000 + +World: + LuaScript: + Scripts: harkonnen03a.lua, harkonnen03a-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: 2 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 diff --git a/mtrsd2k/maps/ordos-02a/map.bin b/mtrsd2k/maps/ordos-02a/map.bin new file mode 100644 index 0000000..a21f767 Binary files /dev/null and b/mtrsd2k/maps/ordos-02a/map.bin differ diff --git a/mtrsd2k/maps/ordos-02a/map.png b/mtrsd2k/maps/ordos-02a/map.png new file mode 100644 index 0000000..852d270 Binary files /dev/null and b/mtrsd2k/maps/ordos-02a/map.png differ diff --git a/mtrsd2k/maps/ordos-02a/map.yaml b/mtrsd2k/maps/ordos-02a/map.yaml new file mode 100644 index 0000000..2d22dab --- /dev/null +++ b/mtrsd2k/maps/ordos-02a/map.yaml @@ -0,0 +1,155 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Ordos 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 + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + Enemies: Ordos, Harkonnen + PlayerReference@Ordos: + Name: Ordos + Playable: True + LockFaction: True + Faction: ordos + LockColor: True + Color: B3EAA5 + Enemies: Harkonnen, Creeps + PlayerReference@Harkonnen: + Name: Harkonnen + LockFaction: True + Faction: harkonnen + LockColor: True + Color: FE0000 + Enemies: Ordos, Creeps + +Actors: + Actor0: spicebloom.spawnpoint + Location: 7,18 + Owner: Neutral + Actor1: light_inf + Location: 18,25 + Owner: Ordos + Actor2: raider + Location: 21,26 + Owner: Ordos + Actor3: light_inf + Location: 22,26 + Owner: Ordos + OConyard: construction_yard + Location: 18,27 + Owner: Ordos + Actor5: raider + Location: 16,28 + Owner: Ordos + Actor6: raider + Location: 22,28 + Owner: Ordos + Actor7: light_inf + Location: 17,30 + Owner: Ordos + Actor8: light_inf + Location: 21,30 + Owner: Ordos + Actor9: wormspawner + Location: 20,37 + Owner: Creeps + Actor10: light_inf + Location: 27,40 + Owner: Harkonnen + Actor11: trike + Location: 31,41 + Owner: Harkonnen + Actor12: light_inf + Location: 34,41 + Owner: Harkonnen + HOutpost: outpost + Location: 25,42 + Owner: Harkonnen + Actor14: trike + Location: 29,42 + Owner: Harkonnen + Actor15: light_inf + Location: 36,42 + Owner: Harkonnen + HPower2: wind_trap + Location: 30,44 + Owner: Harkonnen + HBarracks: barracks + Location: 34,44 + Owner: Harkonnen + HPower1: wind_trap + Location: 22,45 + Owner: Harkonnen + Actor19: trike + Location: 40,45 + Owner: Harkonnen + HConyard: construction_yard + Location: 26,46 + Owner: Harkonnen + Actor21: trike + Location: 37,46 + Owner: Harkonnen + HarkonnenRally1: waypoint + Owner: Neutral + Location: 24,44 + HarkonnenEntry1: waypoint + Owner: Neutral + Location: 24,49 + HarkonnenRally2: waypoint + Owner: Neutral + Location: 39,44 + HarkonnenEntry2: waypoint + Owner: Neutral + Location: 39,49 + OrdosRally: waypoint + Owner: Neutral + Location: 16,25 + OrdosEntry: waypoint + Owner: Neutral + Location: 2,25 + HarkonnenRally3: waypoint + Owner: Neutral + Location: 27,31 + HarkonnenEntry3: waypoint + Owner: Neutral + Location: 27,49 + HarkonnenEntry4: waypoint + Owner: Neutral + Location: 49,38 + HarkonnenEntry5: waypoint + Owner: Neutral + Location: 2,38 + HarkonnenRally4: waypoint + Owner: Neutral + Location: 15,30 + HarkonnenRally5: waypoint + Owner: Neutral + Location: 31,24 + HarkonnenRally6: waypoint + Owner: Neutral + Location: 23,19 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/ordos-02a/ordos02a-AI.lua b/mtrsd2k/maps/ordos-02a/ordos02a-AI.lua new file mode 100644 index 0000000..d7557e3 --- /dev/null +++ b/mtrsd2k/maps/ordos-02a/ordos02a-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) } +} + +HarkonnenInfantryTypes = { "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(HarkonnenBase, 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 HBarracks.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(HarkonnenInfantryTypes) } + harkonnen.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/mtrsd2k/maps/ordos-02a/ordos02a.lua b/mtrsd2k/maps/ordos-02a/ordos02a.lua new file mode 100644 index 0000000..fb0b210 --- /dev/null +++ b/mtrsd2k/maps/ordos-02a/ordos02a.lua @@ -0,0 +1,159 @@ + +HarkonnenBase = { HConyard, HPower1, HPower2, HBarracks, HOutpost } +HarkonnenBaseAreaTrigger = { CPos.New(31, 37), CPos.New(32, 37), CPos.New(33, 37), CPos.New(34, 37), CPos.New(35, 37), CPos.New(36, 37), CPos.New(37, 37), CPos.New(38, 37), CPos.New(39, 37), CPos.New(40, 37), CPos.New(41, 37), CPos.New(42, 37), CPos.New(42, 38), CPos.New(42, 39), CPos.New(42, 40), CPos.New(42, 41), CPos.New(42, 42), CPos.New(42, 43), CPos.New(42, 44), CPos.New(42, 45), CPos.New(42, 46), CPos.New(42, 47), CPos.New(42, 48), CPos.New(42, 49) } + +HarkonnenReinforcements = { } +HarkonnenReinforcements["easy"] = +{ + { "light_inf", "trike" }, + { "light_inf", "trike" }, + { "light_inf", "light_inf", "light_inf", "trike", "trike" } +} + +HarkonnenReinforcements["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" }, +} + +HarkonnenReinforcements["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" } +} + +HarkonnenAttackPaths = +{ + { HarkonnenEntry3.Location, HarkonnenRally3.Location }, + { HarkonnenEntry4.Location, HarkonnenRally5.Location }, + { HarkonnenEntry4.Location, HarkonnenRally6.Location }, + { HarkonnenEntry5.Location, HarkonnenRally4.Location } +} + +InitialHarkonnenReinforcementsPaths = +{ + { HarkonnenEntry1.Location, HarkonnenRally1.Location }, + { HarkonnenEntry2.Location, HarkonnenRally2.Location } +} + +InitialHarkonnenReinforcements = +{ + { "trike", "trike" }, + { "light_inf", "light_inf" } +} + +HarkonnenAttackDelay = { } +HarkonnenAttackDelay["easy"] = DateTime.Minutes(5) +HarkonnenAttackDelay["normal"] = DateTime.Minutes(2) + DateTime.Seconds(40) +HarkonnenAttackDelay["hard"] = DateTime.Minutes(1) + DateTime.Seconds(20) + +HarkonnenAttackWaves = { } +HarkonnenAttackWaves["easy"] = 3 +HarkonnenAttackWaves["normal"] = 6 +HarkonnenAttackWaves["hard"] = 9 + +OrdosReinforcements = { "light_inf", "light_inf", "raider" } +OrdosEntryPath = { OrdosEntry.Location, OrdosRally.Location } + +wave = 0 +SendHarkonnen = function() + Trigger.AfterDelay(HarkonnenAttackDelay[Map.LobbyOption("difficulty")], function() + wave = wave + 1 + if wave > HarkonnenAttackWaves[Map.LobbyOption("difficulty")] then + return + end + + local path = Utils.Random(HarkonnenAttackPaths) + local units = Reinforcements.ReinforceWithTransport(harkonnen, "carryall.reinforce", HarkonnenReinforcements[Map.LobbyOption("difficulty")][wave], path, { path[1] })[2] + Utils.Do(units, IdleHunt) + + SendHarkonnen() + end) +end + +IdleHunt = function(unit) + Trigger.OnIdle(unit, unit.Hunt) +end + +SendInitialUnits = function(areaTrigger, unit, path, check) + Trigger.OnEnteredFootprint(areaTrigger, function(a, id) + if not check and a.Owner == player then + local units = Reinforcements.ReinforceWithTransport(harkonnen, "carryall.reinforce", unit, path, { path[1] })[2] + Utils.Do(units, IdleHunt) + check = true + end + end) +end + +Tick = function() + if player.HasNoRequiredUnits() then + harkonnen.MarkCompletedObjective(KillOrdos) + end + + if harkonnen.HasNoRequiredUnits() and not player.IsObjectiveCompleted(KillHarkonnen) then + Media.DisplayMessage("The Harkonnen have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillHarkonnen) + end +end + +WorldLoaded = function() + harkonnen = Player.GetPlayer("Harkonnen") + player = Player.GetPlayer("Ordos") + + InitObjectives() + + Camera.Position = OConyard.CenterPosition + + Trigger.OnAllKilled(HarkonnenBase, function() + Utils.Do(harkonnen.GetGroundAttackers(), IdleHunt) + end) + + Trigger.AfterDelay(DateTime.Minutes(1), function() + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.ReinforceWithTransport(player, "carryall.reinforce", OrdosReinforcements, OrdosEntryPath, { OrdosEntryPath[1] }) + end) + + SendInitialUnits(HarkonnenBaseAreaTrigger, InitialHarkonnenReinforcements[1], InitialHarkonnenReinforcementsPaths[1], InitialReinforcementsSent1) + SendInitialUnits(HarkonnenBaseAreaTrigger, InitialHarkonnenReinforcements[2], InitialHarkonnenReinforcementsPaths[2], InitialReinforcementsSent2) + + SendHarkonnen() + ActivateAI() +end + +InitObjectives = function() + Trigger.OnObjectiveAdded(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective") + end) + + KillOrdos = harkonnen.AddPrimaryObjective("Kill all Ordos units.") + KillHarkonnen = player.AddPrimaryObjective("Destroy all Harkonnen 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/mtrsd2k/maps/ordos-02a/rules.yaml b/mtrsd2k/maps/ordos-02a/rules.yaml new file mode 100644 index 0000000..bfba36d --- /dev/null +++ b/mtrsd2k/maps/ordos-02a/rules.yaml @@ -0,0 +1,24 @@ +Player: + PlayerResources: + DefaultCash: 5000 + +World: + LuaScript: + Scripts: ordos02a.lua, ordos02a-AI.lua + MissionData: + Briefing: Harkonnen forces are weakened in the Imperial Basin. Use the sensors in our Outpost to find them. Strike hard and destroy everything.\n + BriefingVideo: O_BR02_E.VQA + MapOptions: + TechLevel: 1 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 diff --git a/mtrsd2k/maps/ordos-02b/map.bin b/mtrsd2k/maps/ordos-02b/map.bin new file mode 100644 index 0000000..4c6dc99 Binary files /dev/null and b/mtrsd2k/maps/ordos-02b/map.bin differ diff --git a/mtrsd2k/maps/ordos-02b/map.png b/mtrsd2k/maps/ordos-02b/map.png new file mode 100644 index 0000000..81ebd22 Binary files /dev/null and b/mtrsd2k/maps/ordos-02b/map.png differ diff --git a/mtrsd2k/maps/ordos-02b/map.yaml b/mtrsd2k/maps/ordos-02b/map.yaml new file mode 100644 index 0000000..01b3a1e --- /dev/null +++ b/mtrsd2k/maps/ordos-02b/map.yaml @@ -0,0 +1,140 @@ +MapFormat: 11 + +RequiresMod: d2k + +Title: Ordos 02b + +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 + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + Enemies: Ordos, Harkonnen + PlayerReference@Ordos: + Name: Ordos + Playable: True + LockFaction: True + Faction: ordos + LockColor: True + Color: B3EAA5 + Enemies: Harkonnen, Creeps + PlayerReference@Harkonnen: + Name: Harkonnen + LockFaction: True + Faction: harkonnen + LockColor: True + Color: FE0000 + Enemies: Ordos, Creeps + +Actors: + Actor0: light_inf + Location: 12,3 + Owner: Ordos + OPower1: wind_trap + Location: 15,3 + Owner: Ordos + OConyard: construction_yard + Location: 20,4 + Owner: Ordos + Actor3: light_inf + Location: 9,5 + Owner: Ordos + Actor4: light_inf + Location: 13,5 + Owner: Ordos + Actor5: light_inf + Location: 18,6 + Owner: Ordos + Actor6: raider + Location: 12,7 + Owner: Ordos + Actor7: raider + Location: 19,8 + Owner: Ordos + Actor8: light_inf + Location: 36,10 + Owner: Harkonnen + Actor9: light_inf + Location: 5,19 + Owner: Harkonnen + Actor10: light_inf + Location: 45,25 + Owner: Harkonnen + Actor11: light_inf + Location: 45,32 + Owner: Harkonnen + Actor12: light_inf + Location: 42,33 + Owner: Harkonnen + Actor13: light_inf + Location: 48,33 + Owner: Harkonnen + HPower1: wind_trap + Location: 38,36 + Owner: Harkonnen + HPower2: wind_trap + Location: 40,36 + Owner: Harkonnen + HOutpost: outpost + Location: 45,37 + Owner: Harkonnen + Actor17: light_inf + Location: 38,40 + Owner: Harkonnen + Actor18: light_inf + Location: 41,40 + Owner: Harkonnen + Actor19: light_inf + Location: 10,42 + Owner: Harkonnen + Actor20: wormspawner + Location: 17,42 + Owner: Creeps + HBarracks: barracks + Location: 39,42 + Owner: Harkonnen + HConyard: construction_yard + Location: 45,43 + Owner: Harkonnen + Actor23: trike + Location: 38,46 + Owner: Harkonnen + Actor24: trike + Location: 41,47 + Owner: Harkonnen + HarkonnenRally1: waypoint + Owner: Neutral + Location: 25,15 + HarkonnenEntry1: waypoint + Owner: Neutral + Location: 49,15 + HarkonnenRally2: waypoint + Owner: Neutral + Location: 14,13 + HarkonnenEntry2: waypoint + Owner: Neutral + Location: 31,49 + HarkonnenRally3: waypoint + Owner: Neutral + Location: 9,8 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/ordos-02b/ordos02b-AI.lua b/mtrsd2k/maps/ordos-02b/ordos02b-AI.lua new file mode 100644 index 0000000..d7557e3 --- /dev/null +++ b/mtrsd2k/maps/ordos-02b/ordos02b-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) } +} + +HarkonnenInfantryTypes = { "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(HarkonnenBase, 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 HBarracks.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(HarkonnenInfantryTypes) } + harkonnen.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/mtrsd2k/maps/ordos-02b/ordos02b.lua b/mtrsd2k/maps/ordos-02b/ordos02b.lua new file mode 100644 index 0000000..70966dd --- /dev/null +++ b/mtrsd2k/maps/ordos-02b/ordos02b.lua @@ -0,0 +1,129 @@ + +HarkonnenBase = { HConyard, HPower1, HPower2, HBarracks, HOutpost } + +HarkonnenReinforcements = { } +HarkonnenReinforcements["easy"] = +{ + { "light_inf", "trike" }, + { "light_inf", "trike" }, + { "light_inf", "light_inf", "light_inf", "trike", "trike" } +} + +HarkonnenReinforcements["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" }, +} + +HarkonnenReinforcements["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" } +} + +HarkonnenAttackPaths = +{ + { HarkonnenEntry1.Location, HarkonnenRally1.Location }, + { HarkonnenEntry1.Location, HarkonnenRally2.Location }, + { HarkonnenEntry2.Location, HarkonnenRally2.Location }, + { HarkonnenEntry2.Location, HarkonnenRally3.Location } +} + +HarkonnenAttackDelay = +{ + easy = DateTime.Minutes(5), + normal = DateTime.Minutes(2) + DateTime.Seconds(40), + hard = DateTime.Minutes(1) + DateTime.Seconds(20) +} + +HarkonnenAttackWaves = +{ + easy = 3, + normal = 6, + hard = 9 +} + +wave = 0 +SendHarkonnen = function() + Trigger.AfterDelay(HarkonnenAttackDelay[Map.LobbyOption("difficulty")], function() + wave = wave + 1 + if wave > HarkonnenAttackWaves[Map.LobbyOption("difficulty")] then + return + end + + local path = Utils.Random(HarkonnenAttackPaths) + local units = Reinforcements.ReinforceWithTransport(harkonnen, "carryall.reinforce", HarkonnenReinforcements[Map.LobbyOption("difficulty")][wave], path, { path[1] })[2] + Utils.Do(units, IdleHunt) + + SendHarkonnen() + end) +end + +IdleHunt = function(unit) + Trigger.OnIdle(unit, unit.Hunt) +end + +Tick = function() + if player.HasNoRequiredUnits() then + harkonnen.MarkCompletedObjective(KillOrdos) + end + + if harkonnen.HasNoRequiredUnits() and not player.IsObjectiveCompleted(KillHarkonnen) then + Media.DisplayMessage("The Harkonnen have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillHarkonnen) + end +end + +WorldLoaded = function() + harkonnen = Player.GetPlayer("Harkonnen") + player = Player.GetPlayer("Ordos") + + InitObjectives() + + Camera.Position = OConyard.CenterPosition + + Trigger.OnAllKilled(HarkonnenBase, function() + Utils.Do(harkonnen.GetGroundAttackers(), IdleHunt) + end) + + SendHarkonnen() + ActivateAI() +end + +InitObjectives = function() + Trigger.OnObjectiveAdded(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective") + end) + + KillOrdos = harkonnen.AddPrimaryObjective("Kill all Ordos units.") + KillHarkonnen = player.AddPrimaryObjective("Destroy all Harkonnen 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/mtrsd2k/maps/ordos-02b/rules.yaml b/mtrsd2k/maps/ordos-02b/rules.yaml new file mode 100644 index 0000000..847283f --- /dev/null +++ b/mtrsd2k/maps/ordos-02b/rules.yaml @@ -0,0 +1,24 @@ +Player: + PlayerResources: + DefaultCash: 5000 + +World: + LuaScript: + Scripts: ordos02b.lua, ordos02b-AI.lua + MissionData: + Briefing: Harkonnen forces are weakened in the Imperial Basin. Use the sensors in our Outpost to find them. Strike hard and destroy everything.\n + BriefingVideo: O_BR02_E.VQA + MapOptions: + TechLevel: 1 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 diff --git a/mtrsd2k/maps/ordos-04/map.bin b/mtrsd2k/maps/ordos-04/map.bin new file mode 100644 index 0000000..4af28ad Binary files /dev/null and b/mtrsd2k/maps/ordos-04/map.bin differ diff --git a/mtrsd2k/maps/ordos-04/map.png b/mtrsd2k/maps/ordos-04/map.png new file mode 100644 index 0000000..3f3fa40 Binary files /dev/null and b/mtrsd2k/maps/ordos-04/map.png differ diff --git a/mtrsd2k/maps/ordos-04/map.yaml b/mtrsd2k/maps/ordos-04/map.yaml new file mode 100644 index 0000000..455de78 --- /dev/null +++ b/mtrsd2k/maps/ordos-04/map.yaml @@ -0,0 +1,555 @@ +MapFormat: 11 + +RequiresMod: mtrsd2k + +Title: Ordos 04 + +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 + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + Enemies: Ordos, Harkonnen, Smugglers + PlayerReference@Ordos: + Name: Ordos + Playable: True + LockFaction: True + Faction: ordos + LockColor: True + Color: B3EAA5 + Enemies: Harkonnen, Smugglers, Creeps + PlayerReference@Harkonnen: + Name: Harkonnen + LockFaction: True + Faction: harkonnen + LockColor: True + Color: FE0000 + Allies: Smugglers + Enemies: Ordos + PlayerReference@Smugglers: + Name: Smugglers + LockFaction: True + Faction: smuggler + LockColor: True + Color: 542209 + Allies: Harkonnen + Enemies: Ordos + +Actors: + Actor0: wall + Location: 38,2 + Owner: Harkonnen + Actor1: wall + Location: 42,2 + Owner: Smugglers + HGunTurret1: medium_gun_turret + Location: 38,3 + Owner: Harkonnen + TurretFacing: 0 + Actor3: wall + Location: 42,3 + Owner: Smugglers + SLightFactory: light_factory + Location: 44,3 + Owner: Smugglers + Actor5: wall + Location: 42,4 + Owner: Smugglers + Actor6: wall + Location: 42,5 + Owner: Smugglers + Actor7: wall + Location: 42,6 + Owner: Smugglers + Actor8: light_inf + Location: 43,6 + Owner: Smugglers + HGunTurret2: medium_gun_turret + Location: 38,7 + Owner: Harkonnen + TurretFacing: 0 + Actor10: trooper + Location: 39,7 + Owner: Harkonnen + Actor11: wall + Location: 42,7 + Owner: Smugglers + Actor12: wall + Location: 46,7 + Owner: Smugglers + Actor13: wall + Location: 47,7 + Owner: Smugglers + Actor14: wall + Location: 48,7 + Owner: Smugglers + Actor15: wall + Location: 49,7 + Owner: Smugglers + Actor16: wall + Location: 50,7 + Owner: Smugglers + Actor17: light_inf + Location: 51,7 + Owner: Smugglers + Actor18: wall + Location: 38,8 + Owner: Harkonnen + Actor19: wall + Location: 42,8 + Owner: Smugglers + SPower1: wind_trap + Location: 44,8 + Owner: Smugglers + Actor21: wall + Location: 46,8 + Owner: Smugglers + SOutpost: outpost + Location: 47,8 + Owner: Smugglers + Actor23: wall + Location: 50,8 + Owner: Smugglers + Actor24: wall + Location: 38,9 + Owner: Harkonnen + Actor25: combat_tank_h + Location: 39,9 + Owner: Harkonnen + Actor26: wall + Location: 42,9 + Owner: Smugglers + Actor27: wall + Location: 46,9 + Owner: Smugglers + Actor28: wall + Location: 50,9 + Owner: Smugglers + Actor29: wall + Location: 29,10 + Owner: Harkonnen + Actor30: wall + Location: 30,10 + Owner: Harkonnen + Actor31: wall + Location: 31,10 + Owner: Harkonnen + Actor32: wall + Location: 32,10 + Owner: Harkonnen + Actor33: wall + Location: 33,10 + Owner: Harkonnen + Actor34: wall + Location: 34,10 + Owner: Harkonnen + Actor35: wall + Location: 35,10 + Owner: Harkonnen + Actor36: wall + Location: 36,10 + Owner: Harkonnen + Actor37: wall + Location: 37,10 + Owner: Harkonnen + Actor38: wall + Location: 38,10 + Owner: Harkonnen + SGunTurret1: medium_gun_turret + Location: 42,10 + Owner: Smugglers + TurretFacing: 0 + Actor40: wall + Location: 43,10 + Owner: Smugglers + Actor41: wall + Location: 46,10 + Owner: Smugglers + Actor42: wall + Location: 50,10 + Owner: Smugglers + Actor43: spicebloom.spawnpoint + Location: 61,10 + Owner: Neutral + Actor44: wall + Location: 28,11 + Owner: Harkonnen + Actor45: wall + Location: 29,11 + Owner: Harkonnen + Actor46: harvester + Location: 40,11 + Owner: Harkonnen + Actor47: wall + Location: 46,11 + Owner: Smugglers + Actor48: wall + Location: 47,11 + Owner: Smugglers + Actor49: wall + Location: 48,11 + Owner: Smugglers + Actor50: wall + Location: 49,11 + Owner: Smugglers + Actor51: wall + Location: 50,11 + Owner: Smugglers + Actor52: wall + Location: 28,12 + Owner: Harkonnen + Actor53: wall + Location: 27,13 + Owner: Harkonnen + HBarracks: barracks + Location: 29,13 + Owner: Harkonnen + HHeavyFactory: heavy_factory + Location: 31,13 + Owner: Harkonnen + HRefinery: refinery + Location: 35,13 + Owner: Harkonnen + SGunTurret2: medium_gun_turret + Location: 42,13 + Owner: Smugglers + TurretFacing: 0 + Actor59: wall + Location: 43,13 + Owner: Smugglers + SPower2: wind_trap + Location: 44,13 + Owner: Smugglers + SHeavyFactory: heavy_factory + Location: 46,13 + Owner: Smugglers + SPower3: wind_trap + Location: 49,13 + Owner: Smugglers + SBarracks: barracks + Location: 52,13 + Owner: Smugglers + Actor64: wall + Location: 42,14 + Owner: Smugglers + Actor65: wall + Location: 42,15 + Owner: Smugglers + Actor66: wall + Location: 42,16 + Owner: Smugglers + Actor67: light_inf + Location: 43,16 + Owner: Smugglers + Actor68: wall + Location: 42,17 + Owner: Smugglers + Actor69: wall + Location: 43,17 + Owner: Smugglers + Actor70: wall + Location: 44,17 + Owner: Smugglers + Actor71: wall + Location: 45,17 + Owner: Smugglers + Actor72: wall + Location: 46,17 + Owner: Smugglers + Actor73: wall + Location: 47,17 + Owner: Smugglers + Actor74: wall + Location: 48,17 + Owner: Smugglers + HLightFactory: light_factory + Location: 39,18 + Owner: Harkonnen + HConyard: construction_yard + Location: 44,18 + Owner: Harkonnen + Actor83: spicebloom.spawnpoint + Location: 6,19 + Owner: Neutral + Actor84: spicebloom.spawnpoint + Location: 18,20 + Owner: Neutral + Actor85: wall + Location: 56,20 + Owner: Smugglers + SGunTurret3: medium_gun_turret + Location: 57,20 + Owner: Smugglers + TurretFacing: 0 + HPower1: wind_trap + Location: 44,21 + Owner: Harkonnen + HPower2: wind_trap + Location: 46,21 + Owner: Harkonnen + SGunTurret4: medium_gun_turret + Location: 63,21 + Owner: Smugglers + TurretFacing: 0 + Actor90: wall + Location: 64,21 + Owner: Smugglers + Actor91: wall + Location: 65,21 + Owner: Smugglers + Actor92: spicebloom.spawnpoint + Location: 23,24 + Owner: Neutral + HGunTurret3: wind_trap + Location: 44,24 + Owner: Harkonnen + HPower3: wind_trap + Location: 46,24 + Owner: Harkonnen + Actor95: wall + Location: 46,29 + Owner: Harkonnen + HGunTurret4: medium_gun_turret + Location: 47,29 + Owner: Harkonnen + TurretFacing: 0 + Actor97: wall + Location: 48,29 + Owner: Harkonnen + Actor98: spicebloom.spawnpoint + Location: 53,30 + Owner: Neutral + Actor99: combat_tank_h + Location: 14,31 + Owner: Harkonnen + Actor100: wormspawner + Location: 2,32 + Owner: Creeps + Actor101: wall + Location: 10,32 + Owner: Harkonnen + HGunTurret5: medium_gun_turret + Location: 11,32 + Owner: Harkonnen + TurretFacing: 0 + Actor103: light_inf + Location: 12,32 + Owner: Harkonnen + Actor104: light_inf + Location: 16,32 + Owner: Harkonnen + Actor105: wall + Location: 10,33 + Owner: Harkonnen + Actor106: wall + Location: 15,33 + Owner: Harkonnen + Actor107: wall + Location: 10,34 + Owner: Harkonnen + Actor108: wall + Location: 15,34 + Owner: Harkonnen + Actor109: wall + Location: 16,34 + Owner: Harkonnen + Actor110: wall + Location: 16,35 + Owner: Harkonnen + Actor111: wall + Location: 16,36 + Owner: Harkonnen + Actor112: wall + Location: 17,36 + Owner: Harkonnen + Actor113: trike + Location: 37,39 + Owner: Harkonnen + Actor114: trike + Location: 39,39 + Owner: Harkonnen + Actor115: trike + Location: 41,39 + Owner: Harkonnen + Actor116: spicebloom.spawnpoint + Location: 7,40 + Owner: Neutral + Actor117: wall + Location: 37,40 + Owner: Harkonnen + Actor118: wall + Location: 38,40 + Owner: Harkonnen + Actor119: light_inf + Location: 42,40 + Owner: Harkonnen + Actor120: wall + Location: 37,41 + Owner: Harkonnen + Actor122: wall + Location: 43,41 + Owner: Harkonnen + Actor123: wall + Location: 37,42 + Owner: Harkonnen + Actor124: wall + Location: 43,42 + Owner: Harkonnen + Actor125: wall + Location: 43,43 + Owner: Harkonnen + Actor126: spicebloom.spawnpoint + Location: 32,49 + Owner: Neutral + Actor127: spicebloom.spawnpoint + Location: 59,54 + Owner: Neutral + Actor128: spicebloom.spawnpoint + Location: 5,58 + Owner: Neutral + Actor129: raider + Location: 64,58 + Owner: Ordos + Actor130: light_inf + Location: 65,58 + Owner: Ordos + Actor131: light_inf + Location: 63,59 + Owner: Ordos + Actor132: light_inf + Location: 61,60 + Owner: Ordos + Actor133: combat_tank_o + Location: 64,60 + Owner: Ordos + Actor134: combat_tank_o + Location: 63,61 + Owner: Ordos + Actor135: spicebloom.spawnpoint + Location: 26,62 + Owner: Neutral + Actor136: raider + Location: 60,62 + Owner: Ordos + OConyard: construction_yard + Location: 62,62 + Owner: Ordos + Actor138: raider + Location: 60,63 + Owner: Ordos + Actor139: light_inf + Location: 60,65 + Owner: Ordos + HarkonnenRally1: waypoint + Owner: Neutral + Location: 11,33 + HarkonnenEntry1: waypoint + Owner: Neutral + Location: 2,33 + HarkonnenRally2: waypoint + Owner: Neutral + Location: 31,29 + HarkonnenEntry2: waypoint + Owner: Neutral + Location: 31,2 + HarkonnenRally3: waypoint + Owner: Neutral + Location: 44,32 + HarkonnenEntry3: waypoint + Owner: Neutral + Location: 65,32 + HarkonnenRally4: waypoint + Owner: Neutral + Location: 40,6 + HarkonnenEntry4: waypoint + Owner: Neutral + Location: 40,2 + SmugglerRally: waypoint + Owner: Neutral + Location: 53,12 + SmugglerEntry: waypoint + Owner: Neutral + Location: 53,2 + OrdosRally: waypoint + Owner: Neutral + Location: 37,59 + OrdosEntry: waypoint + Owner: Neutral + Location: 37,65 + Actor156: wall + Owner: Smugglers + Location: 50,20 + Actor157: wall + Owner: Smugglers + Location: 49,20 + Actor158: wall + Owner: Smugglers + Location: 51,20 + Actor153: wind_trap + Owner: Smugglers + Location: 50,17 + Actor151: wall + Owner: Smugglers + Location: 49,18 + Actor152: wall + Owner: Smugglers + Location: 49,19 + Actor154: flame_tower + Owner: Harkonnen + Location: 42,41 + TurretFacing: 92 + Actor155: flame_tower + Owner: Harkonnen + Location: 28,13 + TurretFacing: 92 + Actor159: wall + Owner: Smugglers + Location: 49,17 + Actor160: mine_at + Owner: Smugglers + Location: 61,23 + Actor161: mine_at + Owner: Smugglers + Location: 63,24 + Actor162: mine_at + Owner: Smugglers + Location: 57,25 + Actor163: mine_at + Owner: Smugglers + Location: 60,30 + Actor164: mine_at + Owner: Smugglers + Location: 55,15 + Actor165: mine_ap + Owner: Smugglers + Location: 60,25 + Actor166: mine_ap + Owner: Smugglers + Location: 62,26 + Actor167: mine_ap + Owner: Smugglers + Location: 57,28 + Actor168: mine_ap + Owner: Smugglers + Location: 58,32 + +Rules: d2k|rules/campaign-rules.yaml, rules.yaml diff --git a/mtrsd2k/maps/ordos-04/ordos04-AI.lua b/mtrsd2k/maps/ordos-04/ordos04-AI.lua new file mode 100644 index 0000000..f4c7d37 --- /dev/null +++ b/mtrsd2k/maps/ordos-04/ordos04-AI.lua @@ -0,0 +1,180 @@ +IdlingUnits = +{ + Harkonnen = { }, + Smugglers = { } +} + +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) } +} + +EnemyInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + +HarkonnenVehicleTypes = { "trike", "trike", "quad" } +HarkonnenTankType = { "combat_tank_h" } + +SmugglerVehicleTypes = { "raider", "raider", "quad" } +SmugglerTankType = { "combat_tank_o" } + +IsAttacking = +{ + Harkonnen = false, + Smugglers = false +} + +AttackOnGoing = +{ + Harkonnen = false, + Smugglers = false +} + +HoldProduction = +{ + Harkonnen = false, + Smugglers = false +} + +HarvesterKilled = +{ + Harkonnen = true, + Smugglers = true +} + +IdleHunt = function(unit) if not unit.IsDead then Trigger.OnIdle(unit, unit.Hunt) end end + +SetupAttackGroup = function(house) + local units = { } + + for i = 0, AttackGroupSize[Difficulty], 1 do + if #IdlingUnits[house.Name] == 0 then + return units + end + + local number = Utils.RandomInteger(1, #IdlingUnits[house.Name]) + + if IdlingUnits[house.Name][number] and not IdlingUnits[house.Name][number].IsDead then + units[i] = IdlingUnits[house.Name][number] + table.remove(IdlingUnits[house.Name], number) + end + end + + return units +end + +SendAttack = function(house) + if IsAttacking[house.Name] then + return + end + IsAttacking[house.Name] = true + HoldProduction[house.Name] = true + + local units = SetupAttackGroup(house) + Utils.Do(units, function(unit) + IdleHunt(unit) + end) + + Trigger.OnAllRemovedFromWorld(units, function() + IsAttacking[house.Name] = false + HoldProduction[house.Name] = false + end) +end + +ProtectHarvester = function(unit, house) + DefendActor(unit, house) + Trigger.OnKilled(unit, function() HarvesterKilled[house.Name] = true end) +end + +DefendActor = function(unit, house) + Trigger.OnDamaged(unit, function(self, attacker) + if AttackOnGoing[house.Name] then + return + end + AttackOnGoing[house.Name] = true + + -- Don't try to attack spiceblooms + if attacker and attacker.Type == "spicebloom" then + return + end + + local Guards = SetupAttackGroup(house) + + if #Guards <= 0 then + AttackOnGoing[house.Name] = 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[house.Name] = false end) + end) +end + +InitAIUnits = function(house) + IdlingUnits[house.Name] = Reinforcements.Reinforce(house, InitialReinforcements[house.Name], InitialReinforcementsPaths[house.Name]) + + Utils.Do(Base[house.Name], function(actor) + DefendActor(actor, house) + Trigger.OnDamaged(actor, function(building) + if building.Health < building.MaxHealth * 3/4 and building.Owner.Name == house.Name then + building.StartBuildingRepairs() + end + end) + end) +end + +Produce = function(house, units, factory) + if factory.IsDead then + return + end + + if HoldProduction[house.Name] then + Trigger.AfterDelay(DateTime.Seconds(30), function() Produce(house, units, factory) end) + return + end + + local delay = Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) + local toBuild = { Utils.Random(units) } + house.Build(toBuild, function(unit) + local unitCount = 1 + if IdlingUnits[house.Name] then + unitCount = 1 + #IdlingUnits[house.Name] + end + IdlingUnits[house.Name][unitCount] = unit[1] + Trigger.AfterDelay(delay, function() Produce(house, units, factory) end) + + if unitCount >= (AttackGroupSize[Difficulty] * 2.5) then + SendAttack(house) + end + end) +end + +ActivateAI = function() + InitAIUnits(harkonnen) + InitAIUnits(smuggler) + + -- Finish the upgrades first before trying to build something + Trigger.AfterDelay(DateTime.Seconds(14), function() + Produce(harkonnen, EnemyInfantryTypes, HBarracks) + Produce(harkonnen, HarkonnenVehicleTypes, HLightFactory) + Produce(harkonnen, HarkonnenTankType, HHeavyFactory) + + Produce(smuggler, EnemyInfantryTypes, SBarracks) + Produce(smuggler, SmugglerVehicleTypes, SLightFactory) + Produce(smuggler, SmugglerTankType, SHeavyFactory) + end) +end diff --git a/mtrsd2k/maps/ordos-04/ordos04.lua b/mtrsd2k/maps/ordos-04/ordos04.lua new file mode 100644 index 0000000..c54e7e2 --- /dev/null +++ b/mtrsd2k/maps/ordos-04/ordos04.lua @@ -0,0 +1,163 @@ +Base = +{ + Harkonnen = { HRefinery, SHeavyFactory, SLightFactory, HGunTurret1, HGunTurret2, HGunTurret3, HGunTurret4, HGunTurret5, SBarracks, HPower1, HPower2, HPower3, HPower4 }, + Smugglers = { SOutpost, SHeavyFactory, SLightFactory, SGunTurret1, SGunTurret2, SGunTurret3, SGunTurret4, SBarracks, SPower1, SPower2, SPower3 } +} + +HarkonnenLightInfantryRushers = +{ + easy = { "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf" }, + normal = { "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf" }, + hard = { "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf" } +} + +HarkonnenAttackDelay = +{ + easy = DateTime.Minutes(3) + DateTime.Seconds(30), + normal = DateTime.Minutes(2) + DateTime.Seconds(30), + hard = DateTime.Minutes(1) + DateTime.Seconds(30) +} + +InitialReinforcements = +{ + Harkonnen = { "combat_tank_h", "combat_tank_h", "trike", "quad" }, + Smugglers = { "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper" } +} + +LightInfantryRushersPaths = +{ + { HarkonnenEntry1.Location, HarkonnenRally1.Location }, + { HarkonnenEntry2.Location, HarkonnenRally2.Location }, + { HarkonnenEntry3.Location, HarkonnenRally3.Location } +} + +InitialReinforcementsPaths = +{ + Harkonnen = { HarkonnenEntry4.Location, HarkonnenRally4.Location }, + Smugglers = { SmugglerEntry.Location, SmugglerRally.Location } +} + +OrdosReinforcements = { "light_inf", "light_inf", "light_inf", "light_inf" } + +OrdosPath = { OrdosEntry.Location, OrdosRally.Location } + +SendHarkonnen = function(path) + Trigger.AfterDelay(HarkonnenAttackDelay[Difficulty], function() + if player.IsObjectiveCompleted(KillHarkonnen) then + return + end + + local units = Reinforcements.ReinforceWithTransport(harkonnen, "carryall.reinforce", HarkonnenLightInfantryRushers[Difficulty], path, { path[1] })[2] + Utils.Do(units, function(unit) + unit.AttackMove(HarkonnenAttackLocation) + IdleHunt(unit) + end) + end) +end + +Hunt = function(house) + Trigger.OnAllKilledOrCaptured(Base[house.Name], function() + Utils.Do(house.GetGroundAttackers(), IdleHunt) + end) +end + +CheckHarvester = function(house) + if DateTime.GameTime % DateTime.Seconds(30) and HarvesterKilled[house.Name] then + local units = house.GetActorsByType("harvester") + + if #units > 0 then + HarvesterKilled[house.Name] = false + ProtectHarvester(units[1], house) + end + end +end + +Tick = function() + if player.HasNoRequiredUnits() then + harkonnen.MarkCompletedObjective(KillOrdosH) + smuggler.MarkCompletedObjective(KillOrdosS) + smuggler.MarkCompletedObjective(DefendOutpost) + end + + if harkonnen.HasNoRequiredUnits() and not player.IsObjectiveCompleted(KillHarkonnen) then + Media.DisplayMessage("The Harkonnen have been annihilated!", "Mentat") + player.MarkCompletedObjective(KillHarkonnen) + end + + CheckHarvester(harkonnen) + CheckHarvester(smuggler) + + if SOutpost.IsDead then + player.MarkFailedObjective(CaptureOutpost) + end + + if SOutpost.Owner == player then + player.MarkCompletedObjective(CaptureOutpost) + smuggler.MarkFailedObjective(DefendOutpost) + end +end + +WorldLoaded = function() + harkonnen = Player.GetPlayer("Harkonnen") + smuggler = Player.GetPlayer("Smugglers") + player = Player.GetPlayer("Ordos") + + Difficulty = Map.LobbyOption("difficulty") + + InitObjectives() + + Camera.Position = OConyard.CenterPosition + HarkonnenAttackLocation = OConyard.Location + + Hunt(harkonnen) + Hunt(smuggler) + + SendHarkonnen(LightInfantryRushersPaths[1]) + SendHarkonnen(LightInfantryRushersPaths[2]) + SendHarkonnen(LightInfantryRushersPaths[3]) + ActivateAI() + + Actor.Create("upgrade.barracks", true, { Owner = harkonnen }) + Actor.Create("upgrade.light", true, { Owner = harkonnen }) + Actor.Create("upgrade.barracks", true, { Owner = smuggler }) + Actor.Create("upgrade.light", true, { Owner = smuggler }) + + Trigger.AfterDelay(HarkonnenAttackDelay[Difficulty] - DateTime.Seconds(5), function() + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.Reinforce(player, OrdosReinforcements, OrdosPath) + end) + + Trigger.AfterDelay(HarkonnenAttackDelay[Difficulty], function() + Media.DisplayMessage("WARNING: Large force approaching!", "Mentat") + end) +end + +InitObjectives = function() + Trigger.OnObjectiveAdded(player, function(p, id) + Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective") + end) + + KillOrdosH = harkonnen.AddPrimaryObjective("Kill all Ordos units.") + KillOrdosS = smuggler.AddSecondaryObjective("Kill all Ordos units.") + DefendOutpost = smuggler.AddPrimaryObjective("Don't let the outpost to be captured or dostroyed.") + CaptureOutpost = player.AddPrimaryObjective("Capture the Smuggler Outpost.") + KillHarkonnen = player.AddSecondaryObjective("Destroy the Harkonnen.") + + 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/mtrsd2k/maps/ordos-04/rules.yaml b/mtrsd2k/maps/ordos-04/rules.yaml new file mode 100644 index 0000000..18805e1 --- /dev/null +++ b/mtrsd2k/maps/ordos-04/rules.yaml @@ -0,0 +1,24 @@ +Player: + PlayerResources: + DefaultCash: 5000 + +World: + LuaScript: + Scripts: ordos04.lua, ordos04-AI.lua + MissionData: + Briefing: The Smugglers at Sietch Tabr must be neutralized. Capture the Outpost where their families have taken shelter as insurance. The children's lives will assure the loyalties of their fathers. Use an Engineer to enter and capture the building.\n\nThe Smugglers' new partners, the Harkonnen, may attempt to protect them. Harkonnen firepower is great, but we have recently acquired tanks that may counter this.\n\nEnsure our investment is used wisely.\n + BriefingVideo: O_BR04_E.VQA + MapOptions: + TechLevel: 3 + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: Difficulty + Values: + easy: Easy + normal: Normal + hard: Hard + Default: easy + +carryall.reinforce: + Cargo: + MaxWeight: 10 diff --git a/mtrsd2k/missions.yaml b/mtrsd2k/missions.yaml index 1cf7a75..e16f1f4 100644 --- a/mtrsd2k/missions.yaml +++ b/mtrsd2k/missions.yaml @@ -1,11 +1,23 @@ Atreides Campaign: - ./mods/mtrsd2k/maps/atreides-01a - ./mods/mtrsd2k/maps/atreides-01b - ./mods/mtrsd2k/maps/atreides-02a - ./mods/mtrsd2k/maps/atreides-02b - ./mods/mtrsd2k/maps/atreides-03a - ./mods/mtrsd2k/maps/atreides-03b + ./mods/d2k/maps/atreides-01a + ./mods/d2k/maps/atreides-01b + ./mods/d2k/maps/atreides-02a + ./mods/d2k/maps/atreides-02b + ./mods/d2k/maps/atreides-03a + ./mods/d2k/maps/atreides-03b + ./mods/d2k/maps/atreides-04 + ./mods/d2k/maps/atreides-05 Ordos Campaign: - ./mods/mtrsd2k/maps/ordos-01a - ./mods/mtrsd2k/maps/ordos-01b + ./mods/d2k/maps/ordos-01a + ./mods/d2k/maps/ordos-01b + ./mods/d2k/maps/ordos-02a + ./mods/d2k/maps/ordos-02b + ./mods/d2k/maps/ordos-04 + +Harkonnen Campaign: + ./mods/d2k/maps/harkonnen-01a + ./mods/d2k/maps/harkonnen-01b + ./mods/d2k/maps/harkonnen-02a + ./mods/d2k/maps/harkonnen-02b + ./mods/d2k/maps/harkonnen-03a diff --git a/mtrsd2k/tilesets/arrakis.yaml b/mtrsd2k/tilesets/arrakis.yaml index 8b86f3e..ccd9fe6 100644 --- a/mtrsd2k/tilesets/arrakis.yaml +++ b/mtrsd2k/tilesets/arrakis.yaml @@ -3,7 +3,7 @@ General: Id: ARRAKIS SheetSize: 1024 Palette: PALETTE.BIN - EditorTemplateOrder: Basic, Dune, Sand-Detail, Brick, Sand-Cliff, Sand-Smooth, Cliff-Type-Changer, Rock-Sand-Smooth, Rock-Detail, Rock-Cliff, Rock-Cliff-Rock, Rotten-Base, Dead-Worm, Ice, Ice-Detail, Rock-Cliff-Sand, Sand-Platform, Unidentified + EditorTemplateOrder: Basic, Dune, Sand-Detail, Brick, Sand-Cliff, Sand-Smooth, Cliff-Type-Changer, Rock-Sand-Smooth, Rock-Detail, Rock-Cliff, Rock-Cliff-Rock, Rotten-Base, Dead-Worm, Ice, Ice-Detail, Rock-Cliff-Sand, Sand-Platform, Bridge, Unidentified IgnoreTileSpriteOffsets: True HeightDebugColors: 880000 @@ -16,10 +16,6 @@ Terrain: Type: Cliff TargetTypes: Ground Color: 4A2910 - TerrainType@Cliff-Sietch: - Type: Cliff-Sietch - TargetTypes: Ground - Color: 4A2910 TerrainType@Concrete: Type: Concrete TargetTypes: Ground @@ -213,10 +209,10 @@ Templates: Size: 2,2 Category: Sand-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@13: Id: 13 Images: BLOXBASE.R8 @@ -224,10 +220,10 @@ Templates: Size: 2,2 Category: Sand-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@14: Id: 14 Images: BLOXBASE.R8 @@ -290,10 +286,10 @@ Templates: Size: 2,2 Category: Sand-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@20: Id: 20 Images: BLOXBASE.R8 @@ -993,7 +989,7 @@ Templates: Size: 1,1 Category: Rock-Detail Tiles: - 0: Rock + 0: Rough Template@100: Id: 100 Images: BLOXBASE.R8 @@ -1001,7 +997,7 @@ Templates: Size: 1,1 Category: Rock-Detail Tiles: - 0: Rock + 0: Rough Template@101: Id: 101 Images: BLOXBASE.R8 @@ -1054,8 +1050,8 @@ Templates: Size: 1,2 Category: Sand-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch + 0: Cliff + 1: Cliff Template@107: Id: 107 Images: BLOXBASE.R8 @@ -1465,7 +1461,7 @@ Templates: Size: 2,2 Category: Rock-Cliff Tiles: - 0: Transition + 0: Cliff 1: Cliff 2: Cliff 3: Cliff @@ -1476,10 +1472,10 @@ Templates: Size: 2,2 Category: Rock-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@197: Id: 197 Images: BLOXBASE.R8 @@ -2019,10 +2015,10 @@ Templates: Size: 2,2 Category: Cliff-Type-Changer Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@254: Id: 254 Images: BLOXBASE.R8 @@ -2030,10 +2026,10 @@ Templates: Size: 2,2 Category: Cliff-Type-Changer Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@255: Id: 255 Images: BLOXBASE.R8 @@ -2041,10 +2037,10 @@ Templates: Size: 2,2 Category: Rock-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@256: Id: 256 Images: BLOXBASE.R8 @@ -2052,10 +2048,10 @@ Templates: Size: 2,2 Category: Rock-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@258: Id: 258 Images: BLOXBASE.R8 @@ -2063,8 +2059,8 @@ Templates: Size: 1,2 Category: Rock-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch + 0: Cliff + 1: Cliff Template@259: Id: 259 Images: BLOXBASE.R8 @@ -4175,10 +4171,10 @@ Templates: Size: 2,2 Category: Rock-Cliff Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@476: Id: 476 Images: BLOXWAST.R8 @@ -4355,7 +4351,7 @@ Templates: Images: BLOXXMAS.R8 Frames: 350, 351, 352, 370, 371, 372, 390, 391, 392 Size: 3,3 - Category: Rock-Detail + Category: Bridge Tiles: 0: Rock 1: Rock @@ -4371,7 +4367,7 @@ Templates: Images: BLOXXMAS.R8 Frames: 347, 348, 349, 367, 368, 369, 387, 388, 389 Size: 3,3 - Category: Rock-Detail + Category: Bridge Tiles: 0: Sand 1: Rough @@ -4387,7 +4383,7 @@ Templates: Images: BLOXXMAS.R8 Frames: 340, 341, 342, 343, 360, 361, 362, 363, 380, 381, 382, 383 Size: 4,3 - Category: Rock-Detail + Category: Bridge Tiles: 0: Cliff 1: Sand @@ -4541,8 +4537,8 @@ Templates: Size: 1,2 Category: Rock-Cliff-Rock Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch + 0: Cliff + 1: Cliff Template@610: Id: 610 Images: t610.tmp @@ -4622,30 +4618,30 @@ Templates: Size: 2,2 Category: Rock-Cliff-Rock Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@618: Id: 618 Images: t618.tmp Size: 2,2 Category: Rock-Cliff-Rock Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@619: Id: 619 Images: t619.tmp Size: 2,2 Category: Rock-Cliff-Rock Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@620: Id: 620 Images: t620.tmp @@ -4874,10 +4870,10 @@ Templates: Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@801: Id: 801 Images: t801.tmp @@ -4984,20 +4980,20 @@ Templates: Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@812: Id: 812 Images: t812.tmp Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@813: Id: 813 Images: t813.tmp @@ -5078,8 +5074,8 @@ Templates: Size: 1,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch + 0: Cliff + 1: Cliff Template@822: Id: 822 Images: t822.tmp @@ -5213,20 +5209,20 @@ Templates: Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@835: Id: 835 Images: t835.tmp Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@836: Id: 836 Images: t836.tmp @@ -5331,20 +5327,20 @@ Templates: Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@846: Id: 846 Images: t846.tmp Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@847: Id: 847 Images: t847.tmp @@ -5381,20 +5377,20 @@ Templates: Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@851: Id: 851 Images: t851.tmp Size: 2,2 Category: Rock-Cliff-Sand Tiles: - 0: Cliff-Sietch - 1: Cliff-Sietch - 2: Cliff-Sietch - 3: Cliff-Sietch + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff Template@852: Id: 852 Images: t852.tmp @@ -5596,3 +5592,693 @@ Templates: Category: Sand-Detail Tiles: 0: Rough + Template@1022: + Id: 1022 + Images: BLOXWAST.R8 + Frames: 393, 394, 470, 395 + Size: 2,2 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + 3: Rough + Template@1023: + Id: 1023 + Images: BLOXWAST.R8 + Frames: 353, 354 + Size: 1,2 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + Template@1024: + Id: 1024 + Images: BLOXTREE.R8 + Frames: 373 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1025: + Id: 1025 + Images: BLOXTREE.R8 + Frames: 367, 368, 387, 388 + Size: 2, 2 + Category: Sand-Detail + Tiles: + 0: Rough + 1: Rough + 2: Rough + 3: Rough + Template@1026: + Id: 1026 + Images: BLOXTREE.R8 + Frames: 371 + Size: 1, 1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1027: + Id: 1027 + Images: BLOXTREE.R8 + Frames: 383, 384 + Size: 2, 1 + Category: Sand-Detail + Tiles: + 0: Rough + 1: Rough + Template@1028: + Id: 1028 + Images: BLOXTREE.R8 + Frames: 391 + Size: 1, 1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1029: + Id: 1029 + Images: BLOXTREE.R8 + Frames: 372 + Size: 1, 1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1030: + Id: 1030 + Images: BLOXTREE.R8 + Frames: 385 + Size: 1, 1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1031: + Id: 1031 + Images: BLOXBGBS.R8 + Frames: 710, 711, 730, 630 + Size: 2,2 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + 2: Rough + Template@1032: + Id: 1032 + Images: BLOXBGBS.R8 + Frames: 709, 729 + Size: 1,2 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + Template@1033: + Id: 1033 + Images: BLOXBGBS.R8 + Frames: 714, 715 + Size: 2,1 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + Template@1034: + Id: 1034 + Images: BLOXBGBS.R8 + Frames: 611, 612, 613, 631, 632, 633 + Size: 3,2 + Category: Rock-Detail + Tiles: + 0: Rock + 1: Rock + 2: Rock + 3: Rock + 4: Rock + 5: Rock + Template@1035: + Id: 1035 + Images: BLOXBGBS.R8 + Frames: 614, 615, 616, 634, 635, 636, 654, 655, 656 + Size: 3,3 + Category: Rock-Detail + Tiles: + 0: Rock + 1: Rock + 2: Rock + 3: Rock + 4: Rock + 5: Rock + 6: Rock + 7: Rock + 8: Rock + Template@1036: + Id: 1036 + Images: BLOXBGBS.R8 + Frames: 684, 685, 704, 705 + Size: 2,2 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + 2: Rough + 3: Rough + Template@1037: + Id: 1037 + Images: BLOXBGBS.R8 + Frames: 662, 630, 682, 683 + Size: 2,2 + Category: Rock-Detail + Tiles: + 0: Rough + 2: Rough + 3: Rough + Template@1040: + Id: 1040 + Images: BLOXICE.R8 + Frames: 359 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Cliff + Template@1041: + Id: 1041 + Images: BLOXICE.R8 + Frames: 339 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Cliff + Template@1042: + Id: 1042 + Images: BLOXICE.R8 + Frames: 319 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Cliff + Template@1043: + Id: 1043 + Images: BLOXICE.R8 + Frames: 538 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Sand + Template@1046: + Id: 1046 + Images: BLOXTREE.R8 + Frames: 700 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1047: + Id: 1047 + Images: BLOXBGBS.R8 + Frames: 681 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1048: + Id: 1048 + Images: BLOXBGBS.R8 + Frames: 661 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1049: + Id: 1049 + Images: BLOXBGBS.R8 + Frames: 680 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1050: + Id: 1050 + Images: BLOXTREE.R8 + Frames: 392 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1051: + Id: 1051 + Images: BLOXTREE.R8 + Frames: 393 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1052: + Id: 1052 + Images: BLOXTREE.R8 + Frames: 346, 366 + Size: 1,2 + Category: Sand-Detail + Tiles: + 0: Rough + 1: Rough + Template@1053: + Id: 1053 + Images: BLOXTREE.R8 + Frames: 365 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1054: + Id: 1054 + Images: BLOXTREE.R8 + Frames: 386 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1055: + Id: 1055 + Images: BLOXTREE.R8 + Frames: 639 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Rock + Template@1057: + Id: 1057 + Images: BLOXTREE.R8 + Frames: 382 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Cliff + Template@1058: + Id: 1058 + Images: BLOXWAST.R8 + Frames: 393, 394, 470, 395 + Size: 2,2 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + 3: Rough + Template@1059: + Id: 1059 + Images: BLOXBGBS.R8 + Frames: 660 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1060: + Id: 1060 + Images: BLOXWAST.R8 + Frames: 164, 165, 184, 185 + Size: 2,2 + Category: Dead-Worm + Tiles: + 0: Sand + 1: Sand + 2: Sand + 3: Sand + Template@1061: + Id: 1061 + Images: BLOXWAST.R8 + Frames: 382 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1062: + Id: 1062 + Images: BLOXWAST.R8 + Frames: 368 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1063: + Id: 1063 + Images: BLOXWAST.R8 + Frames: 380 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1064: + Id: 1064 + Images: BLOXWAST.R8 + Frames: 290 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1068: + Id: 1068 + Images: BLOXWAST.R8 + Frames: 367 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Sand + Template@1069: + Id: 1069 + Images: BLOXWAST.R8 + Frames: 381 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1070: + Id: 1070 + Images: BLOXWAST.R8 + Frames: 343 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1074: + Id: 1074 + Images: BLOXWAST.R8 + Frames: 289 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Sand + Template@1075: + Id: 1075 + Images: BLOXWAST.R8 + Frames: 344 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Sand + Template@1076: + Id: 1076 + Images: BLOXWAST.R8 + Frames: 362, 363, 364 + Size: 3,1 + Category: Sand-Detail + Tiles: + 0: Cliff + 1: Rough + 2: Cliff + Template@1077: + Id: 1077 + Images: BLOXWAST.R8 + Frames: 607 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Sand + Template@1078: + Id: 1078 + Images: BLOXBAT.R8 + Frames: 311, 312 + Size: 2,1 + Category: Sand-Detail + Tiles: + 0: Cliff + 1: Cliff + Template@1079: + Id: 1079 + Images: BLOXBAT.R8 + Frames: 185 + Size: 1,1 + Category: Sand-Detail + Tiles: + 0: Rough + Template@1080: + Id: 1080 + Images: BLOXBGBS.R8 + Frames: 713, 733 + Size: 1,2 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + Template@1081: + Id: 1081 + Images: BLOXBGBS.R8 + Frames: 712 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Rough + Template@1082: + Id: 1082 + Images: BLOXBGBS.R8 + Frames: 728 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Rough + Template@1083: + Id: 1083 + Images: BLOXBGBS.R8 + Frames: 707, 708 + Size: 2,1 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + Template@1084: + Id: 1084 + Images: BLOXBGBS.R8 + Frames: 731, 732 + Size: 2,1 + Category: Rock-Detail + Tiles: + 0: Rough + 1: Rough + Template@1085: + Id: 1085 + Images: BLOXXMAS.R8 + Frames: 345, 346 + Size: 2,1 + Category: Bridge + Tiles: + 0: Transition + 1: Cliff + Template@1086: + Id: 1086 + Images: BLOXXMAS.R8 + Frames: 364, 365 + Size: 2,1 + Category: Bridge + Tiles: + 0: Cliff + 1: Transition + Template@1087: + Id: 1087 + Images: BLOXXMAS.R8 + Frames: 365, 366 + Size: 2,1 + Category: Bridge + Tiles: + 0: Transition + 1: Cliff + Template@1088: + Id: 1088 + Images: BLOXXMAS.R8 + Frames: 384, 385 + Size: 2,1 + Category: Bridge + Tiles: + 0: Cliff + 1: Transition + Template@1089: + Id: 1089 + Images: BLOXXMAS.R8 + Frames: 385, 386 + Size: 2,1 + Category: Bridge + Tiles: + 0: Transition + 1: Cliff + Template@1092: + Id: 1092 + Images: BLOXXMAS.R8 + Frames: 1, 340, 2, 360 + Size: 2,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Cliff + 2: Rock + 3: Transition + Template@1091: + Id: 1091 + Images: BLOXXMAS.R8 + Frames: 343, 4, 363, 5 + Size: 2,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Cliff + 2: Transition + 3: Rock + Template@1093: + Id: 1093 + Images: BLOXXMAS.R8 + Frames: 2, 360, 3, 380 + Size: 2,2 + Category: Bridge + Tiles: + 0: Rock + 1: Transition + 2: Cliff + 3: Cliff + Template@1090: + Id: 1090 + Images: BLOXXMAS.R8 + Frames: 363, 5, 383, 6 + Size: 2,2 + Category: Bridge + Tiles: + 0: Transition + 1: Rock + 2: Cliff + 3: Cliff + Template@1094: + Id: 1094 + Images: BLOXXMAS.R8 + Frames: 350, 351, 370, 371 + Size: 2,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Rock + 2: Cliff + 3: Transition + Template@1095: + Id: 1095 + Images: BLOXXMAS.R8 + Frames: 351, 352, 371, 372 + Size: 2,2 + Category: Bridge + Tiles: + 0: Rock + 1: Cliff + 2: Transition + 3: Cliff + Template@1096: + Id: 1096 + Images: BLOXXMAS.R8 + Frames: 367, 368, 387, 388 + Size: 2,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Transition + 2: Cliff + 3: Rock + Template@1097: + Id: 1097 + Images: BLOXXMAS.R8 + Frames: 368, 369, 388, 389 + Size: 2,2 + Category: Bridge + Tiles: + 0: Transition + 1: Cliff + 2: Rock + 3: Cliff + Template@1098: + Id: 1098 + Images: BLOXXMAS.R8 + Frames: 341, 361 + Size: 1,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Transition + Template@1099: + Id: 1099 + Images: BLOXXMAS.R8 + Frames: 342, 362 + Size: 1,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Transition + Template@1102: + Id: 1102 + Images: BLOXXMAS.R8 + Frames: 353, 373 + Size: 1,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Rough + Template@1104: + Id: 1104 + Images: BLOXXMAS.R8 + Frames: 354, 374 + Size: 1,2 + Category: Bridge + Tiles: + 0: Cliff + 1: Rough + Template@1100: + Id: 1100 + Images: BLOXXMAS.R8 + Frames: 361, 381 + Size: 1,2 + Category: Bridge + Tiles: + 0: Transition + 1: Cliff + Template@1101: + Id: 1101 + Images: BLOXXMAS.R8 + Frames: 362, 382 + Size: 1,2 + Category: Bridge + Tiles: + 0: Transition + 1: Cliff + Template@1103: + Id: 1103 + Images: BLOXXMAS.R8 + Frames: 297, 393 + Size: 1,2 + Category: Bridge + Tiles: + 0: Rough + 1: Cliff + Template@1105: + Id: 1105 + Images: BLOXXMAS.R8 + Frames: 316, 394 + Size: 1,2 + Category: Bridge + Tiles: + 0: Rough + 1: Cliff + Template@1106: + Id: 1106 + Images: BLOXTREE.R8 + Frames: 381 + Size: 1,1 + Category: Rock-Detail + Tiles: + 0: Rock + Template@1107: + Id: 1107 + Images: BLOXXMAS.R8 + Frames: 289, 290, 309, 310 + Size: 2,2 + Category: Sand-Detail + Tiles: + 0: Cliff + 1: Cliff + 2: Cliff + 3: Cliff + Template@1108: + Id: 1108 + Images: BLOXXMAS.R8 + Frames: 344, 345 + Size: 2,1 + Category: Bridge + Tiles: + 0: Cliff + 1: Transition