diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index a055125c0b..9f8044d930 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -187,11 +187,6 @@ namespace OpenRA.Mods.Common.AI [Desc("What buildings should the AI have a maximum limit to build.")] public readonly Dictionary BuildingLimits = null; - // TODO Update OpenRA.Utility/Command.cs#L300 to first handle lists and also read nested ones - [Desc("Tells the AI how to use its support powers.")] - [FieldLoader.LoadUsing("LoadDecisions")] - public readonly List SupportPowerDecisions = new List(); - [Desc("Actor types that can capture other actors (via `Captures` or `ExternalCaptures`).", "Leave this empty to disable capturing.")] public HashSet CapturingActorTypes = new HashSet(); @@ -225,17 +220,6 @@ namespace OpenRA.Mods.Common.AI return FieldLoader.Load(categories.Value); } - static object LoadDecisions(MiniYaml yaml) - { - var ret = new List(); - var decisions = yaml.Nodes.FirstOrDefault(n => n.Key == "SupportPowerDecisions"); - if (decisions != null) - foreach (var d in decisions.Value.Nodes) - ret.Add(new SupportPowerDecision(d.Value)); - - return ret; - } - string IBotInfo.Type { get { return Type; } } string IBotInfo.Name { get { return Name; } } @@ -245,6 +229,7 @@ namespace OpenRA.Mods.Common.AI public sealed class HackyAI : ITick, IBot, INotifyDamage { + // DEPRECATED: Modules should use World.LocalRandom. public MersenneTwister Random { get; private set; } public readonly HackyAIInfo Info; @@ -275,8 +260,6 @@ namespace OpenRA.Mods.Common.AI BitArray resourceTypeIndices; - AISupportPowerManager supportPowerManager; - List builders = new List(); List unitsHangingAroundTheBase = new List(); @@ -324,8 +307,6 @@ namespace OpenRA.Mods.Common.AI playerResource = p.PlayerActor.Trait(); tickModules = p.PlayerActor.TraitsImplementing().ToArray(); - supportPowerManager = new AISupportPowerManager(this, p); - foreach (var building in Info.BuildingQueues) builders.Add(new BaseBuilder(this, building, p, playerPower, playerResource)); foreach (var defense in Info.DefenseQueues) @@ -541,7 +522,6 @@ namespace OpenRA.Mods.Common.AI AssignRolesToIdleUnits(self); SetRallyPointsForNewProductionBuildings(self); - supportPowerManager.TryToUseSupportPower(self); foreach (var b in builders) b.Tick(); diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index b526c76c85..50a7e39e2a 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -125,7 +125,7 @@ - + @@ -133,7 +133,7 @@ - + diff --git a/OpenRA.Mods.Common/AI/SupportPowerDecision.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/SupportPowerDecision.cs similarity index 97% rename from OpenRA.Mods.Common/AI/SupportPowerDecision.cs rename to OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/SupportPowerDecision.cs index 2ccf219512..0b59026ac4 100644 --- a/OpenRA.Mods.Common/AI/SupportPowerDecision.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/SupportPowerDecision.cs @@ -10,11 +10,11 @@ #endregion using System.Collections.Generic; -using OpenRA.Mods.Common.Traits; +using OpenRA.Mods.Common.AI; using OpenRA.Primitives; using OpenRA.Traits; -namespace OpenRA.Mods.Common.AI +namespace OpenRA.Mods.Common.Traits { [Desc("Adds metadata for the AI bots.")] public class SupportPowerDecision @@ -112,7 +112,7 @@ namespace OpenRA.Mods.Common.AI return answer; } - public int GetNextScanTime(HackyAI ai) { return ai.Random.Next(MinimumScanTimeInterval, MaximumScanTimeInterval); } + public int GetNextScanTime(World world) { return world.LocalRandom.Next(MinimumScanTimeInterval, MaximumScanTimeInterval); } /// Makes up part of a decision, describing how to evaluate a target. public class Consideration diff --git a/OpenRA.Mods.Common/AI/AISupportPowerManager.cs b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs similarity index 74% rename from OpenRA.Mods.Common/AI/AISupportPowerManager.cs rename to OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs index ea817b6d1e..0bbee1e596 100644 --- a/OpenRA.Mods.Common/AI/AISupportPowerManager.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs @@ -10,37 +10,59 @@ #endregion using System.Collections.Generic; -using OpenRA.Mods.Common.Traits; +using System.Linq; +using OpenRA.Mods.Common.AI; using OpenRA.Traits; -namespace OpenRA.Mods.Common.AI +namespace OpenRA.Mods.Common.Traits { - class AISupportPowerManager + [Desc("Manages bot support power handling.")] + public class SupportPowerBotModuleInfo : ConditionalTraitInfo, Requires + { + [Desc("Tells the AI how to use its support powers.")] + [FieldLoader.LoadUsing("LoadDecisions")] + public readonly List Decisions = new List(); + + static object LoadDecisions(MiniYaml yaml) + { + var ret = new List(); + var decisions = yaml.Nodes.FirstOrDefault(n => n.Key == "Decisions"); + if (decisions != null) + foreach (var d in decisions.Value.Nodes) + ret.Add(new SupportPowerDecision(d.Value)); + + return ret; + } + + public override object Create(ActorInitializer init) { return new SupportPowerBotModule(init.Self, this); } + } + + public class SupportPowerBotModule : ConditionalTrait, IBotTick { - readonly HackyAI ai; readonly World world; readonly Player player; - readonly FrozenActorLayer frozenLayer; - readonly SupportPowerManager supportPowerManager; + FrozenActorLayer frozenLayer; + SupportPowerManager supportPowerManager; Dictionary waitingPowers = new Dictionary(); Dictionary powerDecisions = new Dictionary(); - public AISupportPowerManager(HackyAI ai, Player p) + public SupportPowerBotModule(Actor self, SupportPowerBotModuleInfo info) + : base(info) { - this.ai = ai; - world = p.World; - player = p; - frozenLayer = p.PlayerActor.Trait(); - supportPowerManager = p.PlayerActor.TraitOrDefault(); - foreach (var decision in ai.Info.SupportPowerDecisions) + world = self.World; + player = self.Owner; + } + + protected override void TraitEnabled(Actor self) + { + frozenLayer = player.PlayerActor.TraitOrDefault(); + supportPowerManager = player.PlayerActor.Trait(); + foreach (var decision in Info.Decisions) powerDecisions.Add(decision.OrderName, decision); } - public void TryToUseSupportPower(Actor self) + void IBotTick.BotTick(IBot bot) { - if (supportPowerManager == null) - return; - foreach (var sp in supportPowerManager.Powers.Values) { if (sp.Disabled) @@ -68,7 +90,7 @@ namespace OpenRA.Mods.Common.AI if (attackLocation == null) { AIUtils.BotDebug("AI: {1} can't find suitable coarse attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, player.PlayerName); - waitingPowers[sp] += powerDecision.GetNextScanTime(ai); + waitingPowers[sp] += powerDecision.GetNextScanTime(world); continue; } @@ -78,7 +100,7 @@ namespace OpenRA.Mods.Common.AI if (attackLocation == null) { AIUtils.BotDebug("AI: {1} can't find suitable final attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, player.PlayerName); - waitingPowers[sp] += powerDecision.GetNextScanTime(ai); + waitingPowers[sp] += powerDecision.GetNextScanTime(world); continue; } @@ -86,7 +108,7 @@ namespace OpenRA.Mods.Common.AI // Valid target found, delay by a few ticks to avoid rescanning before power fires via order AIUtils.BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, player.PlayerName); waitingPowers[sp] += 10; - ai.QueueOrder(new Order(sp.Key, supportPowerManager.Self, Target.FromCell(world, attackLocation.Value), false) { SuppressVisualFeedback = true }); + bot.QueueOrder(new Order(sp.Key, supportPowerManager.Self, Target.FromCell(world, attackLocation.Value), false) { SuppressVisualFeedback = true }); } } } @@ -118,7 +140,7 @@ namespace OpenRA.Mods.Common.AI var wbr = world.Map.CenterOfCell(br.ToCPos(map)); var targets = world.ActorMap.ActorsInBox(wtl, wbr); - var frozenTargets = frozenLayer.FrozenActorsInRegion(region); + var frozenTargets = frozenLayer != null ? frozenLayer.FrozenActorsInRegion(region) : Enumerable.Empty(); var consideredAttractiveness = powerDecision.GetAttractiveness(targets, player) + powerDecision.GetAttractiveness(frozenTargets, player); if (consideredAttractiveness <= bestAttractiveness || consideredAttractiveness < powerDecision.MinimumAttractiveness) continue; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20180923/ExtractHackyAIModules.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/ExtractHackyAIModules.cs index 29b13d9a6a..c6a39e9856 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20180923/ExtractHackyAIModules.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/ExtractHackyAIModules.cs @@ -33,6 +33,11 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "HarvesterEnemyAvoidanceRadius", "AssignRolesInterval" }; + readonly string[] supportPowerFields = + { + "SupportPowerDecisions" + }; + public override IEnumerable AfterUpdate(ModData modData) { var message = "You may want to check your AI yamls for possible redundant module entries.\n" + @@ -118,6 +123,21 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules requiresConditionNode.ReplaceValue(oldValue + " || " + conditionString); } } + + if (supportPowerFields.Any(f => hackyAINode.ChildrenMatching(f).Any())) + { + var spNode = new MiniYamlNode("SupportPowerBotModule@" + aiType, ""); + spNode.AddNode(requiresCondition); + + foreach (var spf in supportPowerFields) + { + var fieldNode = hackyAINode.LastChildMatching(spf); + if (fieldNode != null) + fieldNode.MoveAndRenameNode(hackyAINode, spNode, "Decisions"); + } + + addNodes.Add(spNode); + } } addNodes.Add(defaultHarvNode); diff --git a/mods/cnc/rules/ai.yaml b/mods/cnc/rules/ai.yaml index 92b1e00627..8826ec1b42 100644 --- a/mods/cnc/rules/ai.yaml +++ b/mods/cnc/rules/ai.yaml @@ -71,65 +71,6 @@ Player: UnitLimits: harv: 8 SquadSize: 15 - SupportPowerDecisions: - Airstrike: - OrderName: AirstrikePowerInfoOrder - MinimumAttractiveness: 2000 - Consideration@1: - Against: Enemy - Types: Vehicle, Infantry - Attractiveness: 3 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@2: - Against: Ally - Types: Ground, Water - Attractiveness: -20 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@3: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 2c0 - IonCannonPower: - OrderName: IonCannonPowerInfoOrder - MinimumAttractiveness: 1000 - FineScanRadius: 2 - Consideration@1: - Against: Enemy - Types: Air, Tank, Vehicle, Infantry, Water - Attractiveness: 2 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@2: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@3: - Against: Ally - Types: Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 3c0 - NukePower: - OrderName: NukePowerInfoOrder - MinimumAttractiveness: 3000 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 5c0 - Consideration@2: - Against: Ally - Types: Air, Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 7c0 HackyAI@Watson: Name: Watson Type: watson @@ -202,65 +143,6 @@ Player: UnitLimits: harv: 8 SquadSize: 15 - SupportPowerDecisions: - Airstrike: - OrderName: AirstrikePowerInfoOrder - MinimumAttractiveness: 2000 - Consideration@1: - Against: Enemy - Types: Vehicle, Infantry - Attractiveness: 3 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@2: - Against: Ally - Types: Ground, Water - Attractiveness: -20 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@3: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 2c0 - IonCannonPower: - OrderName: IonCannonPowerInfoOrder - MinimumAttractiveness: 1000 - FineScanRadius: 2 - Consideration@1: - Against: Enemy - Types: Air, Tank, Vehicle, Infantry, Water - Attractiveness: 2 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@2: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@3: - Against: Ally - Types: Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 3c0 - NukePower: - OrderName: NukePowerInfoOrder - MinimumAttractiveness: 3000 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 5c0 - Consideration@2: - Against: Ally - Types: Air, Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 7c0 HackyAI@HAL9001: Name: HAL 9001 Type: hal9001 @@ -335,7 +217,9 @@ Player: UnitLimits: harv: 8 SquadSize: 8 - SupportPowerDecisions: + SupportPowerBotModule: + RequiresCondition: enable-cabal-ai || enable-watson-ai || enable-hal9001-ai + Decisions: Airstrike: OrderName: AirstrikePowerInfoOrder MinimumAttractiveness: 2000 diff --git a/mods/d2k/rules/ai.yaml b/mods/d2k/rules/ai.yaml index 4930529b17..c4cad5b572 100644 --- a/mods/d2k/rules/ai.yaml +++ b/mods/d2k/rules/ai.yaml @@ -83,47 +83,6 @@ Player: carryall: 4 SquadSize: 8 MaxBaseRadius: 40 - SupportPowerDecisions: - Airstrike: - OrderName: AirstrikePowerInfoOrder - MinimumAttractiveness: 2000 - Consideration@1: - Against: Enemy - Types: Vehicle, Tank, Infantry - Attractiveness: 2 - TargetMetric: Value - CheckRadius: 3c0 - Consideration@2: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@3: - Against: Ally - Types: Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 4c0 - NukePower: - OrderName: NukePowerInfoOrder - MinimumAttractiveness: 3000 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 5c0 - Consideration@2: - Against: Ally - Types: Air, Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 7c0 - Fremen: - OrderName: ProduceActorPower.Fremen - Consideration@1: - Against: Ally HackyAI@Vidius: Name: Vidious Type: vidious @@ -208,47 +167,6 @@ Player: carryall: 4 SquadSize: 6 MaxBaseRadius: 40 - SupportPowerDecisions: - Airstrike: - OrderName: AirstrikePowerInfoOrder - MinimumAttractiveness: 2000 - Consideration@1: - Against: Enemy - Types: Vehicle, Tank, Infantry - Attractiveness: 2 - TargetMetric: Value - CheckRadius: 3c0 - Consideration@2: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 2c0 - Consideration@3: - Against: Ally - Types: Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 4c0 - NukePower: - OrderName: NukePowerInfoOrder - MinimumAttractiveness: 3000 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 5c0 - Consideration@2: - Against: Ally - Types: Air, Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 7c0 - Fremen: - OrderName: ProduceActorPower.Fremen - Consideration@1: - Against: Ally HackyAI@Gladius: Name: Gladius Type: gladius @@ -332,7 +250,9 @@ Player: carryall: 4 SquadSize: 10 MaxBaseRadius: 40 - SupportPowerDecisions: + SupportPowerBotModule: + RequiresCondition: enable-omnius-ai || enable-vidious-ai || enable-gladius-ai + Decisions: Airstrike: OrderName: AirstrikePowerInfoOrder MinimumAttractiveness: 2000 diff --git a/mods/ra/rules/ai.yaml b/mods/ra/rules/ai.yaml index f40f32d5ad..cc03227fe6 100644 --- a/mods/ra/rules/ai.yaml +++ b/mods/ra/rules/ai.yaml @@ -71,55 +71,6 @@ Player: jeep: 4 ftrk: 4 SquadSize: 20 - SupportPowerDecisions: - spyplane: - OrderName: SovietSpyPlane - MinimumAttractiveness: 1 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 5c0 - paratroopers: - OrderName: SovietParatroopers - MinimumAttractiveness: 5 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 8c0 - Consideration@2: - Against: Enemy - Types: Water - Attractiveness: -5 - TargetMetric: None - CheckRadius: 8c0 - parabombs: - OrderName: UkraineParabombs - MinimumAttractiveness: 1 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 5c0 - nukepower: - OrderName: NukePowerInfoOrder - MinimumAttractiveness: 3000 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 5c0 - Consideration@2: - Against: Ally - Types: Air, Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 7c0 HackyAI@NormalAI: Name: Normal AI Type: normal @@ -211,55 +162,6 @@ Player: jeep: 4 ftrk: 4 SquadSize: 40 - SupportPowerDecisions: - spyplane: - OrderName: SovietSpyPlane - MinimumAttractiveness: 1 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 5c0 - paratroopers: - OrderName: SovietParatroopers - MinimumAttractiveness: 5 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 8c0 - Consideration@2: - Against: Enemy - Types: Water - Attractiveness: -5 - TargetMetric: None - CheckRadius: 8c0 - parabombs: - OrderName: UkraineParabombs - MinimumAttractiveness: 1 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 5c0 - nukepower: - OrderName: NukePowerInfoOrder - MinimumAttractiveness: 3000 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 5c0 - Consideration@2: - Against: Ally - Types: Air, Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 7c0 HackyAI@TurtleAI: Name: Turtle AI Type: turtle @@ -352,55 +254,6 @@ Player: jeep: 4 ftrk: 4 SquadSize: 10 - SupportPowerDecisions: - spyplane: - OrderName: SovietSpyPlane - MinimumAttractiveness: 1 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 5c0 - paratroopers: - OrderName: SovietParatroopers - MinimumAttractiveness: 5 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 8c0 - Consideration@2: - Against: Enemy - Types: Water - Attractiveness: -5 - TargetMetric: None - CheckRadius: 8c0 - parabombs: - OrderName: UkraineParabombs - MinimumAttractiveness: 1 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: None - CheckRadius: 5c0 - nukepower: - OrderName: NukePowerInfoOrder - MinimumAttractiveness: 3000 - Consideration@1: - Against: Enemy - Types: Structure - Attractiveness: 1 - TargetMetric: Value - CheckRadius: 5c0 - Consideration@2: - Against: Ally - Types: Air, Ground, Water - Attractiveness: -10 - TargetMetric: Value - CheckRadius: 7c0 HackyAI@NavalAI: Name: Naval AI Type: naval @@ -468,7 +321,9 @@ Player: UnitLimits: harv: 8 SquadSize: 1 - SupportPowerDecisions: + SupportPowerBotModule: + RequiresCondition: enable-rush-ai || enable-normal-ai || enable-turtle-ai || enable-naval-ai + Decisions: spyplane: OrderName: SovietSpyPlane MinimumAttractiveness: 1