From 6ff394991da0830a8b2bebf483cae450c71dd08b Mon Sep 17 00:00:00 2001 From: reaperrr Date: Tue, 21 Jul 2015 07:24:53 +0200 Subject: [PATCH 1/7] Fixed AI BaseBuilder low power check. This was extremely borked: The priority overrides for refinery, production and silo ignored power completely and never checked whether the structure might drive the AI into low power state; whereas the check for regular building checked whether the structure's power -exceeded- MinimumExcessPower (previously just 0). The catch is that power draw is represented by negative numbers, so the only buildings that would trigger this were - power plants. Now it checks whether the sum of building power draw (negative) and AI's current power level are lower than MinimumExcessPower, if not, build the structure, if yes, build a power plant instead. Additionally, this check is now performed for the early-game priority overrides as well. Last but not least the AI would not(!) give power plants priority if it was already low on power. This has been fixed as well. --- OpenRA.Mods.Common/AI/BaseBuilder.cs | 58 ++++++++++++++++++---------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index e70fa4656e..97edb93780 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -130,22 +130,27 @@ namespace OpenRA.Mods.Common.AI return available.RandomOrDefault(ai.Random); } + bool HasSufficientPowerForActor(ActorInfo actorInfo) + { + return (actorInfo.Traits.WithInterface().Where(i => i.UpgradeMinEnabledLevel < 1) + .Sum(p => p.Amount) + playerPower.ExcessPower) >= ai.Info.MinimumExcessPower; + } + ActorInfo ChooseBuildingToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); + // This gets used quite a bit, so let's cache it here + var power = GetProducibleBuilding("Power", buildableThings, + a => a.Traits.WithInterface().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount)); + // First priority is to get out of a low power situation if (playerPower.ExcessPower < ai.Info.MinimumExcessPower) { - var power = GetProducibleBuilding("Power", buildableThings, a => a.Traits.WithInterface().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount)); if (power != null && power.Traits.WithInterface().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount) > 0) { - // TODO: Handle the case when of when we actually do need a power plant because we don't have enough but are also suffering from a power outage - if (playerPower.PowerOutageRemainingTicks <= 0) - { - HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name); - return power; - } + HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name); + return power; } } @@ -153,33 +158,51 @@ namespace OpenRA.Mods.Common.AI if (!ai.HasAdequateProc() || !ai.HasMinimumProc()) { var refinery = GetProducibleBuilding("Refinery", buildableThings); - if (refinery != null) + if (refinery != null && HasSufficientPowerForActor(refinery)) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (refinery)", queue.Actor.Owner, refinery.Name); return refinery; } + + if (power != null && refinery != null && !HasSufficientPowerForActor(refinery)) + { + HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); + return power; + } } // Make sure that we can can spend as fast as we are earning if (ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold) { var production = GetProducibleBuilding("Production", buildableThings); - if (production != null) + if (production != null && HasSufficientPowerForActor(production)) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (production)", queue.Actor.Owner, production.Name); return production; } + + if (power != null && production != null && !HasSufficientPowerForActor(production)) + { + 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) { var silo = GetProducibleBuilding("Silo", buildableThings); - if (silo != null) + if (silo != null && HasSufficientPowerForActor(silo)) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (silo)", queue.Actor.Owner, silo.Name); return silo; } + + if (power != null && silo != null && !HasSufficientPowerForActor(silo)) + { + HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); + return power; + } } // Build everything else @@ -200,23 +223,18 @@ namespace OpenRA.Mods.Common.AI continue; // Will this put us into low power? - var actor = world.Map.Rules.Actors[frac.Key]; - var pis = actor.Traits.WithInterface().Where(i => i.UpgradeMinEnabledLevel < 1); - if (playerPower.ExcessPower < ai.Info.MinimumExcessPower || playerPower.ExcessPower < pis.Sum(pi => pi.Amount)) + var actor = world.Map.Rules.Actors[name]; + if (playerPower.ExcessPower < ai.Info.MinimumExcessPower || !HasSufficientPowerForActor(actor)) { // Try building a power plant instead - var power = GetProducibleBuilding("Power", - buildableThings, a => a.Traits.WithInterface().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount)); if (power != null && power.Traits.WithInterface().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount) > 0) { - // TODO: Handle the case when of when we actually do need a power plant because we don't have enough but are also suffering from a power outage if (playerPower.PowerOutageRemainingTicks > 0) - HackyAI.BotDebug("AI: {0} is suffering from a power outage; not going to build {1}", queue.Actor.Owner, power.Name); + HackyAI.BotDebug("{0} decided to build {1}: Priority override (is low power)", queue.Actor.Owner, power.Name); else - { HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); - return power; - } + + return power; } } From 0a5b812bb7e1dd7883549f69274133011c4a7dfa Mon Sep 17 00:00:00 2001 From: reaperrr Date: Tue, 21 Jul 2015 07:27:10 +0200 Subject: [PATCH 2/7] Check for consecutive failures to place a structure. After 3 consecutive failed attempts, wait one minute before resetting the counter and trying again. --- OpenRA.Mods.Common/AI/BaseBuilder.cs | 12 +++++++++++- OpenRA.Mods.Common/AI/HackyAI.cs | 11 +++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index 97edb93780..8862c59de3 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -28,6 +28,8 @@ namespace OpenRA.Mods.Common.AI int waitTicks; Actor[] playerBuildings; + int failCount; + int failRetryTicks; public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr) { @@ -37,10 +39,15 @@ namespace OpenRA.Mods.Common.AI playerPower = pm; playerResources = pr; this.category = category; + failRetryTicks = ai.Info.StructureProductionResumeDelay; } public void Tick() { + // If failed to place something N consecutive times, wait M ticks until resuming building production + if (failCount >= ai.Info.MaximumFailedPlacementAttempts && --failRetryTicks <= 0) + failCount = 0; + // Only update once per second or so if (--waitTicks > 0) return; @@ -63,7 +70,7 @@ namespace OpenRA.Mods.Common.AI var currentBuilding = queue.CurrentItem(); // Waiting to build something - if (currentBuilding == null) + if (currentBuilding == null && failCount < ai.Info.MaximumFailedPlacementAttempts) { var item = ChooseBuildingToBuild(queue); if (item == null) @@ -77,6 +84,7 @@ namespace OpenRA.Mods.Common.AI // Production is complete // Choose the placement logic // HACK: HACK HACK HACK + // TODO: Derive this from BuildingCommonNames instead var type = BuildingType.Building; if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains()) type = BuildingType.Defense; @@ -88,9 +96,11 @@ namespace OpenRA.Mods.Common.AI { HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); ai.QueueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); + failCount += failCount; } else { + failCount = 0; ai.QueueOrder(new Order("PlaceBuilding", player.PlayerActor, false) { TargetLocation = location.Value, diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index 82485786bc..15225e8a1c 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -49,6 +49,9 @@ namespace OpenRA.Mods.Common.AI [Desc("Minimum delay (in ticks) between creating squads.")] public readonly int MinimumAttackForceDelay = 0; + [Desc("Minimum portion of pending orders to issue each tick (e.g. 5 issues at least 1/5th of all pending orders). Excess orders remain queued for subsequent ticks.")] + public readonly int MinOrderQuotientPerTick = 5; + [Desc("Minimum excess power the AI should try to maintain.")] public readonly int MinimumExcessPower = 0; @@ -58,8 +61,12 @@ namespace OpenRA.Mods.Common.AI [Desc("How long to wait (in ticks) between structure production checks ticks when actively building things.")] public readonly int StructureProductionActiveDelay = 10; - [Desc("Minimum portion of pending orders to issue each tick (e.g. 5 issues at least 1/5th of all pending orders). Excess orders remain queued for subsequent ticks.")] - public readonly int MinOrderQuotientPerTick = 5; + [Desc("How long to wait (in ticks) until retrying to build structure after the last 3 consecutive attempts failed.")] + public readonly int StructureProductionResumeDelay = 1500; + + [Desc("After how many failed attempts to place a structure should AI give up and wait", + "for StructureProductionResumeDelay before retrying.")] + public readonly int MaximumFailedPlacementAttempts = 3; [Desc("Minimum range at which to build defensive structures near a combat hotspot.")] public readonly int MinimumDefenseRadius = 5; From 65a6489ef1cb1cd30c5bba2c7f2b9e1cd8ca643b Mon Sep 17 00:00:00 2001 From: reaperrr Date: Tue, 21 Jul 2015 07:30:14 +0200 Subject: [PATCH 3/7] Avoid consecutive attempts to build naval structures If not enough water space can be found inside the base perimeter, stop the AI from trying to build naval production buildings permanently until it deploys another construction yard. If enough water is available within the base perimeter, check whether any building that provides buildable area (for adjacency) is close enough to water, otherwise don't even start producing this naval structure. --- OpenRA.Mods.Common/AI/BaseBuilder.cs | 50 +++++++++++++++++++++++- OpenRA.Mods.Common/AI/HackyAI.cs | 57 ++++++++++++++++++++++++++++ mods/ra/rules/ai.yaml | 12 ++++-- 3 files changed, 114 insertions(+), 5 deletions(-) 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 From c7af9c180e1488e8ac6f94fdc176d48e0b1f2fad Mon Sep 17 00:00:00 2001 From: reaperrr Date: Sat, 25 Jul 2015 09:49:38 +0200 Subject: [PATCH 4/7] Further improved building placement failure check Since naval structures have their own safety measures now and therefore shouldn't count towards failCount under normal circumstances, we can now assume that 3 consecutive placement failures mean lack of space. Therefore, rather than unconditionally resetting the failCount and retry every N ticks, we now cache the number of buildings and construction yards at the time of the 3rd consecutive failure and if the number of buildings hasn't decreased and number of construction yards not increased, we assume there is still not enough space and reset the retry delay instead. --- OpenRA.Mods.Common/AI/BaseBuilder.cs | 31 +++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index f17d788b79..b74eddc1cb 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -33,6 +33,7 @@ namespace OpenRA.Mods.Common.AI int failCount; int failRetryTicks; int cachedBases; + int cachedBuildings; public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr) { @@ -49,7 +50,23 @@ namespace OpenRA.Mods.Common.AI { // If failed to place something N consecutive times, wait M ticks until resuming building production if (failCount >= ai.Info.MaximumFailedPlacementAttempts && --failRetryTicks <= 0) - failCount = 0; + { + var currentBuildings = world.ActorsWithTrait() + .Where(a => a.Actor.Owner == player) + .Count(); + + var baseProviders = world.ActorsWithTrait() + .Where(a => a.Actor.Owner == player) + .Count(); + + // Only bother resetting failCount if either a) the number of buildings has decreased since last failure M ticks ago, + // or b) number of BaseProviders (construction yard or similar) has increased since then. + // Otherwise reset failRetryTicks instead to wait again. + if (currentBuildings < cachedBuildings || baseProviders > cachedBases) + failCount = 0; + else + failRetryTicks = ai.Info.StructureProductionResumeDelay; + } // Only update once per second or so if (--waitTicks > 0) @@ -119,6 +136,18 @@ namespace OpenRA.Mods.Common.AI HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); ai.QueueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); failCount += failCount; + + // If we just reached the maximum fail count, cache the number of current structures + if (failCount == ai.Info.MaximumFailedPlacementAttempts) + { + cachedBuildings = world.ActorsWithTrait() + .Where(a => a.Actor.Owner == player) + .Count(); + + cachedBases = world.ActorsWithTrait() + .Where(a => a.Actor.Owner == player) + .Count(); + } } else { From 76f2145db24e9d97225cf190bc6765e8fa970d83 Mon Sep 17 00:00:00 2001 From: reaperrr Date: Sat, 25 Jul 2015 12:30:07 +0200 Subject: [PATCH 5/7] Added random factor to building production delay Early game AI usually follows the same build order (power plant first, then refinery), which also means they all start producing them at the same tick. This adds a random factor to the production delay, so not all AIs produce on the same tick. --- OpenRA.Mods.Common/AI/BaseBuilder.cs | 6 +++++- OpenRA.Mods.Common/AI/HackyAI.cs | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index b74eddc1cb..7b3d7e63bf 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -101,7 +101,11 @@ namespace OpenRA.Mods.Common.AI if (TickQueue(queue)) active = true; - waitTicks = active ? ai.Info.StructureProductionActiveDelay : ai.Info.StructureProductionInactiveDelay; + // Add a random factor so not every AI produces at the same tick early in the game. + // Minimum should not be negative as delays in HackyAI could be zero. + var randomFactor = world.SharedRandom.Next(0, ai.Info.StructureProductionRandomBonusDelay); + waitTicks = active ? ai.Info.StructureProductionActiveDelay + randomFactor + : ai.Info.StructureProductionInactiveDelay + randomFactor; } bool TickQueue(ProductionQueue queue) diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index 191db5831c..af5647f5ad 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -55,12 +55,15 @@ namespace OpenRA.Mods.Common.AI [Desc("Minimum excess power the AI should try to maintain.")] public readonly int MinimumExcessPower = 0; - [Desc("How long to wait (in ticks) between structure production checks when there is no active production.")] + [Desc("Minimum delay (in ticks) between structure production checks when there is no active production.")] public readonly int StructureProductionInactiveDelay = 125; - [Desc("How long to wait (in ticks) between structure production checks ticks when actively building things.")] + [Desc("Minimum delay (in ticks) between structure production checks when actively building things.")] public readonly int StructureProductionActiveDelay = 10; + [Desc("A random delay (in ticks) of up to this is added to production delays.")] + public readonly int StructureProductionRandomBonusDelay = 10; + [Desc("How long to wait (in ticks) until retrying to build structure after the last 3 consecutive attempts failed.")] public readonly int StructureProductionResumeDelay = 1500; From 91178d6f62bcee790a3285aa4d1c533a35f65ab4 Mon Sep 17 00:00:00 2001 From: reaperrr Date: Sun, 26 Jul 2015 13:45:20 +0200 Subject: [PATCH 6/7] Improved naval placement check Moved water checks before --waitTicks. Use Water enum instead of multiple booleans. Check for BaseProvider rather than BaseBuilding. Move expensive ClosEnoughToWater check to last position for naval production override. --- OpenRA.Mods.Common/AI/BaseBuilder.cs | 48 ++++++++++++++++++---------- OpenRA.Mods.Common/AI/HackyAI.cs | 27 +++++++++------- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index 7b3d7e63bf..acda9f2710 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -28,13 +28,21 @@ namespace OpenRA.Mods.Common.AI int waitTicks; Actor[] playerBuildings; - bool waterAvailable; - bool checkedWater; int failCount; int failRetryTicks; + int checkForBasesTicks; int cachedBases; int cachedBuildings; + enum Water + { + NotChecked, + EnoughWater, + NotEnoughWater + } + + Water waterState = Water.NotChecked; + public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr) { this.ai = ai; @@ -55,7 +63,7 @@ namespace OpenRA.Mods.Common.AI .Where(a => a.Actor.Owner == player) .Count(); - var baseProviders = world.ActorsWithTrait() + var baseProviders = world.ActorsWithTrait() .Where(a => a.Actor.Owner == player) .Count(); @@ -68,29 +76,34 @@ namespace OpenRA.Mods.Common.AI failRetryTicks = ai.Info.StructureProductionResumeDelay; } - // Only update once per second or so - if (--waitTicks > 0) - return; - - if (!checkedWater) + if (waterState == Water.NotChecked) { - waterAvailable = ai.EnoughWaterToBuildNaval(); - checkedWater = true; + if (ai.EnoughWaterToBuildNaval()) + waterState = Water.EnoughWater; + else + { + waterState = Water.NotEnoughWater; + checkForBasesTicks = ai.Info.CheckForNewBasesDelay; + } } - if (!waterAvailable) + if (waterState == Water.NotEnoughWater && --checkForBasesTicks <= 0) { - var currentBases = world.ActorsWithTrait() + var currentBases = world.ActorsWithTrait() .Where(a => a.Actor.Owner == player) .Count(); if (currentBases > cachedBases) { cachedBases = currentBases; - checkedWater = false; + waterState = Water.NotChecked; } } + // Only update once per second or so + if (--waitTicks > 0) + return; + playerBuildings = world.ActorsWithTrait() .Where(a => a.Actor.Owner == player) .Select(a => a.Actor) @@ -148,7 +161,7 @@ namespace OpenRA.Mods.Common.AI .Where(a => a.Actor.Owner == player) .Count(); - cachedBases = world.ActorsWithTrait() + cachedBases = world.ActorsWithTrait() .Where(a => a.Actor.Owner == player) .Count(); } @@ -254,8 +267,9 @@ 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) + if (waterState == Water.EnoughWater && ai.Info.NewProductionCashThreshold > 0 + && playerResources.Resources > ai.Info.NewProductionCashThreshold + && ai.CloseEnoughToWater()) { var navalproduction = GetProducibleBuilding("NavalProduction", buildableThings); if (navalproduction != null && HasSufficientPowerForActor(navalproduction)) @@ -310,7 +324,7 @@ namespace OpenRA.Mods.Common.AI // 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())) + && (waterState == Water.NotEnoughWater || !ai.CloseEnoughToWater())) continue; // Will this put us into low power? diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index af5647f5ad..39f31480b8 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -55,22 +55,27 @@ namespace OpenRA.Mods.Common.AI [Desc("Minimum excess power the AI should try to maintain.")] public readonly int MinimumExcessPower = 0; - [Desc("Minimum delay (in ticks) between structure production checks when there is no active production.")] + [Desc("Delay (in ticks) between structure production checks when there is no active production.", + "A StructureProductionRandomBonusDelay is added to this.")] public readonly int StructureProductionInactiveDelay = 125; - [Desc("Minimum delay (in ticks) between structure production checks when actively building things.")] + [Desc("Delay (in ticks) between structure production checks when actively building things.", + "A StructureProductionRandomBonusDelay is added to this.")] public readonly int StructureProductionActiveDelay = 10; - [Desc("A random delay (in ticks) of up to this is added to production delays.")] + [Desc("A random delay (in ticks) of up to this is added to active/inactive production delays.")] public readonly int StructureProductionRandomBonusDelay = 10; - [Desc("How long to wait (in ticks) until retrying to build structure after the last 3 consecutive attempts failed.")] + [Desc("Delay (in ticks) until retrying to build structure after the last 3 consecutive attempts failed.")] public readonly int StructureProductionResumeDelay = 1500; [Desc("After how many failed attempts to place a structure should AI give up and wait", "for StructureProductionResumeDelay before retrying.")] public readonly int MaximumFailedPlacementAttempts = 3; + [Desc("Delay (in ticks) until rechecking for new BaseProviders.")] + public readonly int CheckForNewBasesDelay = 1500; + [Desc("Minimum range at which to build defensive structures near a combat hotspot.")] public readonly int MinimumDefenseRadius = 5; @@ -268,20 +273,20 @@ namespace OpenRA.Mods.Common.AI // TODO: Possibly give this a more generic name when terrain type is unhardcoded public bool EnoughWaterToBuildNaval() { - var baseBuildings = World.Actors.Where( + var baseProviders = World.Actors.Where( a => a.Owner == Player - && a.HasTrait() + && a.HasTrait() && !a.HasTrait()); - foreach (var b in baseBuildings) + foreach (var b in baseProviders) { // 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" + && playerWorld.Map.GetTerrainInfo(c).IsWater && Util.AdjacentCells(playerWorld, Target.FromCell(playerWorld, c)) - .All(a => playerWorld.Map.GetTerrainInfo(a).Type == "Water")) + .All(a => playerWorld.Map.GetTerrainInfo(a).IsWater)) .Count(); if (countWaterCells > 0) @@ -305,9 +310,9 @@ namespace OpenRA.Mods.Common.AI var playerWorld = Player.World; var adjacentWater = Map.FindTilesInCircle(a.Location, Info.CheckForWaterRadius) .Where(c => playerWorld.Map.Contains(c) - && playerWorld.Map.GetTerrainInfo(c).Type == "Water" + && playerWorld.Map.GetTerrainInfo(c).IsWater && Util.AdjacentCells(playerWorld, Target.FromCell(playerWorld, c)) - .All(b => playerWorld.Map.GetTerrainInfo(b).Type == "Water")) + .All(b => playerWorld.Map.GetTerrainInfo(b).IsWater)) .Count(); if (adjacentWater > 0) From d8e458d0283a7b3d236eb1f486fc7d4a9d298aa4 Mon Sep 17 00:00:00 2001 From: reaperrr Date: Mon, 27 Jul 2015 13:50:53 +0200 Subject: [PATCH 7/7] Added TODOs for foundation of checked water cells Fix description style issues --- OpenRA.Mods.Common/AI/HackyAI.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index 39f31480b8..dd1cfeed5f 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -56,11 +56,11 @@ namespace OpenRA.Mods.Common.AI public readonly int MinimumExcessPower = 0; [Desc("Delay (in ticks) between structure production checks when there is no active production.", - "A StructureProductionRandomBonusDelay is added to this.")] + "A StructureProductionRandomBonusDelay is added to this.")] public readonly int StructureProductionInactiveDelay = 125; [Desc("Delay (in ticks) between structure production checks when actively building things.", - "A StructureProductionRandomBonusDelay is added to this.")] + "A StructureProductionRandomBonusDelay is added to this.")] public readonly int StructureProductionActiveDelay = 10; [Desc("A random delay (in ticks) of up to this is added to active/inactive production delays.")] @@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.AI public readonly int StructureProductionResumeDelay = 1500; [Desc("After how many failed attempts to place a structure should AI give up and wait", - "for StructureProductionResumeDelay before retrying.")] + "for StructureProductionResumeDelay before retrying.")] public readonly int MaximumFailedPlacementAttempts = 3; [Desc("Delay (in ticks) until rechecking for new BaseProviders.")] @@ -101,8 +101,8 @@ namespace OpenRA.Mods.Common.AI 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.")] + "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.")] @@ -281,6 +281,7 @@ namespace OpenRA.Mods.Common.AI foreach (var b in baseProviders) { // TODO: Unhardcode terrain type + // TODO2: Properly check building foundation rather than 3x3 area var playerWorld = Player.World; var countWaterCells = Map.FindTilesInCircle(b.Location, Info.MaxBaseRadius) .Where(c => playerWorld.Map.Contains(c) @@ -307,6 +308,7 @@ namespace OpenRA.Mods.Common.AI foreach (var a in areaProviders) { // TODO: Unhardcode terrain type + // TODO2: Properly check building foundation rather than 3x3 area var playerWorld = Player.World; var adjacentWater = Map.FindTilesInCircle(a.Location, Info.CheckForWaterRadius) .Where(c => playerWorld.Map.Contains(c)