diff --git a/mods/d2k/fluent/lua.ftl b/mods/d2k/fluent/lua.ftl index 6e0fd28132..171d9d927c 100644 --- a/mods/d2k/fluent/lua.ftl +++ b/mods/d2k/fluent/lua.ftl @@ -76,6 +76,7 @@ smuggler-leader = Smuggler Leader. were-warned-will-pay = You were warned. Now you will pay. destroy-smugglers-mercenaries = Destroy the Smugglers and their mercenaries. capture-harkonnen-barracks-release-hostages = Capture the Harkonnen barracks to release the hostages. +use-engineer-hint = Use engineers to reclaim destroyed vehicles. ## harkonnen-01ab, harkonnen-03ab eliminate-atreides-units-reinforcements = Eliminate all Atreides forces and reinforcements in the area. @@ -112,6 +113,8 @@ destroy-ordos = Destroy the Ordos. ## harkonnen-06ab ixian-transports-detected = Ixian transports detected. capture-destroy-ordos-starport = Capture or destroy the Ordos Starport. +smuggler-warning-harkonnen = Mind your own business Harkonnen. +smuggler-warning-ordos = Mind your own business Ordos. ## harkonnen-06ab, harkonnen-09ab smugglers-now-hostile = The Smugglers are now hostile! @@ -132,6 +135,8 @@ atreides-commander = Atreides Commander ## harkonnen-09ab build-deploy-mcv = Build and deploy an MCV to establish a Construction Yard. +husk-reclaimed-harkonnen = We reclaimed some Atreides wreckage from battle. +engineers-hint = Our engineers maybe find then useful. ## ordos-01ab, ordos-02ab, ordos-03ab, ordos-04, ordos-05 outpost-not-captured-destroyed = Do not let the Outpost be captured or destroyed. diff --git a/mods/d2k/maps/atreides-02a/atreides02a-AI.lua b/mods/d2k/maps/atreides-02a/atreides02a-AI.lua index 5248c060db..e61f38723e 100644 --- a/mods/d2k/maps/atreides-02a/atreides02a-AI.lua +++ b/mods/d2k/maps/atreides-02a/atreides02a-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -25,10 +25,13 @@ HarkonnenInfantryTypes = { "light_inf" } ActivateAI = function() IdlingUnits[Harkonnen] = { } + Defending[Harkonnen] = { } + AttackDelay[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 5000 * DifficultyModifier[Difficulty] DefendAndRepairBase(Harkonnen, HarkonnenBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local toBuild = function() return HarkonnenInfantryTypes end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 ProduceUnits(Harkonnen, HBarracks, delay, toBuild, AttackGroupSize[Difficulty], attackThresholdSize) end diff --git a/mods/d2k/maps/atreides-02a/map.yaml b/mods/d2k/maps/atreides-02a/map.yaml index 02619db143..d2cf9f267f 100644 --- a/mods/d2k/maps/atreides-02a/map.yaml +++ b/mods/d2k/maps/atreides-02a/map.yaml @@ -168,6 +168,13 @@ Actors: HarkonnenRally4: waypoint Owner: Neutral Location: 30,28 + Actor5: harvester.debris + Owner: Creeps + Facing: 384 + Location: 18,18 + Actor25: crate + Owner: Neutral + Location: 42,39 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/atreides-02b/atreides02b-AI.lua b/mods/d2k/maps/atreides-02b/atreides02b-AI.lua index 5248c060db..b9775e5546 100644 --- a/mods/d2k/maps/atreides-02b/atreides02b-AI.lua +++ b/mods/d2k/maps/atreides-02b/atreides02b-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -25,10 +25,14 @@ HarkonnenInfantryTypes = { "light_inf" } ActivateAI = function() IdlingUnits[Harkonnen] = { } + Defending[Harkonnen] = {} + AttackDelay[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 5000 * DifficultyModifier[Difficulty] + LastHarvesterEaten[Harkonnen] = true DefendAndRepairBase(Harkonnen, HarkonnenBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local toBuild = function() return HarkonnenInfantryTypes end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 ProduceUnits(Harkonnen, HBarracks, delay, toBuild, AttackGroupSize[Difficulty], attackThresholdSize) end diff --git a/mods/d2k/maps/atreides-02b/map.yaml b/mods/d2k/maps/atreides-02b/map.yaml index 22d5a8b50d..1117914898 100644 --- a/mods/d2k/maps/atreides-02b/map.yaml +++ b/mods/d2k/maps/atreides-02b/map.yaml @@ -138,6 +138,9 @@ Actors: HarkonnenRally4: waypoint Owner: Neutral Location: 30,28 + Actor4: crate + Owner: Neutral + Location: 12,21 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/atreides-03a/atreides03a-AI.lua b/mods/d2k/maps/atreides-03a/atreides03a-AI.lua index 755fb66121..a2bdb514b0 100644 --- a/mods/d2k/maps/atreides-03a/atreides03a-AI.lua +++ b/mods/d2k/maps/atreides-03a/atreides03a-AI.lua @@ -13,7 +13,8 @@ AttackGroupSize = normal = 8, hard = 10 } -AttackDelays = + +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -33,13 +34,16 @@ InitAIUnits = function() end ActivateAI = function() + Defending[Ordos] = { } + AttackDelay[Ordos] = 12000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Ordos] = 5000 * DifficultyModifier[Difficulty] LastHarvesterEaten[Ordos] = true Trigger.AfterDelay(0, InitAIUnits) OConyard.Produce(AtreidesUpgrades[1]) OConyard.Produce(AtreidesUpgrades[2]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local infantryToBuild = function() return { Utils.Random(OrdosInfantryTypes) } end local vehiclesToBuild = function() return { Utils.Random(OrdosVehicleTypes) } end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 @@ -49,4 +53,22 @@ ActivateAI = function() ProduceUnits(Ordos, OBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) ProduceUnits(Ordos, OLightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) end) + + if Difficulty ~= "easy" then + Ordos.GrantCondition("base-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + } + + Trigger.OnBuildingPlaced(Ordos, function(p, building) + table.insert(OrdosBase, building) + DefendAndRepairBase(Ordos, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Ordos, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + end diff --git a/mods/d2k/maps/atreides-03a/atreides03a.lua b/mods/d2k/maps/atreides-03a/atreides03a.lua index 0716b240a8..2d70aff44a 100644 --- a/mods/d2k/maps/atreides-03a/atreides03a.lua +++ b/mods/d2k/maps/atreides-03a/atreides03a.lua @@ -143,7 +143,6 @@ WorldLoaded = function() end Trigger.OnRemovedFromWorld(AConyard, function() - -- Mission already failed, no need to check the other conditions as well if checkResourceCapacity() then return diff --git a/mods/d2k/maps/atreides-03a/map.bin b/mods/d2k/maps/atreides-03a/map.bin index 866f870597..25dc0bb884 100644 Binary files a/mods/d2k/maps/atreides-03a/map.bin and b/mods/d2k/maps/atreides-03a/map.bin differ diff --git a/mods/d2k/maps/atreides-03a/map.yaml b/mods/d2k/maps/atreides-03a/map.yaml index 2062814267..03cf23c3b5 100644 --- a/mods/d2k/maps/atreides-03a/map.yaml +++ b/mods/d2k/maps/atreides-03a/map.yaml @@ -135,6 +135,20 @@ Actors: OrdosRally2: waypoint Owner: Neutral Location: 39,30 + Actor3: crate_explosion + Owner: Neutral + Location: 17,5 + Actor4: carryall.debris + Owner: Neutral + Facing: 384 + Location: 44,14 + Actor6: combat_tank_h.debris + Facing: 384 + Location: 18,52 + Owner: Creeps + Actor7: crate_explosion + Owner: Neutral + Location: 4,52 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/atreides-03a/rules.yaml b/mods/d2k/maps/atreides-03a/rules.yaml index d2bec3d626..46b4fd5e2a 100644 --- a/mods/d2k/maps/atreides-03a/rules.yaml +++ b/mods/d2k/maps/atreides-03a/rules.yaml @@ -1,6 +1,33 @@ Player: PlayerResources: DefaultCash: 3000 + ExternalCondition@AI: + Condition: base-rebuilder + BaseBuilderBotModule@Ordos: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks + SiloTypes: silo + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + wind_trap: 4 + light_factory: 1 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 World: LuaScript: diff --git a/mods/d2k/maps/atreides-03b/atreides03b-AI.lua b/mods/d2k/maps/atreides-03b/atreides03b-AI.lua index 174e9102df..ea94976794 100644 --- a/mods/d2k/maps/atreides-03b/atreides03b-AI.lua +++ b/mods/d2k/maps/atreides-03b/atreides03b-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -24,6 +24,10 @@ AttackDelays = OrdosInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } InitAIUnits = function() + Defending[Ordos] = {} + AttackDelay[Ordos] = 12000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Ordos] = 5000 * DifficultyModifier[Difficulty] + DefencePerimeter[Ordos] = GetCellsInRectangle(CPos.New(9,6), CPos.New(20,27)) IdlingUnits[Ordos] = Reinforcements.Reinforce(Ordos, InitialOrdosReinforcements, OrdosPaths[2]) DefendAndRepairBase(Ordos, OrdosBase, 0.75, AttackGroupSize[Difficulty]) end @@ -34,7 +38,7 @@ ActivateAI = function() OConyard.Produce(AtreidesUpgrades[1]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local toBuild = function() return { Utils.Random(OrdosInfantryTypes) } end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 @@ -42,4 +46,29 @@ ActivateAI = function() Trigger.AfterDelay(DateTime.Seconds(14), function() ProduceUnits(Ordos, OBarracks, delay, toBuild, AttackGroupSize[Difficulty], attackThresholdSize) end) + + if Difficulty ~= "easy" then + Ordos.GrantCondition("base-rebuilder") + end + + local productionTypes = + { + barracks = toBuild, + } + + Trigger.OnBuildingPlaced(Ordos, function(p, building) + table.insert(OrdosBase, building) + DefendAndRepairBase(Ordos, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Ordos, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + Utils.Do(OrdosScouts, function(a) + Trigger.OnKilled(a, function(self, killer) + if not killer.IsDead then + CheckArea(Ordos, killer.Location, 4) + end + end) + end) + end diff --git a/mods/d2k/maps/atreides-03b/atreides03b.lua b/mods/d2k/maps/atreides-03b/atreides03b.lua index d94b806e0d..67b46e4c23 100644 --- a/mods/d2k/maps/atreides-03b/atreides03b.lua +++ b/mods/d2k/maps/atreides-03b/atreides03b.lua @@ -9,6 +9,8 @@ OrdosBase = { OBarracks, OWindTrap1, OWindTrap2, OOutpost, OConyard, ORefinery, OSilo } +OrdosScouts = { OScout1, OScout2, OScout3 } + OrdosReinforcements = { easy = @@ -143,7 +145,6 @@ WorldLoaded = function() end Trigger.OnRemovedFromWorld(AConyard, function() - -- Mission already failed, no need to check the other conditions as well if checkResourceCapacity() then return @@ -176,4 +177,10 @@ WorldLoaded = function() Media.PlaySpeechNotification(Atreides, "Reinforce") Reinforcements.ReinforceWithTransport(Atreides, "carryall.reinforce", AtreidesReinforcements, AtreidesPath, { AtreidesPath[1] }) end) + + Trigger.OnDamaged(HarvesterDebris, function(self, attacker) + if self.MaxHealth / 2 > self.Health then + self.Deploy() + end + end) end diff --git a/mods/d2k/maps/atreides-03b/map.bin b/mods/d2k/maps/atreides-03b/map.bin index af8864c036..299afc23ed 100644 Binary files a/mods/d2k/maps/atreides-03b/map.bin and b/mods/d2k/maps/atreides-03b/map.bin differ diff --git a/mods/d2k/maps/atreides-03b/map.yaml b/mods/d2k/maps/atreides-03b/map.yaml index 58caf5631f..41c0bfd606 100644 --- a/mods/d2k/maps/atreides-03b/map.yaml +++ b/mods/d2k/maps/atreides-03b/map.yaml @@ -48,24 +48,9 @@ Actors: Actor3: wormspawner Location: 27,14 Owner: Creeps - Actor8: light_inf - Location: 48,24 - Owner: Ordos - Actor9: light_inf - Location: 19,34 - Owner: Ordos - Actor10: light_inf - Location: 35,34 - Owner: Ordos - Actor11: trooper - Location: 18,35 - Owner: Ordos Actor12: light_inf Location: 18,37 Owner: Ordos - Actor13: trooper - Location: 19,39 - Owner: Ordos Actor14: light_inf Location: 34,50 Owner: Ordos @@ -132,6 +117,33 @@ Actors: OrdosRally2: waypoint Owner: Neutral Location: 15,34 + HarvesterDebris: harvester.debris + Owner: Creeps + Facing: 384 + Location: 50,12 + Actor0: combat_tank_a.debris + Owner: Creeps + Facing: 384 + Location: 22,37 + Actor1: harvester.debris + Owner: Creeps + Facing: 384 + Location: 25,10 + OScout1: light_inf + Location: 48,24 + Owner: Ordos + OScout2: light_inf + Location: 35,34 + Owner: Ordos + Actor13: trooper + Location: 19,39 + Owner: Ordos + Actor9: light_inf + Location: 19,34 + Owner: Ordos + OScout3: trooper + Location: 18,35 + Owner: Ordos Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/atreides-03b/rules.yaml b/mods/d2k/maps/atreides-03b/rules.yaml index cb43f9ac05..ec91d31851 100644 --- a/mods/d2k/maps/atreides-03b/rules.yaml +++ b/mods/d2k/maps/atreides-03b/rules.yaml @@ -1,6 +1,32 @@ Player: PlayerResources: DefaultCash: 3000 + ExternalCondition@AI: + Condition: base-rebuilder + BaseBuilderBotModule@Ordos: + RequiresCondition: base-rebuilder + BuildingQueues: Building + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + wind_trap: 2 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 World: LuaScript: diff --git a/mods/d2k/maps/atreides-04/atreides04-AI.lua b/mods/d2k/maps/atreides-04/atreides04-AI.lua index 47485f3bf9..b133da3809 100644 --- a/mods/d2k/maps/atreides-04/atreides04-AI.lua +++ b/mods/d2k/maps/atreides-04/atreides04-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -26,22 +26,28 @@ HarkonnenTankType = { "combat_tank_h" } -- Overwrite the template function because of the message SendAttack = function(owner, size) - if Attacking[owner] then - return - end + if Attacking[owner] then return end + Attacking[owner] = true HoldProduction[owner] = true local units = SetupAttackGroup(owner, size) - Utils.Do(units, IdleHunt) + Utils.Do(units, Trigger.ClearAll) if #units > 0 then Media.DisplayMessage(UserInterface.GetFluentMessage("harkonnen-units-approaching"), UserInterface.GetFluentMessage("fremen-leader")) end - Trigger.OnAllRemovedFromWorld(units, function() - Attacking[owner] = false - HoldProduction[owner] = false + Trigger.AfterDelay(1, function() + Utils.Do(units, function(u) + u.Stop() + IdleHunt(u) + end) + + Trigger.OnAllRemovedFromWorld(units, function() + Attacking[owner] = false + HoldProduction[owner] = false + end) end) end @@ -52,15 +58,34 @@ InitAIUnits = function() end ActivateAI = function() + Defending[Harkonnen] = {} + HarvesterCount[Harkonnen] = 2 + AttackDelay[Harkonnen] = 9000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 9000 * DifficultyModifier[Difficulty] LastHarvesterEaten[Harkonnen] = true InitAIUnits() FremenProduction() - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local infantryToBuild = function() return { Utils.Random(HarkonnenInfantryTypes) } end local tanksToBuild = function() return HarkonnenTankType end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 ProduceUnits(Harkonnen, HarkonnenBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) ProduceUnits(Harkonnen, HarkonnenHeavyFact, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + + ActivateCrushLogic() + + local productionTypes = + { + barracks = infantryToBuild, + heavy_factory = tanksToBuild + } + + Trigger.OnBuildingPlaced(Harkonnen, function(p, building) + table.insert(HarkonnenBase, building) + DefendAndRepairBase(Harkonnen, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Harkonnen, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end diff --git a/mods/d2k/maps/atreides-04/atreides04.lua b/mods/d2k/maps/atreides-04/atreides04.lua index 1a309c18ae..ca1c378077 100644 --- a/mods/d2k/maps/atreides-04/atreides04.lua +++ b/mods/d2k/maps/atreides-04/atreides04.lua @@ -48,8 +48,8 @@ HarkonnenReinforcements = HarkonnenAttackDelay = { easy = DateTime.Minutes(3), - normal = DateTime.Minutes(2) + DateTime.Seconds(20), - hard = DateTime.Minutes(1) + normal = DateTime.Minutes(2) + DateTime.Seconds(30), + hard = DateTime.Minutes(2) } HarkonnenAttackWaves = @@ -74,6 +74,7 @@ AtreidesReinforcements = { "trike", "combat_tank_a", "combat_tank_a" }, { "quad", "combat_tank_a", "combat_tank_a" } } + AtreidesPath = { AtreidesEntry.Location, AtreidesRally.Location } FremenInterval = @@ -85,9 +86,9 @@ FremenInterval = IntegrityLevel = { - easy = 50, - normal = 75, - hard = 100 + easy = 10, + normal = 50, + hard = 90 } FremenProduction = function() @@ -145,7 +146,7 @@ WorldLoaded = function() ProtectFremen = AddPrimaryObjective(Atreides, "protect-fremen-sietch") KillHarkonnen = AddPrimaryObjective(Atreides, "destroy-harkonnen") local keepSietchIntact = UserInterface.GetFluentMessage("keep-sietch-intact", { ["integrity"] = IntegrityLevel[Difficulty] }) - KeepIntegrity = AddPrimaryObjective(Atreides, keepSietchIntact) + KeepIntegrity = AddSecondaryObjective(Atreides, keepSietchIntact) Camera.Position = AConyard.CenterPosition HarkonnenAttackLocation = AConyard.Location @@ -164,6 +165,7 @@ WorldLoaded = function() UserInterface.SetMissionText(UserInterface.GetFluentMessage("sietch-destroyed"), Atreides.Color) Atreides.MarkFailedObjective(ProtectFremen) end) + Trigger.OnDamaged(Sietch, function() if AttackNotifier <= 0 then AttackNotifier = DateTime.Seconds(10) @@ -185,6 +187,7 @@ WorldLoaded = function() unit.AttackMove(HarkonnenAttackLocation) IdleHunt(unit) end + SendCarryallReinforcements(Harkonnen, 0, HarkonnenAttackWaves[Difficulty], HarkonnenAttackDelay[Difficulty], path, HarkonnenReinforcements[Difficulty], waveCondition, huntFunction) Actor.Create("upgrade.barracks", true, { Owner = Harkonnen }) @@ -194,6 +197,7 @@ WorldLoaded = function() Media.PlaySpeechNotification(Atreides, "Reinforce") Reinforcements.Reinforce(Atreides, AtreidesReinforcements[1], AtreidesPath) end) + Trigger.AfterDelay(DateTime.Minutes(1) + DateTime.Seconds(40), function() Media.PlaySpeechNotification(Atreides, "Reinforce") Reinforcements.ReinforceWithTransport(Atreides, "carryall.reinforce", AtreidesReinforcements[2], AtreidesPath, { AtreidesPath[1] }) diff --git a/mods/d2k/maps/atreides-04/map.bin b/mods/d2k/maps/atreides-04/map.bin index c2d08db18a..b994d2270c 100644 Binary files a/mods/d2k/maps/atreides-04/map.bin and b/mods/d2k/maps/atreides-04/map.bin differ diff --git a/mods/d2k/maps/atreides-04/map.yaml b/mods/d2k/maps/atreides-04/map.yaml index 58d7e5aa0b..d3e89621e6 100644 --- a/mods/d2k/maps/atreides-04/map.yaml +++ b/mods/d2k/maps/atreides-04/map.yaml @@ -413,7 +413,44 @@ Actors: Location: 5,23 FremenRally: waypoint Owner: Neutral - Location: 56,54 + Location: 56,56 + Actor27: combat_tank_h.debris + Owner: Creeps + Facing: 384 + Location: 58,50 + Actor33: frigate.debris + Owner: Creeps + Location: 33,28 + Actor34: crate + Owner: Fremen + Location: 39,57 + Actor36: crate + Owner: Fremen + Location: 38,57 + Actor53: crate + Owner: Fremen + Location: 37,57 + Actor54: crate + Owner: Fremen + Location: 37,56 + Actor57: crate + Owner: Fremen + Location: 38,56 + Actor59: crate + Owner: Fremen + Location: 39,56 + Actor62: combat_tank_a.debris + Owner: Creeps + Facing: 384 + Location: 14,45 + Actor8: combat_tank_h.debris + Owner: Creeps + Facing: 384 + Location: 51,57 + Actor63: combat_tank_a.debris + Owner: Creeps + Facing: 384 + Location: 31,48 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/atreides-05/atreides05-AI.lua b/mods/d2k/maps/atreides-05/atreides05-AI.lua index b969f02851..9ff6a5d850 100644 --- a/mods/d2k/maps/atreides-05/atreides05-AI.lua +++ b/mods/d2k/maps/atreides-05/atreides05-AI.lua @@ -7,6 +7,17 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(5) + +InitialProductionDelay = { + HarkonnenMain = + { + easy = DateTime.Seconds(100), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(30) + } +} + AttackGroupSize = { easy = 6, @@ -14,7 +25,14 @@ AttackGroupSize = hard = 10 } -AttackDelays = +EarlyProductionDelays = +{ + easy = { DateTime.Seconds(7), DateTime.Seconds(10) }, + normal = { DateTime.Seconds(5), DateTime.Seconds(7) }, + hard = { DateTime.Seconds(3), DateTime.Seconds(5) } +} + +LateProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -22,7 +40,9 @@ AttackDelays = } HarkonnenInfantryTypes = { "light_inf", "light_inf", "trooper", "trooper", "trooper" } + HarkonnenVehicleTypes = { "trike", "trike", "trike", "quad", "quad" } + HarkonnenTankType = { "combat_tank_h" } InitAIUnits = function() @@ -49,7 +69,13 @@ ProduceInfantry = function() return end - local delay = Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) + local delay = 0 + if EarlyGameStage >= DateTime.GameTime then + delay = Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + delay = Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + local toBuild = { Utils.Random(HarkonnenInfantryTypes) } Harkonnen.Build(toBuild, function(unit) IdlingUnits[Harkonnen][#IdlingUnits[Harkonnen] + 1] = unit[1] @@ -63,15 +89,56 @@ end ActivateAI = function() Harkonnen.Cash = 15000 + Defending[Harkonnen] = {} + AttackDelay[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 5000 * DifficultyModifier[Difficulty] + HarvesterCount[Harkonnen] = 1 + PatrolPoints[Harkonnen] = { HPatrolPoint1.Location, HPatrolPoint2.Location,HPatrolPoint3.Location } + DefencePerimeter[Harkonnen] = GetCellsInRectangle(CPos.New(13,13), CPos.New(36,25)) LastHarvesterEaten[Harkonnen] = true InitAIUnits() - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end - local vehilcesToBuild = function() return { Utils.Random(HarkonnenVehicleTypes) } end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + + local vehiclesToBuild = function() return { Utils.Random(HarkonnenVehicleTypes) } end local tanksToBuild = function() return HarkonnenTankType end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 - ProduceInfantry() - ProduceUnits(Harkonnen, HarkonnenLightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Harkonnen, HarkonnenHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["HarkonnenMain"][Difficulty], function() + ProduceInfantry() + ProduceUnits(Harkonnen, HarkonnenLightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Harkonnen, HarkonnenHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + ActivateCrushLogic() + + if Difficulty == "normal" then + Harkonnen.GrantCondition("base-rebuilder") + end + + if Difficulty == "hard" then + Harkonnen.GrantCondition("defense-rebuilder") + end + + local productionTypes = + { + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuild + } + + Trigger.OnBuildingPlaced(Harkonnen, function(p, building) + table.insert(HarkonnenBase, building) + DefendAndRepairBase(Harkonnen, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Harkonnen, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end diff --git a/mods/d2k/maps/atreides-05/atreides05.lua b/mods/d2k/maps/atreides-05/atreides05.lua index 8f99237069..590ba6361b 100644 --- a/mods/d2k/maps/atreides-05/atreides05.lua +++ b/mods/d2k/maps/atreides-05/atreides05.lua @@ -8,6 +8,7 @@ ]] HarkonnenBase = { HarkonnenConstructionYard, 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 = @@ -59,6 +60,7 @@ HarkonnenInfantryReinforcements = { "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 = @@ -139,13 +141,14 @@ HarkonnenPaths = } 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) + normal = DateTime.Minutes(9), + hard = DateTime.Minutes(8) } Wave = 0 @@ -310,6 +313,10 @@ WorldLoaded = function() Media.DisplayMessage(contrabandApproaching, Mentat) end) + Trigger.AfterDelay(500, function() + Media.DisplayMessage(UserInterface.GetFluentMessage("use-engineer-hint"), Mentat) + end) + Trigger.OnAllKilledOrCaptured(HarkonnenBase, function() Utils.Do(Harkonnen.GetGroundAttackers(), IdleHunt) end) @@ -330,6 +337,7 @@ WorldLoaded = function() Atreides.MarkFailedObjective(DefendStarport) end end) + Trigger.OnDamaged(Starport, function(_, attacker) if Starport.Owner ~= Smuggler or attacker.IsDead or attacker.Owner ~= Atreides then return @@ -347,6 +355,7 @@ WorldLoaded = function() end end end) + Trigger.OnCapture(Starport, function() DefendStarport = AddSecondaryObjective(Atreides, "defend-captured-starport") @@ -366,12 +375,14 @@ WorldLoaded = function() Trigger.OnKilled(HarkonnenBarracks, function() Atreides.MarkFailedObjective(CaptureBarracks) end) + Trigger.OnDamaged(HarkonnenBarracks, function() if AttackNotifier <= 0 and HarkonnenBarracks.Health < HarkonnenBarracks.MaxHealth * 3/4 then AttackNotifier = DateTime.Seconds(10) Media.DisplayMessage(UserInterface.GetFluentMessage("do-not-destroy-barracks"), Mentat) end end) + Trigger.OnCapture(HarkonnenBarracks, function() Media.DisplayMessage(UserInterface.GetFluentMessage("hostages-released"), Mentat) @@ -400,7 +411,7 @@ WorldLoaded = function() if not Warned and a.Owner == Atreides and a.Type ~= "carryall" then Warned = true Trigger.RemoveFootprintTrigger(id) - Media.DisplayMessage(UserInterface.GetFluentMessage("stay-away-from-starport"), UserInterface.GetFluentMessage("smuggler-leader")) + Media.DisplayMessage(UserInterface.GetFluentMessage("stay-away-from-starport"), UserInterface.GetFluentMessage("smuggler-leader"), HSLColor.FromHex("9C8408")) end end) @@ -408,7 +419,7 @@ WorldLoaded = function() if not Paid and a.Owner == Atreides and a.Type ~= "carryall" then Paid = true Trigger.RemoveFootprintTrigger(id) - Media.DisplayMessage(UserInterface.GetFluentMessage("were-warned-will-pay"), UserInterface.GetFluentMessage("smuggler-leader")) + Media.DisplayMessage(UserInterface.GetFluentMessage("were-warned-will-pay"), UserInterface.GetFluentMessage("smuggler-leader"), HSLColor.FromHex("9C8408")) Utils.Do(Smuggler.GetGroundAttackers(), function(unit) unit.AttackMove(SmugglerWaypoint2.Location) end) @@ -427,4 +438,11 @@ WorldLoaded = function() StopInfantryProduction = true end end) + + Trigger.OnEnteredProximityTrigger(Trigger1.CenterPosition, WDist.New(2 * 1024), function(a, id) + if HarkonnenTankDebris1.IsInWorld and a.Owner == Atreides then + HarkonnenTankDebris1.Deploy() + Trigger.RemoveProximityTrigger(id) + end + end) end diff --git a/mods/d2k/maps/atreides-05/map.bin b/mods/d2k/maps/atreides-05/map.bin index dd1f33d4a4..5e2cf00f61 100644 Binary files a/mods/d2k/maps/atreides-05/map.bin and b/mods/d2k/maps/atreides-05/map.bin differ diff --git a/mods/d2k/maps/atreides-05/map.yaml b/mods/d2k/maps/atreides-05/map.yaml index f3253b5456..e8e88f2438 100644 --- a/mods/d2k/maps/atreides-05/map.yaml +++ b/mods/d2k/maps/atreides-05/map.yaml @@ -365,6 +365,41 @@ Actors: HarkonnenRally4: waypoint Owner: Neutral Location: 49,4 + HPatrolPoint1: waypoint + Owner: Neutral + Location: 40,27 + HPatrolPoint2: waypoint + Owner: Neutral + Location: 21,38 + HPatrolPoint3: waypoint + Owner: Neutral + Location: 24,60 + Actor0: repair_pad + Owner: Atreides + Location: 59,59 + Actor1: combat_tank_a.debris + Owner: Atreides + Facing: 384 + Location: 59,63 + Actor2: combat_tank_a.debris + Owner: Atreides + Facing: 384 + Location: 62,63 + Actor3: combat_tank_a.debris + Owner: Atreides + Facing: 384 + Location: 56,62 + Actor9: harvester.debris + Owner: Creeps + Facing: 384 + Location: 17,47 + HarkonnenTankDebris1: combat_tank_h.debris + Owner: Creeps + Facing: 384 + Location: 8,58 + Trigger1: waypoint + Owner: Neutral + Location: 8,58 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/atreides-05/rules.yaml b/mods/d2k/maps/atreides-05/rules.yaml index 42d5eb85fc..acc2150cbc 100644 --- a/mods/d2k/maps/atreides-05/rules.yaml +++ b/mods/d2k/maps/atreides-05/rules.yaml @@ -1,6 +1,64 @@ Player: PlayerResources: DefaultCash: 7000 + ExternalCondition@AI: + Condition: base-rebuilder + ExternalCondition@AIDefense: + Condition: defense-rebuilder + BaseBuilderBotModule@campaign: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 6 + silo: 1 + BuildingFractions: + wind_trap: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@defense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory + SiloTypes: silo + DefenseTypes: medium_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 6 + silo: 1 + medium_gun_turret: 6 + BuildingFractions: + wind_trap: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + medium_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-01a/harkonnen01a.lua b/mods/d2k/maps/harkonnen-01a/harkonnen01a.lua index 2ba7b3c7b1..d47f96a454 100644 --- a/mods/d2k/maps/harkonnen-01a/harkonnen01a.lua +++ b/mods/d2k/maps/harkonnen-01a/harkonnen01a.lua @@ -32,6 +32,7 @@ AtreidesReinforcements = } AtreidesEntryWaypoints = { AtreidesWaypoint1.Location, AtreidesWaypoint2.Location, AtreidesWaypoint3.Location, AtreidesWaypoint4.Location } + AtreidesAttackDelay = DateTime.Seconds(30) AtreidesAttackWaves = @@ -49,6 +50,7 @@ ToHarvest = } HarkonnenReinforcements = { "light_inf", "light_inf", "light_inf", "trike" } + HarkonnenEntryPath = { HarkonnenWaypoint.Location, HarkonnenRally.Location } Messages = @@ -118,11 +120,8 @@ WorldLoaded = function() end Trigger.OnRemovedFromWorld(HarkonnenConyard, function() - -- Mission already failed, no need to check the other conditions as well - if checkResourceCapacity() then - return - end + if checkResourceCapacity() then return end local refs = Utils.Where(Map.ActorsInWorld, function(actor) return actor.Type == "refinery" and actor.Owner == Harkonnen end) diff --git a/mods/d2k/maps/harkonnen-01b/harkonnen01b.lua b/mods/d2k/maps/harkonnen-01b/harkonnen01b.lua index 2ba7b3c7b1..d47f96a454 100644 --- a/mods/d2k/maps/harkonnen-01b/harkonnen01b.lua +++ b/mods/d2k/maps/harkonnen-01b/harkonnen01b.lua @@ -32,6 +32,7 @@ AtreidesReinforcements = } AtreidesEntryWaypoints = { AtreidesWaypoint1.Location, AtreidesWaypoint2.Location, AtreidesWaypoint3.Location, AtreidesWaypoint4.Location } + AtreidesAttackDelay = DateTime.Seconds(30) AtreidesAttackWaves = @@ -49,6 +50,7 @@ ToHarvest = } HarkonnenReinforcements = { "light_inf", "light_inf", "light_inf", "trike" } + HarkonnenEntryPath = { HarkonnenWaypoint.Location, HarkonnenRally.Location } Messages = @@ -118,11 +120,8 @@ WorldLoaded = function() end Trigger.OnRemovedFromWorld(HarkonnenConyard, function() - -- Mission already failed, no need to check the other conditions as well - if checkResourceCapacity() then - return - end + if checkResourceCapacity() then return end local refs = Utils.Where(Map.ActorsInWorld, function(actor) return actor.Type == "refinery" and actor.Owner == Harkonnen end) diff --git a/mods/d2k/maps/harkonnen-02a/harkonnen02a-AI.lua b/mods/d2k/maps/harkonnen-02a/harkonnen02a-AI.lua index 28629d01ba..7a043e3eb1 100644 --- a/mods/d2k/maps/harkonnen-02a/harkonnen02a-AI.lua +++ b/mods/d2k/maps/harkonnen-02a/harkonnen02a-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -24,10 +24,13 @@ AttackDelays = AtreidesInfantryTypes = { "light_inf" } ActivateAI = function() + Defending[Atreides] = {} + AttackDelay[Atreides] = 3000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Atreides] = 3000 * DifficultyModifier[Difficulty] IdlingUnits[Atreides] = { } - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local toBuild = function() return AtreidesInfantryTypes end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 DefendAndRepairBase(Atreides, AtreidesBase, 0.75, AttackGroupSize[Difficulty]) ProduceUnits(Atreides, ABarracks, delay, toBuild, AttackGroupSize[Difficulty], attackThresholdSize) diff --git a/mods/d2k/maps/harkonnen-02a/map.bin b/mods/d2k/maps/harkonnen-02a/map.bin index 37f5e8252f..6baf482f83 100644 Binary files a/mods/d2k/maps/harkonnen-02a/map.bin and b/mods/d2k/maps/harkonnen-02a/map.bin differ diff --git a/mods/d2k/maps/harkonnen-02a/map.yaml b/mods/d2k/maps/harkonnen-02a/map.yaml index 85fdeb1768..4012caef70 100644 --- a/mods/d2k/maps/harkonnen-02a/map.yaml +++ b/mods/d2k/maps/harkonnen-02a/map.yaml @@ -162,6 +162,12 @@ Actors: AtreidesRally4: waypoint Owner: Neutral Location: 47,4 + Actor1: crate + Owner: Neutral + Location: 33,47 + Actor3: crate_reveal + Owner: Neutral + Location: 27,16 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-02b/harkonnen02b-AI.lua b/mods/d2k/maps/harkonnen-02b/harkonnen02b-AI.lua index 347e66dbbd..b8c1c828f0 100644 --- a/mods/d2k/maps/harkonnen-02b/harkonnen02b-AI.lua +++ b/mods/d2k/maps/harkonnen-02b/harkonnen02b-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -22,16 +22,20 @@ AttackDelays = } AtreidesInfantryTypes = { "light_inf" } + AtreidesVehicleTypes = { "trike" } ActivateAI = function() + Defending[Atreides] = {} + AttackDelay[Atreides] = 3000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Atreides] = 3000 * DifficultyModifier[Difficulty] IdlingUnits[Atreides] = { } - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local infantryToBuild = function() return AtreidesInfantryTypes end - local vehilcesToBuild = function() return AtreidesVehicleTypes end + local vehiclesToBuild = function() return AtreidesVehicleTypes end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 DefendAndRepairBase(Atreides, AtreidesBase, 0.75, AttackGroupSize[Difficulty]) ProduceUnits(Atreides, ABarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Atreides, ALightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Atreides, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) end diff --git a/mods/d2k/maps/harkonnen-03a/harkonnen03a-AI.lua b/mods/d2k/maps/harkonnen-03a/harkonnen03a-AI.lua index 93eb479543..d24d46096e 100644 --- a/mods/d2k/maps/harkonnen-03a/harkonnen03a-AI.lua +++ b/mods/d2k/maps/harkonnen-03a/harkonnen03a-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -22,9 +22,13 @@ AttackDelays = } AtreidesInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + AtreidesVehicleTypes = { "trike", "trike", "quad" } ActivateAI = function() + Defending[Atreides] = {} + AttackDelay[Atreides] = 7000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Atreides] = 5000 * DifficultyModifier[Difficulty] IdlingUnits[Atreides] = { } LastHarvesterEaten[Atreides] = true DefendAndRepairBase(Atreides, AtreidesBase, 0.75, AttackGroupSize[Difficulty]) @@ -32,14 +36,31 @@ ActivateAI = function() AConyard.Produce(HarkonnenUpgrades[1]) AConyard.Produce(HarkonnenUpgrades[2]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local infantryToBuild = function() return { Utils.Random(AtreidesInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 -- Finish the upgrades first before trying to build something Trigger.AfterDelay(DateTime.Seconds(14), function() ProduceUnits(Atreides, ABarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Atreides, ALightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Atreides, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + if Difficulty ~= "easy" then + Atreides.GrantCondition("base-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + } + + Trigger.OnBuildingPlaced(Atreides, function(p, building) + table.insert(OrdosMainBase, building) + DefendAndRepairBase(Atreides, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Atreides, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) end) end diff --git a/mods/d2k/maps/harkonnen-03a/harkonnen03a.lua b/mods/d2k/maps/harkonnen-03a/harkonnen03a.lua index df84c7ee91..f4b61f51d2 100644 --- a/mods/d2k/maps/harkonnen-03a/harkonnen03a.lua +++ b/mods/d2k/maps/harkonnen-03a/harkonnen03a.lua @@ -8,6 +8,7 @@ ]] 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)}, @@ -88,9 +89,11 @@ AtreidesHunterPaths = } HarkonnenReinforcements = { "trike", "trike", "quad" } + HarkonnenPath = { HarkonnenEntry.Location, HarkonnenRally.Location } HarkonnenBaseBuildings = { "barracks", "light_factory" } + HarkonnenUpgrades = { "upgrade.barracks", "upgrade.light" } MessageCheck = function(index) diff --git a/mods/d2k/maps/harkonnen-03a/map.bin b/mods/d2k/maps/harkonnen-03a/map.bin index 6b3ce0087a..4efaf3dd90 100644 Binary files a/mods/d2k/maps/harkonnen-03a/map.bin and b/mods/d2k/maps/harkonnen-03a/map.bin differ diff --git a/mods/d2k/maps/harkonnen-03a/map.yaml b/mods/d2k/maps/harkonnen-03a/map.yaml index c3242bddd6..a52484a736 100644 --- a/mods/d2k/maps/harkonnen-03a/map.yaml +++ b/mods/d2k/maps/harkonnen-03a/map.yaml @@ -177,6 +177,17 @@ Actors: HarkonnenEntry: waypoint Owner: Neutral Location: 29,2 + Actor2: carryall.debris + Owner: Creeps + Facing: 384 + Location: 24,40 + Actor18: crate + Owner: Neutral + Location: 17,33 + Actor19: harvester.debris + Owner: Creeps + Facing: 384 + Location: 12,61 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-03a/rules.yaml b/mods/d2k/maps/harkonnen-03a/rules.yaml index a955418389..7073197ec4 100644 --- a/mods/d2k/maps/harkonnen-03a/rules.yaml +++ b/mods/d2k/maps/harkonnen-03a/rules.yaml @@ -1,6 +1,34 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AI: + Condition: base-rebuilder + BaseBuilderBotModule@campaign: + RequiresCondition: base-rebuilder + BuildingQueues: Building + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + wind_trap: 3 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-03b/harkonnen03b-AI.lua b/mods/d2k/maps/harkonnen-03b/harkonnen03b-AI.lua index 1cf7a5a352..42eac2d7e8 100644 --- a/mods/d2k/maps/harkonnen-03b/harkonnen03b-AI.lua +++ b/mods/d2k/maps/harkonnen-03b/harkonnen03b-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -22,29 +22,48 @@ AttackDelays = } AtreidesInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + AtreidesVehicleTypes = { "trike", "trike", "quad" } InitAIUnits = function() IdlingUnits[Atreides] = Reinforcements.Reinforce(Atreides, AtreidesInitialReinforcements, AtreidesInitialPath) - DefendAndRepairBase(Atreides, AtreidesBase, 0.75, AttackGroupSize[Difficulty]) end ActivateAI = function() + Defending[Atreides] = {} + AttackDelay[Atreides] = 7000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Atreides] = 5000 * DifficultyModifier[Difficulty] LastHarvesterEaten[Atreides] = true Trigger.AfterDelay(0, InitAIUnits) - AConyard.Produce(HarkonnenUpgrades[1]) AConyard.Produce(HarkonnenUpgrades[2]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local infantryToBuild = function() return { Utils.Random(AtreidesInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 -- Finish the upgrades first before trying to build something Trigger.AfterDelay(DateTime.Seconds(14), function() ProduceUnits(Atreides, ABarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Atreides, ALightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Atreides, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + if Difficulty ~= "easy" then + Atreides.GrantCondition("base-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + } + + Trigger.OnBuildingPlaced(Atreides, function(p, building) + table.insert(OrdosMainBase, building) + DefendAndRepairBase(Atreides, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Atreides, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) end) end diff --git a/mods/d2k/maps/harkonnen-03b/harkonnen03b.lua b/mods/d2k/maps/harkonnen-03b/harkonnen03b.lua index 60bb894979..3bbc548f8b 100644 --- a/mods/d2k/maps/harkonnen-03b/harkonnen03b.lua +++ b/mods/d2k/maps/harkonnen-03b/harkonnen03b.lua @@ -8,6 +8,7 @@ ]] AtreidesBase = { ABarracks, AWindTrap1, AWindTrap2, AWindTrap3, ALightFactory, AOutpost, AConyard, ARefinery, ASilo1, ASilo2, ASilo3, ASilo4 } + AtreidesBaseAreaTriggers = { { CPos.New(10, 53), CPos.New(11, 53), CPos.New(12, 53), CPos.New(13, 53), CPos.New(14, 53), CPos.New(15, 53), CPos.New(16, 53), CPos.New(17, 53), CPos.New(17, 52), CPos.New(17, 51), CPos.New(17, 50), CPos.New(17, 49), CPos.New(17, 48), CPos.New(17, 47), CPos.New(17, 46), CPos.New(17, 45), CPos.New(17, 44), CPos.New(17, 43), CPos.New(17, 42), CPos.New(17, 41), CPos.New(17, 40), CPos.New(17, 39), CPos.New(17, 38), CPos.New(2, 35), CPos.New(3, 35), CPos.New(4, 35), CPos.New(5, 35), CPos.New(6, 35), CPos.New(7, 35), CPos.New(8, 35), CPos.New(9, 35), CPos.New(10, 35), CPos.New(11, 35), CPos.New(12, 35) }, @@ -87,12 +88,15 @@ AtreidesHunterPaths = } AtreidesInitialPath = { AtreidesEntry2.Location, AtreidesRally2.Location } + AtreidesInitialReinforcements = { "light_inf", "light_inf", "quad", "quad", "trike", "trike", "trooper", "trooper" } HarkonnenReinforcements = { "quad", "quad" } + HarkonnenPath = { HarkonnenEntry.Location, HarkonnenRally.Location } HarkonnenBaseBuildings = { "barracks", "light_factory" } + HarkonnenUpgrades = { "upgrade.barracks", "upgrade.light" } MessageCheck = function(index) @@ -150,4 +154,10 @@ WorldLoaded = function() TriggerCarryallReinforcements(Harkonnen, Atreides, AtreidesBaseAreaTriggers[2], AtreidesHunters[2], AtreidesHunterPaths[2]) TriggerCarryallReinforcements(Harkonnen, Atreides, AtreidesBaseAreaTriggers[3], AtreidesHunters[3], AtreidesHunterPaths[3]) TriggerCarryallReinforcements(Harkonnen, Atreides, AtreidesBaseAreaTriggers[4], AtreidesHunters[4], AtreidesHunterPaths[4]) + + Trigger.OnRemovedFromWorld(CrateScript, function() + VisionPoint.GrantCondition("activate", 150) + Media.PlaySound("MULTI1.WAV") + Camera.Position = VisionPoint.CenterPosition + end) end diff --git a/mods/d2k/maps/harkonnen-03b/map.yaml b/mods/d2k/maps/harkonnen-03b/map.yaml index 8728295ba3..4e2546d112 100644 --- a/mods/d2k/maps/harkonnen-03b/map.yaml +++ b/mods/d2k/maps/harkonnen-03b/map.yaml @@ -207,6 +207,13 @@ Actors: HarkonnenEntry: waypoint Owner: Neutral Location: 65,45 + CrateScript: crate_script + Owner: Neutral + Location: 55,9 + VisionPoint: vision_provider + Faction: + Location: 46,8 + Owner: Harkonnen Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-03b/rules.yaml b/mods/d2k/maps/harkonnen-03b/rules.yaml index 134bf781f0..acedd47296 100644 --- a/mods/d2k/maps/harkonnen-03b/rules.yaml +++ b/mods/d2k/maps/harkonnen-03b/rules.yaml @@ -1,6 +1,34 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AI: + Condition: base-rebuilder + BaseBuilderBotModule@campaign: + RequiresCondition: base-rebuilder + BuildingQueues: Building + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + wind_trap: 3 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-04/harkonnen04-AI.lua b/mods/d2k/maps/harkonnen-04/harkonnen04-AI.lua index 5fe5b05049..99b1af3349 100644 --- a/mods/d2k/maps/harkonnen-04/harkonnen04-AI.lua +++ b/mods/d2k/maps/harkonnen-04/harkonnen04-AI.lua @@ -7,6 +7,14 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(5) + +InitialProductionDelay = { + easy = DateTime.Seconds(150), + normal = DateTime.Seconds(100), + hard = DateTime.Seconds(50) +} + AttackGroupSize = { easy = 6, @@ -14,7 +22,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -22,24 +30,71 @@ AttackDelays = } AtreidesInfantryTypes = { "light_inf", "light_inf", "trooper", "trooper", "trooper" } + AtreidesVehicleTypes = { "trike", "trike", "quad" } + AtreidesTankType = { "combat_tank_a" } +local attackThresholdSize = AttackGroupSize[Difficulty] * 2 + ActivateAI = function() + Defending[Atreides] = { } + Defending[Fremen] = { } + AttackDelay[Atreides] = 5000 * DifficultyModifier[Difficulty] + AttackDelay[Fremen] = 5000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Atreides] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Fremen] = 5000 * DifficultyModifier[Difficulty] + HarvesterCount[Atreides] = 2 + HarvesterCount[Fremen] = 2 IdlingUnits[Fremen] = { } IdlingUnits[Atreides] = Utils.Concat(Reinforcements.Reinforce(Atreides, InitialAtreidesReinforcements[1], AtreidesPaths[2]), Reinforcements.Reinforce(Atreides, InitialAtreidesReinforcements[2], AtreidesPaths[3])) FremenProduction() + DefencePerimeter[Atreides] = GetCellsInRectangle(CPos.New(4,68), CPos.New(44,82)) + Utils.Do(IdlingUnits[Atreides], function(a) SelectRoutine(Atreides, a) end) + PatrolPoints[Atreides] = { APatrolPoint1.Location, APatrolPoint2.Location, APatrolPoint3.Location, APatrolPoint4.Location } + DefendAndRepairBase(Atreides, AtreidesBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(Fremen, FremenBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) + end + local infantryToBuild = function() return { Utils.Random(AtreidesInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end local tanksToBuild = function() return AtreidesTankType end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 ProduceUnits(Atreides, ABarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Atreides, ALightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Atreides, AHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay[Difficulty], function() + ProduceUnits(Atreides, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Atreides, AHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + ActivateCrushLogic() + + if Difficulty == "normal" then + Atreides.GrantCondition("base-rebuilder") + end + + if Difficulty == "hard" then + Atreides.GrantCondition("defense-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuild + } + + Trigger.OnBuildingPlaced(Atreides, function(p, building) + table.insert(AtreidesBase, building) + DefendAndRepairBase(Atreides, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Atreides, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end diff --git a/mods/d2k/maps/harkonnen-04/harkonnen04.lua b/mods/d2k/maps/harkonnen-04/harkonnen04.lua index 7cd4bc9e4c..10280c17f9 100644 --- a/mods/d2k/maps/harkonnen-04/harkonnen04.lua +++ b/mods/d2k/maps/harkonnen-04/harkonnen04.lua @@ -7,7 +7,8 @@ information, see COPYING. ]] -AtreidesBase = { AConyard, AOutpost, ARefinery, AHeavyFactory, ALightFactory, AGunt1, AGunt2, ABarracks, ASilo, APower1, APower2, APower3, APower4, APower5, APower6 } +AtreidesBase = { AConyard, AOutpost, ARefinery, AHeavyFactory, ALightFactory, AGunt1, AGunt2, ABarracks, APower1, APower2, APower3, APower4, APower5, APower6 } + FremenBase = { FGunt1, FGunt2 } BaseAreaTriggers = @@ -105,6 +106,8 @@ FremenHunterPaths = { FremenEntry3.Location, FremenRally3.Location } } +FremenAmbushSquad = { FremenAmbuser1, FremenAmbuser2, FremenAmbuser3, FremenAmbuser4 } + HarkonnenReinforcements = { "combat_tank_h", "combat_tank_h" } HarkonnenPath = { HarkonnenEntry.Location, HarkonnenRally.Location } @@ -156,7 +159,8 @@ WorldLoaded = function() Atreides = Player.GetPlayer("Atreides") Fremen = Player.GetPlayer("Fremen") Harkonnen = Player.GetPlayer("Harkonnen") - + Defending[Atreides] = {} + Defending[Fremen] = {} InitObjectives(Harkonnen) KillAtreides = AddPrimaryObjective(Harkonnen, "destroy-atreides") KillFremen = AddPrimaryObjective(Harkonnen, "destroy-fremen") @@ -164,11 +168,7 @@ WorldLoaded = function() Camera.Position = HConyard.CenterPosition FremenAttackLocation = HConyard.Location - - Trigger.OnAllKilledOrCaptured(AtreidesBase, function() - Utils.Do(Atreides.GetGroundAttackers(), IdleHunt) - end) - + IdleHuntOnBaseDestroyed(Atreides, AtreidesBase) Trigger.OnAllKilled(Sietches, function() SietchesAreDestroyed = true end) @@ -179,6 +179,7 @@ WorldLoaded = function() unit.AttackMove(FremenAttackLocation) IdleHunt(unit) end + SendCarryallReinforcements(Fremen, 0, FremenAttackWaves[Difficulty], FremenAttackDelay[Difficulty], path, FremenReinforcements[Difficulty], waveCondition, huntFunction) Actor.Create("upgrade.barracks", true, { Owner = Atreides }) @@ -201,4 +202,17 @@ WorldLoaded = function() TriggerCarryallReinforcements(Harkonnen, Fremen, BaseAreaTriggers[1], FremenHunters[1], FremenHunterPaths[3], fremenCondition) TriggerCarryallReinforcements(Harkonnen, Fremen, BaseAreaTriggers[2], FremenHunters[2], FremenHunterPaths[2], fremenCondition) TriggerCarryallReinforcements(Harkonnen, Fremen, BaseAreaTriggers[3], FremenHunters[3], FremenHunterPaths[1], fremenCondition) + + Trigger.OnRemovedFromWorld(ExplosionCrate, function() + Utils.Do(FremenAmbushSquad, function(fremen) + fremen.Stance = "AttackAnything" + end) + end) + + Trigger.OnAllKilled({AGunt1, AGunt2}, function() + EmergencyBuildRate[Atreides] = true + Trigger.AfterDelay(600, function() + EmergencyBuildRate[Atreides] = false + end) + end) end diff --git a/mods/d2k/maps/harkonnen-04/map.yaml b/mods/d2k/maps/harkonnen-04/map.yaml index 3eab314c4f..676fbd9f5b 100644 --- a/mods/d2k/maps/harkonnen-04/map.yaml +++ b/mods/d2k/maps/harkonnen-04/map.yaml @@ -376,6 +376,54 @@ Actors: AtreidesEntry3: waypoint Owner: Neutral Location: 22,91 + APatrolPoint1: waypoint + Owner: Neutral + Location: 56,67 + APatrolPoint2: waypoint + Owner: Neutral + Location: 34,43 + APatrolPoint3: waypoint + Owner: Neutral + Location: 23,18 + APatrolPoint4: waypoint + Owner: Neutral + Location: 60,17 + Actor6: crate + Owner: Neutral + Location: 25,12 + Actor8: crate + Owner: Neutral + Location: 8,18 + ExplosionCrate: crate_explosion + Owner: Neutral + Location: 18,28 + FremenAmbuser1: fremen + Owner: Fremen + Location: 15,25 + SubCell: 3 + Facing: 384 + Stance: ReturnFire + FremenAmbuser2: fremen + Owner: Fremen + Location: 20,25 + SubCell: 3 + Stance: ReturnFire + Facing: 384 + FremenAmbuser3: fremen + Owner: Fremen + Location: 15,28 + SubCell: 3 + Stance: ReturnFire + Facing: 384 + FremenAmbuser4: fremen + Owner: Fremen + Location: 15,29 + SubCell: 3 + Stance: ReturnFire + Facing: 384 + Actor32: crate_reveal + Owner: Neutral + Location: 16,47 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-04/rules.yaml b/mods/d2k/maps/harkonnen-04/rules.yaml index 5520367a56..5508d90fc2 100644 --- a/mods/d2k/maps/harkonnen-04/rules.yaml +++ b/mods/d2k/maps/harkonnen-04/rules.yaml @@ -1,6 +1,68 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AI: + Condition: base-rebuilder + ExternalCondition@AIDefense: + Condition: defense-rebuilder + BaseBuilderBotModule@AIMainBase: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 7 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AIMainBaseWithDefense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 7 + silo: 1 + medium_gun_turret: 2 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + medium_gun_turret: 2 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-05/harkonnen05-AI.lua b/mods/d2k/maps/harkonnen-05/harkonnen05-AI.lua index b332b54668..f0d7bfb20e 100644 --- a/mods/d2k/maps/harkonnen-05/harkonnen05-AI.lua +++ b/mods/d2k/maps/harkonnen-05/harkonnen05-AI.lua @@ -7,6 +7,24 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(6) + +InitialProductionDelay = { + OrdosMain = + { + easy = DateTime.Seconds(150), + normal = DateTime.Seconds(100), + hard = DateTime.Seconds(50) + }, + + OrdosSmall = + { + easy = DateTime.Seconds(120), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(30) + }, +} + AttackGroupSize = { easy = 6, @@ -14,7 +32,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -22,28 +40,86 @@ AttackDelays = } OrdosInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + OrdosVehicleTypes = { "raider", "raider", "quad" } + OrdosTankType = { "combat_tank_o" } ActivateAI = function() + Defending[OrdosMain] = {} + Defending[OrdosSmall] = {} + AttackDelay[OrdosMain] = 8000 * DifficultyModifier[Difficulty] + AttackDelay[OrdosSmall] = 10000 * DifficultyModifier[Difficulty] + AttackDelay[Corrino] = 1000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[OrdosMain] = 8000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[OrdosSmall] = 7000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Corrino] = 2000 * DifficultyModifier[Difficulty] + HarvesterCount[OrdosMain] = 2 + HarvesterCount[OrdosSmall] = 2 + + Trigger.OnAllKilledOrCaptured(ProductionBuildingsOrdosSmallBase, function() + TimeBetweenAttacks[OrdosMain] = 0 + end) + + PatrolPoints[OrdosMain] = {OrdosPatrol1.Location, OrdosPatrol2.Location } + PatrolPoints[OrdosSmall] = {OrdosRally1.Location, OrdosRally2.Location, OrdosRally4.Location } + DefencePerimeter[OrdosMain] = GetCellsInRectangle(CPos.New(51,11), CPos.New(67,38)) + DefencePerimeter[OrdosSmall] = GetCellsInRectangle(CPos.New(6,69), CPos.New(22,80)) + IdlingUnits[OrdosMain] = Utils.Concat(Reinforcements.Reinforce(OrdosMain, InitialOrdosReinforcements[1], InitialOrdosPaths[1]), Reinforcements.Reinforce(OrdosMain, InitialOrdosReinforcements[2], InitialOrdosPaths[2])) IdlingUnits[OrdosSmall] = Reinforcements.Reinforce(OrdosSmall, InitialOrdosReinforcements[1], InitialOrdosPaths[3]) - IdlingUnits[Corrino] = { CSaraukar1, CSaraukar2, CSaraukar3, CSaraukar4, CSaraukar5 } + IdlingUnits[Corrino] = { CSardaukar1, CSardaukar2, CSardaukar3, CSardaukar4, CSardaukar5 } DefendAndRepairBase(OrdosMain, OrdosMainBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(OrdosSmall, OrdosSmallBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(Corrino, CorrinoBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) + end + local infantryToBuild = function() return { Utils.Random(OrdosInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(OrdosVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(OrdosVehicleTypes) } end local tanksToBuild = function() return OrdosTankType end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + local attackThresholdSize = {} + attackThresholdSize[OrdosMain] = AttackGroupSize[Difficulty] * 3 + attackThresholdSize[OrdosSmall] = AttackGroupSize[Difficulty] * 2 - ProduceUnits(OrdosMain, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OLightFactory1, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["OrdosMain"][Difficulty], function() + ProduceUnits(OrdosMain, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OLightFactory1, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + end) - ProduceUnits(OrdosSmall, OBarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosSmall, OLightFactory2, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["OrdosSmall"][Difficulty], function() + ProduceUnits(OrdosSmall, OBarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall]) + ProduceUnits(OrdosSmall, OLightFactory2, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall]) + end) + + ActivateCrushLogic() + + if Difficulty == "normal" then + OrdosMain.GrantCondition("base-rebuilder") + end + + if Difficulty == "hard" then + OrdosMain.GrantCondition("defense-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuild + } + + Trigger.OnBuildingPlaced(OrdosMain, function(p, building) + table.insert(OrdosMainBase, building) + DefendAndRepairBase(OrdosMain, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(OrdosMain, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + end) end diff --git a/mods/d2k/maps/harkonnen-05/harkonnen05.lua b/mods/d2k/maps/harkonnen-05/harkonnen05.lua index 557dc85b0d..27cb92169d 100644 --- a/mods/d2k/maps/harkonnen-05/harkonnen05.lua +++ b/mods/d2k/maps/harkonnen-05/harkonnen05.lua @@ -8,9 +8,15 @@ ]] OrdosMainBase = { OConYard, OOutpost, ORefinery1, ORefinery2, OHeavyFactory, OLightFactory1, OHiTechFactory, OGunt1, OGunt2, OGunt3, OGunt4, OBarracks1, OBarracks2, OPower1, OPower2, OPower3, OPower4, OPower5, OPower6, OPower7, OPower8, OPower9 } + OrdosSmallBase = { ORefinery3, OBarracks3, OLightFactory2, OGunt5, OGunt6, OPower10, OPower11, OPower12, OPower13, OSilo } + CorrinoBase = { CStarport, CPower1, CPower2 } +ProductionBuildingsOrdosSmallBase = { ORefinery3, OBarracks3, OLightFactory2 } + +SmugglerSquad = {Smuggler1, Smuggler2, Smuggler3} + BaseAreaTriggers = { { CPos.New(68, 70), CPos.New(69, 70), CPos.New(70, 70), CPos.New(71, 70) }, @@ -135,6 +141,7 @@ SendStarportReinforcements = function() return end + Media.DisplayMessage(UserInterface.GetFluentMessage("imperial-ships-penetrating-defense-grid"), Mentat) local units = Reinforcements.ReinforceWithTransport(Corrino, "frigate", CorrinoStarportReinforcements[Difficulty], { CorrinoStarportEntry.Location, CStarport.Location + CVec.New(1, 1) }, { CorrinoStarportExit.Location })[2] Utils.Do(units, function(unit) unit.AttackMove(OrdosAttackLocation) @@ -142,12 +149,6 @@ SendStarportReinforcements = function() end) SendStarportReinforcements() - - if Harkonnen.IsObjectiveFailed(GuardOutpost) then - return - end - - Media.DisplayMessage(UserInterface.GetFluentMessage("imperial-ships-penetrating-defense-grid"), Mentat) end) end @@ -222,13 +223,28 @@ Tick = function() end end +ChangeOwner = function(old_owner, new_owner) + local units = old_owner.GetActors() + Utils.Do(units, function(unit) + if not unit.IsDead then + unit.Owner = new_owner + end + end) +end + WorldLoaded = function() OrdosMain = Player.GetPlayer("Ordos Main Base") OrdosSmall = Player.GetPlayer("Ordos Small Base") Corrino = Player.GetPlayer("Corrino") Harkonnen = Player.GetPlayer("Harkonnen") + SmugglerNeutral = Player.GetPlayer("Smugglers - Neutral") + SmugglerEnemy = Player.GetPlayer("Smugglers - Enemy") + Defending[OrdosMain] = {} + Defending[OrdosSmall] = {} + Defending[Corrino] = {} InitObjectives(Harkonnen) + KillOrdos = AddPrimaryObjective(Harkonnen, "destroy-ordos") KillCorrino = AddPrimaryObjective(Harkonnen, "destroy-imperial-forces") GuardOutpost = AddSecondaryObjective(Harkonnen, "keep-modified-outpost-intact") @@ -257,17 +273,20 @@ WorldLoaded = function() Media.DisplayMessage(UserInterface.GetFluentMessage("protect-outpost"), Mentat) end) + Trigger.OnAllKilledOrCaptured({ HOutpost }, function() + SendStarportReinforcements() + end) + local path = function() return Utils.Random(OrdosPaths) end local waveCondition = function() return Harkonnen.IsObjectiveCompleted(KillOrdos) end local huntFunction = function(unit) unit.AttackMove(OrdosAttackLocation) IdleHunt(unit) end + SendCarryallReinforcements(OrdosMain, 0, OrdosAttackWaves[Difficulty], OrdosAttackDelay[Difficulty], path, OrdosReinforcements[Difficulty], waveCondition, huntFunction) OrdosReinforcementNotification(0, OrdosAttackWaves[Difficulty]) - SendStarportReinforcements() - Actor.Create("upgrade.barracks", true, { Owner = OrdosMain }) Actor.Create("upgrade.light", true, { Owner = OrdosMain }) Actor.Create("upgrade.heavy", true, { Owner = OrdosMain }) diff --git a/mods/d2k/maps/harkonnen-05/map.bin b/mods/d2k/maps/harkonnen-05/map.bin index f1d7cb8b49..13fc074e1b 100644 Binary files a/mods/d2k/maps/harkonnen-05/map.bin and b/mods/d2k/maps/harkonnen-05/map.bin differ diff --git a/mods/d2k/maps/harkonnen-05/map.yaml b/mods/d2k/maps/harkonnen-05/map.yaml index 48c42b680e..e491486c63 100644 --- a/mods/d2k/maps/harkonnen-05/map.yaml +++ b/mods/d2k/maps/harkonnen-05/map.yaml @@ -514,6 +514,18 @@ Actors: HarkonnenEntry2: waypoint Owner: Neutral Location: 2,29 + OrdosPatrol1: waypoint + Owner: Neutral + Location: 33,19 + OrdosPatrol2: waypoint + Owner: Neutral + Location: 37,41 + Actor9: crate + Owner: Neutral + Location: 48,54 + Actor10: crate + Owner: Neutral + Location: 49,53 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-05/rules.yaml b/mods/d2k/maps/harkonnen-05/rules.yaml index d4c1580da8..1eb94251e9 100644 --- a/mods/d2k/maps/harkonnen-05/rules.yaml +++ b/mods/d2k/maps/harkonnen-05/rules.yaml @@ -1,6 +1,68 @@ Player: PlayerResources: DefaultCash: 6000 + ExternalCondition@AI: + Condition: base-rebuilder + ExternalCondition@AIDefense: + Condition: defense-rebuilder + BaseBuilderBotModule@campaign: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 8 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@defense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 8 + silo: 1 + medium_gun_turret: 4 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + medium_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-06a/harkonnen06a-AI.lua b/mods/d2k/maps/harkonnen-06a/harkonnen06a-AI.lua index bdc665b518..988330156d 100644 --- a/mods/d2k/maps/harkonnen-06a/harkonnen06a-AI.lua +++ b/mods/d2k/maps/harkonnen-06a/harkonnen06a-AI.lua @@ -7,6 +7,24 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(6) + +InitialProductionDelay = { + OrdosMain = + { + easy = DateTime.Seconds(140), + normal = DateTime.Seconds(80), + hard = DateTime.Seconds(40) + }, + + OrdosSmall = + { + easy = DateTime.Seconds(120), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(30) + }, +} + AttackGroupSize = { easy = 6, @@ -14,7 +32,14 @@ AttackGroupSize = hard = 10 } -AttackDelays = +EarlyProductionDelays = +{ + easy = { DateTime.Seconds(10), DateTime.Seconds(13) }, + normal = { DateTime.Seconds(6), DateTime.Seconds(8) }, + hard = { DateTime.Seconds(3), DateTime.Seconds(5) } +} + +LateProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -22,29 +47,90 @@ AttackDelays = } OrdosInfantryTypes = { "light_inf", "light_inf", "trooper", "trooper", "trooper" } + OrdosVehicleTypes = { "raider", "raider", "quad" } + OrdosTankTypes = { "combat_tank_o", "combat_tank_o", "siege_tank" } + OrdosStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_o.starport", "combat_tank_o.starport", "siege_tank.starport", "missile_tank.starport" } ActivateAI = function() + Defending[OrdosMain] = {} + Defending[OrdosSmall] = {} + AttackDelay[OrdosMain] = 14000 * DifficultyModifier[Difficulty] + AttackDelay[OrdosSmall] = 14000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[OrdosMain] = 10000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[OrdosSmall] = 8000 * DifficultyModifier[Difficulty] + HarvesterCount[OrdosMain] = 4 + HarvesterCount[OrdosSmall] = 2 + + Trigger.OnAllKilledOrCaptured(ProductionBuildingsOrdosSmallBase, function() + TimeBetweenAttacks[OrdosMain] = 0 + end) + + PatrolPoints[OrdosMain] = {OrdosRally5.Location, OrdosRally11.Location, OrdosRally12.Location } + PatrolPoints[OrdosSmall] = {OrdosRally5.Location, OrdosRally7.Location, OrdosRally4.Location, OrdosRally5.Location } + DefencePerimeter[OrdosMain] = Utils.Concat( GetCellsInRectangle(CPos.New(37,30), CPos.New(69,50)), GetCellsInRectangle(CPos.New(54,13), CPos.New(70,30))) + DefencePerimeter[OrdosSmall] = GetCellsInRectangle(CPos.New(4,20), CPos.New(15,40)) + IdlingUnits[OrdosMain] = Utils.Concat(Reinforcements.Reinforce(OrdosMain, InitialOrdosReinforcements[1], InitialOrdosPaths[1]), Reinforcements.Reinforce(OrdosMain, InitialOrdosReinforcements[2], InitialOrdosPaths[2])) IdlingUnits[OrdosSmall] = Reinforcements.Reinforce(OrdosSmall, InitialOrdosReinforcements[1], InitialOrdosPaths[3]) DefendAndRepairBase(OrdosMain, OrdosMainBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(OrdosSmall, OrdosSmallBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(OrdosInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(OrdosVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(OrdosVehicleTypes) } end local tanksToBuild = function() return { Utils.Random(OrdosTankTypes) } end local unitsToBuy = function() return { Utils.Random(OrdosStarportTypes) } end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + local attackThresholdSize = {} + attackThresholdSize[OrdosMain] = AttackGroupSize[Difficulty] * 3 + attackThresholdSize[OrdosSmall] = AttackGroupSize[Difficulty] * 2 - ProduceUnits(OrdosMain, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OLightFactory1, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OHeavyFactory1, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OStarport, delay, unitsToBuy, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["OrdosMain"][Difficulty], function() + ProduceUnits(OrdosMain, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OLightFactory1, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OHeavyFactory1, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OStarport, delay, unitsToBuy, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + end) - ProduceUnits(OrdosSmall, OBarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosSmall, OLightFactory2, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["OrdosSmall"][Difficulty], function() + ProduceUnits(OrdosSmall, OBarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall] ) + ProduceUnits(OrdosSmall, OLightFactory2, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall] ) + end) + + ActivateCrushLogic() + + if Difficulty == "normal" then + OrdosSmall.GrantCondition("base-rebuilder2") + end + + if Difficulty == "hard" then + OrdosSmall.GrantCondition("defense-rebuilder2") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuild + } + + Trigger.OnBuildingPlaced(OrdosSmall, function(p, building) + table.insert(OrdosSmallBase, building) + DefendAndRepairBase(OrdosSmall, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(OrdosSmall, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall]) + end) end diff --git a/mods/d2k/maps/harkonnen-06a/harkonnen06a.lua b/mods/d2k/maps/harkonnen-06a/harkonnen06a.lua index fb74251a07..47b9e1cdc5 100644 --- a/mods/d2k/maps/harkonnen-06a/harkonnen06a.lua +++ b/mods/d2k/maps/harkonnen-06a/harkonnen06a.lua @@ -8,8 +8,18 @@ ]] OrdosMainBase = { OOutpost, ORefinery1, ORefinery2, OHeavyFactory1, OHeavyFactory2, OLightFactory1, OHiTechFactory, OResearch, ORepair, OStarport, OGunt1, OGunt2, OGunt3, OGunt4, OGunt5, OGunt6, OGunt7, OGunt8, OGunt9, OGunt10, OGunt11, OGunt12, OBarracks1, OPower1, OPower2, OPower3, OPower4, OPower5, OPower6, OPower7, OPower8, OPower9, OPower10 } + OrdosSmallBase = { OConyard, ORefinery3, OBarracks2, OLightFactory2, OGunt13, OGunt14, OGunt15, OGunt16, OPower11, OPower12, OPower13, OPower14 } +ProductionBuildingsOrdosSmallBase = { OConyard, ORefinery3, OBarracks2, OLightFactory2 } + +SmugglerTriggerZone = Utils.Concat( + GetCellsInRectangle(CPos.New(73,67), CPos.New(81,72)), + GetCellsInRectangle(CPos.New(77,73), CPos.New(82,77)) +) + +SmugglerPatrol = {Smuggler1, Smuggler2, Smuggler3, Smuggler4, Smuggler5, Smuggler6 } + OrdosReinforcements = { easy = @@ -149,9 +159,62 @@ ChangeOwner = function(old_owner, new_owner) end) end +Message = {} +StopFollowing = {} +Trigger.OnEnteredFootprint(SmugglerTriggerZone, function(a, id) + if SmugglerNeutral.HasNoRequiredUnits() then + Trigger.RemoveFootprintTrigger(id) + end + + if a.Owner == Harkonnen and not Message[Harkonnen] then + Media.DisplayMessage(UserInterface.GetFluentMessage("smuggler-warning-harkonnen"), "Smugglers", HSLColor.FromHex("7B2910")) + Message[Harkonnen] = true + end + + if a.Owner == OrdosMain or a.Owner == OrdosSmall and not Message[Ordos] then + Media.DisplayMessage(UserInterface.GetFluentMessage("smuggler-warning-ordos"), "Smugglers", HSLColor.FromHex("7B2910")) + Message[Ordos] = true + end + + if a.Owner ~= SmugglerNeutral then + Utils.Do(SmugglerPatrol, function(u) + if not u.IsDead then + StopFollowing[u] = false + FollowIntruder(u,a) + end + end) + end +end) + +Trigger.OnExitedFootprint(SmugglerTriggerZone, function(a, id) + if SmugglerNeutral.HasNoRequiredUnits() then + Trigger.RemoveFootprintTrigger(id) + end + if a.Owner ~= SmugglerNeutral then + Utils.Do(SmugglerPatrol, function(u) + if not u.IsDead then StopFollowing[u] = true end + end) + end +end) + +FollowIntruder = function(unit, intruder) + if intruder.IsDead or unit.IsDead then return end + if StopFollowing[unit] == true then unit.Stop() return end + unit.Stop() + unit.AttackMove(intruder.Location,1) + Trigger.AfterDelay(100, function() FollowIntruder(unit, intruder) end) +end + CheckSmugglerEnemies = function() Utils.Do(SmugglerUnits, function(unit) Trigger.OnDamaged(unit, function(self, attacker) + if Utils.Any(IgnoreDamageFromTypes, function(a) return a == attacker.Type end) then + return + end + + if not self.HasProperty("Health") then return end + if self.MaxHealth * 0.9 < self.Health then return end + if unit.Owner == SmugglerNeutral and attacker.Owner == Harkonnen then ChangeOwner(SmugglerNeutral, SmugglerHarkonnen) @@ -197,6 +260,25 @@ CheckSmugglerEnemies = function() end) end +EmergencyBehaviour = function(player, target) + if player ~= OrdosSmall then return end + if not OGunt15.IsDead and not OGunt16.IsDead then return end + if #IdlingUnits[OrdosMain] > 10 and Utils.Any(ProductionBuildingsOrdosSmallBase, function(a) return not a.IsDead end) then + local reinforcements = SetupAttackGroup(OrdosMain, Utils.RandomInteger(5, #IdlingUnits[OrdosMain])) + Utils.Do(reinforcements, function(unit) + Trigger.ClearAll(unit) + Trigger.AfterDelay(1, function() + unit.Stop() + unit.Move(OrdosRally13.Location,1) + unit.AttackMove(target) + unit.CallFunc(function() + FindTargetsInArea(player, unit) + end) + end) + end) + end +end + Tick = function() if Harkonnen.HasNoRequiredUnits() then OrdosMain.MarkCompletedObjective(KillHarkonnen1) @@ -234,6 +316,8 @@ Tick = function() ProtectHarvester(units[1], OrdosSmall, AttackGroupSize[Difficulty]) end end + + if IdlingUnits[OrdosMain] == nil and IdlingUnits[OrdosSmall] == nil then return end end WorldLoaded = function() @@ -273,6 +357,7 @@ WorldLoaded = function() unit.AttackMove(OrdosAttackLocation) IdleHunt(unit) end + SendCarryallReinforcements(OrdosMain, 0, OrdosAttackWaves[Difficulty], OrdosAttackDelay[Difficulty], path, OrdosReinforcements[Difficulty], waveCondition, huntFunction) SendStarportReinforcements() diff --git a/mods/d2k/maps/harkonnen-06a/map.yaml b/mods/d2k/maps/harkonnen-06a/map.yaml index 6de2bb1bf6..dcb8af39da 100644 --- a/mods/d2k/maps/harkonnen-06a/map.yaml +++ b/mods/d2k/maps/harkonnen-06a/map.yaml @@ -424,12 +424,14 @@ Actors: OGunt15: medium_gun_turret Location: 7,37 Owner: Ordos Small Base + TurretFacing: 512 Actor111: wall Location: 11,37 Owner: Ordos Small Base OGunt16: medium_gun_turret Location: 11,38 Owner: Ordos Small Base + TurretFacing: 512 Actor113: wall Location: 12,38 Owner: Ordos Small Base @@ -568,18 +570,12 @@ Actors: Actor158: light_inf Location: 9,72 Owner: Harkonnen - Actor159: light_inf - Location: 77,72 - Owner: Smugglers - Neutral SSilo3: silo Location: 81,72 Owner: Smugglers - Neutral Actor161: trike Location: 11,73 Owner: Harkonnen - Actor162: trooper - Location: 80,73 - Owner: Smugglers - Neutral Actor163: trooper Location: 7,74 Owner: Harkonnen @@ -592,15 +588,9 @@ Actors: SRefinery: refinery Location: 78,74 Owner: Smugglers - Neutral - Actor167: light_inf - Location: 82,74 - Owner: Smugglers - Neutral Actor168: combat_tank_h Location: 10,75 Owner: Harkonnen - Actor169: trooper - Location: 82,75 - Owner: Smugglers - Neutral Actor170: light_inf Location: 15,76 Owner: Harkonnen @@ -625,12 +615,6 @@ Actors: Actor177: spicebloom.spawnpoint Location: 72,77 Owner: Neutral - Actor178: light_inf - Location: 82,77 - Owner: Smugglers - Neutral - Actor179: light_inf - Location: 83,77 - Owner: Smugglers - Neutral Actor180: light_inf Location: 11,78 Owner: Harkonnen @@ -715,6 +699,45 @@ Actors: HarkonnenEntry2: waypoint Owner: Neutral Location: 2,68 + OrdosRally11: waypoint + Owner: Neutral + Location: 51,66 + OrdosRally12: waypoint + Owner: Neutral + Location: 79,40 + OrdosRally13: waypoint + Owner: Neutral + Location: 19,16 + Actor5: crate + Location: 81,67 + Faction: + Owner: Smugglers - Neutral + Actor13: crate + Location: 81,69 + Faction: + Owner: Smugglers - Neutral + Actor11: crate + Location: 81,71 + Faction: + Owner: Smugglers - Neutral + Smuggler1: light_inf + Location: 77,72 + Owner: Smugglers - Neutral + Smuggler2: trooper + Location: 80,73 + Owner: Smugglers - Neutral + Smuggler3: light_inf + Location: 82,74 + Owner: Smugglers - Neutral + Smuggler4: trooper + Location: 82,75 + Owner: Smugglers - Neutral + Smuggler5: light_inf + Location: 82,77 + Owner: Smugglers - Neutral + Smuggler6: light_inf + Location: 83,77 + Owner: Smugglers - Neutral Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-06a/rules.yaml b/mods/d2k/maps/harkonnen-06a/rules.yaml index f36e5558d7..df5e52f494 100644 --- a/mods/d2k/maps/harkonnen-06a/rules.yaml +++ b/mods/d2k/maps/harkonnen-06a/rules.yaml @@ -1,6 +1,66 @@ Player: PlayerResources: DefaultCash: 6000 + ExternalCondition@AISmall: + Condition: base-rebuilder2 + ExternalCondition@AISmallDefense: + Condition: defense-rebuilder2 + BaseBuilderBotModule@SmallBase: + RequiresCondition: base-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + research_centre: 1 + high_tech_factory: 1 + wind_trap: 4 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + research_centre: 1 + high_tech_factory: 1 + BaseBuilderBotModule@SmallBasewithDefence: + RequiresCondition: defense-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret + SellRefineryInterval: -1 + PlaceDefenseTowardsEnemyChance: 50 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + wind_trap: 4 + silo: 1 + medium_gun_turret: 4 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + medium_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-06b/harkonnen06b-AI.lua b/mods/d2k/maps/harkonnen-06b/harkonnen06b-AI.lua index 98259e07dd..170c2c9ea7 100644 --- a/mods/d2k/maps/harkonnen-06b/harkonnen06b-AI.lua +++ b/mods/d2k/maps/harkonnen-06b/harkonnen06b-AI.lua @@ -7,6 +7,23 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(7) + +InitialProductionDelay = { + OrdosMain = + { + easy = DateTime.Seconds(180), + normal = DateTime.Seconds(120), + hard = DateTime.Seconds(60) + }, + OrdosSmall = + { + easy = DateTime.Seconds(120), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(0) + } +} + AttackGroupSize = { easy = 6, @@ -14,7 +31,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -22,29 +39,108 @@ AttackDelays = } OrdosInfantryTypes = { "light_inf", "light_inf", "trooper", "trooper", "trooper" } + OrdosVehicleTypes = { "raider", "raider", "quad" } + OrdosTankTypes = { "combat_tank_o", "combat_tank_o", "siege_tank" } -OrdosStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_o.starport", "combat_tank_o.starport", "siege_tank.starport", "missile_tank.starport" } + +OrdosStarportTypes = +{ + EarlyGame = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_o.starport", "combat_tank_o.starport", "siege_tank.starport" }, + LateGame = { "trike.starport", "quad.starport", "combat_tank_o.starport", "siege_tank.starport", "missile_tank.starport", "missile_tank.starport" } +} ActivateAI = function() + Defending[OrdosMain] = {} + Defending[OrdosSmall] = {} + AttackDelay[OrdosMain] = 14000 * DifficultyModifier[Difficulty] + AttackDelay[OrdosSmall] = 12000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[OrdosMain] = 7000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[OrdosSmall] = 7000 * DifficultyModifier[Difficulty] + HarvesterCount[OrdosMain] = 4 + HarvesterCount[OrdosSmall] = 2 + + Trigger.OnAllKilledOrCaptured(ProductionBuildingsOrdosSmallBase, function() + TimeBetweenAttacks[OrdosMain] = 0 + end) + + PatrolPoints[OrdosMain] = {OrdosPatrol3.Location, OrdosPatrol4.Location, OrdosPatrol2.Location } + PatrolPoints[OrdosSmall] = {OrdosPatrol1.Location, OrdosPatrol2.Location, OrdosRally1.Location} + DefencePerimeter[OrdosMain] = GetCellsInRectangle(CPos.New(50,3), CPos.New(94,21)) + DefencePerimeter[OrdosSmall] = GetCellsInRectangle(CPos.New(3,3), CPos.New(25,20)) IdlingUnits[OrdosMain] = Utils.Concat(Reinforcements.Reinforce(OrdosMain, InitialOrdosReinforcements[1], InitialOrdosPaths[1]), Reinforcements.Reinforce(OrdosMain, InitialOrdosReinforcements[2], InitialOrdosPaths[2])) IdlingUnits[OrdosSmall] = Reinforcements.Reinforce(OrdosSmall, InitialOrdosReinforcements[1], InitialOrdosPaths[3]) DefendAndRepairBase(OrdosMain, OrdosMainBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(OrdosSmall, OrdosSmallBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) + end + local infantryToBuild = function() return { Utils.Random(OrdosInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(OrdosVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(OrdosVehicleTypes) } end local tanksToBuild = function() return { Utils.Random(OrdosTankTypes) } end - local unitsToBuy = function() return { Utils.Random(OrdosStarportTypes) } end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + local unitsToBuy = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(OrdosStarportTypes["EarlyGame"]) } + else + return { Utils.Random(OrdosStarportTypes["LateGame"]) } + end + end - ProduceUnits(OrdosMain, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OLightFactory1, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosMain, OStarport, delay, unitsToBuy, AttackGroupSize[Difficulty], attackThresholdSize) + local attackThresholdSize = {} + attackThresholdSize[OrdosMain] = AttackGroupSize[Difficulty] * 3 + attackThresholdSize[OrdosSmall] = AttackGroupSize[Difficulty] * 2 - ProduceUnits(OrdosSmall, OBarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(OrdosSmall, OLightFactory2, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["OrdosMain"][Difficulty], function() + ProduceUnits(OrdosMain, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OLightFactory1, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + ProduceUnits(OrdosMain, OStarport, delay, unitsToBuy, AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + end) + + Trigger.AfterDelay(InitialProductionDelay["OrdosSmall"][Difficulty], function() + ProduceUnits(OrdosSmall, OBarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall]) + ProduceUnits(OrdosSmall, OLightFactory2, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall]) + end) + + local crusherTypes = { "combat_tank_o", "combat_tank_o.starport" } + local crusherFactories = { OHeavyFactory, OStarport } + + ActivateCrushLogic() + + if Difficulty == "easy" then + OrdosMain.GrantCondition("base-rebuilder") + OrdosMain.GrantCondition("base-rebuilder2") + end + + if Difficulty == "hard" then + OrdosMain.GrantCondition("defense-rebuilder") + OrdosMain.GrantCondition("base-rebuilder2") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuild + } + + Trigger.OnBuildingPlaced(OrdosMain, function(p, building) + table.insert(OrdosMainBase, building) + DefendAndRepairBase(OrdosMain, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(OrdosMain, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize[OrdosMain]) + end) + + Trigger.OnBuildingPlaced(OrdosSmall, function(p, building) + table.insert(OrdosSmallBase, building) + DefendAndRepairBase(OrdosSmall, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(OrdosSmall, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize[OrdosSmall]) + end) end diff --git a/mods/d2k/maps/harkonnen-06b/harkonnen06b.lua b/mods/d2k/maps/harkonnen-06b/harkonnen06b.lua index 82a0522f0d..bb8dd98500 100644 --- a/mods/d2k/maps/harkonnen-06b/harkonnen06b.lua +++ b/mods/d2k/maps/harkonnen-06b/harkonnen06b.lua @@ -8,8 +8,18 @@ ]] OrdosMainBase = { OConYard1, OOutpost, ORefinery1, ORefinery2, OHeavyFactory, OLightFactory1, OHiTechFactory, ORepair, OStarport, OGunt1, OGunt2, OGunt3, OGunt4, OGunt5, OGunt6, ORocket1, ORocket2, OBarracks1, OPower1, OPower2, OPower3, OPower4, OPower5, OPower6, OPower7, OPower8, OPower9, OPower10, OSilo1, OSilo2, OSilo3, OSilo4, OSilo5, OSilo6 } + OrdosSmallBase = { OConYard2, ORefinery3, OBarracks2, OLightFactory2, OGunt6, OGunt7, ORocket3, ORocket4, OPower11, OPower12, OPower13, OPower14, OSilo7, OSilo8, OSilo9 } +ProductionBuildingsOrdosSmallBase = { OConyard2, ORefinery3, OBarracks2, OLightFactory2 } + +SmugglerTriggerZone = Utils.Concat( + GetCellsInRectangle(CPos.New(48,24), CPos.New(53,29)), + GetCellsInRectangle(CPos.New(43,30), CPos.New(53,35)) +) + +SmugglerPatrol = {Smuggler1, Smuggler2, Smuggler3, Smuggler4, Smuggler5, Smuggler6 } + OrdosReinforcements = { easy = @@ -118,6 +128,53 @@ SendStarportReinforcements = function() end) end +Message = {} +StopFollowing = {} +Trigger.OnEnteredFootprint(SmugglerTriggerZone, function(a, id) + if SmugglerNeutral.HasNoRequiredUnits() then + Trigger.RemoveFootprintTrigger(id) + end + + if a.Owner == Harkonnen and not Message[Harkonnen] then + Media.DisplayMessage(UserInterface.GetFluentMessage("smuggler-warning-harkonnen"), "Smugglers", HSLColor.FromHex("7B2910")) + Message[Harkonnen] = true + end + + if a.Owner == OrdosMain or a.Owner == OrdosSmall and not Message[OrdosMain] then + Media.DisplayMessage(UserInterface.GetFluentMessage("smuggler-warning-ordos"), "Smugglers", HSLColor.FromHex("7B2910")) + Message[OrdosMain] = true + end + + if a.Owner ~= SmugglerNeutral then + Utils.Do(SmugglerPatrol, function(u) + if not u.IsDead then + StopFollowing[u] = false + FollowIntruder(u,a) + end + end) + end +end) + +Trigger.OnExitedFootprint(SmugglerTriggerZone, function(a, id) + if SmugglerNeutral.HasNoRequiredUnits() then + Trigger.RemoveFootprintTrigger(id) + end + + if a.Owner ~= SmugglerNeutral then + Utils.Do(SmugglerPatrol, function(u) + if not u.IsDead then StopFollowing[u] = true end + end) + end +end) + +FollowIntruder = function(unit, intruder) + if intruder.IsDead or unit.IsDead then return end + if StopFollowing[unit] == true then unit.Stop() return end + unit.Stop() + unit.AttackMove(intruder.Location,1) + Trigger.AfterDelay(100, function() FollowIntruder(unit, intruder) end) +end + ChangeOwner = function(old_owner, new_owner) local units = old_owner.GetActors() Utils.Do(units, function(unit) @@ -130,20 +187,47 @@ end CheckSmugglerEnemies = function() Utils.Do(SmugglerUnits, function(unit) Trigger.OnDamaged(unit, function(self, attacker) + if Utils.Any(IgnoreDamageFromTypes, function(a) return a == attacker.Type end) then + return + end + + if not self.HasProperty("Health") then return end + if self.MaxHealth * 0.9 < self.Health then return end + if unit.Owner == SmugglerNeutral and attacker.Owner == Harkonnen then ChangeOwner(SmugglerNeutral, SmugglerHarkonnen) + + -- Ensure that harvesters that was on a carryall switched sides. + Trigger.AfterDelay(DateTime.Seconds(15), function() + ChangeOwner(SmugglerNeutral, SmugglerHarkonnen) + end) end if unit.Owner == SmugglerOrdos and attacker.Owner == Harkonnen then ChangeOwner(SmugglerOrdos, SmugglerBoth) + + -- Ensure that harvesters that was on a carryall switched sides. + Trigger.AfterDelay(DateTime.Seconds(15), function() + ChangeOwner(SmugglerOrdos, SmugglerBoth) + end) end if unit.Owner == SmugglerNeutral and (attacker.Owner == OrdosMain or attacker.Owner == OrdosSmall) then ChangeOwner(SmugglerNeutral, SmugglerOrdos) + + -- Ensure that harvesters that was on a carryall switched sides. + Trigger.AfterDelay(DateTime.Seconds(15), function() + ChangeOwner(SmugglerNeutral, SmugglerOrdos) + end) end if unit.Owner == SmugglerHarkonnen and (attacker.Owner == OrdosMain or attacker.Owner == OrdosSmall) then ChangeOwner(SmugglerHarkonnen, SmugglerBoth) + + -- Ensure that harvesters that was on a carryall switched sides. + Trigger.AfterDelay(DateTime.Seconds(15), function() + ChangeOwner(SmugglerHarkonnen, SmugglerBoth) + end) end if attacker.Owner == Harkonnen and not MessageCheck then @@ -155,6 +239,13 @@ CheckSmugglerEnemies = function() end) end +EmergencyBehaviour = function(player, target) + if player ~= OrdosSmall then return end + if #IdlingUnits[OrdosMain] > 10 and Utils.Any(OrdosSmallBase, function(a) return not a.IsDead end) then + CheckArea(OrdosMain, target, 10) + end +end + Tick = function() if Harkonnen.HasNoRequiredUnits() then OrdosMain.MarkCompletedObjective(KillHarkonnen1) @@ -231,8 +322,8 @@ WorldLoaded = function() unit.AttackMove(OrdosAttackLocation) IdleHunt(unit) end - SendCarryallReinforcements(OrdosMain, 0, OrdosAttackWaves[Difficulty], OrdosAttackDelay[Difficulty], path, OrdosReinforcements[Difficulty], waveCondition, huntFunction) + SendCarryallReinforcements(OrdosMain, 0, OrdosAttackWaves[Difficulty], OrdosAttackDelay[Difficulty], path, OrdosReinforcements[Difficulty], waveCondition, huntFunction) SendStarportReinforcements() Actor.Create("upgrade.barracks", true, { Owner = OrdosMain }) diff --git a/mods/d2k/maps/harkonnen-06b/map.bin b/mods/d2k/maps/harkonnen-06b/map.bin index d89cb9a035..652e33c657 100644 Binary files a/mods/d2k/maps/harkonnen-06b/map.bin and b/mods/d2k/maps/harkonnen-06b/map.bin differ diff --git a/mods/d2k/maps/harkonnen-06b/map.yaml b/mods/d2k/maps/harkonnen-06b/map.yaml index 0600221107..09dc7dabbb 100644 --- a/mods/d2k/maps/harkonnen-06b/map.yaml +++ b/mods/d2k/maps/harkonnen-06b/map.yaml @@ -394,9 +394,11 @@ Actors: OGunt6: medium_gun_turret Location: 9,17 Owner: Ordos Small Base + TurretFacing: 512 OGunt7: medium_gun_turret Location: 14,17 Owner: Ordos Small Base + TurretFacing: 512 Actor102: wall Location: 15,17 Owner: Ordos Small Base @@ -463,12 +465,6 @@ Actors: SSilo3: silo Location: 51,26 Owner: Smugglers - Neutral - Actor124: trooper - Location: 51,28 - Owner: Smugglers - Neutral - Actor125: light_inf - Location: 53,28 - Owner: Smugglers - Neutral Actor126: wormspawner Location: 76,28 Owner: Creeps @@ -487,24 +483,12 @@ Actors: SPower2: wind_trap Location: 50,30 Owner: Smugglers - Neutral - Actor132: light_inf - Location: 53,30 - Owner: Smugglers - Neutral SRefinery: refinery Location: 43,33 Owner: Smugglers - Neutral - Actor134: light_inf - Location: 50,33 - Owner: Smugglers - Neutral Actor135: harvester Location: 46,35 Owner: Smugglers - Neutral - Actor136: light_inf - Location: 48,35 - Owner: Smugglers - Neutral - Actor137: light_inf - Location: 52,35 - Owner: Smugglers - Neutral Actor138: light_inf Location: 42,36 Owner: Smugglers - Neutral @@ -652,6 +636,42 @@ Actors: OrdosStarportExit: waypoint Owner: Neutral Location: 2,9 + Smuggler1: trooper + Location: 51,28 + Owner: Smugglers - Neutral + Smuggler2: light_inf + Location: 53,28 + Owner: Smugglers - Neutral + Smuggler3: light_inf + Location: 53,30 + Owner: Smugglers - Neutral + Smuggler4: light_inf + Location: 50,33 + Owner: Smugglers - Neutral + Smuggler5: light_inf + Location: 48,35 + Owner: Smugglers - Neutral + Smuggler6: light_inf + Location: 52,35 + Owner: Smugglers - Neutral + OrdosPatrol1: waypoint + Owner: Neutral + Location: 26,49 + OrdosPatrol2: waypoint + Owner: Neutral + Location: 36,33 + OrdosPatrol3: waypoint + Owner: Neutral + Location: 60,29 + OrdosPatrol4: waypoint + Owner: Neutral + Location: 82,43 + Actor4: crate_reveal + Owner: Neutral + Location: 13,27 + Actor5: crate + Owner: Neutral + Location: 82,48 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-06b/rules.yaml b/mods/d2k/maps/harkonnen-06b/rules.yaml index 2fec83a711..327a0f5552 100644 --- a/mods/d2k/maps/harkonnen-06b/rules.yaml +++ b/mods/d2k/maps/harkonnen-06b/rules.yaml @@ -1,6 +1,136 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AIMain: + Condition: base-rebuilder + ExternalCondition@AISmall: + Condition: base-rebuilder2 + ExternalCondition@AIMainWithDefense: + Condition: defense-rebuilder + ExternalCondition@AISmallWithDefense: + Condition: defense-rebuilder2 + BaseBuilderBotModule@MainBase: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + wind_trap: 9 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + BaseBuilderBotModule@MainBaseWithDefence: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + PlaceDefenseTowardsEnemyChance: 50 + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + wind_trap: 7 + silo: 1 + medium_gun_turret: 5 + large_gun_turret: 2 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + medium_gun_turret: 1 + large_gun_turret: 1 + BaseBuilderBotModule@SmallBase: + RequiresCondition: base-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + wind_trap: 4 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + BaseBuilderBotModule@SmallBasewithDefence: + RequiresCondition: defense-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret + SellRefineryInterval: -1 + PlaceDefenseTowardsEnemyChance: 50 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + wind_trap: 4 + silo: 1 + medium_gun_turret: 2 + large_gun_turret: 2 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + medium_gun_turret: 1 + large_gun_turret: 2 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-07/harkonnen07-AI.lua b/mods/d2k/maps/harkonnen-07/harkonnen07-AI.lua index ced8d9b74c..5884c4f1e6 100644 --- a/mods/d2k/maps/harkonnen-07/harkonnen07-AI.lua +++ b/mods/d2k/maps/harkonnen-07/harkonnen07-AI.lua @@ -6,6 +6,30 @@ the License, or (at your option) any later version. For more information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(10) + +InitialProductionDelay = { + AtreidesMain = + { + easy = DateTime.Seconds(400), + normal = DateTime.Seconds(300), + hard = DateTime.Seconds(150) + }, + + AtreidesSmall = + { + easy = DateTime.Seconds(300), + normal = DateTime.Seconds(200), + hard = DateTime.Seconds(100) + }, + + Corrino = + { + easy = DateTime.Seconds(100), + normal = DateTime.Seconds(120), + hard = DateTime.Seconds(60) + } +} AttackGroupSize = { @@ -14,29 +38,60 @@ AttackGroupSize = hard = 10 } -AttackDelays = +EarlyProductionDelays = { - easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, - normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, - hard = { DateTime.Seconds(1), DateTime.Seconds(3) } + easy = { DateTime.Seconds(11), DateTime.Seconds(15) }, + normal = { DateTime.Seconds(10), DateTime.Seconds(12) }, + hard = { DateTime.Seconds(8), DateTime.Seconds(10) } } -InitialProductionDelay = +LateProductionDelays = { - easy = DateTime.Seconds(30), - normal = DateTime.Seconds(15), - hard = 0 + easy = { DateTime.Seconds(6), DateTime.Seconds(10) }, + normal = { DateTime.Seconds(4), DateTime.Seconds(6) }, + hard = { DateTime.Seconds(2), DateTime.Seconds(4) } } AtreidesInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + AtreidesVehicleTypes = { "trike", "trike", "quad" } -AtreidesMainTankTypes = { "combat_tank_a", "combat_tank_a", "siege_tank", "missile_tank", "sonic_tank" } + +AtreidesMainTankTypes = +{ + EarlyGame = { "combat_tank_a", "combat_tank_a" }, + LateGame = { "combat_tank_a", "siege_tank", "missile_tank", "sonic_tank" } +} + AtreidesSmallTankTypes = { "combat_tank_a", "combat_tank_a", "siege_tank" } + AtreidesStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_a.starport", "combat_tank_a.starport", "siege_tank.starport", "missile_tank.starport" } CorrinoInfantryTypes = { "light_inf", "trooper", "sardaukar" } ActivateAI = function() + Defending[AtreidesMain] = {} + Defending[AtreidesSmall] = {} + Defending[Corrino] = {} + -- this is also first attack timing + AttackDelay[AtreidesMain] = 10000 + AttackDelay[AtreidesSmall] = 8000 + AttackDelay[Corrino] = 7000 + TimeBetweenAttacks[AtreidesMain] = 12000 + TimeBetweenAttacks[AtreidesSmall] = 5000 + TimeBetweenAttacks[Corrino] = 11000 + HarvesterCount[AtreidesMain] = 2 + HarvesterCount[AtreidesSmall] = 2 + + Trigger.OnAllKilledOrCaptured(AtreidesSmallBase, function() + TimeBetweenAttacks[AtreidesMain] = 0 + end) + + PatrolPoints[AtreidesMain] = {AtreidesPoint1.Location, AtreidesPoint2.Location, AtreidesPoint3.Location } + PatrolPoints[AtreidesSmall] = {AtreidesPoint4.Location, AtreidesPoint5.Location, AtreidesPoint6.Location } + DefencePerimeter[AtreidesMain] = Utils.Concat( GetCellsInRectangle(CPos.New(73,9), CPos.New(88,26)), GetCellsInRectangle(CPos.New(65,44), CPos.New(82,56))) + DefencePerimeter[AtreidesSmall] = GetCellsInRectangle(CPos.New(5,18), CPos.New(16,32)) + DefencePerimeter[Corrino] = GetCellsInRectangle(CPos.New(89,27), CPos.New(95,35)) + IdlingUnits[AtreidesMain] = Utils.Concat(Reinforcements.Reinforce(AtreidesMain, InitialAtreidesReinforcements[Difficulty][1], InitialAtreidesPaths[1]), Reinforcements.Reinforce(AtreidesMain, InitialAtreidesReinforcements[Difficulty][2], InitialAtreidesPaths[2])) IdlingUnits[AtreidesSmall] = Reinforcements.Reinforce(AtreidesSmall, InitialAtreidesReinforcements[Difficulty][1], InitialAtreidesPaths[3]) IdlingUnits[Corrino] = Reinforcements.Reinforce(Corrino, InitialCorrinoReinforcements, InitialCorrinoPath) @@ -45,23 +100,77 @@ ActivateAI = function() DefendAndRepairBase(AtreidesSmall, AtreidesSmallBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(Corrino, CorrinoBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuildAtreides = function() return { Utils.Random(AtreidesInfantryTypes) } end local infantryToBuildCorrino = function() return { Utils.Random(CorrinoInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end - local tanksToBuildMain = function() return { Utils.Random(AtreidesMainTankTypes) } end + local vehiclesToBuild = function() return { Utils.Random(AtreidesVehicleTypes) } end + local tanksToBuildMain = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(AtreidesMainTankTypes["EarlyGame"]) } + else + return { Utils.Random(AtreidesMainTankTypes["LateGame"]) } + end + end + local tanksToBuildSmall = function() return { Utils.Random(AtreidesSmallTankTypes) } end local unitsToBuy = function() return { Utils.Random(AtreidesStarportTypes) } end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + local attackThresholdSize = {} + attackThresholdSize[AtreidesMain] = AttackGroupSize[Difficulty] * 3 + attackThresholdSize[AtreidesSmall] = AttackGroupSize[Difficulty] * 2 - Trigger.AfterDelay(InitialProductionDelay[Difficulty], function() - ProduceUnits(AtreidesMain, ALightFactory1, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, AHeavyFactory1, delay, tanksToBuildMain, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, AStarport, delay, unitsToBuy, AttackGroupSize[Difficulty], attackThresholdSize) - - ProduceUnits(AtreidesSmall, ABarracks, delay, infantryToBuildAtreides, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesSmall, AHeavyFactory2, delay, tanksToBuildSmall, AttackGroupSize[Difficulty], attackThresholdSize) - - ProduceUnits(Corrino, CBarracks, delay, infantryToBuildCorrino, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesMain"][Difficulty], function() + ProduceUnits(AtreidesMain, ALightFactory1, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize[AtreidesMain]) + ProduceUnits(AtreidesMain, AHeavyFactory1, delay, tanksToBuildMain, AttackGroupSize[Difficulty], attackThresholdSize[AtreidesMain]) + ProduceUnits(AtreidesMain, AStarport, delay, unitsToBuy, AttackGroupSize[Difficulty], attackThresholdSize[AtreidesMain]) end) + + Trigger.AfterDelay(InitialProductionDelay["AtreidesSmall"][Difficulty], function() + ProduceUnits(AtreidesSmall, ABarracks, delay, infantryToBuildAtreides, AttackGroupSize[Difficulty], attackThresholdSize[AtreidesSmall]) + ProduceUnits(AtreidesSmall, AHeavyFactory2, delay, tanksToBuildSmall, AttackGroupSize[Difficulty], attackThresholdSize[AtreidesSmall]) + end) + + Trigger.AfterDelay(InitialProductionDelay["Corrino"][Difficulty], function() + ProduceUnits(Corrino, CBarracks, delay, infantryToBuildCorrino, AttackGroupSize[Difficulty], attackThresholdSize[AtreidesSmall]) + end) + + if Difficulty == "normal" then + AtreidesMain.GrantCondition("base-rebuilder") + end + + if Difficulty == "hard" then + AtreidesMain.GrantCondition("defense-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuildAtreides, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuildMain + } + + Trigger.OnBuildingPlaced(AtreidesMain, function(p, building) + table.insert(AtreidesMainBase, building) + DefendAndRepairBase(AtreidesMain, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(AtreidesMain, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize[AtreidesMain]) + end) + + Trigger.OnBuildingPlaced(AtreidesSmall, function(p, building) + table.insert(AtreidesSmallBase, building) + DefendAndRepairBase(AtreidesSmall, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(AtreidesSmall, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize[AtreidesSmall]) + end) + + ActivateCrushLogic() end diff --git a/mods/d2k/maps/harkonnen-07/harkonnen07.lua b/mods/d2k/maps/harkonnen-07/harkonnen07.lua index 934777b7dc..7e8ef235c4 100644 --- a/mods/d2k/maps/harkonnen-07/harkonnen07.lua +++ b/mods/d2k/maps/harkonnen-07/harkonnen07.lua @@ -8,9 +8,13 @@ ]] AtreidesMainBase = { AConYard1, AOutpost1, ARefinery1, ARefinery2, AHeavyFactory1, ALightFactory1, ALightFactory2, AHiTechFactory, AResearch, ARepair, AStarport, AGunt1, AGunt2, AGunt3, AGunt4, AGunt5, AGunt6, ARock1, ARock2, ARock3, ARock4, ARock5, ARock6, APower1, APower2, APower3, APower4, APower5, APower6, APower7, APower8, APower9, APower10, APower11, APower12, APower13, APower14, APower15, APower16, APower17, APower18, APower19, APower20, APower21, APower22, ASilo1, ASilo2, ASilo3 } + AtreidesSmallBase = { AOutpost2, ARefinery3, ABarracks, AHeavyFactory2, AGunt7, AGunt8, APower23, APower24, APower25, APower26, ASilo4, ASilo5, ASilo6, ASilo7 } + CorrinoBase = { CBarracks, COutpost, CPower1, CPower2, CPower3, CRock1, CRock2 } +EarlyGameStage = DateTime.Minutes(7) + AtreidesReinforcements = { easy = { "sonic_tank" }, @@ -41,16 +45,16 @@ CorrinoReinforcements = EnemyAttackDelay = { - easy = DateTime.Minutes(3) + DateTime.Seconds(30), - normal = DateTime.Minutes(2) + DateTime.Seconds(30), - hard = DateTime.Minutes(1) + DateTime.Seconds(30) + easy = 7000 , + normal = 6500 , + hard = 6000 } CorrinoInitialAttackDelay = { - easy = DateTime.Seconds(45), - normal = DateTime.Seconds(30), - hard = DateTime.Seconds(15) + easy = DateTime.Minutes(4) , + normal = DateTime.Minutes(3) , + hard = DateTime.Minutes(2) } InitialAtreidesReinforcements = @@ -112,8 +116,8 @@ HarkonnenReinforcements = hard = { - { "trooper", "trooper", "light_inf", "light_inf", "light_inf" }, - { "quad", "trike", "trike", "combat_tank_h", "combat_tank_h" } + { "trooper", "trooper", "light_inf", "light_inf", "light_inf", "light_inf" }, + { "quad", "trike", "trike", "combat_tank_h", "combat_tank_h", "siege_tank" } }, } @@ -147,8 +151,32 @@ SendEnemyReinforcements = function(player, delay, path, unitTypes, customConditi end) end -SendAirStrike = function() - if HiTechIsDead then +AirStrikeTimer = 7500 +AirStrikeChargeTime = 7500 +AirstrikeLogic = function(airstrikeProvider) + if airstrikeProvider.IsDead then + return + end + + if DateTime.GameTime <= AirStrikeTimer then + Trigger.AfterDelay(AirStrikeTimer - DateTime.GameTime + 1, function() + AirstrikeLogic(airstrikeProvider) + end) + return + end + + -- randomly choose if wait again or strike. During waiting Airstrike can still be used by DefensiveAirStrike + if Utils.RandomInteger(1, 100) < 30 then + Trigger.AfterDelay(1000, function() AirstrikeLogic(airstrikeProvider)end) + else + AirStrikeVSBuilding(airstrikeProvider) + Trigger.AfterDelay(7500, function() AirstrikeLogic(airstrikeProvider) end) + IsAirstrikeReady = false + end +end + +AirStrikeVSBuilding = function(airstrikeProvider) + if airstrikeProvider.IsDead or DateTime.GameTime < AirStrikeTimer then return end @@ -163,10 +191,32 @@ SendAirStrike = function() end) if #targets > 0 then - AHiTechFactory.TargetAirstrike(Utils.Random(targets).CenterPosition) + airstrikeProvider.TargetAirstrike(Utils.Random(targets).CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime end +end - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) +DefensiveAirStrike = function(airstrikeProvider, possibleTargets) + if airstrikeProvider.IsDead or DateTime.GameTime <= AirStrikeTimer then return end + local bestValue = {} + local bestIndex = 1 + for i = 1, #possibleTargets, 1 do + local ActorsInCircle = Map.ActorsInCircle(possibleTargets[i].CenterPosition, WDist.FromCells(4), function(a) + return + a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + bestValue[i] = 0 + Utils.Do(ActorsInCircle, function(a) + bestValue[i] = bestValue[i] + Actor.Cost(a.Type) + end) + if bestValue[i] > bestValue[bestIndex] then + bestIndex = i + end + end + airstrikeProvider.TargetAirstrike(possibleTargets[bestIndex].CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime end Tick = function() @@ -195,12 +245,12 @@ Tick = function() HiTechIsDead = true end - if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[OrdosMain] then - local units = OrdosMain.GetActorsByType("harvester") + if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[AtreidesMain] then + local units = AtreidesMain.GetActorsByType("harvester") if #units > 0 then - LastHarvesterEaten[OrdosMain] = false - ProtectHarvester(units[1], OrdosMain, AttackGroupSize[Difficulty]) + LastHarvesterEaten[AtreidesMain] = false + ProtectHarvester(units[1], AtreidesMain, AttackGroupSize[Difficulty]) end end @@ -212,15 +262,40 @@ Tick = function() ProtectHarvester(units[1], OrdosSmall, AttackGroupSize[Difficulty]) end end + + if IdlingUnits[AtreidesMain] == nil then return end +end + +EmergencyBehaviour = function(player, target) + if HiTechIsDead or not IsAirstrikeReady or player == Corrino then return end + + local targets = {} + if player == AtreidesMain then + targets = Map.ActorsInBox(Map.CenterOfCell(CPos.New(73,9)), Map.CenterOfCell(CPos.New(88,26))) + else + targets = Map.ActorsInBox(Map.CenterOfCell(CPos.New(5,18)), Map.CenterOfCell(CPos.New(16,32))) + end + + targets = Utils.Where(targets, function(a) + return a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + + if targets[1] == nil then return end + DefensiveAirStrike(AHiTechFactory, targets) end WorldLoaded = function() AtreidesMain = Player.GetPlayer("Atreides Main Base") AtreidesSmall = Player.GetPlayer("Atreides Small Base") Corrino = Player.GetPlayer("Corrino") + AtreidesMain.Cash = 5000 + AtreidesSmall.Cash = 5000 + Corrino.Cash = 5000 Harkonnen = Player.GetPlayer("Harkonnen") - InitObjectives(Harkonnen) + CaptureAtreidesConYard = AddPrimaryObjective(Harkonnen, "capture-atreides-construction-yard-south") KillAtreides = AddPrimaryObjective(Harkonnen, "destroy-atreides") KillCorrino = AddPrimaryObjective(Harkonnen, "destroy-corrino") @@ -232,8 +307,7 @@ WorldLoaded = function() Camera.Position = HEngineer.CenterPosition AtreidesAttackLocation = AConYard2.Location - - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) + Trigger.AfterDelay(EarlyGameStage,function() AirstrikeLogic(AHiTechFactory) end) Trigger.OnCapture(AConYard2, function() Harkonnen.MarkCompletedObjective(CaptureAtreidesConYard) @@ -257,7 +331,9 @@ WorldLoaded = function() unit.AttackMove(AtreidesAttackLocation) IdleHunt(unit) end + SendEnemyReinforcements(AtreidesMain, EnemyAttackDelay[Difficulty], AtreidesPath, AtreidesReinforcements[Difficulty], atreidesWaveCondition, huntFunction) + Trigger.AfterDelay(CorrinoInitialAttackDelay[Difficulty], function() SendEnemyReinforcements(Corrino, EnemyAttackDelay[Difficulty], CorrinoPaths[1], CorrinoReinforcements[Difficulty][1], corrinoWaveCondition, huntFunction) SendEnemyReinforcements(Corrino, EnemyAttackDelay[Difficulty], CorrinoPaths[2], CorrinoReinforcements[Difficulty][2], corrinoWaveCondition, huntFunction) @@ -273,4 +349,14 @@ WorldLoaded = function() SendHarkonnenReinforcements(1) SendHarkonnenReinforcements(2) + + Trigger.AfterDelay(7000, function() + SendHarkonnenReinforcements(1) + end) + + Trigger.OnRemovedFromWorld(Crate1, function() + VisionPoint.GrantCondition("activate", 100) + Media.PlaySound("MULTI1.WAV") + Camera.Position = VisionPoint.CenterPosition + end) end diff --git a/mods/d2k/maps/harkonnen-07/map.bin b/mods/d2k/maps/harkonnen-07/map.bin index 482423a0e3..df380301f0 100644 Binary files a/mods/d2k/maps/harkonnen-07/map.bin and b/mods/d2k/maps/harkonnen-07/map.bin differ diff --git a/mods/d2k/maps/harkonnen-07/map.yaml b/mods/d2k/maps/harkonnen-07/map.yaml index a4ac2b1fd0..fb2efc5355 100644 --- a/mods/d2k/maps/harkonnen-07/map.yaml +++ b/mods/d2k/maps/harkonnen-07/map.yaml @@ -298,6 +298,7 @@ Actors: AGunt1: medium_gun_turret Location: 80,27 Owner: Atreides Main Base + TurretFacing: 512 Actor77: wall Location: 81,27 Owner: Atreides Main Base @@ -769,6 +770,33 @@ Actors: AtreidesEntry2: waypoint Owner: Neutral Location: 2,27 + AtreidesPoint2: waypoint + Owner: Neutral + Location: 72,13 + AtreidesPoint3: waypoint + Owner: Neutral + Location: 55,53 + AtreidesPoint4: waypoint + Owner: Neutral + Location: 24,33 + AtreidesPoint5: waypoint + Owner: Neutral + Location: 28,55 + AtreidesPoint6: waypoint + Owner: Neutral + Location: 33,9 + VisionPoint: vision_provider + Owner: Harkonnen + Location: 64,64 + Crate1: crate_script + Owner: Harkonnen + Location: 57,62 + AtreidesPoint1: waypoint + Owner: Neutral + Location: 54,6 + Actor7: crate + Owner: Neutral + Location: 49,9 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-07/rules.yaml b/mods/d2k/maps/harkonnen-07/rules.yaml index 7a28330b9e..091bf33372 100644 --- a/mods/d2k/maps/harkonnen-07/rules.yaml +++ b/mods/d2k/maps/harkonnen-07/rules.yaml @@ -1,6 +1,79 @@ Player: PlayerResources: - DefaultCash: 6000 + DefaultCash: 7000 + ExternalCondition@AIMain: + Condition: base-rebuilder + ExternalCondition@AIMainWithDefense: + Condition: defense-rebuilder + BaseBuilderBotModule@MainBase: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + starport: 1 + wind_trap: 7 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + starport: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + BaseBuilderBotModule@MainBaseWithDefence: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + PlaceDefenseTowardsEnemyChance: 50 + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + starport: 1 + wind_trap: 7 + silo: 1 + medium_gun_turret: 4 + large_gun_turret: 4 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + starport: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + medium_gun_turret: 1 + large_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-08/harkonnen08-AI.lua b/mods/d2k/maps/harkonnen-08/harkonnen08-AI.lua index f5ba61822f..d99cb3dfe7 100644 --- a/mods/d2k/maps/harkonnen-08/harkonnen08-AI.lua +++ b/mods/d2k/maps/harkonnen-08/harkonnen08-AI.lua @@ -7,6 +7,31 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(7) + +InitialProductionDelay = { + Ordos = + { + easy = DateTime.Seconds(150), + normal = DateTime.Seconds(120), + hard = DateTime.Seconds(60) + }, + + AtreidesEnemy = + { + easy = DateTime.Seconds(180), + normal = DateTime.Seconds(120), + hard = DateTime.Seconds(60) + }, + + MercenaryEnemy = + { + easy = DateTime.Seconds(160), + normal = DateTime.Seconds(80), + hard = DateTime.Seconds(30) + } +} + AttackGroupSize = { easy = 6, @@ -14,7 +39,14 @@ AttackGroupSize = hard = 10 } -AttackDelays = +EarlyProductionDelays = +{ + easy = { DateTime.Seconds(10), DateTime.Seconds(13) }, + normal = { DateTime.Seconds(6), DateTime.Seconds(8) }, + hard = { DateTime.Seconds(3), DateTime.Seconds(5) } +} + +LateProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -24,16 +56,61 @@ AttackDelays = EnemyInfantryTypes = { "light_inf", "light_inf", "trooper", "trooper", "trooper" } OrdosVehicleTypes = { "raider", "raider", "quad" } -OrdosTankTypes = { "combat_tank_o", "combat_tank_o", "siege_tank", "deviator" } -OrdosStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_o.starport", "combat_tank_o.starport", "siege_tank.starport", "missile_tank.starport" } + +OrdosTankTypes = +{ + EarlyGame = { "combat_tank_o", "combat_tank_o", "siege_tank"}, + LateGame = { "combat_tank_o", "combat_tank_o", "siege_tank", "deviator" } +} + +OrdosStarportTypes = +{ + EarlyGame = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_o.starport" }, + LateGame = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_o.starport", "combat_tank_o.starport", "siege_tank.starport", "missile_tank.starport" } +} AtreidesVehicleTypes = { "trike", "trike", "quad" } -AtreidesTankTypes = { "combat_tank_a", "combat_tank_a", "siege_tank" } -AtreidesStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_a.starport", "combat_tank_a.starport", "siege_tank.starport", "missile_tank.starport" } + +AtreidesTankTypes = +{ + EarlyGame = { "combat_tank_a" }, + LateGame = { "combat_tank_a", "combat_tank_a", "siege_tank" } +} + +AtreidesStarportTypes = +{ + EarlyGame ={ "trike.starport", "trike.starport", "quad.starport", "combat_tank_a.starport", "combat_tank_a.starport" }, + LateGame ={ "trike.starport", "quad.starport", "combat_tank_a.starport", "combat_tank_a.starport", "siege_tank.starport", "missile_tank.starport" } +} MercenaryTankTypes = { "combat_tank_o", "combat_tank_o", "siege_tank" } ActivateAI = function() + Defending[Ordos] = {} + Defending[AtreidesEnemy] = {} + Defending[AtreidesNeutral] = {} + Defending[MercenaryEnemy] = {} + Defending[MercenaryAlly] = {} + -- this is also first attack timing + AttackDelay[Ordos] = 16000 + AttackDelay[AtreidesEnemy] = 10000 + AttackDelay[MercenaryEnemy] = 7000 + TimeBetweenAttacks[Ordos] = 8000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesEnemy] = 7000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[MercenaryEnemy] = 7000 * DifficultyModifier[Difficulty] + HarvesterCount[Ordos] = 4 + HarvesterCount[AtreidesEnemy] = 4 + HarvesterCount[AtreidesNeutral] = 0 + HarvesterCount[MercenaryEnemy] = 0 + HarvesterCount[MercenaryAlly] = 0 + PatrolPoints[Ordos] = {OrdosPatrolPoint1.Location, OrdosPatrolPoint2.Location, OrdosPatrolPoint3.Location, OrdosPatrolPoint4.Location } + PatrolPoints[AtreidesEnemy] = {AtreidesPatrolPoint1.Location, AtreidesPatrolPoint2.Location, OrdosPatrolPoint3.Location } + DefencePerimeter[Ordos] = GetCellsInRectangle(CPos.New(39,36), CPos.New(71,54)) + DefencePerimeter[AtreidesEnemy] = Utils.Concat(GetCellsInRectangle(CPos.New(3,3), CPos.New(17,20)), GetCellsInRectangle(CPos.New(18,3), CPos.New(31,9))) + DefencePerimeter[MercenaryEnemy] = GetCellsInRectangle(CPos.New(29,64), CPos.New(40,71)) + DefencePerimeter[MercenaryAlly] = DefencePerimeter[MercenaryEnemy] + DefencePerimeter[AtreidesNeutral] = DefencePerimeter[AtreidesEnemy] + IdlingUnits[Ordos] = Utils.Concat(Reinforcements.Reinforce(Ordos, InitialOrdosReinforcements[1], InitialOrdosPaths[1]), Reinforcements.Reinforce(Ordos, InitialOrdosReinforcements[2], InitialOrdosPaths[2])) IdlingUnits[AtreidesEnemy] = Reinforcements.Reinforce(AtreidesEnemy, InitialAtreidesReinforcements, InitialAtreidesPath) IdlingUnits[AtreidesNeutral] = { } @@ -44,26 +121,112 @@ ActivateAI = function() DefendAndRepairBase(AtreidesEnemy, AtreidesBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(MercenaryEnemy, MercenaryBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(EnemyInfantryTypes) } end - local vehilcesToBuildOrdos = function() return { Utils.Random(OrdosVehicleTypes) } end - local vehilcesToBuildAtreides = function() return { Utils.Random(AtreidesVehicleTypes) } end - local tanksToBuildOrdos = function() return { Utils.Random(OrdosTankTypes) } end - local tanksToBuildAtreides = function() return { Utils.Random(AtreidesTankTypes) } end + local vehiclesToBuildOrdos = function() return { Utils.Random(OrdosVehicleTypes) } end + local vehiclesToBuildAtreides = function() return { Utils.Random(AtreidesVehicleTypes) } end + + local tanksToBuildOrdos = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(OrdosTankTypes["EarlyGame"]) } + else + return { Utils.Random(OrdosTankTypes["LateGame"]) } + end + end + + local tanksToBuildAtreides = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(AtreidesTankTypes["EarlyGame"]) } + else + return { Utils.Random(AtreidesTankTypes["LateGame"]) } + end + end + local tanksToBuildMercenary = function() return { Utils.Random(MercenaryTankTypes) } end - local unitsToBuyOrdos = function() return { Utils.Random(OrdosStarportTypes) } end - local unitsToBuyAtreides = function() return { Utils.Random(AtreidesStarportTypes) } end + local unitsToBuyOrdos = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(OrdosStarportTypes["EarlyGame"]) } + else + return { Utils.Random(OrdosStarportTypes["LateGame"]) } + end + end + + local unitsToBuyAtreides = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(AtreidesStarportTypes["EarlyGame"]) } + else + return { Utils.Random(AtreidesStarportTypes["LateGame"]) } + end + end + local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 - ProduceUnits(Ordos, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Ordos, OLightFactory, delay, vehilcesToBuildOrdos, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Ordos, OHeavyFactory, delay, tanksToBuildOrdos, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Ordos, OStarport, delay, unitsToBuyOrdos, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["Ordos"][Difficulty], function () + ProduceUnits(Ordos, OBarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Ordos, OLightFactory, delay, vehiclesToBuildOrdos, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Ordos, OHeavyFactory, delay, tanksToBuildOrdos, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Ordos, OStarport, delay, unitsToBuyOrdos, AttackGroupSize[Difficulty], attackThresholdSize) + end) - ProduceUnits(AtreidesEnemy, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesEnemy, ALightFactory, delay, vehilcesToBuildAtreides, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesEnemy, AHeavyFactory, delay, tanksToBuildAtreides, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesEnemy, AStarport, delay, unitsToBuyAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesEnemy"][Difficulty], function () + ProduceUnits(AtreidesEnemy, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesEnemy, ALightFactory, delay, vehiclesToBuildAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesEnemy, AHeavyFactory, delay, tanksToBuildAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesEnemy, AStarport, delay, unitsToBuyAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + end) - ProduceUnits(MercenaryEnemy, MHeavyFactory, delay, tanksToBuildMercenary, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesEnemy"][Difficulty], function () + ProduceUnits(MercenaryEnemy, MHeavyFactory, delay, tanksToBuildMercenary, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + ActivateCrushLogic() + + if Difficulty == "normal" then + Ordos.GrantCondition("base-rebuilder") + AtreidesEnemy.GrantCondition("base-rebuilder2") + end + + if Difficulty == "hard" then + Ordos.GrantCondition("defense-rebuilder") + AtreidesEnemy.GrantCondition("defense-rebuilder2") + end + + local productionTypesAtreidis = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuildAtreides, + heavy_factory = tanksToBuildAtreides + } + + local productionTypesOrdos = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuildOrdos, + heavy_factory = tanksToBuildOrdos + } + + Trigger.OnBuildingPlaced(Ordos, function(p, building) + table.insert(OrdosBase, building) + DefendAndRepairBase(Ordos, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypesOrdos[building.Type] == nil then return end + ProduceUnits(Ordos, building, delay, productionTypesOrdos[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + Trigger.OnBuildingPlaced(AtreidesEnemy, function(p, building) + table.insert(AtreidesBase, building) + DefendAndRepairBase(AtreidesEnemy, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypesAtreidis[building.Type] == nil then return end + ProduceUnits(AtreidesEnemy, building, delay, productionTypesAtreidis[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end diff --git a/mods/d2k/maps/harkonnen-08/harkonnen08.lua b/mods/d2k/maps/harkonnen-08/harkonnen08.lua index b29898af70..d340023bbd 100644 --- a/mods/d2k/maps/harkonnen-08/harkonnen08.lua +++ b/mods/d2k/maps/harkonnen-08/harkonnen08.lua @@ -8,7 +8,9 @@ ]] OrdosBase = { OConYard, OOutpost, OPalace, ORefinery1, ORefinery2, OHeavyFactory, OLightFactory, OHiTechFactory, OResearch, ORepair, OStarport, OGunt1, OGunt2, OGunt3, OGunt4, OGunt5, OGunt6, ORock1, ORock2, ORock3, ORock4, OBarracks1, OBarracks2, OPower1, OPower2, OPower3, OPower4, OPower5, OPower6, OPower7, OPower8, OPower9, OPower10, OPower11, OPower12, OPower13 } -AtreidesBase = { AConYard, AOutpost, ARefinery1, ARefinery2, AHeavyFactory, ALightFactory, AHiTechFactory, ARepair, AStarport, AGunt1, AGunt2, ARock1, ARock2, APower1, APower2, APower3, APower4, APower5, APower6, APower7, APower8, APower9 } + +AtreidesBase = { AConYard,ABarracks1, ABarracks2, AOutpost, ARefinery1, ARefinery2, AHeavyFactory, ALightFactory, AHiTechFactory, ARepair, AStarport, AGunt1, AGunt2, ARock1, ARock2, APower1, APower2, APower3, APower4, APower5, APower6, APower7, APower8, APower9 } + MercenaryBase = { MHeavyFactory, MStarport, MGunt, MPower1, MPower2 } OrdosReinforcements = @@ -56,7 +58,7 @@ OrdosAttackDelay = hard = DateTime.Minutes(1) } -MercenaryStarportDelay = DateTime.Minutes(1) + DateTime.Seconds(20) +MercenaryStarportDelay = 4970 OrdosAttackWaves = { @@ -92,7 +94,9 @@ InitialOrdosPaths = SaboteurPaths = { { SaboteurWaypoint1.Location, SaboteurWaypoint2.Location, SaboteurWaypoint3.Location }, - { SaboteurWaypoint4.Location, SaboteurWaypoint5.Location, SaboteurWaypoint6.Location } + { SaboteurWaypoint4.Location, SaboteurWaypoint5.Location, SaboteurWaypoint6.Location }, + { SaboteurWaypoint1.Location, OrdosPatrolPoint2.Location, OrdosRally1.Location, SaboteurWaypoint6.Location }, + { OrdosRally4.Location, OrdosPatrolPoint3.Location, OrdosPatrolPoint2.Location} } InitialAtreidesPath = { AStarport.Location, AtreidesRally.Location } @@ -117,14 +121,36 @@ SendStarportReinforcements = function(faction) end) end -SendAirStrike = function() - if AHiTechFactory.IsDead or AHiTechFactory.Owner ~= AtreidesEnemy then +AirStrikeTimer = 7500 +AirStrikeChargeTime = 7500 +AirstrikeLogic = function(airstrikeProvider) + if airstrikeProvider.IsDead then + return + end + if DateTime.GameTime <= AirStrikeTimer then + Trigger.AfterDelay(AirStrikeTimer - DateTime.GameTime + 1, function() + AirstrikeLogic(airstrikeProvider) + end) + return + end + + -- randomly choose if wait again or strike. During waiting Airstrike can still be used by DefensiveAirStrike + if Utils.RandomInteger(1, 100) < 30 then + Trigger.AfterDelay(1000, function() AirstrikeLogic(airstrikeProvider)end) + else + AirStrikeVSBuilding(airstrikeProvider) + Trigger.AfterDelay(7500, function() AirstrikeLogic(airstrikeProvider) end) + IsAirstrikeReady = false + end +end + +AirStrikeVSBuilding = function(airstrikeProvider) + if airstrikeProvider.IsDead or DateTime.GameTime < AirStrikeTimer then return end local targets = Utils.Where(Harkonnen.GetActors(), function(actor) - return - actor.HasProperty("Sell") and + return actor.HasProperty("Sell") and actor.Type ~= "wall" and actor.Type ~= "medium_gun_turret" and actor.Type ~= "large_gun_turret" and @@ -133,16 +159,40 @@ SendAirStrike = function() end) if #targets > 0 then - AHiTechFactory.TargetAirstrike(Utils.Random(targets).CenterPosition) + airstrikeProvider.TargetAirstrike(Utils.Random(targets).CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime + end +end + +DefensiveAirStrike = function(airstrikeProvider, possibleTargets) + if airstrikeProvider.IsDead or DateTime.GameTime <= AirStrikeTimer then return end + local bestValue = {} + local bestIndex = 1 + for i = 1, #possibleTargets, 1 do + local ActorsInCircle = Map.ActorsInCircle(possibleTargets[i].CenterPosition, WDist.FromCells(4), function(a) + return + a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + + bestValue[i] = 0 + Utils.Do(ActorsInCircle, function(a) + bestValue[i] = bestValue[i] + Actor.Cost(a.Type) + end) + + if bestValue[i] > bestValue[bestIndex] then + bestIndex = i + end end - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) + airstrikeProvider.TargetAirstrike(possibleTargets[bestIndex].CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime end GetSaboteurTargets = function(player) return Utils.Where(player.GetActors(), function(actor) - return - actor.HasProperty("Sell") and + return actor.HasProperty("Sell") and actor.Type ~= "wall" and actor.Type ~= "medium_gun_turret" and actor.Type ~= "large_gun_turret" and @@ -162,14 +212,54 @@ BuildSaboteur = function() saboteur.Wait(DateTime.Seconds(5)) local path = Utils.Random(SaboteurPaths) - saboteur.Move(path[1]) - saboteur.Move(path[2]) - saboteur.Move(path[3]) + Utils.Do(path, function(waypoint) + saboteur.Move(waypoint) + end) SendSaboteur(saboteur) + ScanForBetterTargets(saboteur) end - Trigger.AfterDelay(DateTime.Minutes(1) + DateTime.Seconds(30), BuildSaboteur) + Trigger.AfterDelay(DateTime.Minutes(5) + DateTime.Seconds(30), BuildSaboteur) +end + +DemolishType = { "harvester", "mcv", "siege_tank", "missile_tank", "sonic_tank", "devastator", "deviator", "combat_tank_a", "combat_tank_h", "combat_tank_o"} + +ScanForBetterTargets = function(saboteur) + if saboteur.IsDead or not saboteur.IsInWorld then return end + + local possibleTargets = Map.ActorsInCircle(saboteur.CenterPosition, WDist.FromCells(6), function(a) + return not saboteur.Owner.IsAlliedWith(a.Owner) and + Utils.Any(DemolishType, function(d) return d == a.Type end) + end) + + if possibleTargets[1] == nil then + Trigger.AfterDelay(200, function() + ScanForBetterTargets(saboteur) + end) + return + end + + -- filter out targets where infantry is nearby + for index = #possibleTargets, 1, -1 do + local infantryunits = Map.ActorsInCircle(possibleTargets[index].CenterPosition, WDist.New(1536), function(u) return u.Type == "light_inf" or u.Type == "trooper" end) + if infantryunits[1] ~= nil then + table.remove(possibleTargets, index) + end + end + + if possibleTargets[1] ~= nil then + saboteur.Stop() + local dfd = Utils.Random(possibleTargets) + saboteur.Demolish(dfd) + saboteur.CallFunc(function() + ScanForBetterTargets(saboteur) + end) + else + Trigger.AfterDelay(200, function() + ScanForBetterTargets(saboteur) + end) + end end SendSaboteur = function(saboteur) @@ -216,6 +306,21 @@ ChangeOwner = function(old_owner, new_owner) end) end +EmergencyBehaviour = function(player, target) + HoldProduction[player] = false + if player == Ordos and not AtreidesEnemy.HasNoRequiredUnits() then + if AHiTechFactory.IsDead then return end + local enemyunits = Map.ActorsInCircle(Map.CenterOfCell(target), WDist.FromCells(15), function(a) + return a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + + if enemyunits[1] == nil then return end + DefensiveAirStrike(AHiTechFactory, enemyunits) + end +end + Tick = function() if Harkonnen.HasNoRequiredUnits() then Ordos.MarkCompletedObjective(KillHarkonnen1) @@ -239,7 +344,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[Ordos] then local units = Ordos.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[Ordos] = false ProtectHarvester(units[1], Ordos, AttackGroupSize[Difficulty]) @@ -248,7 +352,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[AtreidesEnemy] then local units = AtreidesEnemy.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[AtreidesEnemy] = false ProtectHarvester(units[1], AtreidesEnemy, AttackGroupSize[Difficulty]) @@ -275,8 +378,8 @@ WorldLoaded = function() OrdosAttackLocation = HMCV.Location MercenaryAttackLocation = HMCV.Location - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) - Trigger.AfterDelay(DateTime.Minutes(1) + DateTime.Seconds(30), BuildSaboteur) + Trigger.AfterDelay(EarlyGameStage, function() AirstrikeLogic(AHiTechFactory) end) + Trigger.AfterDelay(EarlyGameStage, BuildSaboteur) Trigger.OnCapture(MHeavyFactory, function() Harkonnen.MarkCompletedObjective(AllyWithMercenaries) @@ -296,7 +399,7 @@ WorldLoaded = function() end) Trigger.OnKilledOrCaptured(OPalace, function() - Media.DisplayMessage(UserInterface.GetFluentMessage("cannot-stand-harkonnen-must-become-neutral"), UserInterface.GetFluentMessage("atreides-commander")) + Media.DisplayMessage(UserInterface.GetFluentMessage("cannot-stand-harkonnen-must-become-neutral"), UserInterface.GetFluentMessage("atreides-commander"), HSLColor.FromHex("5A7394")) ChangeOwner(AtreidesEnemy, AtreidesNeutral) DefendAndRepairBase(AtreidesNeutral, AtreidesBase, 0.75, AttackGroupSize[Difficulty]) @@ -328,8 +431,8 @@ WorldLoaded = function() unit.AttackMove(OrdosAttackLocation) IdleHunt(unit) end - SendCarryallReinforcements(Ordos, 0, OrdosAttackWaves[Difficulty], OrdosAttackDelay[Difficulty], path, OrdosReinforcements[Difficulty], waveCondition, huntFunction) + SendCarryallReinforcements(Ordos, 0, OrdosAttackWaves[Difficulty], OrdosAttackDelay[Difficulty], path, OrdosReinforcements[Difficulty], waveCondition, huntFunction) SendStarportReinforcements(MercenaryEnemy) Actor.Create("upgrade.barracks", true, { Owner = Ordos }) diff --git a/mods/d2k/maps/harkonnen-08/map.yaml b/mods/d2k/maps/harkonnen-08/map.yaml index edf8615669..cc65ee5831 100644 --- a/mods/d2k/maps/harkonnen-08/map.yaml +++ b/mods/d2k/maps/harkonnen-08/map.yaml @@ -643,15 +643,33 @@ Actors: SaboteurWaypoint4: waypoint Owner: Neutral Location: 18,59 - SaboteurWaypoint5: waypoint - Owner: Neutral - Location: 31,91 SaboteurWaypoint6: waypoint Owner: Neutral Location: 64,91 MercenaryAttackPoint: waypoint Owner: Neutral Location: 53,38 + OrdosPatrolPoint1: waypoint + Owner: Neutral + Location: 78,45 + OrdosPatrolPoint2: waypoint + Owner: Neutral + Location: 55,63 + OrdosPatrolPoint3: waypoint + Owner: Neutral + Location: 30,54 + OrdosPatrolPoint4: waypoint + Owner: Neutral + Location: 73,21 + AtreidesPatrolPoint1: waypoint + Owner: Neutral + Location: 14,65 + AtreidesPatrolPoint2: waypoint + Owner: Neutral + Location: 48,10 + SaboteurWaypoint5: waypoint + Owner: Neutral + Location: 31,81 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-08/rules.yaml b/mods/d2k/maps/harkonnen-08/rules.yaml index c027e0ddc7..c437902596 100644 --- a/mods/d2k/maps/harkonnen-08/rules.yaml +++ b/mods/d2k/maps/harkonnen-08/rules.yaml @@ -1,6 +1,150 @@ Player: PlayerResources: DefaultCash: 7000 + ExternalCondition@AIOrdos: + Condition: base-rebuilder + ExternalCondition@AIOrdosDefense: + Condition: defense-rebuilder + ExternalCondition@AIAtreides: + Condition: base-rebuilder2 + ExternalCondition@AIAtreidesDefense: + Condition: defense-rebuilder2 + BaseBuilderBotModule@Ordos: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 2 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + starport: 1 + wind_trap: 12 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 2 + high_tech_factory: 1 + starport: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@OrdosDefnse: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 2 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 12 + silo: 1 + high_tech_factory: 1 + starport: 1 + medium_gun_turret: 8 + large_gun_turret: 4 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + starport: 1 + medium_gun_turret: 6 + large_gun_turret: 4 + BaseBuilderBotModule@Atreides: + RequiresCondition: base-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 2 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + starport: 1 + wind_trap: 12 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + high_tech_factory: 1 + starport: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AtreidesDefense: + RequiresCondition: defense-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 2 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 9 + silo: 1 + high_tech_factory: 1 + starport: 1 + medium_gun_turret: 6 + large_gun_turret: 6 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + high_tech_factory: 1 + starport: 1 + medium_gun_turret: 1 + large_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-09a/harkonnen09a-AI.lua b/mods/d2k/maps/harkonnen-09a/harkonnen09a-AI.lua index 64e833e2b3..9f35045883 100644 --- a/mods/d2k/maps/harkonnen-09a/harkonnen09a-AI.lua +++ b/mods/d2k/maps/harkonnen-09a/harkonnen09a-AI.lua @@ -7,6 +7,8 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(10) + AttackGroupSize = { easy = 6, @@ -14,26 +16,117 @@ AttackGroupSize = hard = 10 } -AttackDelays = +EarlyProductionDelays = { - easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, - normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, - hard = { DateTime.Seconds(1), DateTime.Seconds(3) } + + easy = { DateTime.Seconds(12), DateTime.Seconds(15) }, + normal = { DateTime.Seconds(10), DateTime.Seconds(13) }, + hard = { DateTime.Seconds(9), DateTime.Seconds(10) } +} + +LateProductionDelays = +{ + easy = { DateTime.Seconds(5), DateTime.Seconds(8) }, + normal = { DateTime.Seconds(3), DateTime.Seconds(6) }, + hard = { DateTime.Seconds(2), DateTime.Seconds(4) } +} + +InitialProductionDelay = { + AtreidesMain = + { + easy = DateTime.Seconds(200), + normal = DateTime.Seconds(150), + hard = DateTime.Seconds(100) + }, + + AtreidesSmall = + { + easy = DateTime.Seconds(160), + normal = DateTime.Seconds(80), + hard = DateTime.Seconds(50) + }, + + CorrinoMain = + { + easy = DateTime.Seconds(250), + normal = DateTime.Seconds(200), + hard = DateTime.Seconds(150) + }, + + CorrinoSmall = + { + easy = DateTime.Seconds(100), + normal = DateTime.Seconds(80), + hard = DateTime.Seconds(60) + } } EnemyInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + EnemyVehicleTypes = { "trike", "trike", "quad" } -AtreidesMainTankTypes = { "combat_tank_a", "combat_tank_a", "siege_tank", "missile_tank", "sonic_tank" } +AtreidesMainTankTypes = { + EarlyGame = { "combat_tank_a", "combat_tank_a", "siege_tank", "missile_tank" }, + LateGame = {"combat_tank_a", "siege_tank", "missile_tank", "sonic_tank"} +} + AtreidesSmallTankTypes = { "combat_tank_a", "combat_tank_a", "siege_tank" } -AtreidesStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_a.starport", "combat_tank_a.starport", "siege_tank.starport", "missile_tank.starport" } CorrinoMainInfantryTypes = { "light_inf", "light_inf", "trooper", "sardaukar" } -CorrinoMainTankTypes = { "combat_tank_h", "combat_tank_h", "siege_tank", "missile_tank" } + +CorrinoMainTankTypes = { + EarlyGame = { "combat_tank_h" }, + LateGame = { "combat_tank_h", "combat_tank_h", "siege_tank", "missile_tank" } +} + CorrinoSmallTankTypes = { "combat_tank_h", "combat_tank_h", "siege_tank" } -CorrinoStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_h.starport", "combat_tank_h.starport", "siege_tank.starport", "missile_tank.starport" } ActivateAI = function() + Defending[AtreidesMain] = {} + Defending[AtreidesSmall1] = {} + Defending[AtreidesSmall2] = {} + Defending[CorrinoMain] = {} + Defending[CorrinoSmall] = {} + -- this is also first attack timing + AttackDelay[AtreidesMain] = 16000 + AttackDelay[AtreidesSmall1] = 16000 + AttackDelay[AtreidesSmall2] = 16000 + AttackDelay[CorrinoMain] = 16000 + AttackDelay[CorrinoSmall] = 16000 + TimeBetweenAttacks[AtreidesMain] = 13000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesSmall1] = 1000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesSmall2] = 4000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[CorrinoMain] = 12000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[CorrinoSmall] = 10000 * DifficultyModifier[Difficulty] + + Trigger.OnAllKilledOrCaptured(AtreidesSmall1Base, function() + TimeBetweenAttacks[AtreidesMain] = 0 + end) + + Trigger.OnAllKilledOrCaptured(CorrinoMainBase, function() + TimeBetweenAttacks[CorrinoSmall] = 0 + end) + + Trigger.OnAllKilledOrCaptured(CorrinoSmallBase, function() + TimeBetweenAttacks[CorrinoMain] = 0 + end) + + HarvesterCount[AtreidesMain] = 4 + HarvesterCount[AtreidesSmall1] = 2 + HarvesterCount[AtreidesSmall2] = 2 + HarvesterCount[CorrinoMain] = 4 + HarvesterCount[CorrinoSmall] = 2 + + PatrolPoints[AtreidesMain] = {AtreidesPatrolPoint3.Location, AtreidesPatrolPoint4.Location, AtreidesPatrolPoint2.Location, AtreidesRally10.Location, AtreidesPatrolPoint5.Location } + PatrolPoints[AtreidesSmall1] = {AtreidesPatrolPoint1.Location, AtreidesPatrolPoint2.Location, AtreidesRally3.Location } + PatrolPoints[CorrinoMain] = {CorrinoPatrolPoint1.Location, CorrinoPatrolPoint2.Location, AtreidesPatrolPoint5.Location } + PatrolPoints[CorrinoSmall] = {CorrinoPatrolPoint3.Location, AtreidesPatrolPoint3.Location, AtreidesPatrolPoint5.Location } + DefencePerimeter[AtreidesMain] = GetCellsInRectangle(CPos.New(36,15), CPos.New(57,30)) + DefencePerimeter[AtreidesSmall1] = GetCellsInRectangle(CPos.New(34,51), CPos.New(46,74)) + DefencePerimeter[AtreidesSmall2] = GetCellsInRectangle(CPos.New(18,32), CPos.New(30,37)) + DefencePerimeter[CorrinoMain] = GetCellsInRectangle(CPos.New(3,13), CPos.New(24,26)) + DefencePerimeter[CorrinoSmall] = GetCellsInRectangle(CPos.New(74,27), CPos.New(96,48)) + IdlingUnits[AtreidesMain] = Utils.Concat(Reinforcements.Reinforce(AtreidesMain, InitialAtreidesReinforcements[1], InitialAtreidesPaths[1]), Utils.Concat(Reinforcements.Reinforce(AtreidesMain, InitialAtreidesReinforcements[2], InitialAtreidesPaths[2]), Reinforcements.Reinforce(AtreidesMain, InitialAtreidesReinforcements[3], InitialAtreidesPaths[3]))) IdlingUnits[AtreidesSmall1] = Utils.Concat(Reinforcements.Reinforce(AtreidesSmall1, InitialAtreidesReinforcements[4], InitialAtreidesPaths[4]), Reinforcements.Reinforce(AtreidesSmall1, InitialAtreidesReinforcements[5], InitialAtreidesPaths[5])) IdlingUnits[AtreidesSmall2] = Reinforcements.Reinforce(AtreidesSmall2, InitialAtreidesReinforcements[6], InitialAtreidesPaths[6]) @@ -46,35 +139,118 @@ ActivateAI = function() DefendAndRepairBase(CorrinoMain, CorrinoMainBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(CorrinoSmall, CorrinoSmallBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(EnemyInfantryTypes) } end local infantryToBuildCorrinoMain = function() return { Utils.Random(CorrinoMainInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(EnemyVehicleTypes) } end - local tanksToBuildAtreidesMain = function() return { Utils.Random(AtreidesMainTankTypes) } end + local vehiclesToBuild = function() return { Utils.Random(EnemyVehicleTypes) } end + local tanksToBuildAtreidesMain = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(AtreidesMainTankTypes["EarlyGame"]) } + else + return { Utils.Random(AtreidesMainTankTypes["LateGame"]) } + end + end + local tanksToBuildAtreidesSmall = function() return { Utils.Random(AtreidesSmallTankTypes) } end - local tanksToBuildCorrinoMain = function() return { Utils.Random(CorrinoMainTankTypes) } end + + local tanksToBuildCorrinoMain = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(CorrinoMainTankTypes["EarlyGame"]) } + else + return { Utils.Random(CorrinoMainTankTypes["LateGame"]) } + end + end + local tanksToBuildCorrinoSmall = function() return { Utils.Random(CorrinoSmallTankTypes) } end - local unitsToBuyAtreides = function() return { Utils.Random(AtreidesStarportTypes) } end - local unitsToBuyCorrino = function() return { Utils.Random(CorrinoStarportTypes) } end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + local attackThresholdSize = AttackGroupSize[Difficulty] * 3 + local attackThresholdSizeCorrino = AttackGroupSize[Difficulty] * 4 + local attackThresholdSizeSmallBase = AttackGroupSize[Difficulty] * 2 + Trigger.AfterDelay(InitialProductionDelay["AtreidesMain"][Difficulty], function () + ProduceUnits(AtreidesMain, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesMain, ALightFactory1, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesMain, AHeavyFactory1, delay, tanksToBuildAtreidesMain, AttackGroupSize[Difficulty], attackThresholdSize) + --ProduceUnits(AtreidesMain, AStarport, delay, unitsToBuyAtreides, AttackGroupSize[Difficulty], AttackThresholdSize) + end) - ProduceUnits(AtreidesMain, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, ALightFactory1, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, AHeavyFactory1, delay, tanksToBuildAtreidesMain, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, AStarport, delay, unitsToBuyAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesSmall"][Difficulty], function () + ProduceUnits(AtreidesSmall1, ABarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmallBase) + ProduceUnits(AtreidesSmall1, ALightFactory2, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmallBase) + ProduceUnits(AtreidesSmall1, AHeavyFactory2, delay, tanksToBuildAtreidesSmall, AttackGroupSize[Difficulty], attackThresholdSizeSmallBase) + end) - ProduceUnits(AtreidesSmall1, ABarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesSmall1, ALightFactory2, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesSmall1, AHeavyFactory2, delay, tanksToBuildAtreidesSmall, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesSmall2, ABarracks4, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmallBase) - ProduceUnits(AtreidesSmall2, ABarracks4, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["CorrinoMain"][Difficulty], function () + ProduceUnits(CorrinoMain, CBarracks1, delay, infantryToBuildCorrinoMain, AttackGroupSize[Difficulty], attackThresholdSizeCorrino) + ProduceUnits(CorrinoMain, CLightFactory1, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSizeCorrino) + ProduceUnits(CorrinoMain, CHeavyFactory1, delay, tanksToBuildCorrinoMain, AttackGroupSize[Difficulty], attackThresholdSizeCorrino) + --ProduceUnits(CorrinoMain, CStarport, delay, unitsToBuyCorrino, AttackGroupSize[Difficulty], AttackThresholdSize) + end) - ProduceUnits(CorrinoMain, CBarracks1, delay, infantryToBuildCorrinoMain, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoMain, CLightFactory1, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoMain, CHeavyFactory1, delay, tanksToBuildCorrinoMain, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoMain, CStarport, delay, unitsToBuyCorrino, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["CorrinoSmall"][Difficulty], function () + ProduceUnits(CorrinoSmall, CBarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmallBase) + ProduceUnits(CorrinoSmall, CLightFactory2, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmallBase) + ProduceUnits(CorrinoSmall, CHeavyFactory2, delay, tanksToBuildCorrinoSmall, AttackGroupSize[Difficulty], attackThresholdSizeSmallBase) + end) - ProduceUnits(CorrinoSmall, CBarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoSmall, CLightFactory2, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoSmall, CHeavyFactory2, delay, tanksToBuildCorrinoSmall, AttackGroupSize[Difficulty], attackThresholdSize) + local crusherTypes = { "combat_tank_a", "combat_tank_a.starport", "combat_tank_h", "combat_tank_h.starport" } + local crusherFactories = { AStarport, AHeavyFactory1, AHeavyFactory2, CHeavyFactory1, CHeavyFactory2, CStarport } + ActivateCrusherLogic(crusherTypes, crusherFactories) + + if Difficulty == "normal" then + AtreidesMain.GrantCondition("base-rebuilder") + AtreidesSmall1.GrantCondition("base-rebuilder2") + CorrinoSmall.GrantCondition("base-rebuilder3") + end + + if Difficulty == "hard" then + AtreidesMain.GrantCondition("defense-rebuilder") + AtreidesSmall1.GrantCondition("defense-rebuilder2") + CorrinoSmall.GrantCondition("defense-rebuilder3") + end + + local productionTypesAtreidis = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuildAtreidesMain + } + + local productionTypesCorrino = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuildCorrinoSmall + } + + Trigger.OnBuildingPlaced(AtreidesMain, function(p, building) + table.insert(AtreidesMainBase, building) + DefendAndRepairBase(AtreidesMain, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypesAtreidis[building.Type] == nil then return end + ProduceUnits(AtreidesMain, building, delay, productionTypesAtreidis[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + Trigger.OnBuildingPlaced(AtreidesSmall1, function(p, building) + table.insert(AtreidesSmallBase, building) + DefendAndRepairBase(AtreidesSmall1, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypesAtreidis[building.Type] == nil then return end + ProduceUnits(AtreidesSmall1, building, delay, productionTypesAtreidis[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + Trigger.OnBuildingPlaced(CorrinoMain, function(p, building) + table.insert(CorrinoMainBase, building) + DefendAndRepairBase(CorrinoMain, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypesCorrino[building.Type] == nil then return end + ProduceUnits(CorrinoMain, building, delay, productionTypesCorrino[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end diff --git a/mods/d2k/maps/harkonnen-09a/harkonnen09a.lua b/mods/d2k/maps/harkonnen-09a/harkonnen09a.lua index 8692f53bae..dfe74a70d2 100644 --- a/mods/d2k/maps/harkonnen-09a/harkonnen09a.lua +++ b/mods/d2k/maps/harkonnen-09a/harkonnen09a.lua @@ -8,9 +8,13 @@ ]] AtreidesMainBase = { AConYard1, AOutpost1, APalace, ARefinery1, ARefinery2, ARefinery3, AHeavyFactory1, ALightFactory1, AStarport, AHiTechFactory, AResearch, AGunt1, AGunt2, AGunt3, AGunt4, AGunt5, ARock1, ARock2, ARock3, ARock4, ABarracks1, ABarracks2, APower1, APower2, APower3, APower4, APower5, APower6, APower7, APower8, APower9, APower10, APower11, APower12, APower13, APower14 } + AtreidesSmall1Base = { AConYard2, ARefinery4, ABarracks3, AHeavyFactory2, ALightFactory2, ARepair, ARock5, ARock6, ARock7, ARock8, ARock9, APower15, APower16, APower17, APower18, APower19, APower20, APower21 } + AtreidesSmall2Base = { AOutpost2, ABarracks4, AGunt6, AGunt7, AGunt8, ARock10, APower22, APower23 } + CorrinoMainBase = { COutpost, CPalace, CRefinery1, CHeavyFactory1, CLightFactory1, CStarport, CResearch, CGunt1, CGunt2, CRock1, CRock2, CBarracks1, CPower1, CPower2, CPower3, CPower4, CPower5, CPower6, CPower7 } + CorrinoSmallBase = { CConYard, CRefinery2, CHeavyFactory2, CLightFactory2, CRock3, CRock4, CBarracks2, CPower8, CPower9, CPower10, CPower11 } AtreidesReinforcements = @@ -131,9 +135,13 @@ InitialCorrinoPaths = { CorrinoEntry2.Location, CorrinoRally2.Location } } -HarkonnenReinforcements = { "combat_tank_h", "combat_tank_h", "siege_tank", "siege_tank", "missile_tank" } - -HarkonnenPath = { HarkonnenEntry.Location, HarkonnenRally.Location } +HarkonnenReinforcements = { + { "combat_tank_h", "combat_tank_h", "siege_tank", "siege_tank", "missile_tank" }, + { "trooper", "light_inf", "light_inf", "light_inf", "trooper", "light_inf", "trooper", "light_inf", "trooper", "light_inf", "trooper" }, + { "trooper", "light_inf", "quad", "trike", "quad" }, + { "devastator", "light_inf", "quad", "trike", "quad" }, + { "trooper", "combat_tank_h", "combat_tank_h", "combat_tank_h", "quad" } +} SendStarportReinforcements = function() Trigger.AfterDelay(CorrinoStarportDelay[Difficulty], function() @@ -142,7 +150,6 @@ SendStarportReinforcements = function() end local reinforcements = Utils.Random(CorrinoStarportReinforcements[Difficulty]) - local units = Reinforcements.ReinforceWithTransport(CorrinoMain, "frigate", reinforcements, { CorrinoStarportEntry.Location, CStarport.Location + CVec.New(1, 1) }, { CorrinoStarportExit.Location })[2] Utils.Do(units, function(unit) unit.AttackMove(AtreidesAttackLocation) @@ -153,23 +160,56 @@ SendStarportReinforcements = function() end) end -SendHarkonnenReinforcements = function(delay) +HarkonnenPaths = +{ + { HarkonnenEntry.Location, HarkonnenRally.Location }, + { AtreidesEntry3.Location, HarkonnenRally1.Location }, + { HarkonnenEntry.Location, HarkonnenRally2.Location }, + { AtreidesEntry3.Location, AtreidesRally3.Location }, + { HarkonnenRally3.Location, AtreidesRally9.Location }, + { HarkonnenRally4.Location, AtreidesRally8.Location } +} + +SendHarkonnenReinforcements = function(delay, number) Trigger.AfterDelay(delay, function() - Reinforcements.ReinforceWithTransport(Harkonnen, "carryall.reinforce", HarkonnenReinforcements, HarkonnenPath, { HarkonnenPath[1] }) + Reinforcements.ReinforceWithTransport(Harkonnen, "carryall.reinforce", HarkonnenReinforcements[number], HarkonnenPaths[number], { HarkonnenPaths[number][1] }) Trigger.AfterDelay(DateTime.Seconds(5), function() Media.PlaySpeechNotification(Harkonnen, "Reinforce") end) end) end -SendAirStrike = function() - if AHiTechFactory.IsDead or AHiTechFactory.Owner ~= AtreidesMain then +AirStrikeTimer = 7500 +AirStrikeChargeTime = 7500 +AirstrikeLogic = function(airstrikeProvider) + if airstrikeProvider.IsDead then + return + end + + if DateTime.GameTime <= AirStrikeTimer then + Trigger.AfterDelay(AirStrikeTimer - DateTime.GameTime + 1, function() + AirstrikeLogic(airstrikeProvider) + end) + return + end + + -- randomly choose if wait again or strike. During waiting Airstrike can still be used by DefensiveAirStrike + if Utils.RandomInteger(1, 100) < 30 then + Trigger.AfterDelay(1000, function() AirstrikeLogic(airstrikeProvider)end) + else + AirStrikeVSBuilding(airstrikeProvider) + Trigger.AfterDelay(7500, function() AirstrikeLogic(airstrikeProvider) end) + IsAirstrikeReady = false + end +end + +AirStrikeVSBuilding = function(airstrikeProvider) + if airstrikeProvider.IsDead or DateTime.GameTime < AirStrikeTimer then return end local targets = Utils.Where(Harkonnen.GetActors(), function(actor) - return - actor.HasProperty("Sell") and + return actor.HasProperty("Sell") and actor.Type ~= "wall" and actor.Type ~= "medium_gun_turret" and actor.Type ~= "large_gun_turret" and @@ -178,12 +218,86 @@ SendAirStrike = function() end) if #targets > 0 then - AHiTechFactory.TargetAirstrike(Utils.Random(targets).CenterPosition) + airstrikeProvider.TargetAirstrike(Utils.Random(targets).CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime + end +end + +DefensiveAirStrike = function(airstrikeProvider, possibleTargets) + if airstrikeProvider.IsDead or DateTime.GameTime <= AirStrikeTimer then return end + local bestValue = {} + local bestIndex = 1 + for i = 1, #possibleTargets, 1 do + local ActorsInCircle = Map.ActorsInCircle(possibleTargets[i].CenterPosition, WDist.FromCells(4), function(a) + return a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + + bestValue[i] = 0 + Utils.Do(ActorsInCircle, function(a) + bestValue[i] = bestValue[i] + Actor.Cost(a.Type) + end) + + if bestValue[i] > bestValue[bestIndex] then + bestIndex = i + end + end + airstrikeProvider.TargetAirstrike(possibleTargets[bestIndex].CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime +end + +EmergencyBehaviour = function(player,target) + if player == AtreidesMain or player == AtreidesSmall1 then + if AHiTechFactory.IsDead then return end + local enemyunits = Map.ActorsInCircle(Map.CenterOfCell(target), WDist.FromCells(15), function(a) + return a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + + if enemyunits[1] == nil then return end + DefensiveAirStrike(AHiTechFactory, enemyunits) end - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) + if player == AtreidesSmall1 and #IdlingUnits[AtreidesMain] > 10 then + local reinforcements = SetupAttackGroup(AtreidesMain, Utils.RandomInteger(10, #IdlingUnits[AtreidesMain])) + Utils.Do(reinforcements, function(unit) + Trigger.ClearAll(unit) + Trigger.AfterDelay(1, function() + unit.Stop() + unit.Move(CorrinoPatrolPoint1.Location,1) + unit.AttackMove(target) + unit.CallFunc(function() + FindTargetsInArea(player, unit) + end) + end) + end) + end + + if player == AtreidesSmall2 and #IdlingUnits[AtreidesMain] > 10 then + CheckArea(AtreidesMain, target, 10) + end end +ReleaseSardaukars = true +Trigger.OnDamaged(CPalace, function(self) + if self.Health < self.MaxHealth * 0.8 and ReleaseSardaukars then + local index = 0 + while index < 100 do + index = index + 5 + Trigger.AfterDelay(index,function() + if self.IsDead then return end + local actor = Actor.Create("sardaukar", true, { Owner = CorrinoMain, Location = CPalace.Location + CVec.New(1,2) }) + actor.Move(CPos.New(3,31)) + IdlingUnits[CorrinoMain][#IdlingUnits[CorrinoMain] + 1] = actor + end) + end + + ReleaseSardaukars = false + end +end) + BuildFremen = function() if APalace.IsDead or APalace.Owner ~= AtreidesMain then return @@ -194,7 +308,6 @@ BuildFremen = function() Trigger.AfterDelay(DateTime.Seconds(5), function() IdleFremen = Utils.Where(AtreidesMain.GetActorsByType('fremen'), function(actor) return actor.IsIdle end) - if #IdleFremen >= FremenGroupSize[Difficulty] then SendFremen() end @@ -231,7 +344,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[AtreidesMain] then local units = AtreidesMain.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[AtreidesMain] = false ProtectHarvester(units[1], AtreidesMain, AttackGroupSize[Difficulty]) @@ -240,7 +352,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[AtreidesSmall1] then local units = AtreidesSmall1.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[AtreidesSmall1] = false ProtectHarvester(units[1], AtreidesSmall1, AttackGroupSize[Difficulty]) @@ -249,7 +360,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[CorrinoMain] then local units = CorrinoMain.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[CorrinoMain] = false ProtectHarvester(units[1], CorrinoMain, AttackGroupSize[Difficulty]) @@ -258,7 +368,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[CorrinoSmall] then local units = CorrinoSmall.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[CorrinoSmall] = false ProtectHarvester(units[1], CorrinoSmall, AttackGroupSize[Difficulty]) @@ -286,8 +395,8 @@ WorldLoaded = function() Camera.Position = HMCV.CenterPosition AtreidesAttackLocation = HarkonnenRally.Location - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) - Trigger.AfterDelay(DateTime.Minutes(1) + DateTime.Seconds (30), BuildFremen) + Trigger.AfterDelay(DateTime.Minutes(10), function() AirstrikeLogic(AHiTechFactory) end ) + Trigger.AfterDelay(DateTime.Minutes(3) + DateTime.Seconds (30), BuildFremen) Trigger.OnAllKilledOrCaptured(AtreidesMainBase, function() Utils.Do(AtreidesMain.GetGroundAttackers(), IdleHunt) @@ -315,8 +424,8 @@ WorldLoaded = function() unit.AttackMove(AtreidesAttackLocation) IdleHunt(unit) end - SendCarryallReinforcements(AtreidesMain, 0, AtreidesAttackWaves[Difficulty], AtreidesAttackDelay[Difficulty], path, AtreidesReinforcements[Difficulty], waveCondition, huntFunction) + SendCarryallReinforcements(AtreidesMain, 0, AtreidesAttackWaves[Difficulty], AtreidesAttackDelay[Difficulty], path, AtreidesReinforcements[Difficulty], waveCondition, huntFunction) SendStarportReinforcements() Actor.Create("upgrade.barracks", true, { Owner = AtreidesMain }) @@ -335,5 +444,19 @@ WorldLoaded = function() Actor.Create("upgrade.heavy", true, { Owner = CorrinoSmall }) Trigger.AfterDelay(0, ActivateAI) - SendHarkonnenReinforcements(DateTime.Minutes(2) + DateTime.Seconds(30)) + SendHarkonnenReinforcements(DateTime.Minutes(2) + DateTime.Seconds(30), 1) + SendHarkonnenReinforcements(17030,2) + SendHarkonnenReinforcements(17020,3) + + Trigger.OnAllKilled({ARock6, ARock7, ARock8}, function() + SendHarkonnenReinforcements(0,4) + SendHarkonnenReinforcements(10,5) + end) end + +Trigger.OnDamaged(DebrisHarvester2, function(self) + if self.Health < self.MaxHealth * 0.6 then + self.Deploy() + Trigger.ClearAll(self) + end +end) diff --git a/mods/d2k/maps/harkonnen-09a/map.bin b/mods/d2k/maps/harkonnen-09a/map.bin index bdabf5cf58..2c7fd73f32 100644 Binary files a/mods/d2k/maps/harkonnen-09a/map.bin and b/mods/d2k/maps/harkonnen-09a/map.bin differ diff --git a/mods/d2k/maps/harkonnen-09a/map.yaml b/mods/d2k/maps/harkonnen-09a/map.yaml index d5bbaf4e0c..43b3054b96 100644 --- a/mods/d2k/maps/harkonnen-09a/map.yaml +++ b/mods/d2k/maps/harkonnen-09a/map.yaml @@ -562,9 +562,6 @@ Actors: ARock4: large_gun_turret Location: 54,27 Owner: Atreides Main Base - CPalace: palace - Location: 2,28 - Owner: Corrino Main Base Actor161: wall Location: 49,28 Owner: Atreides Main Base @@ -844,6 +841,7 @@ Actors: ARock6: large_gun_turret Location: 47,62 Owner: Atreides Small Base 1 + TurretFacing: 512 AHeavyFactory2: heavy_factory Location: 36,63 Owner: Atreides Small Base 1 @@ -862,12 +860,10 @@ Actors: ARock7: large_gun_turret Location: 44,66 Owner: Atreides Small Base 1 + TurretFacing: 512 APower21: wind_trap Location: 38,67 Owner: Atreides Small Base 1 - ARock8: large_gun_turret - Location: 44,67 - Owner: Atreides Small Base 1 ABarracks3: barracks Location: 36,68 Owner: Atreides Small Base 1 @@ -967,18 +963,12 @@ Actors: AtreidesRally1: waypoint Owner: Neutral Location: 96,87 - AtreidesEntry1: waypoint - Owner: Neutral - Location: 97,87 AtreidesRally2: waypoint Owner: Neutral Location: 76,93 AtreidesEntry2: waypoint Owner: Neutral Location: 76,97 - AtreidesRally3: waypoint - Owner: Neutral - Location: 63,65 AtreidesEntry3: waypoint Owner: Neutral Location: 97,65 @@ -1042,6 +1032,85 @@ Actors: CorrinoStarportExit: waypoint Owner: Neutral Location: 2,13 + AtreidesPatrolPoint2: waypoint + Owner: Neutral + Location: 16,63 + AtreidesPatrolPoint3: waypoint + Owner: Neutral + Location: 62,47 + AtreidesPatrolPoint4: waypoint + Owner: Neutral + Location: 72,28 + CorrinoPatrolPoint1: waypoint + Owner: Neutral + Location: 13,39 + CorrinoPatrolPoint2: waypoint + Owner: Neutral + Location: 32,29 + AtreidesPatrolPoint5: waypoint + Owner: Neutral + Location: 44,42 + CorrinoPatrolPoint3: waypoint + Owner: Neutral + Location: 72,50 + Actor5: combat_tank_a.debris + Owner: Creeps + Facing: 384 + Location: 93,53 + Actor8: frigate.debris + Owner: Creeps + Location: 86,54 + Actor1: harvester.debris + Owner: Creeps + Facing: 384 + Location: 77,63 + Actor9: carryall.debris + Owner: Creeps + Facing: 384 + Location: 48,75 + Actor6: siege_tank.debris + Owner: Creeps + Facing: 384 + Location: 92,58 + Actor10: combat_tank_h.debris + Owner: Creeps + Facing: 384 + Location: 88,54 + HarkonnenRally1: waypoint + Owner: Neutral + Location: 74,71 + HarkonnenRally2: waypoint + Owner: Neutral + Location: 68,79 + AtreidesEntry1: waypoint + Owner: Neutral + Location: 97,87 + Actor11: crate + Owner: Neutral + Location: 6,76 + ARock8: large_gun_turret + Owner: Atreides Small Base 1 + Location: 44,67 + TurretFacing: 512 + HarkonnenRally4: waypoint + Owner: Neutral + Location: 97,57 + HarkonnenRally3: waypoint + Owner: Neutral + Location: 97,70 + AtreidesRally3: waypoint + Owner: Neutral + Location: 51,65 + AtreidesPatrolPoint1: waypoint + Owner: Neutral + Location: 46,84 + DebrisHarvester2: harvester.debris + Owner: Creeps + Facing: 384 + Location: 58,39 + CPalace: palace + Owner: Corrino Main Base + Location: 2,26 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-09a/rules.yaml b/mods/d2k/maps/harkonnen-09a/rules.yaml index 8b45d5835c..ec8c08772e 100644 --- a/mods/d2k/maps/harkonnen-09a/rules.yaml +++ b/mods/d2k/maps/harkonnen-09a/rules.yaml @@ -1,6 +1,204 @@ Player: PlayerResources: DefaultCash: 7000 + ExternalCondition@AIAMail: + Condition: base-rebuilder + ExternalCondition@AIASmall: + Condition: base-rebuilder2 + ExternalCondition@AICSmall: + Condition: base-rebuilder3 + ExternalCondition@AIAMail-defense: + Condition: defense-rebuilder + ExternalCondition@AIASmall-defense: + Condition: defense-rebuilder2 + ExternalCondition@AICSmall-defense: + Condition: defense-rebuilder3 + BaseBuilderBotModule@AIAMainBase: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 3 + outpost: 1 + light_factory: 1 + starport: 1 + palace: 1 + heavy_factory: 1 + high_tech_factory: 1 + wind_trap: 11 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + starport: 1 + high_tech_factory: 1 + palace: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AIASmallBase: + RequiresCondition: base-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + palace: 1 + heavy_factory: 1 + wind_trap: 7 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AIAMainCorrino: + RequiresCondition: base-rebuilder3 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 3 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 4 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AIAMainDefense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 3 + outpost: 1 + light_factory: 1 + starport: 1 + palace: 1 + heavy_factory: 1 + high_tech_factory: 1 + wind_trap: 11 + silo: 1 + medium_gun_turret: 5 + large_gun_turret: 4 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + starport: 1 + high_tech_factory: 1 + palace: 1 + light_factory: 1 + heavy_factory: 1 + medium_gun_turret: 1 + large_gun_turret: 1 + BaseBuilderBotModule@AIASmallDefense: + RequiresCondition: defense-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + palace: 1 + heavy_factory: 1 + wind_trap: 7 + silo: 1 + large_gun_turret: 5 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + large_gun_turret: 1 + BaseBuilderBotModule@AISmallCorrino: + RequiresCondition: defense-rebuilder3 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 4 + silo: 1 + large_gun_turret: 2 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + large_gun_turret: 2 World: LuaScript: diff --git a/mods/d2k/maps/harkonnen-09b/harkonnen09b-AI.lua b/mods/d2k/maps/harkonnen-09b/harkonnen09b-AI.lua index 56cf5e538e..47f128e11b 100644 --- a/mods/d2k/maps/harkonnen-09b/harkonnen09b-AI.lua +++ b/mods/d2k/maps/harkonnen-09b/harkonnen09b-AI.lua @@ -7,6 +7,38 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(7) + +InitialProductionDelay = { + AtreidesMain = + { + easy = DateTime.Seconds(180), + normal = DateTime.Seconds(120), + hard = DateTime.Seconds(60) + }, + + AtreidesSmall = + { + easy = DateTime.Seconds(120), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(0) + }, + + CorrinoMain = + { + easy = DateTime.Seconds(160), + normal = DateTime.Seconds(120), + hard = DateTime.Seconds(80) + }, + + CorrinoSmall = + { + easy = DateTime.Seconds(80), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(30) + } +} + AttackGroupSize = { easy = 6, @@ -14,25 +46,81 @@ AttackGroupSize = hard = 10 } -AttackDelays = +EarlyProductionDelays = +{ + easy = { DateTime.Seconds(9), DateTime.Seconds(12) }, + normal = { DateTime.Seconds(6), DateTime.Seconds(8) }, + hard = { DateTime.Seconds(4), DateTime.Seconds(7) } +} + +LateProductionDelays = { 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" } +EnemyInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + EnemyVehicleTypes = { "trike", "trike", "quad" } -AtreidesMainTankTypes = { "combat_tank_a", "combat_tank_a", "siege_tank", "missile_tank", "sonic_tank" } +AtreidesMainTankTypes = { + EarlyGame = { "combat_tank_a", "combat_tank_a", "siege_tank", "missile_tank" }, + LateGame = { "combat_tank_a", "combat_tank_a", "siege_tank", "missile_tank", "sonic_tank" } +} + AtreidesSmallTankTypes = { "combat_tank_a", "combat_tank_a", "siege_tank" } -AtreidesStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_a.starport", "combat_tank_a.starport", "siege_tank.starport", "missile_tank.starport" } CorrinoMainInfantryTypes = { "light_inf", "light_inf", "trooper", "sardaukar" } -CorrinoTankTypes = { "combat_tank_h", "combat_tank_h", "siege_tank", "missile_tank" } -CorrinoStarportTypes = { "trike.starport", "trike.starport", "quad.starport", "combat_tank_h.starport", "combat_tank_h.starport", "siege_tank.starport", "missile_tank.starport" } + +CorrinoTankTypes = { + EarlyGame = { "combat_tank_h" }, + LateGame = { "combat_tank_h", "combat_tank_h", "siege_tank", "missile_tank" } +} ActivateAI = function() + Defending[AtreidesMain] = {} + Defending[AtreidesSmall] = {} + Defending[CorrinoMain] = {} + Defending[CorrinoSmall] = {} + -- this is also first attack timing + AttackDelay[AtreidesMain] = 15000 * DifficultyModifier[Difficulty] + AttackDelay[AtreidesSmall] = 10000 * DifficultyModifier[Difficulty] + AttackDelay[CorrinoMain] = 15000 * DifficultyModifier[Difficulty] + AttackDelay[CorrinoSmall] = 15000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesMain] = 13000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesSmall] = 4000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[CorrinoMain] = 8600 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[CorrinoSmall] = 8600 * DifficultyModifier[Difficulty] + + Trigger.OnKilledOrCaptured(AConYard1, function() + TimeBetweenAttacks[AtreidesMain] = 0 + TimeBetweenAttacks[CorrinoMain] = 0 + end) + + Trigger.OnAllKilledOrCaptured(CorrinoMainBase, function() + TimeBetweenAttacks[CorrinoSmall] = 0 + TimeBetweenAttacks[AtreidesMain] = 0 + end) + + Trigger.OnAllKilledOrCaptured(CorrinoSmallBase, function() + TimeBetweenAttacks[CorrinoMain] = 0 + end) + + HarvesterCount[AtreidesMain] = 4 + HarvesterCount[AtreidesSmall] = 2 + HarvesterCount[CorrinoMain] = 4 + HarvesterCount[CorrinoSmall] = 1 + + PatrolPoints[AtreidesMain] = {APatrolPoint1.Location, APatrolPoint2.Location, APatrolPoint3.Location, APatrolPoint4.Location } + PatrolPoints[AtreidesSmall] = {APatrolPoint5.Location, APatrolPoint6.Location, APatrolPoint7.Location } + PatrolPoints[CorrinoMain] = {APatrolPoint7.Location, AtreidesRally5.Location, CPatrolPoint1.Location } + PatrolPoints[CorrinoSmall] = {CPatrolPoint2.Location, AtreidesRally4.Location, APatrolPoint4.Location } + DefencePerimeter[AtreidesMain] = GetCellsInRectangle(CPos.New(25,58), CPos.New(47,70)) + DefencePerimeter[AtreidesSmall] = GetCellsInRectangle(CPos.New(103,5), CPos.New(119,18)) + DefencePerimeter[CorrinoMain] = GetCellsInRectangle(CPos.New(65,38), CPos.New(91,55)) + DefencePerimeter[CorrinoSmall] = GetCellsInRectangle(CPos.New(33,35), CPos.New(49,52)) + IdlingUnits[AtreidesMain] = Utils.Concat(Reinforcements.Reinforce(AtreidesMain, InitialAtreidesReinforcements[1], InitialAtreidesPaths[1]), Reinforcements.Reinforce(AtreidesMain, InitialAtreidesReinforcements[2], InitialAtreidesPaths[2])) IdlingUnits[AtreidesSmall] = Reinforcements.Reinforce(AtreidesSmall, InitialAtreidesReinforcements[3], InitialAtreidesPaths[3]) IdlingUnits[CorrinoMain] = Utils.Concat(Reinforcements.Reinforce(CorrinoMain, InitialCorrinoReinforcements[1], InitialCorrinoPaths[1]), Reinforcements.Reinforce(CorrinoMain, InitialCorrinoReinforcements[2], InitialCorrinoPaths[2])) @@ -43,31 +131,109 @@ ActivateAI = function() DefendAndRepairBase(CorrinoMain, CorrinoMainBase, 0.75, AttackGroupSize[Difficulty]) DefendAndRepairBase(CorrinoSmall, CorrinoSmallBase, 0.75, AttackGroupSize[Difficulty]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(EnemyInfantryTypes) } end local infantryToBuildCorrinoMain = function() return { Utils.Random(CorrinoMainInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(EnemyVehicleTypes) } end - local tanksToBuildAtreidesMain = function() return { Utils.Random(AtreidesMainTankTypes) } end + local vehiclesToBuild = function() return { Utils.Random(EnemyVehicleTypes) } end + local tanksToBuildAtreidesMain = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(AtreidesMainTankTypes["EarlyGame"]) } + else + return { Utils.Random(AtreidesMainTankTypes["LateGame"]) } + end + end + local tanksToBuildAtreidesSmall = function() return { Utils.Random(AtreidesSmallTankTypes) } end - local tanksToBuildCorrino = function() return { Utils.Random(CorrinoTankTypes) } end - local unitsToBuyAtreides = function() return { Utils.Random(AtreidesStarportTypes) } end - local unitsToBuyCorrino = function() return { Utils.Random(CorrinoStarportTypes) } end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + local tanksToBuildCorrino = function() + if EarlyGameStage >= DateTime.GameTime then + return { Utils.Random(CorrinoTankTypes["EarlyGame"]) } + else + return { Utils.Random(CorrinoTankTypes["LateGame"]) } + end + end - ProduceUnits(AtreidesMain, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, ALightFactory1, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, AHeavyFactory1, delay, tanksToBuildAtreidesMain, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesMain, AStarport1, delay, unitsToBuyAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + local attackThresholdSize = AttackGroupSize[Difficulty] * 3 + local attackThresholdSizeSmall = AttackGroupSize[Difficulty] * 2 + local attackThresholdSizeEmperor = AttackGroupSize[Difficulty] * 5 - ProduceUnits(AtreidesSmall, ABarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesSmall, ALightFactory2, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesSmall, AHeavyFactory2, delay, tanksToBuildAtreidesSmall, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(AtreidesSmall, AStarport2, delay, unitsToBuyAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesMain"][Difficulty], function () + ProduceUnits(AtreidesMain, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesMain, ALightFactory1, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesMain, AHeavyFactory1, delay, tanksToBuildAtreidesMain, AttackGroupSize[Difficulty], attackThresholdSize) + --ProduceUnits(AtreidesMain, AStarport1, delay, unitsToBuyAtreides, AttackGroupSize[Difficulty], AttackThresholdSize) + end) - ProduceUnits(CorrinoMain, CBarracks1, delay, infantryToBuildCorrinoMain, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoMain, CHeavyFactory, delay, tanksToBuildCorrino, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesSmall"][Difficulty], function () + ProduceUnits(AtreidesSmall, ABarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmall) + ProduceUnits(AtreidesSmall, ALightFactory2, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmall) + ProduceUnits(AtreidesSmall, AHeavyFactory2, delay, tanksToBuildAtreidesSmall, AttackGroupSize[Difficulty], attackThresholdSizeSmall) + end) - ProduceUnits(CorrinoSmall, CBarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoSmall, CLightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(CorrinoSmall, CStarport, delay, unitsToBuyCorrino, AttackGroupSize[Difficulty], attackThresholdSize) + Trigger.AfterDelay(InitialProductionDelay["CorrinoMain"][Difficulty], function () + ProduceUnits(CorrinoMain, CBarracks1, delay, infantryToBuildCorrinoMain, AttackGroupSize[Difficulty], attackThresholdSizeEmperor) + ProduceUnits(CorrinoMain, CHeavyFactory, delay, tanksToBuildCorrino, AttackGroupSize[Difficulty], attackThresholdSizeEmperor) + end) + + Trigger.AfterDelay(InitialProductionDelay["CorrinoSmall"][Difficulty], function () + ProduceUnits(CorrinoSmall, CBarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmall) + ProduceUnits(CorrinoSmall, CLightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSizeSmall) + end) + + local productionTypesAtreidis = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuildAtreidesMain + } + + local productionTypesCorrino = + { + barracks = infantryToBuildCorrinoMain, + heavy_factory = tanksToBuildCorrino + } + + if Difficulty == "normal" then + AtreidesMain.GrantCondition("base-rebuilder") + AtreidesSmall.GrantCondition("base-rebuilder2") + CorrinoMain.GrantCondition("base-rebuilder3") + end + + if Difficulty == "hard" then + AtreidesMain.GrantCondition("defense-rebuilder") + AtreidesSmall.GrantCondition("defense-rebuilder2") + CorrinoMain.GrantCondition("defense-rebuilder3") + end + + Trigger.OnBuildingPlaced(AtreidesMain, function(p, building) + table.insert(AtreidesMainBase, building) + DefendAndRepairBase(AtreidesMain, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypesAtreidis[building.Type] == nil then return end + ProduceUnits(AtreidesMain, building, delay, productionTypesAtreidis[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + Trigger.OnBuildingPlaced(AtreidesSmall, function(p, building) + table.insert(AtreidesSmallBase, building) + DefendAndRepairBase(AtreidesSmall, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypesAtreidis[building.Type] == nil then return end + ProduceUnits(AtreidesSmall, building, delay, productionTypesAtreidis[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + Trigger.OnBuildingPlaced(CorrinoMain, function(p, building) + table.insert(CorrinoMainBase, building) + DefendAndRepairBase(CorrinoMain, {building}, 0.5, AttackGroupSize[Difficulty] ) + if productionTypesCorrino[building.Type] == nil then return end + ProduceUnits(CorrinoMain, building, delay, productionTypesCorrino[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + ActivateCrushLogic() end diff --git a/mods/d2k/maps/harkonnen-09b/harkonnen09b.lua b/mods/d2k/maps/harkonnen-09b/harkonnen09b.lua index f855e4168d..3f241f4842 100644 --- a/mods/d2k/maps/harkonnen-09b/harkonnen09b.lua +++ b/mods/d2k/maps/harkonnen-09b/harkonnen09b.lua @@ -8,8 +8,11 @@ ]] AtreidesMainBase = { AConYard1, AOutpost1, APalace, ARefinery1, ARefinery2, AHeavyFactory1, ALightFactory1, ARepair1, AStarport1, AHiTechFactory, AResearch, ARock1, ARock2, ARock3, ARock4, ARock5, ARock6, ARock7, ARock8, ARock9, ARock10, ABarracks1, ABarracks2, APower1, APower2, APower3, APower4, APower5, APower6, APower7, APower8, APower9, APower10, APower11, ASilo1, ASilo2, ASilo3 } + AtreidesSmallBase = { AConYard2, ARefinery3, ABarracks3, AHeavyFactory2, ALightFactory2, ARepair2, AGunt1, AGunt2, ARock11, APower12, APower13, APower14, APower15, APower16, APower17, APower18, APower19, APower20 } + CorrinoMainBase = { CConYard, COutpost, CPalace, CRefinery1, CHeavyFactory, CResearch, CGunt1, CGunt2, CGunt3, CGunt4, CGunt5, CGunt6, CRock1, CRock2, CRock3, CRock4, CBarracks1, CBarracks2, CPower1, CPower2, CPower3, CPower4, CPower5, CPower6, CPower7, CPower8 } + CorrinoSmallBase = { CRefinery2, CLightFactory, CStarport, CGunt7, CGunt8, CBarracks3, CPower9, CPower10, CPower11, CPower12, CSilo1, CSilo2, CSilo3, CSilo4 } AtreidesReinforcements = @@ -148,7 +151,6 @@ SendStarportReinforcements = function() end local reinforcements = Utils.Random(CorrinoStarportReinforcements[Difficulty]) - local units = Reinforcements.ReinforceWithTransport(CorrinoSmall, "frigate", reinforcements, { CorrinoStarportEntry.Location, CStarport.Location + CVec.New(1, 1) }, { CorrinoStarportExit.Location })[2] Utils.Do(units, function(unit) unit.AttackMove(AtreidesAttackLocation) @@ -168,14 +170,33 @@ SendHarkonnenReinforcements = function(delay) end) end -SendAirStrike = function() - if AHiTechFactory.IsDead or AHiTechFactory.Owner ~= AtreidesMain then +AirStrikeTimer = 7500 +AirStrikeChargeTime = 7500 +AirstrikeLogic = function(airstrikeProvider) + if airstrikeProvider.IsDead then return end + + if DateTime.GameTime <= AirStrikeTimer then + Trigger.AfterDelay(AirStrikeTimer - DateTime.GameTime + 1, function() + AirstrikeLogic(airstrikeProvider) + end) return end + -- randomly choose if wait again or strike. During waiting Airstrike can still be used by DefensiveAirStrike + if Utils.RandomInteger(1, 100) < 30 then + Trigger.AfterDelay(1501, function() AirstrikeLogic(airstrikeProvider)end) + else + AirStrikeVSBuilding(airstrikeProvider) + Trigger.AfterDelay(7500, function() AirstrikeLogic(airstrikeProvider) end) + IsAirstrikeReady = false + end +end + +AirStrikeVSBuilding = function(airstrikeProvider) + if airstrikeProvider.IsDead then return end + local targets = Utils.Where(Harkonnen.GetActors(), function(actor) - return - actor.HasProperty("Sell") and + return actor.HasProperty("Sell") and actor.Type ~= "wall" and actor.Type ~= "medium_gun_turret" and actor.Type ~= "large_gun_turret" and @@ -184,12 +205,71 @@ SendAirStrike = function() end) if #targets > 0 then - AHiTechFactory.TargetAirstrike(Utils.Random(targets).CenterPosition) + airstrikeProvider.TargetAirstrike(Utils.Random(targets).CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime end - - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) end +DefensiveAirStrike = function(airstrikeProvider, possibleTargets) + if airstrikeProvider.IsDead then return end + local bestValue = {} + local bestIndex = 1 + for i = 1, #possibleTargets, 1 do + local ActorsInCircle = Map.ActorsInCircle(possibleTargets[i].CenterPosition, WDist.FromCells(4), function(a) + return + a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + + bestValue[i] = 0 + Utils.Do(ActorsInCircle, function(a) + bestValue[i] = bestValue[i] + Actor.Cost(a.Type) + end) + + if bestValue[i] > bestValue[bestIndex] then + bestIndex = i + end + end + airstrikeProvider.TargetAirstrike(possibleTargets[bestIndex].CenterPosition) + AirStrikeTimer = DateTime.GameTime + AirStrikeChargeTime +end + +EmergencyBehaviour = function(player,target) + if player == AtreidesMain or player == AtreidesSmall then + if AHiTechFactory.IsDead then return end + + local enemyunits = Map.ActorsInCircle(Map.CenterOfCell(target), WDist.FromCells(15), function(a) + return a.Owner == Harkonnen + and not a.IsDead + and a.HasProperty("Attack") + end) + + if enemyunits[1] == nil then return end + DefensiveAirStrike(AHiTechFactory, enemyunits) + end + if player == CorrinoSmall then + player.Cash = player.Cash + 2000 + end +end + +ReleaseSardaukars = true +Trigger.OnDamaged(CPalace, function(self) + if self.Health < self.MaxHealth * 0.8 and ReleaseSardaukars then + local index = 0 + while index < 100 do + index = index + 5 + Trigger.AfterDelay(index,function() + if self.IsDead then return end + local actor = Actor.Create("sardaukar", true, {Owner = CorrinoMain, Location = CPalace.Location + CVec.New(1,2)}) + actor.Move(CPos.New(90,45)) + IdlingUnits[CorrinoMain][#IdlingUnits[CorrinoMain] + 1] = actor + end) + end + ReleaseSardaukars = false + end +end) + BuildFremen = function() if APalace.IsDead or APalace.Owner ~= AtreidesMain then return @@ -302,7 +382,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[AtreidesMain] then local units = AtreidesMain.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[AtreidesMain] = false ProtectHarvester(units[1], AtreidesMain, AttackGroupSize[Difficulty]) @@ -311,7 +390,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[AtreidesSmall] then local units = AtreidesSmall.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[AtreidesSmall] = false ProtectHarvester(units[1], AtreidesSmall, AttackGroupSize[Difficulty]) @@ -320,7 +398,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[CorrinoMain] then local units = CorrinoMain.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[CorrinoMain] = false ProtectHarvester(units[1], CorrinoMain, AttackGroupSize[Difficulty]) @@ -329,7 +406,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[CorrinoSmall] then local units = CorrinoSmall.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[CorrinoSmall] = false ProtectHarvester(units[1], CorrinoSmall, AttackGroupSize[Difficulty]) @@ -366,8 +442,8 @@ WorldLoaded = function() Camera.Position = HBarracks.CenterPosition AtreidesAttackLocation = HBarracks.Location - Trigger.AfterDelay(DateTime.Minutes(5), SendAirStrike) - Trigger.AfterDelay(DateTime.Minutes(1) + DateTime.Seconds (30), BuildFremen) + Trigger.AfterDelay(DateTime.Minutes(10), function() AirstrikeLogic(AHiTechFactory) end ) + Trigger.AfterDelay(EarlyGameStage, BuildFremen) Trigger.OnAllKilledOrCaptured(AtreidesMainBase, function() Utils.Do(AtreidesMain.GetGroundAttackers(), IdleHunt) @@ -385,14 +461,22 @@ WorldLoaded = function() Utils.Do(CorrinoSmall.GetGroundAttackers(), IdleHunt) end) + Trigger.AfterDelay(300, function() + Media.DisplayMessage(UserInterface.GetFluentMessage("husk-reclaimed-harkonnen"), Mentat) + end) + + Trigger.AfterDelay(350, function() + Media.DisplayMessage(UserInterface.GetFluentMessage("engineers-hint"), Mentat) + end) + local path = function() return Utils.Random(AtreidesPaths) end local waveCondition = function() return Harkonnen.IsObjectiveCompleted(KillAtreides) end local huntFunction = function(unit) unit.AttackMove(AtreidesAttackLocation) IdleHunt(unit) end - SendCarryallReinforcements(AtreidesMain, 0, AtreidesAttackWaves[Difficulty], AtreidesAttackDelay[Difficulty], path, AtreidesReinforcements[Difficulty], waveCondition, huntFunction) + SendCarryallReinforcements(AtreidesMain, 0, AtreidesAttackWaves[Difficulty], AtreidesAttackDelay[Difficulty], path, AtreidesReinforcements[Difficulty], waveCondition, huntFunction) SendStarportReinforcements() Actor.Create("upgrade.barracks", true, { Owner = AtreidesMain }) diff --git a/mods/d2k/maps/harkonnen-09b/map.bin b/mods/d2k/maps/harkonnen-09b/map.bin index 5e0dc7e37a..eeb92b7850 100644 Binary files a/mods/d2k/maps/harkonnen-09b/map.bin and b/mods/d2k/maps/harkonnen-09b/map.bin differ diff --git a/mods/d2k/maps/harkonnen-09b/map.yaml b/mods/d2k/maps/harkonnen-09b/map.yaml index 1d82f9f7ba..f60e30443b 100644 --- a/mods/d2k/maps/harkonnen-09b/map.yaml +++ b/mods/d2k/maps/harkonnen-09b/map.yaml @@ -454,9 +454,6 @@ Actors: COutpost: outpost Location: 79,42 Owner: Corrino Main Base - CPalace: palace - Location: 89,42 - Owner: Corrino Main Base Actor116: wall Location: 93,42 Owner: Corrino Main Base @@ -1005,6 +1002,68 @@ Actors: CPower8: wind_trap Owner: Corrino Main Base Location: 90,48 + APatrolPoint1: waypoint + Owner: Neutral + Location: 10,60 + APatrolPoint2: waypoint + Owner: Neutral + Location: 38,85 + APatrolPoint3: waypoint + Owner: Neutral + Location: 75,73 + APatrolPoint4: waypoint + Owner: Neutral + Location: 44,32 + APatrolPoint5: waypoint + Owner: Neutral + Location: 99,33 + APatrolPoint6: waypoint + Owner: Neutral + Location: 82,17 + APatrolPoint7: waypoint + Owner: Neutral + Location: 65,30 + CPatrolPoint1: waypoint + Owner: Neutral + Location: 49,13 + CPatrolPoint2: waypoint + Owner: Neutral + Location: 15,47 + Actor6: combat_tank_h.debris + Owner: Harkonnen + Facing: 384 + Location: 17,3 + Actor7: siege_tank.debris + Owner: Harkonnen + Facing: 384 + Location: 16,3 + Actor5: combat_tank_a.debris + Facing: 384 + Faction: + Owner: Atreides Small Base + Location: 14,3 + CPalace: palace + Owner: Corrino Main Base + Location: 89,41 + Actor10: light_inf + Owner: Smugglers - Neutral + Facing: 384 + Location: 12,89 + SubCell: 3 + Actor9: trooper + Owner: Smugglers - Neutral + Facing: 384 + Location: 11,88 + SubCell: 3 + Actor15: siege_tank.debris + Owner: Smugglers - Neutral + Facing: 384 + Location: 10,88 + Actor8: engineer + Owner: Smugglers - Neutral + Facing: 384 + Location: 9,88 + SubCell: 3 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/harkonnen-09b/rules.yaml b/mods/d2k/maps/harkonnen-09b/rules.yaml index d8e93721b8..49322d2f8e 100644 --- a/mods/d2k/maps/harkonnen-09b/rules.yaml +++ b/mods/d2k/maps/harkonnen-09b/rules.yaml @@ -1,6 +1,208 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AIAMail: + Condition: base-rebuilder + ExternalCondition@AIASmall: + Condition: base-rebuilder2 + ExternalCondition@AICSmall: + Condition: base-rebuilder3 + ExternalCondition@AIAMail-defense: + Condition: defense-rebuilder + ExternalCondition@AIASmall-defense: + Condition: defense-rebuilder2 + ExternalCondition@AICSmall-defense: + Condition: defense-rebuilder3 + BaseBuilderBotModule@AtreidesMain: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 3 + outpost: 1 + light_factory: 1 + starport: 1 + palace: 1 + heavy_factory: 1 + high_tech_factory: 1 + wind_trap: 11 + research_centre: 1 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + research_centre: 1 + outpost: 1 + starport: 1 + high_tech_factory: 1 + palace: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AtreidesSmallBase: + RequiresCondition: base-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + palace: 1 + heavy_factory: 1 + wind_trap: 7 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@CorrinoMain: + RequiresCondition: base-rebuilder3 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 3 + outpost: 1 + heavy_factory: 1 + wind_trap: 4 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + heavy_factory: 1 + BaseBuilderBotModule@AtreidesMainDefense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 3 + outpost: 1 + light_factory: 1 + starport: 1 + palace: 1 + heavy_factory: 1 + high_tech_factory: 1 + wind_trap: 11 + silo: 1 + research_centre: 1 + large_gun_turret: 10 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + starport: 1 + high_tech_factory: 1 + palace: 1 + light_factory: 1 + research_centre: 1 + heavy_factory: 1 + large_gun_turret: 1 + BaseBuilderBotModule@AtreidesSmallBaseDefense: + RequiresCondition: defense-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + palace: 1 + heavy_factory: 1 + wind_trap: 7 + silo: 1 + large_gun_turret: 1 + medium_gun_turret: 2 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + large_gun_turret: 1 + medium_gun_turret: 1 + BaseBuilderBotModule@CorrinoMainDefense: + RequiresCondition: defense-rebuilder3 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: heavy_factory, barracks, starport + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 3 + outpost: 1 + heavy_factory: 1 + wind_trap: 4 + silo: 1 + large_gun_turret: 2 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + heavy_factory: 1 + large_gun_turret: 2 World: LuaScript: @@ -62,3 +264,28 @@ grenadier: thumper: Buildable: Prerequisites: ~disabled + +palace: + ProduceActorPower@ReleaseSardaukars: + Name: actor-palace.produceactorpower-fremen-name + Description: actor-palace.produceactorpower-fremen-description + Icon: fremen + PauseOnCondition: disabled + RequiresCondition: corrino + Prerequisites: ~techlevel.superweapons, ~palace.sardaukar + Actors: mpsardaukar, mpsardaukar, mpsardaukar, mpsardaukar, mpsardaukar + Type: sardaukar + ChargeInterval: 2250 + EndChargeTextNotification: notification-fremen-ready + ReadyAudio: Reinforce + ReadyTextNotification: notification-reinforcements-have-arrived + BlockedAudio: NoRoom + BlockedTextNotification: notification-no-room-for-new-unit + OrderName: ProduceActorPower.Fremen + SupportPowerPaletteOrder: 2040 + GrantConditionOnFaction@corrino: + Condition: corrino + Factions: corrino + Production@Corrino: + Produces: sardaukar + RequiresCondition: corrino diff --git a/mods/d2k/maps/ordos-01a/ordos01a.lua b/mods/d2k/maps/ordos-01a/ordos01a.lua index f70b15b523..ea87e4ec68 100644 --- a/mods/d2k/maps/ordos-01a/ordos01a.lua +++ b/mods/d2k/maps/ordos-01a/ordos01a.lua @@ -32,6 +32,7 @@ HarkonnenReinforcements = } HarkonnenEntryWaypoints = { HarkonnenWaypoint1.Location, HarkonnenWaypoint2.Location, HarkonnenWaypoint3.Location } + HarkonnenAttackDelay = DateTime.Seconds(30) HarkonnenAttackWaves = @@ -49,6 +50,7 @@ ToHarvest = } OrdosReinforcements = { "light_inf", "light_inf", "light_inf", "light_inf", "raider" } + OrdosEntryPath = { OrdosWaypoint.Location, OrdosRally.Location } Messages = @@ -118,14 +120,10 @@ WorldLoaded = function() end Trigger.OnRemovedFromWorld(OrdosConyard, function() - -- Mission already failed, no need to check the other conditions as well - if checkResourceCapacity() then - return - end + if checkResourceCapacity() then return end local refs = Utils.Where(Map.ActorsInWorld, function(actor) return actor.Type == "refinery" and actor.Owner == Ordos end) - if #refs == 0 then Harkonnen.MarkCompletedObjective(KillOrdos) else diff --git a/mods/d2k/maps/ordos-01b/ordos01b.lua b/mods/d2k/maps/ordos-01b/ordos01b.lua index d70e9b70a0..866e8ffeb4 100644 --- a/mods/d2k/maps/ordos-01b/ordos01b.lua +++ b/mods/d2k/maps/ordos-01b/ordos01b.lua @@ -32,6 +32,7 @@ HarkonnenReinforcements = } HarkonnenEntryWaypoints = { HarkonnenWaypoint1.Location, HarkonnenWaypoint2.Location, HarkonnenWaypoint3.Location, HarkonnenWaypoint4.Location } + HarkonnenAttackDelay = DateTime.Seconds(30) HarkonnenAttackWaves = @@ -49,6 +50,7 @@ ToHarvest = } OrdosReinforcements = { "light_inf", "light_inf", "light_inf", "light_inf", "raider" } + OrdosEntryPath = { OrdosWaypoint.Location, OrdosRally.Location } Messages = @@ -118,14 +120,10 @@ WorldLoaded = function() end Trigger.OnRemovedFromWorld(OrdosConyard, function() - -- Mission already failed, no need to check the other conditions as well - if checkResourceCapacity() then - return - end + if checkResourceCapacity() then return end local refs = Utils.Where(Map.ActorsInWorld, function(actor) return actor.Type == "refinery" and actor.Owner == Ordos end) - if #refs == 0 then Harkonnen.MarkCompletedObjective(KillOrdos) else diff --git a/mods/d2k/maps/ordos-02a/ordos02a-AI.lua b/mods/d2k/maps/ordos-02a/ordos02a-AI.lua index 075f001e2f..836c6e89b1 100644 --- a/mods/d2k/maps/ordos-02a/ordos02a-AI.lua +++ b/mods/d2k/maps/ordos-02a/ordos02a-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -24,10 +24,13 @@ AttackDelays = HarkonnenInfantryTypes = { "light_inf" } ActivateAI = function() + Defending[Harkonnen] = {} + AttackDelay[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 5000 * DifficultyModifier[Difficulty] IdlingUnits[Harkonnen] = { } - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local toBuild = function() return HarkonnenInfantryTypes end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 DefendAndRepairBase(Harkonnen, HarkonnenBase, 0.75, AttackGroupSize[Difficulty]) ProduceUnits(Harkonnen, HBarracks, delay, toBuild, AttackGroupSize[Difficulty], attackThresholdSize) diff --git a/mods/d2k/maps/ordos-02a/ordos02a.lua b/mods/d2k/maps/ordos-02a/ordos02a.lua index 6a844915e1..db7f6b37a2 100644 --- a/mods/d2k/maps/ordos-02a/ordos02a.lua +++ b/mods/d2k/maps/ordos-02a/ordos02a.lua @@ -8,6 +8,7 @@ ]] 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 = @@ -78,6 +79,7 @@ HarkonnenAttackWaves = } OrdosReinforcements = { "light_inf", "light_inf", "raider" } + OrdosEntryPath = { OrdosEntry.Location, OrdosRally.Location } Tick = function() diff --git a/mods/d2k/maps/ordos-02b/ordos02b-AI.lua b/mods/d2k/maps/ordos-02b/ordos02b-AI.lua index 075f001e2f..54318d2f85 100644 --- a/mods/d2k/maps/ordos-02b/ordos02b-AI.lua +++ b/mods/d2k/maps/ordos-02b/ordos02b-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -24,10 +24,14 @@ AttackDelays = HarkonnenInfantryTypes = { "light_inf" } ActivateAI = function() + Defending[Harkonnen] = {} + AttackDelay[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 5000 * DifficultyModifier[Difficulty] + LastHarvesterEaten[Harkonnen] = true IdlingUnits[Harkonnen] = { } - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local toBuild = function() return HarkonnenInfantryTypes end - local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 + attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 DefendAndRepairBase(Harkonnen, HarkonnenBase, 0.75, AttackGroupSize[Difficulty]) ProduceUnits(Harkonnen, HBarracks, delay, toBuild, AttackGroupSize[Difficulty], attackThresholdSize) diff --git a/mods/d2k/maps/ordos-03a/map.bin b/mods/d2k/maps/ordos-03a/map.bin index cb5c56e176..cc025fe27b 100644 Binary files a/mods/d2k/maps/ordos-03a/map.bin and b/mods/d2k/maps/ordos-03a/map.bin differ diff --git a/mods/d2k/maps/ordos-03a/map.yaml b/mods/d2k/maps/ordos-03a/map.yaml index 0d3bc0716b..7f26ac4fb2 100644 --- a/mods/d2k/maps/ordos-03a/map.yaml +++ b/mods/d2k/maps/ordos-03a/map.yaml @@ -143,6 +143,19 @@ Actors: HSilo4: silo Owner: Harkonnen Location: 4,60 + Actor3: crate + Owner: Neutral + Location: 53,25 + Actor9: harvester.debris + Owner: Creeps + Facing: 384 + Location: 63,64 + Actor10: crate_reveal + Owner: Neutral + Location: 11,35 + HPatrolPoint1: waypoint + Owner: Neutral + Location: 32,17 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/ordos-03a/ordos03a-AI.lua b/mods/d2k/maps/ordos-03a/ordos03a-AI.lua index cb83c4d80a..e1f8a6911e 100644 --- a/mods/d2k/maps/ordos-03a/ordos03a-AI.lua +++ b/mods/d2k/maps/ordos-03a/ordos03a-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -22,9 +22,16 @@ AttackDelays = } HarkonnenInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + HarkonnenVehicleTypes = { "trike", "trike", "quad" } InitAIUnits = function() + Defending[Harkonnen] = { } + AttackDelay[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 5000 * DifficultyModifier[Difficulty] + LastHarvesterEaten[Harkonnen] = true + PatrolPoints[Harkonnen] = { HarkonnenRally3.Location, HPatrolPoint1.Location, Actor6.Location } + DefencePerimeter[Harkonnen] = GetCellsInRectangle(CPos.New(3,42), CPos.New(13,63)) IdlingUnits[Harkonnen] = Reinforcements.Reinforce(Harkonnen, HarkonnenInitialReinforcements, HarkonnenInitialPath) DefendAndRepairBase(Harkonnen, HarkonnenBase, 0.75, AttackGroupSize[Difficulty]) @@ -37,14 +44,31 @@ ActivateAI = function() HConyard.Produce(OrdosUpgrades[1]) HConyard.Produce(OrdosUpgrades[2]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end local infantryToBuild = function() return { Utils.Random(HarkonnenInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(HarkonnenVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(HarkonnenVehicleTypes) } end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 -- Finish the upgrades first before trying to build something Trigger.AfterDelay(DateTime.Seconds(14), function() ProduceUnits(Harkonnen, HBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Harkonnen, HLightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Harkonnen, HLightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + if Difficulty ~= "easy" then + Harkonnen.GrantCondition("base-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + } + + Trigger.OnBuildingPlaced(Harkonnen, function(p, building) + table.insert(HarkonnenBase, building) + DefendAndRepairBase(Harkonnen, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Harkonnen, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) end) end diff --git a/mods/d2k/maps/ordos-03a/ordos03a.lua b/mods/d2k/maps/ordos-03a/ordos03a.lua index 3f44476fa4..340f8791aa 100644 --- a/mods/d2k/maps/ordos-03a/ordos03a.lua +++ b/mods/d2k/maps/ordos-03a/ordos03a.lua @@ -8,6 +8,7 @@ ]] HarkonnenBase = { HBarracks, HWindTrap1, HWindTrap2, HLightFactory, HOutpost, HConyard, HRefinery, HSilo1, HSilo2, HSilo3, HSilo4 } + HarkonnenBaseAreaTrigger = { CPos.New(2, 58), CPos.New(3, 58), CPos.New(4, 58), CPos.New(5, 58), CPos.New(6, 58), CPos.New(7, 58), CPos.New(8, 58), CPos.New(9, 58), CPos.New(10, 58), CPos.New(11, 58), CPos.New(12, 58), CPos.New(13, 58), CPos.New(14, 58), CPos.New(15, 58), CPos.New(16, 58), CPos.New(16, 59), CPos.New(16, 60) } HarkonnenReinforcements = @@ -65,15 +66,19 @@ HarkonnenPaths = } HarkonnenHunters = { "light_inf", "light_inf", "trike", "quad" } + HarkonnenInitialReinforcements = { "light_inf", "light_inf", "quad", "quad", "trike", "trike", "trooper", "trooper" } HarkonnenHunterPath = { HarkonnenEntry5.Location, HarkonnenRally5.Location } + HarkonnenInitialPath = { HarkonnenEntry4.Location, HarkonnenRally4.Location } OrdosReinforcements = { "quad", "raider" } + OrdosPath = { OrdosEntry.Location, OrdosRally.Location } OrdosBaseBuildings = { "barracks", "light_factory" } + OrdosUpgrades = { "upgrade.barracks", "upgrade.light" } MessageCheck = function(index) diff --git a/mods/d2k/maps/ordos-03a/rules.yaml b/mods/d2k/maps/ordos-03a/rules.yaml index 21f794cc03..ef759add9d 100644 --- a/mods/d2k/maps/ordos-03a/rules.yaml +++ b/mods/d2k/maps/ordos-03a/rules.yaml @@ -1,6 +1,32 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AIMain: + Condition: base-rebuilder + BaseBuilderBotModule@Harkonnen: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + wind_trap: 4 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 World: LuaScript: diff --git a/mods/d2k/maps/ordos-03b/map.yaml b/mods/d2k/maps/ordos-03b/map.yaml index a3698ae703..dcb2ca6689 100644 --- a/mods/d2k/maps/ordos-03b/map.yaml +++ b/mods/d2k/maps/ordos-03b/map.yaml @@ -254,6 +254,16 @@ Actors: HarkonnenEntry8: waypoint Owner: Neutral Location: 2,20 + Actor1: harvester.debris + Owner: Creeps + Facing: 384 + Location: 30,41 + HPatrolPoint1: waypoint + Owner: Neutral + Location: 18,38 + HPatrolPoint2: waypoint + Owner: Neutral + Location: 47,21 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/ordos-03b/ordos03b-AI.lua b/mods/d2k/maps/ordos-03b/ordos03b-AI.lua index cb83c4d80a..a94d17e3ef 100644 --- a/mods/d2k/maps/ordos-03b/ordos03b-AI.lua +++ b/mods/d2k/maps/ordos-03b/ordos03b-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(9) }, normal = { DateTime.Seconds(2), DateTime.Seconds(7) }, @@ -22,29 +22,59 @@ AttackDelays = } HarkonnenInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + HarkonnenVehicleTypes = { "trike", "trike", "quad" } InitAIUnits = function() IdlingUnits[Harkonnen] = Reinforcements.Reinforce(Harkonnen, HarkonnenInitialReinforcements, HarkonnenInitialPath) - DefendAndRepairBase(Harkonnen, HarkonnenBase, 0.75, AttackGroupSize[Difficulty]) end ActivateAI = function() + Defending[Harkonnen] = {} + AttackDelay[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 5000 * DifficultyModifier[Difficulty] LastHarvesterEaten[Harkonnen] = true + HarvesterCount[Harkonnen] = 0 + PatrolPoints[Harkonnen] = { HPatrolPoint1.Location, HPatrolPoint2.Location, HarkonnenRally7.Location } + DefencePerimeter[Harkonnen] = Utils.Concat(GetCellsInRectangle(CPos.New(35,37), CPos.New(54,51)), GetCellsInRectangle(CPos.New(45,28), CPos.New(58,36))) Trigger.AfterDelay(0, InitAIUnits) HConyard.Produce(OrdosUpgrades[1]) HConyard.Produce(OrdosUpgrades[2]) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + else + return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(HarkonnenInfantryTypes) } end - local vehilcesToBuild = function() return { Utils.Random(HarkonnenVehicleTypes) } end + local vehiclesToBuild = function() return { Utils.Random(HarkonnenVehicleTypes) } end local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 -- Finish the upgrades first before trying to build something Trigger.AfterDelay(DateTime.Seconds(14), function() ProduceUnits(Harkonnen, HBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) - ProduceUnits(Harkonnen, HLightFactory, delay, vehilcesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Harkonnen, HLightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + if Difficulty ~= "easy" then + Harkonnen.GrantCondition("base-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + } + + Trigger.OnBuildingPlaced(Harkonnen, function(p, building) + table.insert(HarkonnenBase, building) + DefendAndRepairBase(Harkonnen, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Harkonnen, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) end) end diff --git a/mods/d2k/maps/ordos-03b/ordos03b.lua b/mods/d2k/maps/ordos-03b/ordos03b.lua index 410478f8e5..4e60afd9e6 100644 --- a/mods/d2k/maps/ordos-03b/ordos03b.lua +++ b/mods/d2k/maps/ordos-03b/ordos03b.lua @@ -8,6 +8,7 @@ ]] HarkonnenBase = { HBarracks, HWindTrap1, HWindTrap2, HWindTrap3, HWindTrap4, HLightFactory, HOutpost, HConyard, HRefinery, HSilo1, HSilo2, HSilo3, HSilo4 } + HarkonnenBaseAreaTrigger = { CPos.New(2, 58), CPos.New(3, 58), CPos.New(4, 58), CPos.New(5, 58), CPos.New(6, 58), CPos.New(7, 58), CPos.New(8, 58), CPos.New(9, 58), CPos.New(10, 58), CPos.New(11, 58), CPos.New(12, 58), CPos.New(13, 58), CPos.New(14, 58), CPos.New(15, 58), CPos.New(16, 58), CPos.New(16, 59), CPos.New(16, 60) } HarkonnenReinforcements = @@ -79,6 +80,7 @@ HarkonnenHunterPaths = } HarkonnenInitialReinforcements = { "light_inf", "light_inf", "quad", "quad", "trike", "trike", "trooper", "trooper" } + HarkonnenInitialPath = { HarkonnenEntry4.Location, HarkonnenRally4.Location } OrdosReinforcements = @@ -103,6 +105,7 @@ OrdosReinforcementDelays = } OrdosBaseBuildings = { "barracks", "light_factory" } + OrdosUpgrades = { "upgrade.barracks", "upgrade.light" } MessageCheck = function(index) @@ -129,7 +132,6 @@ Tick = function() if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[Harkonnen] then local units = Harkonnen.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[Harkonnen] = false ProtectHarvester(units[1], Harkonnen, AttackGroupSize[Difficulty]) diff --git a/mods/d2k/maps/ordos-03b/rules.yaml b/mods/d2k/maps/ordos-03b/rules.yaml index e0376a2ed5..fb0211214d 100644 --- a/mods/d2k/maps/ordos-03b/rules.yaml +++ b/mods/d2k/maps/ordos-03b/rules.yaml @@ -1,6 +1,32 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AIMain: + Condition: base-rebuilder + BaseBuilderBotModule@Harkonnen: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + wind_trap: 4 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 World: LuaScript: diff --git a/mods/d2k/maps/ordos-04/map.yaml b/mods/d2k/maps/ordos-04/map.yaml index 9560efcf03..301b89ce25 100644 --- a/mods/d2k/maps/ordos-04/map.yaml +++ b/mods/d2k/maps/ordos-04/map.yaml @@ -66,6 +66,7 @@ Actors: HGunTurret1: medium_gun_turret Location: 38,3 Owner: Harkonnen + TurretFacing: 512 Actor3: wall Location: 42,3 Owner: Smugglers @@ -87,6 +88,7 @@ Actors: HGunTurret2: medium_gun_turret Location: 38,7 Owner: Harkonnen + TurretFacing: 512 Actor10: trooper Location: 39,7 Owner: Harkonnen @@ -300,6 +302,7 @@ Actors: SGunTurret3: medium_gun_turret Location: 57,20 Owner: Smugglers + TurretFacing: 512 HPower1: wind_trap Location: 44,21 Owner: Harkonnen @@ -330,6 +333,7 @@ Actors: HGunTurret3: medium_gun_turret Location: 47,29 Owner: Harkonnen + TurretFacing: 512 Actor97: wall Location: 48,29 Owner: Harkonnen @@ -348,6 +352,7 @@ Actors: HGunTurret4: medium_gun_turret Location: 11,32 Owner: Harkonnen + TurretFacing: 512 Actor103: light_inf Location: 12,32 Owner: Harkonnen @@ -378,15 +383,6 @@ Actors: 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 @@ -396,9 +392,6 @@ Actors: Actor118: wall Location: 38,40 Owner: Harkonnen - Actor119: light_inf - Location: 42,40 - Owner: Harkonnen Actor120: wall Location: 37,41 Owner: Harkonnen @@ -516,6 +509,24 @@ Actors: Actor152: wall Owner: Smugglers Location: 49,19 + HPatrolPoint1: waypoint + Owner: Neutral + Location: 13,42 + HPatrolPoint2: waypoint + Owner: Neutral + Location: 47,40 + HPatrol1: trike + Location: 37,39 + Owner: Harkonnen + HPatrol2: trike + Location: 39,39 + Owner: Harkonnen + HPatrol3: trike + Location: 41,39 + Owner: Harkonnen + HPatrol4: light_inf + Location: 42,40 + Owner: Harkonnen Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/ordos-04/ordos04-AI.lua b/mods/d2k/maps/ordos-04/ordos04-AI.lua index 428f7b2f8d..6db9498224 100644 --- a/mods/d2k/maps/ordos-04/ordos04-AI.lua +++ b/mods/d2k/maps/ordos-04/ordos04-AI.lua @@ -14,7 +14,7 @@ AttackGroupSize = hard = 10 } -AttackDelays = +ProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -24,23 +24,42 @@ AttackDelays = 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" } InitAIUnits = function(house) LastHarvesterEaten[house] = true IdlingUnits[house] = Reinforcements.Reinforce(house, InitialReinforcements[house.InternalName], InitialReinforcementsPaths[house.InternalName]) - DefendAndRepairBase(house, Base[house.InternalName], 0.75, AttackGroupSize[Difficulty]) end ActivateAI = function() + Defending[Harkonnen] = { } + Defending[Smuggler] = { } + AttackDelay[Harkonnen] = 7000 + AttackDelay[Smuggler] = 7000 + TimeBetweenAttacks[Harkonnen] = 8000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Smuggler] = 15000 * DifficultyModifier[Difficulty] + HarvesterCount[Harkonnen] = 2 + HarvesterCount[Smuggler] = 0 + PatrolPoints[Harkonnen] = { HPatrolPoint1.Location, HPatrolPoint2.Location, HarkonnenRally2.Location } + DefencePerimeter[Harkonnen] = GetCellsInRectangle(CPos.New(23,3), CPos.New(41,22)) + DefencePerimeter[Smuggler] = Utils.Concat(GetCellsInRectangle(CPos.New(57,15), CPos.New(65, 24)), GetCellsInRectangle(CPos.New(43,4), CPos.New(54, 16))) InitAIUnits(Harkonnen) InitAIUnits(Smuggler) - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + else + return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(EnemyInfantryTypes) } end local hVehiclesToBuild = function() return { Utils.Random(HarkonnenVehicleTypes) } end local hTanksToBuild = function() return HarkonnenTankType end @@ -55,4 +74,29 @@ ActivateAI = function() ProduceUnits(Smuggler, SBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize) ProduceUnits(Smuggler, SLightFactory, delay, sVehiclesToBuild, AttackGroupSize[Difficulty], attackTresholdSize) ProduceUnits(Smuggler, SHeavyFactory, delay, sTanksToBuild, AttackGroupSize[Difficulty], attackTresholdSize) + + ActivateCrushLogic() + + AIProductionActivated = true + if Difficulty == "normal" then + Harkonnen.GrantCondition("base-rebuilder") + end + + if Difficulty == "hard" then + Harkonnen.GrantCondition("defense-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = hVehiclesToBuild, + heavy_factory = hTanksToBuild + } + + Trigger.OnBuildingPlaced(Harkonnen, function(p, building) + table.insert(Base[Harkonnen], building) + DefendAndRepairBase(Harkonnen, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(Harkonnen, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end diff --git a/mods/d2k/maps/ordos-04/ordos04.lua b/mods/d2k/maps/ordos-04/ordos04.lua index 857b9972bc..894bf63df5 100644 --- a/mods/d2k/maps/ordos-04/ordos04.lua +++ b/mods/d2k/maps/ordos-04/ordos04.lua @@ -73,7 +73,6 @@ end CheckHarvester = function(house) if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[house] then local units = house.GetActorsByType("harvester") - if #units > 0 then LastHarvesterEaten[house] = false ProtectHarvester(units[1], house, AttackGroupSize[Difficulty]) @@ -131,6 +130,7 @@ WorldLoaded = function() Trigger.OnKilled(SOutpost, function() Ordos.MarkFailedObjective(CaptureOutpost) end) + Trigger.OnCapture(SOutpost, function() Trigger.AfterDelay(DateTime.Seconds(2), function() Ordos.MarkCompletedObjective(CaptureOutpost) @@ -138,6 +138,7 @@ WorldLoaded = function() end) SOutpost.GrantCondition("modified") end) + Trigger.OnDamaged(SOutpost, function(_, attacker) if SOutpost.Owner ~= Smuggler or attacker.IsDead or attacker.Owner ~= Ordos then return @@ -157,4 +158,14 @@ WorldLoaded = function() Trigger.AfterDelay(HarkonnenAttackDelay[Difficulty], function() Media.DisplayMessage(UserInterface.GetFluentMessage("warning-large-force-approaching"), Mentat) end) + + Trigger.OnKilled(HGunTurret4, function() + if #IdlingUnits[Harkonnen] > 10 then + CheckArea(Harkonnen, HarkonnenRally1.Location, 10) + end + end) + + Trigger.OnAnyKilled({HPatrol1, HPatrol2, HPatrol3, HPatrol4}, function() + CheckArea(Harkonnen, HPatrolPoint2.Location, 10) + end) end diff --git a/mods/d2k/maps/ordos-04/rules.yaml b/mods/d2k/maps/ordos-04/rules.yaml index 42b68a18e3..d78325c0cb 100644 --- a/mods/d2k/maps/ordos-04/rules.yaml +++ b/mods/d2k/maps/ordos-04/rules.yaml @@ -1,6 +1,64 @@ Player: PlayerResources: DefaultCash: 5000 + ExternalCondition@AIMain: + Condition: base-rebuilder + ExternalCondition@AIMainDefense: + Condition: defense-rebuilder + BaseBuilderBotModule@Harkonnen: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 4 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@HarkonnenDefense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 4 + silo: 1 + medium_gun_turret: 3 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + medium_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/maps/ordos-05/map.bin b/mods/d2k/maps/ordos-05/map.bin index 629515fcf4..dcb0c9f144 100644 Binary files a/mods/d2k/maps/ordos-05/map.bin and b/mods/d2k/maps/ordos-05/map.bin differ diff --git a/mods/d2k/maps/ordos-05/map.yaml b/mods/d2k/maps/ordos-05/map.yaml index 98f71205b3..64123cce73 100644 --- a/mods/d2k/maps/ordos-05/map.yaml +++ b/mods/d2k/maps/ordos-05/map.yaml @@ -570,6 +570,28 @@ Actors: AtreidesEntry8: waypoint Owner: Neutral Location: 2,58 + APatrolPoint1: waypoint + Owner: Neutral + Location: 51,17 + APatrolPoint2: waypoint + Owner: Neutral + Location: 52,44 + Actor1: carryall.debris + Owner: Creeps + Facing: 384 + Location: 60,41 + Actor2: crate_explosion + Owner: Neutral + Location: 41,60 + Actor6: crate_explosion + Owner: Neutral + Location: 42,60 + Actor8: crate_explosion + Owner: Neutral + Location: 42,59 + Actor7: crate_explosion + Owner: Neutral + Location: 41,59 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/ordos-05/ordos05-AI.lua b/mods/d2k/maps/ordos-05/ordos05-AI.lua index 69d5025f48..1a6f1e0acc 100644 --- a/mods/d2k/maps/ordos-05/ordos05-AI.lua +++ b/mods/d2k/maps/ordos-05/ordos05-AI.lua @@ -7,6 +7,31 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(5) + +InitialProductionDelay = { + AtreidesMain = + { + easy = DateTime.Seconds(130), + normal = DateTime.Seconds(80), + hard = DateTime.Seconds(50) + }, + + AtreidesSmall1 = + { + easy = DateTime.Seconds(120), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(30) + }, + + AtreidesSmall2 = + { + easy = DateTime.Seconds(60), + normal = DateTime.Seconds(30), + hard = DateTime.Seconds(0) + }, +} + AttackGroupSize = { easy = 6, @@ -14,7 +39,14 @@ AttackGroupSize = hard = 10 } -AttackDelays = +EarlyProductionDelays = +{ + easy = { DateTime.Seconds(7), DateTime.Seconds(11) }, + normal = { DateTime.Seconds(5), DateTime.Seconds(7) }, + hard = { DateTime.Seconds(3), DateTime.Seconds(5) } +} + +LateProductionDelays = { easy = { DateTime.Seconds(4), DateTime.Seconds(7) }, normal = { DateTime.Seconds(2), DateTime.Seconds(5) }, @@ -22,7 +54,9 @@ AttackDelays = } EnemyInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" } + EnemyVehicleTypes = { "trike", "trike", "quad" } + EnemyTankType = { "combat_tank_a" } InitAIUnits = function(house) @@ -37,21 +71,60 @@ InitAIUnits = function(house) end ActivateAIProduction = function() - local delay = function() return Utils.RandomInteger(AttackDelays[Difficulty][1], AttackDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(EnemyInfantryTypes) } end local vehiclesToBuild = function() return { Utils.Random(EnemyVehicleTypes) } end local tanksToBuild = function() return EnemyTankType end - local attackTresholdSize = AttackGroupSize[Difficulty] * 2.5 + local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 - ProduceUnits(AtreidesMain, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize) - ProduceUnits(AtreidesMain, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackTresholdSize) - ProduceUnits(AtreidesMain, AHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackTresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesMain"][Difficulty], function() + ProduceUnits(AtreidesMain, ABarracks1, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesMain, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(AtreidesMain, AHeavyFactory, delay, tanksToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) - ProduceUnits(AtreidesSmall1, ABarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesSmall1"][Difficulty], function() + ProduceUnits(AtreidesSmall1, ABarracks2, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) - ProduceUnits(AtreidesSmall2, ABarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesSmall2"][Difficulty], function() + ProduceUnits(AtreidesSmall2, ABarracks3, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + end) + ActivateCrushLogic() AIProductionActivated = true + + if Difficulty == "normal" then + AtreidesMain.GrantCondition("base-rebuilder") + end + + if Difficulty == "hard" then + AtreidesMain.GrantCondition("defense-rebuilder") + end + + local productionTypes = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuild + } + + Trigger.OnBuildingPlaced(AtreidesMain, function(p, building) + table.insert(Base[AtreidesMainBase], building) + DefendAndRepairBase(AtreidesMain, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypes[building.Type] == nil then return end + ProduceUnits(AtreidesMain, building, delay, productionTypes[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end ActivateAI = function() @@ -59,4 +132,19 @@ ActivateAI = function() InitAIUnits(AtreidesSmall1) InitAIUnits(AtreidesSmall2) InitAIUnits(AtreidesSmall3) + Defending[AtreidesMain] = {} + Defending[AtreidesSmall1] = {} + Defending[AtreidesSmall2] = {} + Defending[AtreidesSmall3] = {} + AttackDelay[AtreidesMain] = 14000 * DifficultyModifier[Difficulty] + AttackDelay[AtreidesSmall1] = 14000 * DifficultyModifier[Difficulty] + AttackDelay[AtreidesSmall2] = 14000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesMain] = 7000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesSmall1] = 9000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[AtreidesSmall2] = 9000 * DifficultyModifier[Difficulty] + HarvesterCount[AtreidesMain] = 2 + PatrolPoints[AtreidesMain] = { APatrolPoint1.Location, APatrolPoint2.Location, AtreidesRally8.Location } + DefencePerimeter[AtreidesMain] = GetCellsInRectangle(CPos.New(3,3), CPos.New(18,27)) + DefencePerimeter[AtreidesSmall1] = GetCellsInRectangle(CPos.New(29,17), CPos.New(34,29)) + DefencePerimeter[AtreidesSmall2] = GetCellsInRectangle(CPos.New(39,46), CPos.New(48,59)) end diff --git a/mods/d2k/maps/ordos-05/ordos05.lua b/mods/d2k/maps/ordos-05/ordos05.lua index 2ae7d9d050..883db038fc 100644 --- a/mods/d2k/maps/ordos-05/ordos05.lua +++ b/mods/d2k/maps/ordos-05/ordos05.lua @@ -109,6 +109,16 @@ CheckHarvester = function(house) end CachedResources = -1 +EmergencyBehaviour = function(player, target) + HoldProduction[player] = false + if Difficulty == "hard" then + player.Cash = player.Cash + 2000 + end + if player == AtreidesSmall2 and #IdlingUnits[AtreidesMain] > 10 then + CheckArea(AtreidesMain, target, 10) + end +end + Tick = function() if Ordos.Resources > SpiceToHarvest - 1 then Ordos.MarkCompletedObjective(GatherSpice) @@ -215,11 +225,13 @@ WorldLoaded = function() APower9.Sell() end end) + Trigger.OnKilledOrCaptured(APower8, function() if not AIProductionActivated then ActivateAIProduction() end end) + Trigger.OnKilledOrCaptured(APower9, function() if not AIProductionActivated then ActivateAIProduction() diff --git a/mods/d2k/maps/ordos-05/rules.yaml b/mods/d2k/maps/ordos-05/rules.yaml index 22539f98d7..33bf158841 100644 --- a/mods/d2k/maps/ordos-05/rules.yaml +++ b/mods/d2k/maps/ordos-05/rules.yaml @@ -1,6 +1,64 @@ Player: PlayerResources: DefaultCash: 6000 + ExternalCondition@AIMain: + Condition: base-rebuilder + ExternalCondition@AIMainDefense: + Condition: defense-rebuilder + BaseBuilderBotModule@AtriedesMain: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 3 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AtriedesMainDefense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 3 + silo: 1 + medium_gun_turret: 5 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + medium_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/maps/ordos-06a/map.yaml b/mods/d2k/maps/ordos-06a/map.yaml index 55dbdd8e3c..821776a94b 100644 --- a/mods/d2k/maps/ordos-06a/map.yaml +++ b/mods/d2k/maps/ordos-06a/map.yaml @@ -576,6 +576,25 @@ Actors: HarkonnenEntry7: waypoint Owner: Neutral Location: 34,2 + APatrolPoint1: waypoint + Owner: Neutral + Location: 46,7 + APatrolPoint2: waypoint + Owner: Neutral + Location: 10,45 + HPatrolPoint1: waypoint + Owner: Neutral + Location: 40,49 + HPatrolPoint2: waypoint + Owner: Neutral + Location: 62,76 + Actor2: frigate.debris + Owner: Creeps + Location: 73,55 + Actor1: missile_tank.debris + Owner: Creeps + Facing: 384 + Location: 72,53 Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/d2k/maps/ordos-06a/ordos06a-AI.lua b/mods/d2k/maps/ordos-06a/ordos06a-AI.lua index 1d16d31d45..6e31abb460 100644 --- a/mods/d2k/maps/ordos-06a/ordos06a-AI.lua +++ b/mods/d2k/maps/ordos-06a/ordos06a-AI.lua @@ -7,6 +7,24 @@ information, see COPYING. ]] +EarlyGameStage = DateTime.Minutes(7) + +InitialProductionDelay = { + AtreidesMain = + { + easy = DateTime.Seconds(130), + normal = DateTime.Seconds(80), + hard = DateTime.Seconds(50) + }, + + HarkonnenSmall = + { + easy = DateTime.Seconds(120), + normal = DateTime.Seconds(60), + hard = DateTime.Seconds(30) + } +} + AttackGroupSize = { easy = 6, @@ -14,41 +32,116 @@ AttackGroupSize = hard = 10 } -ProductionDelays = +EarlyProductionDelays = { - easy = { DateTime.Seconds(16), DateTime.Seconds(30) }, + easy = { DateTime.Seconds(7), DateTime.Seconds(10) }, + normal = { DateTime.Seconds(5), DateTime.Seconds(7) }, + hard = { DateTime.Seconds(3), DateTime.Seconds(5) } +} + +LateProductionDelays = +{ + 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" } + EnemyVehicleTypes = { "trike", "trike", "quad" } AtreidesTankTypes = { "combat_tank_a", "siege_tank", "missile_tank" } + HarkonnenTankTypes = { "combat_tank_h", "siege_tank" } InitAIUnits = function(house) LastHarvesterEaten[house] = true IdlingUnits[house] = Reinforcements.Reinforce(house, InitialReinforcements[house.InternalName], InitialReinforcementsPaths[house.InternalName]) - DefendAndRepairBase(house, Base[house.InternalName], 0.75, AttackGroupSize[Difficulty]) end ActivateAI = function() + Defending[Atreides] = {} + Defending[Harkonnen] = {} + AttackDelay[Atreides] = 15000 * DifficultyModifier[Difficulty] + AttackDelay[Harkonnen] = 12000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Atreides] = 4000 * DifficultyModifier[Difficulty] + TimeBetweenAttacks[Harkonnen] = 6000 * DifficultyModifier[Difficulty] + HarvesterCount[Atreides] = 2 + HarvesterCount[Harkonnen] = 0 + PatrolPoints[Atreides] = { HarkonnenRally7.Location, APatrolPoint1.Location, APatrolPoint2.Location } + PatrolPoints[Harkonnen] = {HarkonnenRally4.Location, HarkonnenRally5.Location, HPatrolPoint1.Location, HPatrolPoint2.Location } + DefencePerimeter[Atreides] = GetCellsInRectangle(CPos.New(3,4), CPos.New(20,27)) + DefencePerimeter[Harkonnen] = Utils.Concat(GetCellsInRectangle(CPos.New(15,73), CPos.New(44,80)), GetCellsInRectangle(CPos.New(27,55), CPos.New(38,68))) InitAIUnits(Atreides) InitAIUnits(Harkonnen) - local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end + local delay = function(player) + if EmergencyBuildRate[player] and Difficulty ~= "easy" then + return 1 + end + + if EarlyGameStage >= DateTime.GameTime then + return Utils.RandomInteger(EarlyProductionDelays[Difficulty][1], EarlyProductionDelays[Difficulty][2] + 1) + else + return Utils.RandomInteger(LateProductionDelays[Difficulty][1], LateProductionDelays[Difficulty][2] + 1) + end + end + local infantryToBuild = function() return { Utils.Random(EnemyInfantryTypes) } end local vehiclesToBuild = function() return { Utils.Random(EnemyVehicleTypes) } end local tanksToBuildAtreides = function() return { Utils.Random(AtreidesTankTypes) } end local tanksToBuildHarkonnen = function() return { Utils.Random(HarkonnenTankTypes) } end - local attackTresholdSize = AttackGroupSize[Difficulty] * 2.5 + local attackThresholdSize = AttackGroupSize[Difficulty] * 2.5 - ProduceUnits(Atreides, ABarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize) - ProduceUnits(Atreides, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackTresholdSize) - ProduceUnits(Atreides, AHeavyFactory, delay, tanksToBuildAtreides, AttackGroupSize[Difficulty], attackTresholdSize) + Trigger.AfterDelay(InitialProductionDelay["AtreidesMain"][Difficulty], function() + ProduceUnits(Atreides, ABarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Atreides, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Atreides, AHeavyFactory, delay, tanksToBuildAtreides, AttackGroupSize[Difficulty], attackThresholdSize) + end) - ProduceUnits(Harkonnen, HBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize) - ProduceUnits(Harkonnen, HHeavyFactory, delay, tanksToBuildHarkonnen, AttackGroupSize[Difficulty], attackTresholdSize) + Trigger.AfterDelay(InitialProductionDelay["HarkonnenSmall"][Difficulty], function() + ProduceUnits(Harkonnen, HBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackThresholdSize) + ProduceUnits(Harkonnen, HHeavyFactory, delay, tanksToBuildHarkonnen, AttackGroupSize[Difficulty], attackThresholdSize) + end) + + ActivateCrushLogic() + + if Difficulty == "normal" then + Atreides.GrantCondition("base-rebuilder") + Harkonnen.GrantCondition("base-rebuilder2") + end + + if Difficulty == "hard" then + Atreides.GrantCondition("defense-rebuilder") + Harkonnen.GrantCondition("defense-rebuilder2") + end + + local productionTypesAtreides = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuildAtreides + } + + local productionTypesHarkonnen = + { + barracks = infantryToBuild, + light_factory = vehiclesToBuild, + heavy_factory = tanksToBuildHarkonnen + } + + Trigger.OnBuildingPlaced(Atreides, function(p, building) + table.insert(Base["Atreides"], building) + DefendAndRepairBase(Atreides, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypesAtreides[building.Type] == nil then return end + ProduceUnits(Atreides, building, delay, productionTypesAtreides[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) + + Trigger.OnBuildingPlaced(Harkonnen, function(p, building) + table.insert(Base["Harkonnen"], building) + DefendAndRepairBase(Harkonnen, {building}, 0.75, AttackGroupSize[Difficulty] ) + if productionTypesHarkonnen[building.Type] == nil then return end + ProduceUnits(Atreides, building, delay, productionTypesHarkonnen[building.Type], AttackGroupSize[Difficulty], attackThresholdSize) + end) end diff --git a/mods/d2k/maps/ordos-06a/ordos06a.lua b/mods/d2k/maps/ordos-06a/ordos06a.lua index b69914effc..9df3f232d8 100644 --- a/mods/d2k/maps/ordos-06a/ordos06a.lua +++ b/mods/d2k/maps/ordos-06a/ordos06a.lua @@ -153,6 +153,7 @@ SendContraband = function() Trigger.AfterDelay(DateTime.Seconds(i), function() UserInterface.SetMissionText(IxianReinforcementsHaveArrived, c) end) end + Trigger.AfterDelay(DateTime.Seconds(6), function() TimerTicks = ContrabandTimes[Difficulty] end) @@ -259,6 +260,7 @@ WorldLoaded = function() unit.AttackMove(Utils.Random(EnemyAttackLocations)) IdleHunt(unit) end + local announcementFunction = function() Media.DisplayMessage(UserInterface.GetFluentMessage("enemy-reinforcements-arrived"), Mentat) end diff --git a/mods/d2k/maps/ordos-06a/rules.yaml b/mods/d2k/maps/ordos-06a/rules.yaml index 4bf1da0e52..9ad894ba61 100644 --- a/mods/d2k/maps/ordos-06a/rules.yaml +++ b/mods/d2k/maps/ordos-06a/rules.yaml @@ -1,6 +1,131 @@ Player: PlayerResources: DefaultCash: 6000 + ExternalCondition@AIAtreides: + Condition: base-rebuilder + ExternalCondition@AIAtreidesDefense: + Condition: defense-rebuilder + ExternalCondition@AIHarkonnen: + Condition: base-rebuilder2 + ExternalCondition@AIHarkonnenDefense: + Condition: defense-rebuilder2 + BaseBuilderBotModule@AtriedesMain: + RequiresCondition: base-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + repair_pad: 1 + wind_trap: 12 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@AtriedesMainDefense: + RequiresCondition: defense-rebuilder + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 12 + repair_pad: 1 + silo: 1 + medium_gun_turret: 2 + large_gun_turret: 4 + BuildingFractions: + wind_trap: 1 + barracks: 1 + repair_pad: 1 + refinery: 1 + light_factory: 1 + heavy_factory: 1 + medium_gun_turret: 1 + large_gun_turret: 1 + BaseBuilderBotModule@HarkonnenMain: + RequiresCondition: base-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: light_factory, heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret,large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + wind_trap: 9 + silo: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + light_factory: 1 + heavy_factory: 1 + BaseBuilderBotModule@HarkonnenMainDefense: + RequiresCondition: defense-rebuilder2 + BuildingQueues: Building, Upgrade + MinimumExcessPower: 0 + MaximumExcessPower: 250 + MaxBaseRadius: 40 + ConstructionYardTypes: construction_yard + RefineryTypes: refinery + PowerTypes: wind_trap + ProductionTypes: heavy_factory, barracks + SiloTypes: silo + DefenseTypes: medium_gun_turret, large_gun_turret + SellRefineryInterval: -1 + BuildingLimits: + barracks: 1 + refinery: 1 + outpost: 1 + heavy_factory: 1 + wind_trap: 9 + silo: 1 + medium_gun_turret: 4 + large_gun_turret: 1 + BuildingFractions: + wind_trap: 1 + barracks: 1 + refinery: 1 + outpost: 1 + heavy_factory: 1 + medium_gun_turret: 4 + large_gun_turret: 1 World: LuaScript: diff --git a/mods/d2k/rules/ai.yaml b/mods/d2k/rules/ai.yaml index 4aca72e29a..30b2f6eca4 100644 --- a/mods/d2k/rules/ai.yaml +++ b/mods/d2k/rules/ai.yaml @@ -17,7 +17,7 @@ Player: GrantConditionOnBotOwner@gladius: Condition: enable-gladius-ai Bots: gladius - ProvidesPrerequisite@autoConreteForBots: + ProvidesPrerequisite@autoConcreteForBots: Prerequisite: global-auto-concrete RequiresCondition: enable-omnius-ai || enable-vidious-ai || enable-gladius-ai SupportPowerBotModule: diff --git a/mods/d2k/rules/campaign-rules.yaml b/mods/d2k/rules/campaign-rules.yaml index 24531d8435..929804d7d4 100644 --- a/mods/d2k/rules/campaign-rules.yaml +++ b/mods/d2k/rules/campaign-rules.yaml @@ -19,9 +19,16 @@ Player: Visible: False DeveloperMode: CheckboxVisible: False + CheckboxEnabled: true ModularBot@CampaignAI: Name: bot-campaign-ai.name Type: campaign + GrantConditionOnBotOwner@campaign: + Condition: campaign-ai + Bots: campaign + ProvidesPrerequisite@autoConcreteForBots: + Prerequisite: global-auto-concrete + RequiresCondition: campaign-ai World: -CrateSpawner: @@ -65,5 +72,441 @@ sandworm: Sandworm: ChanceToDisappear: 40 +sandworm.script: + Inherits: sandworm + Sandworm: + ChanceToDisappear: 50 + RenderSprites: + Image: sandworm + ScriptTags: + WithMakeAnimation: + Sequence: mouth + Condition: eating + Mobile: + PauseOnCondition: eating + AmbientSound: + RequiresCondition: !attacking && !eating + AmbientSound@make: + RequiresCondition: eating + SoundFiles: WORM.WAV + harvester: -MustBeDestroyed: + +silo: + -MustBeDestroyed: + +saboteur: + ExternalCondition@script: + Condition: triggered + +crate: + Crate: + Duration: 0 + ScriptTriggers: + -HideMapCrateAction: + -RevealMapCrateAction: + +crate_explosion: + Crate: + ScriptTriggers: + QuantizeFacingsFromSequence: + RenderSprites: + Palette: effect + WithCrateBody: + MapEditorData: + Categories: Debris + Interactable: + Bounds: 640, 640 + ExplodeCrateAction: + Weapon: CrateExplosion + SelectionShares: 100 + +crate_reveal: + Crate: + ScriptTriggers: + QuantizeFacingsFromSequence: + RenderSprites: + Palette: effect + WithCrateBody: + MapEditorData: + Categories: Debris + Interactable: + Bounds: 640, 640 + RevealMapCrateAction: + SelectionShares: 100 + Sequence: reveal-map + Sound: SONIC3.WAV + +crate_script: + ScriptTriggers: + Crate: + QuantizeFacingsFromSequence: + RenderSprites: + Palette: effect + WithCrateBody: + MapEditorData: + Categories: Debris + Interactable: + Bounds: 640, 640 + GrantExternalConditionCrateAction: + SelectionShares: 100 + Sequence: reveal-map + Sound: MULTI1.WAV + Condition: activator + Duration: 500 + +vision_provider: + Interactable: + Immobile: + OccupiesSpace: false + RenderSpritesEditorOnly: + WithSpriteBody: + BodyOrientation: + QuantizedFacings: 1 + MapEditorData: + Categories: System + ExternalCondition: + Condition: activate + RevealsShroud: + Range: 10c0 + Type: CenterPosition + RequiresCondition: activate + +debris.small: + Inherits: ^SpriteActor + Immobile: + OccupiesSpace: false + WithSpriteBody: + BodyOrientation: + QuantizedFacings: 1 + ThrowsParticle@debris01: + Anim: tankdebris01 + ThrowsParticle@debris02: + Anim: tankdebris02 + Offset: 256,128,0 + ThrowsParticle@debris03: + Anim: tankdebris03 + ThrowsParticle@debris04: + Anim: tankdebris04 + +debris.large: + Inherits: debris.small + RenderSprites: + Image: debris.small + ThrowsParticle@debris01: + MaxThrowRange: 1c0 + ThrowsParticle@debris02: + Offset: 256,128,0 + MaxThrowRange: 1c0 + ThrowsParticle@debris03: + MaxThrowRange: 1c0 + ThrowsParticle@debris04: + MaxThrowRange: 1c0 + ThrowsParticle@debris05: + Anim: tankdebris01 + Offset: -512,480,0 + MaxThrowRange: 1c0 + ThrowsParticle@debris06: + Anim: tankdebris02 + Offset: 236,128,250 + ThrowsParticle@debris07: + Anim: tankdebris03 + Offset: 756,312,512 + ThrowsParticle@debris08: + Anim: tankdebris04 + Offset: 0,512,326 + MapEditorData: + Categories: Debris + +^Debris: + Inherits@1: ^SpriteActor + Interactable: + Health: + HP: 10000 + Armor: + Type: light + FireWarheadsOnDeath: + Weapon: UnitExplodeSmall + EmptyWeapon: UnitExplodeSmall + ScriptTriggers: + WithFacingSpriteBody: + HitShape: + Type: Circle + Radius: 1024 + MapEditorData: + Categories: Debris + Husk: + Targetable: + TargetTypes: Ground, Vehicle, Husk + RequiresForceFire: true + +carryall.debris: + Inherits@1: ^Debris + Interactable: + Bounds: 1024, 1024, 0, 0 + WithIdleOverlay@1: + Sequence: 1 + WithIdleOverlay@2: + Sequence: 2 + WithIdleOverlay@3: + Sequence: 3 + Husk: + Locomotor: tank + FloatingSpriteEmitter@SMOKE: + Image: smoke3 + Lifetime: 10, 20 + Speed: 7 + Gravity: 50 + SpawnFrequency: 2, 10 + RandomFacing: true + RandomRate: 4 + Offset: 0, 0, 200 + TurnRate: 3 + Duration: -1 + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + SpawnActorOnDeath@debris1: + Actor: debris.large + OwnerType: InternalName + +frigate.debris: + Inherits@1: ^Debris + Inherits@2: ^ExistsInWorld + WithIdleOverlay@1: + Sequence: 1 + WithIdleOverlay@2: + Sequence: 2 + WithIdleOverlay@3: + Sequence: 3 + Interactable: + Bounds: 2048, 2048 + -WithFacingSpriteBody: + -Husk: + WithSpriteBody: + CombatDebugOverlay: + Targetable: + TargetTypes: Ground, C4, Structure + RequiresForceFire: false + Building: + Footprint: _X XX + Dimensions: 2,2 + LocalCenterOffset: 0,0,0 + HitShape: + UseTargetableCellsOffsets: true + Type: Rectangle + TopLeft: -1024, -1024 + BottomRight: 1024, 1024 + FloatingSpriteEmitter@SMOKE: + Image: smoke3 + Lifetime: 10, 20 + Speed: 7 + Gravity: 50 + Offset: -128,512,200 + SpawnFrequency: 2, 10 + RandomFacing: true + RandomRate: 4 + TurnRate: 3 + Duration: -1 + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + Offset: 0,1 + SpawnActorOnDeath@1: + Actor: crate + OwnerType: InternalName + Offset: 1,1 + SpawnActorOnDeath@debris: + Actor: debris.small + OwnerType: InternalName + Offset: 0,1 + SpawnActorOnDeath@debris1: + Actor: debris.large + OwnerType: InternalName + Offset: 1,1 + +harvester.debris: + Inherits@1: ^Debris + Interactable: + Transforms: + IntoActor: sandworm.script + FloatingSpriteEmitter@SMOKE: + Image: smoke3 + Lifetime: 10, 20 + Speed: 7 + Gravity: 50 + SpawnFrequency: 2, 10 + RandomFacing: true + RandomRate: 4 + Offset: 0, 0, 200 + TurnRate: 3 + Duration: -1 + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + SpawnActorOnDeath@1: + Actor: debris.small + OwnerType: InternalName + +combat_tank_a.debris: + Inherits: combat_tank_a.husk + RenderSprites: + Image: combat_tank_a.husk + ThrowsParticle@turret: + Anim: turret + TurnSpeed: 150 + MaxThrowRange: 512 + WithColoredOverlay@husk: + Color: 00000040 + -FloatingSpriteEmitter@SMOKE: + -WithIdleOverlay@Burns: + -GrantRandomCondition: + -ChangesHealth: + MapEditorData: + Categories: Debris + Transforms: + IntoActor: sandworm.script + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + SpawnActorOnDeath@1: + Actor: debris.small + OwnerType: InternalName + +combat_tank_h.debris: + Inherits: combat_tank_h.husk + RenderSprites: + Image: combat_tank_h.husk + ThrowsParticle@turret: + Anim: turret + TurnSpeed: 150 + MaxThrowRange: 512 + WithColoredOverlay@husk: + Color: 00000040 + Transforms: + IntoActor: sandworm.script + MapEditorData: + Categories: Debris + -FloatingSpriteEmitter@SMOKE: + -WithIdleOverlay@Burns: + -GrantRandomCondition: + -ChangesHealth: + -HiddenUnderFog: + FloatingSpriteEmitter@SMOKE: + Image: smoke3 + Lifetime: 10, 20 + Speed: 7 + Gravity: 50 + SpawnFrequency: 20, 50 + RandomFacing: true + RandomRate: 7 + Offset: 0, 0, 200 + TurnRate: 3 + Duration: -1 + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + SpawnActorOnDeath@1: + Actor: debris.small + OwnerType: InternalName + +combat_tank_o.debris: + Inherits: combat_tank_o.husk + RenderSprites: + Image: combat_tank_o.husk + ThrowsParticle@turret: + Anim: turret + TurnSpeed: 150 + MaxThrowRange: 512 + -WithColoredOverlay@husk: + Transforms: + IntoActor: sandworm.script + MapEditorData: + Categories: Debris + -FloatingSpriteEmitter@SMOKE: + -WithIdleOverlay@Burns: + -GrantRandomCondition: + -ChangesHealth: + -HiddenUnderFog: + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + SpawnActorOnDeath@1: + Actor: debris.small + OwnerType: InternalName + FloatingSpriteEmitter@SMOKE: + Image: smoke3 + Lifetime: 10, 20 + Speed: 7 + Gravity: 50 + SpawnFrequency: 20, 50 + RandomFacing: true + RandomRate: 7 + Offset: 0, 0, 200 + TurnRate: 3 + Duration: -1 + +siege_tank.debris: + Inherits: siege_tank.husk + RenderSprites: + Image: siege_tank.husk + -FloatingSpriteEmitter@SMOKE: + -WithIdleOverlay@Burns: + -GrantRandomCondition: + -ChangesHealth: + -HiddenUnderFog: + -WithColoredOverlay@husk: + MapEditorData: + Categories: Debris + Transforms: + IntoActor: sandworm.script + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + SpawnActorOnDeath@1: + Actor: debris.small + OwnerType: InternalName + FloatingSpriteEmitter@SMOKE: + Image: smoke3 + Lifetime: 10, 20 + Speed: 7 + Gravity: 50 + SpawnFrequency: 20, 50 + RandomFacing: true + RandomRate: 7 + Offset: 0, 0, 200 + TurnRate: 3 + Duration: -1 + +missile_tank.debris: + Inherits: missile_tank.husk + RenderSprites: + Image: missile_tank.husk + -FloatingSpriteEmitter@SMOKE: + -WithIdleOverlay@Burns: + -GrantRandomCondition: + -ChangesHealth: + -HiddenUnderFog: + -WithColoredOverlay@husk: + MapEditorData: + Categories: Debris + Transforms: + IntoActor: sandworm.script + SpawnActorOnDeath: + Actor: crate + OwnerType: InternalName + SpawnActorOnDeath@1: + Actor: debris.small + OwnerType: InternalName + FloatingSpriteEmitter@SMOKE: + Image: smoke3 + Lifetime: 10, 20 + Speed: 7 + Gravity: 50 + SpawnFrequency: 20, 50 + RandomFacing: true + RandomRate: 7 + Offset: 0, 0, 200 + TurnRate: 3 + Duration: -1 diff --git a/mods/d2k/scripts/campaign.lua b/mods/d2k/scripts/campaign.lua index 02accb8b9e..7897664b24 100644 --- a/mods/d2k/scripts/campaign.lua +++ b/mods/d2k/scripts/campaign.lua @@ -9,6 +9,34 @@ Difficulty = Map.LobbyOptionOrDefault("difficulty", "normal") +DifficultyModifier = +{ + easy = 1, + normal = 0.8, + hard = 0.7 +} + +CrushChance = +{ + easy = 20, + normal = 40, + hard = 60 +} + +CrusherTypes = { "combat_tank_a", "combat_tank_a.starport", "combat_tank_h", "combat_tank_h.starport, combat_tank_o, combat_tank_o.starport"} + +ActivateCrushLogic = function () + Trigger.OnAnyProduction( function(producer, produced) + if not producer.Owner.IsBot then return end + if Utils.Any(CrusherTypes, function(crusherType) + return crusherType == produced.Type + end) + then + AICrushLogic(produced, producer.Owner) + end + end) +end + --- Prepare basic messages for a player's win, loss, or objective updates. ---@param player player InitObjectives = function(player) @@ -108,14 +136,59 @@ end ---@type table IdlingUnits = { } ---- Is a bot currently using idle units to attack or defend? +--- Is a bot currently using idle units to attack? ---@type table Attacking = { } +--- return true if actor is already defended +---@type table +Defending = { } + +--- Collection of cells around player base +--- @type table +DefencePerimeter = {} + +--- Collection of patrol point where player should patrol +---@type table +PatrolPoints = {} + +--- Time in ticks for next attack. AI cant attack until AttackDelay < GameTime +--- Serve also as initial attack delay. +--- @type table +AttackDelay = {} + +--- Minimum delays between attacks +--- @type table +TimeBetweenAttacks = {} + +--- How much harvesters AI should maintain +--- @type table +HarvesterCount = {} + +--- List of all unit combat types. +AllAttackUnitTypes = { "light_inf", "trooper", "trike", "quad", "siege_tank", "missile_tank", "sonic_tank", "devastator", "raider", "stealth_raider", "deviator", "combat_tank_a", "combat_tank_h", "combat_tank_o" } + +--- When true ProductionDelays is 1 and HoldProduction is always false +---@type table +EmergencyBuildRate = {} + +--- Activates when EmergencyBuildRate is True. +--- Override this function for mission specific behaviour +---@param player player +---@param target cpos -- position where emergency event take place +EmergencyBehaviour = function(player, target) + HoldProduction[player] = false + if Difficulty == "hard" then + player.Cash = player.Cash + 2000 + end +end + --- Is a bot's production routine on hold? ---@type table HoldProduction = { } +IgnoreDamageFromTypes = {"spicebloom", "SpiceExplosion", "Debris", "Debris2", "Debris3", "Debris4"} + --- Was a bot's last harvester eaten by a sandworm? ---@type table LastHarvesterEaten = { } @@ -127,6 +200,7 @@ LastHarvesterEaten = { } SetupAttackGroup = function(owner, size) local units = { } + RemoveDeadActors(IdlingUnits[owner]) for i = 0, size, 1 do if #IdlingUnits[owner] == 0 then return units @@ -134,7 +208,7 @@ SetupAttackGroup = function(owner, size) local number = Utils.RandomInteger(1, #IdlingUnits[owner] + 1) - if IdlingUnits[owner][number] and not IdlingUnits[owner][number].IsDead then + if IdlingUnits[owner][number] then units[i] = IdlingUnits[owner][number] table.remove(IdlingUnits[owner], number) end @@ -147,14 +221,19 @@ end ---@param owner player ---@param size integer SendAttack = function(owner, size) - if Attacking[owner] then - return - end + if Attacking[owner] then return end + Attacking[owner] = true HoldProduction[owner] = true local units = SetupAttackGroup(owner, size) - Utils.Do(units, IdleHunt) + Utils.Do(units, Trigger.ClearAll) + Trigger.AfterDelay(1, function() + Utils.Do(units, function(u) + u.Stop() + IdleHunt(u) + end) + end) Trigger.OnAllRemovedFromWorld(units, function() Attacking[owner] = false @@ -162,41 +241,82 @@ SendAttack = function(owner, size) end) end +--- Order patrol routine along side predefined patrol points. When Patrol routine ends, squad return into IdlingUnits pool. +--- If no patrol points set - ignore request +---@param owner player +---@param size integer +SendPatrol = function(owner, size) + if PatrolPoints[owner] == nil then return end + + local units = SetupAttackGroup(owner, size) + local cells = Utils.Shuffle(PatrolPoints[owner]) + + Utils.Do(units, function(unit) + Trigger.ClearAll(unit) + Trigger.AfterDelay(1, function() + if unit.IsDead then return end + unit.Patrol(cells, false, 1000) + unit.CallFunc(function() + table.insert(IdlingUnits[owner], unit) + SelectRoutine(owner, unit) + end) + Trigger.OnDamaged(unit, function(self, attacker) + if Defending[owner][self] or owner.IsAlliedWith(attacker.Owner) then + return + end + if Utils.Any(IgnoreDamageFromTypes, function(a) return a == attacker.Type end) then + return + end + Defending[owner][unit] = true + Utils.Do(units, function(u) + if not u.IsDead then + u.Stop() + u.AttackMove(self.Location) + u.CallFunc(function() + FindTargetsInArea(owner, self) + end) + end + end) + end) + end) + end) +end + --- Prepare a unit to call for help if attacked. ---@param unit actor ---@param defendingPlayer player ---@param defenderCount integer DefendActor = function(unit, defendingPlayer, defenderCount) Trigger.OnDamaged(unit, function(self, attacker) - if unit.Owner ~= defendingPlayer then + if unit.Owner ~= defendingPlayer or attacker.Owner == defendingPlayer then return end - -- Don't try to attack spiceblooms - if attacker and attacker.Type == "spicebloom" then + if Defending[defendingPlayer][unit] then return end - if Attacking[defendingPlayer] then - return - end - Attacking[defendingPlayer] = true - - local Guards = SetupAttackGroup(defendingPlayer, defenderCount) - - if #Guards <= 0 then - Attacking[defendingPlayer] = false + -- Don't try to attack spiceblooms, debris, etc + if Utils.Any(IgnoreDamageFromTypes, + function(a) return a == attacker.Type end) then return end - Utils.Do(Guards, function(unit) - if not self.IsDead then - unit.AttackMove(self.Location) - end - IdleHunt(unit) + Defending[defendingPlayer][unit] = true + Trigger.AfterDelay(1000, function() + if unit.IsDead then return end + Defending[defendingPlayer][unit] = false end) - Trigger.OnAllRemovedFromWorld(Guards, function() Attacking[defendingPlayer] = false end) + -- if all units are busy or dead, activate emergency mode + if #IdlingUnits[defendingPlayer] == 0 then + if EmergencyBuildRate[defendingPlayer] then return end + EmergencyBuildRate[defendingPlayer] = true + EmergencyBehaviour(defendingPlayer, unit.Location) + Trigger.AfterDelay(1500, function() EmergencyBuildRate[defendingPlayer] = false end) + end + + CheckArea(defendingPlayer, unit.Location) end) end @@ -248,32 +368,222 @@ DefendAndRepairBase = function(owner, baseBuildings, modifier, defenderCount) DefendActor(actor, owner, defenderCount) RepairBuilding(owner, actor, modifier) + if Difficulty == "hard" then + Trigger.OnKilled(actor, function(killer) + if EmergencyBuildRate[owner] then return end + EmergencyBuildRate[owner] = true + EmergencyBehaviour(owner, killer.Location) + Trigger.AfterDelay(1500, function() EmergencyBuildRate[owner] = false end) + end) + end end) end --- Schedule production and attacks for a factory or other unit producer. ---@param player player Owner of the factory. ---@param factory actor The factory itself. ----@param delay fun():integer Function that returns a delay until production repeats. +---@param delay fun(player):integer Function that returns a delay until production repeats. ---@param toBuild fun():string[] Function that returns a list of unit types to be produced. ---@param attackSize integer Number of units that will form the next attack wave. ---@param attackThresholdSize integer Number of idle units that will trigger an attack wave. ProduceUnits = function(player, factory, delay, toBuild, attackSize, attackThresholdSize) - if factory.IsDead or factory.Owner ~= player then - return - end + if factory.IsDead or factory.Owner ~= player then return end if HoldProduction[player] then Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceUnits(player, factory, delay, toBuild, attackSize, attackThresholdSize) end) return end + --- Check harvester count + if factory.Type == "heavy_factory" and #player.GetActorsByType("harvester") < HarvesterCount[player] then + player.Build({"harvester"}, function() + Trigger.AfterDelay(delay(player), function() ProduceUnits(player, factory, delay, toBuild, attackSize, attackThresholdSize) end) + end) + return + end + player.Build(toBuild(), function(unit) IdlingUnits[player][#IdlingUnits[player] + 1] = unit[1] - Trigger.AfterDelay(delay(), function() ProduceUnits(player, factory, delay, toBuild, attackSize, attackThresholdSize) end) + SelectRoutine(player, unit[1]) if #IdlingUnits[player] >= attackThresholdSize then - SendAttack(player, attackSize) + if DateTime.GameTime < AttackDelay[player] then + SendPatrol(player, attackSize / 2) + HoldProduction[player] = true + Trigger.AfterDelay((AttackDelay[player] - DateTime.GameTime) / 2, function() + HoldProduction[player] = false + end) + else + AttackDelay[player] = DateTime.GameTime + TimeBetweenAttacks[player] + SendAttack(player, attackSize) + end + end + + Trigger.AfterDelay(delay(player), function() ProduceUnits(player, factory, delay, toBuild, attackSize, attackThresholdSize) end) + end) +end + +--- Periodically checks for nearby infantry units to crush. +---@param unit actor +---@param bot player +function AICrushLogic(unit, bot) + if unit.IsDead then return end + + if Utils.RandomInteger(1,101) >= CrushChance[Difficulty] then + Trigger.AfterDelay(200, function () + AICrushLogic(unit, bot) + end) + return + end + + local targets = Map.ActorsInCircle(unit.CenterPosition, WDist.FromCells(5), function (a) + return a.Owner.IsAlliedWith(bot) == false and + a.Type == "light_inf" or + a.Type == "trooper" or + a.Type == "engineer" and + Map.TerrainType(a.Location) ~= "Rough" + end) + + if targets[1] ~= nil then + unit.Stop() + unit.Move(Utils.Random(targets).Location) + Trigger.AfterDelay(55, function () + AICrushLogic(unit, bot) + end) + unit.Hunt() + else + Trigger.AfterDelay(200, function () + AICrushLogic(unit, bot) + end) + end +end + +function IdleHuntOnBaseDestroyed(player, base) + local checkedBase = Utils.Where(base, function(building) return not building.IsDead and building.Owner == player end) + Trigger.OnAllKilledOrCaptured(checkedBase , function() + if Utils.Any(base, function(building) return not building.IsDead and building.Owner == player end) then + IdleHuntOnBaseDestroyed(player, base) + else + Utils.Do(player.GetGroundAttackers(), function(a) + a.Stop() + IdleHunt(a) + end) end end) end + +--- Take number of units from IdlingUnits pool and check target area for enemy activity +--- When nothing found return to base and IdlingUnits pool/ +--- @param owner player +--- @param targetArea cpos +--- @param size? integer (optional). When nil random integer is selected +CheckArea = function(owner, targetArea, size) + if #IdlingUnits[owner] == 0 then return end + + size = size or Utils.RandomInteger(1 , #IdlingUnits[owner]) + local squad = SetupAttackGroup(owner, size) + Utils.Do(squad, function(unit) + if unit.IsDead then return end + Trigger.ClearAll(unit) + unit.Stop() + unit.AttackMove(targetArea) + unit.CallFunc(function() + Trigger.AfterDelay(1, function() + FindTargetsInArea(owner, unit) + end) + end) + end) +end + +FindTargetsInArea = function(owner, unit) + if unit.IsDead then return end + + Trigger.OnIdle(unit, function () + local enemies = Map.ActorsInCircle(unit.CenterPosition, WDist.FromCells(8), function(a) + return a.IsInWorld + and not a.IsDead + and not a.Owner.IsAlliedWith(unit.Owner) + and a.Owner.InternalName ~= "Neutral" + and a.Owner.InternalName ~= "Creeps" + end) + + if #enemies > 0 then + unit.Hunt() + unit.Wait(10) + else + unit.Wait(Utils.RandomInteger(200 , 500)) + if DefencePerimeter[owner] ~= nil then + unit.AttackMove(Utils.Random(DefencePerimeter[owner])) + end + unit.CallFunc(function() + if not unit.IsDead then + table.insert(IdlingUnits[owner], unit) + SelectRoutine(owner, unit) + end + end) + end + end) +end + +SelectRoutine = function(owner, unit) + Trigger.ClearAll(unit) + Trigger.AfterDelay(1, function() + if unit.IsDead then return end + if DefencePerimeter[owner] == nil then return end + + -- ensure unit return into defence perimeter before apply new triggers + unit.Move(Utils.Random(DefencePerimeter[owner])) + unit.CallFunc(function() + PatrolPerimeter(owner, unit) + Trigger.OnKilled(unit, function(killer) + RemoveActor(IdlingUnits[owner], unit) + CheckArea(owner, killer.Location) + end) + end) + end) +end + +PatrolPerimeter = function(owner, unit) + if unit.IsDead then return end + + local targetCell = Utils.Random(DefencePerimeter[owner]) + unit.AttackMove(targetCell, 2) + unit.CallFunc(function () + Trigger.AfterDelay(Utils.RandomInteger(300, 500), function() + if unit.IsDead then return end + PatrolPerimeter(owner, unit) + end) + end) +end + +GetCellsInRectangle = function (topLeft, bottomRight) + local cells = {} + local index = 1 + for x = topLeft.X, bottomRight.X, 1 do + for y = topLeft.Y, bottomRight.Y, 1 do + local cell = CPos.New(x, y) + if Map.TerrainType(cell) ~= "Cliff" and Map.TerrainType(cell) ~= "Rough" then + cells[index] = cell + index = index + 1 + end + end + end + return cells +end + +RemoveDeadActors = function (actors) + for i = #actors, 1, -1 do + if actors[i].IsDead or not actors[i].IsInWorld then + table.remove(actors, i) + end + end +end + +RemoveActor = function (actors, actor) + for i = #actors, 1, -1 do + if actors[i] == actor then + table.remove(actors, i) + return + end + end +end diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index b473b8640a..d6d50d5341 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -516,6 +516,34 @@ crate: ZOffset: -511 Offset: -16,-16 +crate_explosion: + idle: + Filename: DATA.R16 + Start: 102 + ZOffset: -511 + Offset: -16,-16 + +crate_reveal: + idle: + Filename: DATA.R16 + Start: 105 + ZOffset: -511 + Offset: -16,-16 + +crate_script: + idle: + Filename: DATA.R16 + Start: 103 + ZOffset: -511 + Offset: -16,-16 + +vision_provider: + idle: + Filename: DATA.R16 + Start: 0 + ZOffset: -511 + Offset: -16,-16 + spicebloom: grow0: Filename: DATA.R16 @@ -691,3 +719,71 @@ null: idle: Filename: DATA.R16 Start: 3552 + +carryall.debris: + idle: + Filename: BLOXBASE.R16 + Start: 349 + Offset: -32,-32 + ZOffset: -3024 + 1: + Filename: BLOXBASE.R16 + Start: 350 + Offset: 0,-32 + ZOffset: -3024 + 2: + Filename: BLOXBASE.R16 + Start: 369 + Offset: -32,0 + ZOffset: -3024 + 3: + Filename: BLOXBASE.R16 + Start: 370 + Offset: 0,0 + ZOffset: -3024 + +frigate.debris: + idle: + Filename: BLOXBAT.R16 + Start: 652 + Offset: -32,-32 + ZOffset: -3024 + 1: + Filename: BLOXBAT.R16 + Start: 653 + Offset: 0,-32 + ZOffset: -3024 + 2: + Filename: BLOXBAT.R16 + Start: 672 + Offset: -32,0 + ZOffset: -3024 + 3: + Filename: BLOXBAT.R16 + Start: 673 + Offset: 0,0 + ZOffset: -3024 + +harvester.debris: + idle: + Filename: BLOXBASE.R16 + Start: 389 + Offset: -16,-16 + ZOffset: -3024 + +debris.small: + idle: + Filename: DATA.R16 + Start: 38 + tankdebris01: + Filename: missiletankhusk/missiletankhuskdebris-0000.png + ZOffset: -2048 + tankdebris02: + Filename: missiletankhusk/missiletankhuskdebris-0001.png + ZOffset: -2048 + tankdebris03: + Filename: missiletankhusk/missiletankhuskdebris-0002.png + ZOffset: -2048 + tankdebris04: + Filename: missiletankhusk/missiletankhuskdebris-0003.png + ZOffset: -2048