diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index 8862c59de3..f17d788b79 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -28,8 +28,11 @@ namespace OpenRA.Mods.Common.AI int waitTicks; Actor[] playerBuildings; + bool waterAvailable; + bool checkedWater; int failCount; int failRetryTicks; + int cachedBases; public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr) { @@ -52,6 +55,25 @@ namespace OpenRA.Mods.Common.AI if (--waitTicks > 0) return; + if (!checkedWater) + { + waterAvailable = ai.EnoughWaterToBuildNaval(); + checkedWater = true; + } + + if (!waterAvailable) + { + var currentBases = world.ActorsWithTrait() + .Where(a => a.Actor.Owner == player) + .Count(); + + if (currentBases > cachedBases) + { + cachedBases = currentBases; + checkedWater = false; + } + } + playerBuildings = world.ActorsWithTrait() .Where(a => a.Actor.Owner == player) .Select(a => a.Actor) @@ -181,7 +203,7 @@ namespace OpenRA.Mods.Common.AI } } - // Make sure that we can can spend as fast as we are earning + // Make sure that we can spend as fast as we are earning if (ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold) { var production = GetProducibleBuilding("Production", buildableThings); @@ -198,6 +220,24 @@ namespace OpenRA.Mods.Common.AI } } + // Only consider building this if there is enough water inside the base perimeter and there are close enough adjacent buildings + if (waterAvailable && ai.CloseEnoughToWater() + && ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold) + { + var navalproduction = GetProducibleBuilding("NavalProduction", buildableThings); + if (navalproduction != null && HasSufficientPowerForActor(navalproduction)) + { + HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (navalproduction)", queue.Actor.Owner, navalproduction.Name); + return navalproduction; + } + + if (power != null && navalproduction != null && !HasSufficientPowerForActor(navalproduction)) + { + HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); + return power; + } + } + // Create some head room for resource storage if we really need it if (playerResources.AlertSilo) { @@ -232,6 +272,14 @@ namespace OpenRA.Mods.Common.AI if (ai.Info.BuildingLimits.ContainsKey(name) && ai.Info.BuildingLimits[name] <= count) continue; + // If we're considering to build a naval structure, check whether there is enough water inside the base perimeter + // and any structure providing buildable area close enough to that water. + // TODO: Extend this check to cover any naval structure, not just production. + if (ai.Info.BuildingCommonNames.ContainsKey("NavalProduction") + && ai.Info.BuildingCommonNames["NavalProduction"].Contains(name) + && (!waterAvailable || !ai.CloseEnoughToWater())) + continue; + // Will this put us into low power? var actor = world.Map.Rules.Actors[name]; if (playerPower.ExcessPower < ai.Info.MinimumExcessPower || !HasSufficientPowerForActor(actor)) diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index 15225e8a1c..191db5831c 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -92,6 +92,11 @@ namespace OpenRA.Mods.Common.AI [Desc("Radius in cells around the center of the base to expand.")] public readonly int MaxBaseRadius = 20; + [Desc("Radius in cells around each building with ProvideBuildableArea", + "to check for a 3x3 area of water where naval structures can be built.", + "Should match maximum adjacency of naval structures.")] + public readonly int CheckForWaterRadius = 8; + [Desc("Production queues AI uses for producing units.")] public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" }; @@ -257,6 +262,58 @@ namespace OpenRA.Mods.Common.AI resourceTypeIndices.Set(World.TileSet.GetTerrainIndex(t.TerrainType), true); } + // TODO: Possibly give this a more generic name when terrain type is unhardcoded + public bool EnoughWaterToBuildNaval() + { + var baseBuildings = World.Actors.Where( + a => a.Owner == Player + && a.HasTrait() + && !a.HasTrait()); + + foreach (var b in baseBuildings) + { + // TODO: Unhardcode terrain type + var playerWorld = Player.World; + var countWaterCells = Map.FindTilesInCircle(b.Location, Info.MaxBaseRadius) + .Where(c => playerWorld.Map.Contains(c) + && playerWorld.Map.GetTerrainInfo(c).Type == "Water" + && Util.AdjacentCells(playerWorld, Target.FromCell(playerWorld, c)) + .All(a => playerWorld.Map.GetTerrainInfo(a).Type == "Water")) + .Count(); + + if (countWaterCells > 0) + return true; + } + + return false; + } + + // Check whether we have at least one building providing buildable area close enough to water to build naval structures + public bool CloseEnoughToWater() + { + var areaProviders = World.Actors.Where( + a => a.Owner == Player + && a.HasTrait() + && !a.HasTrait()); + + foreach (var a in areaProviders) + { + // TODO: Unhardcode terrain type + var playerWorld = Player.World; + var adjacentWater = Map.FindTilesInCircle(a.Location, Info.CheckForWaterRadius) + .Where(c => playerWorld.Map.Contains(c) + && playerWorld.Map.GetTerrainInfo(c).Type == "Water" + && Util.AdjacentCells(playerWorld, Target.FromCell(playerWorld, c)) + .All(b => playerWorld.Map.GetTerrainInfo(b).Type == "Water")) + .Count(); + + if (adjacentWater > 0) + return true; + } + + return false; + } + public void QueueOrder(Order order) { orders.Enqueue(order); diff --git a/mods/ra/rules/ai.yaml b/mods/ra/rules/ai.yaml index de49a66fd2..ffccd407cf 100644 --- a/mods/ra/rules/ai.yaml +++ b/mods/ra/rules/ai.yaml @@ -8,7 +8,8 @@ Player: Power: powr,apwr Barracks: barr,tent VehiclesFactory: weap - Production: barr,tent,weap,afld,hpad,spen,syrd + Production: barr,tent,weap,afld,hpad + NavalProduction: spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv @@ -118,7 +119,8 @@ Player: Power: powr,apwr Barracks: barr,tent VehiclesFactory: weap - Production: barr,tent,weap,afld,hpad,spen,syrd + Production: barr,tent,weap,afld,hpad + NavalProduction: spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv @@ -245,7 +247,8 @@ Player: Power: powr,apwr Barracks: barr,tent VehiclesFactory: weap - Production: barr,tent,weap,afld,hpad,spen,syrd + Production: barr,tent,weap,afld,hpad + NavalProduction: spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv @@ -371,7 +374,8 @@ Player: Power: powr,apwr Barracks: barr,tent VehiclesFactory: weap - Production: barr,tent,weap,afld,hpad,spen,syrd + Production: barr,tent,weap,afld,hpad + NavalProduction: spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv