From 920e348622d9b1a215985ced9f33d083cf20961f Mon Sep 17 00:00:00 2001 From: Mustafa Alperen Seki Date: Thu, 26 Mar 2020 19:57:52 +0300 Subject: [PATCH] AI Changes and Fixes. --- OpenRA.Mods.Common/AIUtils.cs | 5 + .../BotModuleLogic/BaseBuilderQueueManager.cs | 30 +++++- .../BotModules/SquadManagerBotModule.cs | 93 ++++++++++++++++++- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/OpenRA.Mods.Common/AIUtils.cs b/OpenRA.Mods.Common/AIUtils.cs index 4b285b8becae..a5770a7f2765 100644 --- a/OpenRA.Mods.Common/AIUtils.cs +++ b/OpenRA.Mods.Common/AIUtils.cs @@ -51,6 +51,11 @@ public static bool IsAreaAvailable(World world, Player player, Map map, int r .Any(availableCells => availableCells > 0); } + public static bool IsOwnedByEnemy(Actor a, Player player) + { + return player.Stances[a.Owner] == Stance.Enemy && a.Owner.InternalName.ToLowerInvariant().StartsWith("multi"); + } + public static IEnumerable FindQueues(Player player, string category) { return player.World.ActorsWithTrait() diff --git a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs index b49806ed9f82..e5cf200a333e 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs @@ -185,10 +185,10 @@ ActorInfo GetProducibleBuilding(HashSet actors, IEnumerable b if (!baseBuilder.Info.BuildingLimits.ContainsKey(actor.Name)) return true; - var producers = world.Actors.Where(a => a.Owner == player && a.TraitsImplementing() != null); + var producers = world.Actors.Where(a => a.Owner == player && a.TraitsImplementing().Any()); var productionQueues = producers.SelectMany(a => a.TraitsImplementing()); var activeProductionQueues = productionQueues.Where(pq => pq.AllQueued().Any()); - var queues = activeProductionQueues.Select(pq => pq.AllQueued().Where(q => q.Item == actor.Name)); + var queues = activeProductionQueues.Where(pq => pq.AllQueued().Where(q => q.Item == actor.Name).Any()); return playerBuildings.Count(a => a.Info.Name == actor.Name) + queues.Count() < baseBuilder.Info.BuildingLimits[actor.Name]; }); @@ -328,6 +328,10 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable world.WorldTick) continue; + // Can we build this structure? + if (!buildableThings.Any(b => b.Name == name)) + continue; + if (playerResources != null && playerResources.Cash <= baseBuilder.Info.ConstructionMinimumCash && !baseBuilder.Info.CashGeneratorTypes.Contains(name)) continue; @@ -345,10 +349,10 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable a.Owner == queue.Actor.Owner && a.TraitsImplementing() != null); + var producers = world.Actors.Where(a => a.Owner == queue.Actor.Owner && a.TraitsImplementing().Any()); var productionQueues = producers.SelectMany(a => a.TraitsImplementing()); var activeProductionQueues = productionQueues.Where(pq => pq.AllQueued().Any()); - var queues = activeProductionQueues.Select(pq => pq.AllQueued().Where(q => q.Item == name)); + var queues = activeProductionQueues.Where(pq => pq.AllQueued().Where(q => q.Item == name).Any()); var count = playerBuildings.Count(a => a.Info.Name == name) + (queues == null ? 0 : queues.Count()); if (count * 100 > frac.Value * playerBuildings.Length) @@ -365,14 +369,30 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable(world, player, world.Map, baseBuilder.Info.CheckForWaterRadius, baseBuilder.Info.WaterTerrainTypes))) continue; - // Will this put us into low power? if (!world.Map.Rules.Actors.ContainsKey(name)) { AIUtils.BotDebug("{0} tryed to build an actor named {1}, no such actor exists.", queue.Actor.Owner, name); continue; } + // Maybe we can't queue this because of InstantCashDrain logic? var actor = world.Map.Rules.Actors[name]; + if (playerResources != null) + { + var nonICDQueues = productionQueues.Where(pq => !pq.Info.InstantCashDrain); + if (!nonICDQueues.Any()) + { + var ICDQueues = productionQueues.Where(pq => pq.Info.InstantCashDrain); + if (ICDQueues.Any()) + { + var cost = ICDQueues.Min(q => q.GetProductionCost(actor)); + if (playerResources.Cash < cost) + continue; + } + } + } + + // Will this put us into low power? if (playerPower != null && (playerPower.ExcessPower < minimumExcessPower || !HasSufficientPowerForActor(actor))) { // Try building a power plant instead diff --git a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs index 6527d13efea2..78368e1bcce4 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs @@ -42,6 +42,12 @@ public class SquadManagerBotModuleInfo : ConditionalTraitInfo "Those units are not used by defensive squads.")] public readonly HashSet SiegeUnitTypes = new HashSet(); + [Desc("These units are built and deployed in a safe position in the base. Needs GrantConditionOnDeploy on the unit.")] + public readonly HashSet FragileDeployerTypes = new HashSet(); + + [Desc("These units are randomly sent around after their production.")] + public readonly HashSet DozerTypes = new HashSet(); + [Desc("Minimum number of units AI must have before attacking.")] public readonly int SquadSize = 8; @@ -66,6 +72,15 @@ public class SquadManagerBotModuleInfo : ConditionalTraitInfo [Desc("Radius in cells around the base that should be scanned for units to be protected.")] public readonly int ProtectUnitScanRadius = 15; + [Desc("Minimum radius in cells around base center to send dozer after building it.")] + public readonly int MinDozerSendingRadius = 4; + + [Desc("Maximum radius in cells around base center to send dozer after building it.")] + public readonly int MaxDozerSendingRadius = 16; + + [Desc("Same as MinBaseRadius but for fragile structures to push them further away.")] + public readonly int MinFragilePlacementRadius = 8; + [Desc("Maximum distance in cells from center of the base when checking for MCV deployment location.", "Only applies if RestrictMCVDeploymentFallbackToBase is enabled and there's at least one construction yard.")] public readonly int MaxBaseRadius = 20; @@ -101,6 +116,7 @@ public CPos GetRandomBaseCenter() readonly Predicate unitCannotBeOrdered; + public List Squads = new List(); IBotPositionsUpdated[] notifyPositionsUpdated; @@ -218,6 +234,57 @@ void AssignRolesToIdleUnits(IBot bot) } } + CPos FindPosFrontForUnit(CPos center, CVec front, int minRange, int maxRange, Actor actor) + { + var mobile = actor.TraitOrDefault(); + if (mobile == null) + return center; + + // zero vector case. we can't define front or rear. + if (front == CVec.Zero) + return FindPosForUnit(center, center, minRange, maxRange, actor); + + var cells = World.Map.FindTilesInAnnulus(center, minRange, maxRange).Shuffle(World.LocalRandom); + foreach (var cell in cells) + { + var v = cell - center; + if (!mobile.CanEnterCell(cell)) + continue; + + if (CVec.Dot(front, v) > 0) + return cell; + } + + // Front is so full of stuff that we can't move there. + return center; + } + + // Find the moveable cell that is closest to pos and centered around center + CPos FindPosForUnit(CPos center, CPos target, int minRange, int maxRange, Actor actor) + { + var mobile = actor.TraitOrDefault(); + if (mobile == null) + return center; + + var cells = World.Map.FindTilesInAnnulus(center, minRange, maxRange); + + // Sort by distance to target if we have one + if (center != target) + cells = cells.OrderBy(c => (c - target).LengthSquared); + else + cells = cells.Shuffle(World.LocalRandom); + + foreach (var cell in cells) + { + if (!mobile.CanEnterCell(cell)) + continue; + + return cell; + } + + return center; + } + void FindNewUnits(IBot bot) { var newUnits = World.ActorsHavingTrait() @@ -227,7 +294,31 @@ void FindNewUnits(IBot bot) foreach (var a in newUnits) { - unitsHangingAroundTheBase.Add(a); + var closestEnemy = World.ActorsHavingTrait().Where(actor => !actor.Disposed && AIUtils.IsOwnedByEnemy(actor, bot.Player)) + .ClosestTo(World.Map.CenterOfCell(GetRandomBaseCenter())); + var baseCenter = GetRandomBaseCenter(); + var mobile = a.TraitOrDefault(); + + CVec direction = CVec.Zero; + if (closestEnemy != null) + direction = baseCenter - closestEnemy.Location; + + if (Info.FragileDeployerTypes.Contains(a.Info.Name) && mobile != null) + { + bot.QueueOrder(new Order("Move", a, + Target.FromCell(World, FindPosFrontForUnit(baseCenter, direction, Info.MinFragilePlacementRadius, Info.MaxBaseRadius, a)), true)); + bot.QueueOrder(new Order("GrantConditionOnDeploy", a, true)); + } + else if (Info.DozerTypes.Contains(a.Info.Name) && mobile != null) + { + var dozerTargetPos = World.Map.FindTilesInAnnulus(baseCenter, Info.MinDozerSendingRadius, Info.MaxDozerSendingRadius) + .Where(c => mobile.CanEnterCell(c)).Random(World.LocalRandom); + + AIUtils.BotDebug("AI: {0} has chosen {1} to move its Dozer ({2})".F(a.Owner, dozerTargetPos, a)); + bot.QueueOrder(new Order("Move", a, Target.FromCell(World, dozerTargetPos), true)); + } + else + unitsHangingAroundTheBase.Add(a); if (a.Info.HasTraitInfo() && a.Info.HasTraitInfo() && a.Info.HasTraitInfo()) {