diff --git a/OpenRA.Mods.RA/AI/HackyAI.cs b/OpenRA.Mods.RA/AI/HackyAI.cs index e3429408b8..a5478d66d1 100644 --- a/OpenRA.Mods.RA/AI/HackyAI.cs +++ b/OpenRA.Mods.RA/AI/HackyAI.cs @@ -23,14 +23,25 @@ namespace OpenRA.Mods.RA.AI { public sealed class HackyAIInfo : IBotInfo, ITraitInfo { + [Desc("Ingame name this bot uses.")] public readonly string Name = "Unnamed Bot"; + + [Desc("Minimum number of units AI must have before attacking.")] public readonly int SquadSize = 8; + [Desc("Production queues AI uses for buildings.")] public readonly string[] BuildingQueues = { "Building" }; + + [Desc("Production queues AI uses for defenses.")] public readonly string[] DefenseQueues = { "Defense" }; + [Desc("Delay (in ticks) between giving out orders to units.")] public readonly int AssignRolesInterval = 20; + + [Desc("Delay (in ticks) between attempting rush attacks.")] public readonly int RushInterval = 600; + + [Desc("Delay (in ticks) between updating squads.")] public readonly int AttackForceInterval = 30; [Desc("How long to wait (in ticks) between structure production checks when there is no active production.")] @@ -63,26 +74,39 @@ namespace OpenRA.Mods.RA.AI [Desc("Radius in cells around the center of the base to expand.")] public readonly int MaxBaseRadius = 20; + [Desc("Production queues AI uses for producing units.")] public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" }; + + [Desc("Should the AI repair its buildings if damaged?")] public readonly bool ShouldRepairBuildings = true; string IBotInfo.Name { get { return this.Name; } } + [Desc("What units to the AI should build.", "What % of the total army must be this type of unit.")] [FieldLoader.LoadUsing("LoadUnits")] public readonly Dictionary UnitsToBuild = null; + [Desc("What buildings to the AI should build.", "What % of the total base must be this type of building.")] [FieldLoader.LoadUsing("LoadBuildings")] public readonly Dictionary BuildingFractions = null; + [Desc("Tells the AI what unit types fall under the same common name.")] [FieldLoader.LoadUsing("LoadUnitsCommonNames")] public readonly Dictionary UnitsCommonNames = null; + [Desc("Tells the AI what building types fall under the same common name.")] [FieldLoader.LoadUsing("LoadBuildingsCommonNames")] public readonly Dictionary BuildingCommonNames = null; + [Desc("What buildings should the AI have max limits n.", "What is the limit of the building.")] [FieldLoader.LoadUsing("LoadBuildingLimits")] 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 PowerDecisions = new List(); + static object LoadList(MiniYaml y, string field) { var nd = y.ToDictionary(); @@ -99,6 +123,16 @@ namespace OpenRA.Mods.RA.AI static object LoadBuildingLimits(MiniYaml y) { return LoadList(y, "BuildingLimits"); } + static object LoadDecisions(MiniYaml yaml) + { + var ret = new List(); + foreach (var d in yaml.Nodes) + if (d.Key.Split('@')[0] == "SupportPowerDecision") + ret.Add(new SupportPowerDecision(d.Value)); + + return ret; + } + public object Create(ActorInitializer init) { return new HackyAI(this, init); } } @@ -113,6 +147,9 @@ namespace OpenRA.Mods.RA.AI public CPos baseCenter { get; private set; } public Player p { get; private set; } + Dictionary waitingPowers = new Dictionary(); + Dictionary powerDecisions = new Dictionary(); + PowerManager playerPower; SupportPowerManager supportPowerMngr; PlayerResources playerResource; @@ -142,6 +179,9 @@ namespace OpenRA.Mods.RA.AI { Info = info; world = init.world; + + foreach (var decision in info.PowerDecisions) + powerDecisions.Add(decision.OrderName, decision); } public static void BotDebug(string s, params object[] args) @@ -695,46 +735,120 @@ namespace OpenRA.Mods.RA.AI foreach (var kv in powers) { var sp = kv.Value; - if (sp.Ready) - { - var attackLocation = FindAttackLocationToSupportPower(5); - if (attackLocation == null) - return; + // Add power to dictionary if not in delay dictionary yet + if (!waitingPowers.ContainsKey(sp)) + waitingPowers.Add(sp, 0); + if (waitingPowers[sp] > 0) + waitingPowers[sp]--; + + // If we have recently tried and failed to find a use location for a power, then do not try again until later + var isDelayed = (waitingPowers[sp] > 0); + if (sp.Ready && !isDelayed && powerDecisions.ContainsKey(sp.Info.OrderName)) + { + var powerDecision = powerDecisions[sp.Info.OrderName]; + if (powerDecision == null) + { + BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", sp.Info.OrderName); + continue; + } + + var attackLocation = FindCoarseAttackLocationToSupportPower(sp); + if (attackLocation == null) + { + BotDebug("AI: {1} can't find suitable coarse attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, p.PlayerName); + waitingPowers[sp] += powerDecision.GetNextScanTime(this); + + continue; + } + + // Found a target location, check for precise target + attackLocation = FindFineAttackLocationToSupportPower(sp, (CPos)attackLocation); + if (attackLocation == null) + { + BotDebug("AI: {1} can't find suitable final attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, p.PlayerName); + waitingPowers[sp] += powerDecision.GetNextScanTime(this); + + continue; + } + + // Valid target found, delay by a few ticks to avoid rescanning before power fires via order + BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, p.PlayerName); + waitingPowers[sp] += 10; world.IssueOrder(new Order(sp.Info.OrderName, supportPowerMngr.self, false) { TargetLocation = attackLocation.Value, SuppressVisualFeedback = true }); } } } - CPos? FindAttackLocationToSupportPower(int radiusOfPower) + ///Scans the map in chunks, evaluating all actors in each. + CPos? FindCoarseAttackLocationToSupportPower(SupportPowerInstance readyPower) { - CPos? resLoc = null; - var countUnits = 0; - - var x = (world.Map.MapSize.X % radiusOfPower) == 0 ? world.Map.MapSize.X : world.Map.MapSize.X + radiusOfPower; - var y = (world.Map.MapSize.Y % radiusOfPower) == 0 ? world.Map.MapSize.Y : world.Map.MapSize.Y + radiusOfPower; - - for (var i = 0; i < x; i += radiusOfPower * 2) + CPos? bestLocation = null; + var bestAttractiveness = 0; + var powerDecision = powerDecisions[readyPower.Info.OrderName]; + if (powerDecision == null) { - for (var j = 0; j < y; j += radiusOfPower * 2) - { - var pos = world.Map.CenterOfCell(new CPos(i, j)); - var targets = world.FindActorsInCircle(pos, WRange.FromCells(radiusOfPower)).ToList(); - var enemies = targets.Where(unit => p.Stances[unit.Owner] == Stance.Enemy).ToList(); - var ally = targets.Where(unit => p.Stances[unit.Owner] == Stance.Ally || unit.Owner == p).ToList(); + BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", readyPower.Info.OrderName); + return null; + } - if (enemies.Count < ally.Count || !enemies.Any()) + var checkRadius = powerDecision.CoarseScanRadius; + for (var i = 0; i < world.Map.MapSize.X; i += checkRadius) + { + for (var j = 0; j < world.Map.MapSize.Y; j += checkRadius) + { + var consideredAttractiveness = 0; + + var tl = world.Map.CenterOfCell(new CPos(i, j)); + var br = world.Map.CenterOfCell(new CPos(i + checkRadius, j + checkRadius)); + var targets = world.ActorMap.ActorsInBox(tl, br); + + consideredAttractiveness = powerDecision.GetAttractiveness(targets, p); + if (consideredAttractiveness <= bestAttractiveness || consideredAttractiveness < powerDecision.MinimumAttractiveness) continue; - if (enemies.Count > countUnits) - { - countUnits = enemies.Count; - resLoc = enemies.Random(random).Location; - } + bestAttractiveness = consideredAttractiveness; + bestLocation = new CPos(i, j); } } - return resLoc; + return bestLocation; + } + + ///Detail scans an area, evaluating positions. + CPos? FindFineAttackLocationToSupportPower(SupportPowerInstance readyPower, CPos checkPos, int extendedRange = 1) + { + CPos? bestLocation = null; + var bestAttractiveness = 0; + var powerDecision = powerDecisions[readyPower.Info.OrderName]; + if (powerDecision == null) + { + BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", readyPower.Info.OrderName); + return null; + } + + var checkRadius = powerDecision.CoarseScanRadius; + var fineCheck = powerDecision.FineScanRadius; + for (var i = (0 - extendedRange); i <= (checkRadius + extendedRange); i += fineCheck) + { + var x = checkPos.X + i; + + for (var j = (0 - extendedRange); j <= (checkRadius + extendedRange); j += fineCheck) + { + var y = checkPos.Y + j; + var pos = world.Map.CenterOfCell(new CPos(x, y)); + var consideredAttractiveness = 0; + consideredAttractiveness += powerDecision.GetAttractiveness(pos, p); + + if (consideredAttractiveness <= bestAttractiveness || consideredAttractiveness < powerDecision.MinimumAttractiveness) + continue; + + bestAttractiveness = consideredAttractiveness; + bestLocation = new CPos(x, y); + } + } + + return bestLocation; } internal IEnumerable FindQueues(string category) diff --git a/OpenRA.Mods.RA/AI/SupportPowerDecision.cs b/OpenRA.Mods.RA/AI/SupportPowerDecision.cs new file mode 100644 index 0000000000..5eacf869e1 --- /dev/null +++ b/OpenRA.Mods.RA/AI/SupportPowerDecision.cs @@ -0,0 +1,155 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRA; +using OpenRA.Mods.RA.AI; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + [Desc("Adds metadata for the AI bots.")] + public class SupportPowerDecision + { + [Desc("What is the minimum attractiveness we will use this power for?")] + public readonly int MinimumAttractiveness = 1; + + [Desc("What support power does this decision apply to?")] + public readonly string OrderName = "AirstrikePowerInfoOrder"; + + [Desc("What is the coarse scan radius of this power?","For finding the general target area, before doing a detail scan","Should be 10 or more to avoid lag")] + public readonly int CoarseScanRadius = 20; + + [Desc("What is the fine scan radius of this power?", "For doing a detailed scan in the general target area.", "Minimum is 1")] + public readonly int FineScanRadius = 2; + + [FieldLoader.LoadUsing("LoadConsiderations")] + [Desc("The decisions associated with this power")] + readonly List Considerations = new List(); + + [Desc("Minimum ticks to wait until next Decision scan attempt.")] + public readonly int MinimumScanTimeInterval = 250; + + [Desc("Maximum ticks to wait until next Decision scan attempt.")] + public readonly int MaximumScanTimeInterval = 262; + + public SupportPowerDecision(MiniYaml yaml) + { + FieldLoader.Load(this, yaml); + } + + static object LoadConsiderations(MiniYaml yaml) + { + var ret = new List(); + foreach (var d in yaml.Nodes) + if (d.Key.Split('@')[0] == "Consideration") + ret.Add(new Consideration(d.Value)); + + return ret; + } + + ///Evaluates the attractiveness of a position according to all considerations + public int GetAttractiveness(WPos pos, Player firedBy) + { + var answer = 0; + var world = firedBy.World; + var targetTile = world.Map.CellContaining(pos); + + if (!world.Map.Contains(targetTile)) + return 0; + + foreach (var consideration in Considerations) + { + var radiusToUse = new WRange(consideration.CheckRadius.Range); + + var checkActors = world.FindActorsInCircle(pos, radiusToUse); + foreach (var scrutinized in checkActors) + answer += consideration.GetAttractiveness(scrutinized, firedBy.Stances[scrutinized.Owner]); + } + + return answer; + } + + ///Evaluates the attractiveness of a group of actors according to all considerations + public int GetAttractiveness(IEnumerable actors, Player firedBy) + { + var answer = 0; + + foreach (var consideration in Considerations) + foreach (var scrutinized in actors) + answer += consideration.GetAttractiveness(scrutinized, firedBy.Stances[scrutinized.Owner]); + + return answer; + } + + public int GetNextScanTime(HackyAI ai) { return ai.random.Next(MinimumScanTimeInterval, MaximumScanTimeInterval); } + + ///Makes up part of a decision, describing how to evaluate a target. + class Consideration + { + public enum DecisionMetric { Health, Value, None }; + + [Desc("Against whom should this power be used?", "Allowed keywords: Ally, Neutral, Enemy")] + public readonly Stance Against = Stance.Enemy; + + [Desc("What types should the desired targets of this power be?")] + public readonly string[] Types = { "Air", "Ground", "Water" }; + + [Desc("How attractive are these types of targets?")] + public readonly int Attractiveness = 100; + + [Desc("Weight the target attractiveness by this property", "Allowed keywords: Health, Value, None")] + public readonly DecisionMetric TargetMetric = DecisionMetric.None; + + [Desc("What is the check radius of this decision?")] + public readonly WRange CheckRadius = WRange.FromCells(5); + + public Consideration(MiniYaml yaml) + { + FieldLoader.Load(this, yaml); + } + + ///Evaluates a single actor according to the rules defined in this consideration + public int GetAttractiveness(Actor a, Stance stance) + { + if (stance != Against) + return 0; + + if (a == null) + return 0; + + var targetable = a.TraitOrDefault(); + if (targetable == null) + return 0; + + if (Types.Intersect(targetable.TargetTypes).Any()) + { + switch (TargetMetric) + { + case DecisionMetric.Value: + var valueInfo = a.Info.Traits.GetOrDefault(); + return (valueInfo != null) ? valueInfo.Cost * Attractiveness : 0; + + case DecisionMetric.Health: + var health = a.TraitOrDefault(); + return (health != null) ? (health.HP / health.MaxHP) * Attractiveness : 0; + + default: + return Attractiveness; + } + } + return 0; + } + } + } +} diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 057f7642ee..f5ff3d3d64 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -117,6 +117,7 @@ + diff --git a/mods/cnc/rules/ai.yaml b/mods/cnc/rules/ai.yaml index 91315106b6..8540edd123 100644 --- a/mods/cnc/rules/ai.yaml +++ b/mods/cnc/rules/ai.yaml @@ -65,6 +65,58 @@ Player: heli: 5% orca: 5% SquadSize: 15 + SupportPowerDecision@Airstrike: + OrderName: AirstrikePowerInfoOrder + MinimumAttractiveness: 2000 + Consideration@1: + Against: Enemy + Types: Vehicle, Infantry + Attractiveness: 1 + TargetMetric: Value + CheckRadius: 2c0 + Consideration@2: + Against: Ally + Types: Ground, Water + Attractiveness: -10 + TargetMetric: Value + CheckRadius: 2c0 + SupportPowerDecision@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 + SupportPowerDecision@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 BuildingCommonNames: @@ -131,6 +183,58 @@ Player: jeep: 20% mtnk: 50% SquadSize: 15 + SupportPowerDecision@Airstrike: + OrderName: AirstrikePowerInfoOrder + MinimumAttractiveness: 2000 + Consideration@1: + Against: Enemy + Types: Vehicle, Infantry + Attractiveness: 1 + TargetMetric: Value + CheckRadius: 2c0 + Consideration@2: + Against: Ally + Types: Ground, Water + Attractiveness: -10 + TargetMetric: Value + CheckRadius: 2c0 + SupportPowerDecision@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 + SupportPowerDecision@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 BuildingCommonNames: @@ -199,3 +303,55 @@ Player: htnk: 50% orca: 10% SquadSize: 8 + SupportPowerDecision@Airstrike: + OrderName: AirstrikePowerInfoOrder + MinimumAttractiveness: 2000 + Consideration@1: + Against: Enemy + Types: Vehicle, Infantry + Attractiveness: 1 + TargetMetric: Value + CheckRadius: 2c0 + Consideration@2: + Against: Ally + Types: Ground, Water + Attractiveness: -10 + TargetMetric: Value + CheckRadius: 2c0 + SupportPowerDecision@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 + SupportPowerDecision@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 diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index 8081af4080..3205f64673 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -324,7 +324,7 @@ Selectable: Priority: 3 TargetableBuilding: - TargetTypes: Ground, C4 + TargetTypes: Ground, C4, Structure Armor: Type: Wood Building: diff --git a/mods/d2k/rules/ai.yaml b/mods/d2k/rules/ai.yaml index bfdeb2e410..3a095fc2e4 100644 --- a/mods/d2k/rules/ai.yaml +++ b/mods/d2k/rules/ai.yaml @@ -107,6 +107,57 @@ Player: combato: 100% SquadSize: 8 MaxBaseRadius: 40 + SupportPowerDecision@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 + SupportPowerDecision@Paratroopers: + OrderName: ParatroopersPowerInfoOrder + MinimumAttractiveness: 5 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 10c0 + Consideration@2: + Against: Enemy + Types: Vehicle, Tank, Infantry + Attractiveness: -5 + TargetMetric: None + CheckRadius: 4c0 + SupportPowerDecision@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@Vidius: Name: Vidious UnitQueues: Infantry, Vehicle, Armor, Starport @@ -215,6 +266,57 @@ Player: combato: 100% SquadSize: 6 MaxBaseRadius: 40 + SupportPowerDecision@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 + SupportPowerDecision@Paratroopers: + OrderName: ParatroopersPowerInfoOrder + MinimumAttractiveness: 5 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 10c0 + Consideration@2: + Against: Enemy + Types: Vehicle, Tank, Infantry + Attractiveness: -5 + TargetMetric: None + CheckRadius: 4c0 + SupportPowerDecision@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@Gladius: Name: Gladius UnitQueues: Infantry, Vehicle, Armor, Starport @@ -323,3 +425,54 @@ Player: combato: 100% SquadSize: 10 MaxBaseRadius: 40 + SupportPowerDecision@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 + SupportPowerDecision@Paratroopers: + OrderName: ParatroopersPowerInfoOrder + MinimumAttractiveness: 5 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 10c0 + Consideration@2: + Against: Enemy + Types: Vehicle, Tank, Infantry + Attractiveness: -5 + TargetMetric: None + CheckRadius: 4c0 + SupportPowerDecision@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 diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index fd26a51cce..a56bd7177d 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -237,7 +237,7 @@ Selectable: Priority: 2 TargetableBuilding: - TargetTypes: Ground, C4 + TargetTypes: Ground, C4, Structure Building: Dimensions: 1,1 Footprint: x diff --git a/mods/ra/rules/ai.yaml b/mods/ra/rules/ai.yaml index c175b2791c..ed1206409c 100644 --- a/mods/ra/rules/ai.yaml +++ b/mods/ra/rules/ai.yaml @@ -52,6 +52,45 @@ Player: 2tnk: 25% 3tnk: 50% SquadSize: 20 + SupportPowerDecision@Airstrike: + OrderName: AirstrikePowerInfoOrder + MinimumAttractiveness: 1 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 5c0 + SupportPowerDecision@Paratroopers: + OrderName: ParatroopersPowerInfoOrder + MinimumAttractiveness: 5 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 10c0 + Consideration@2: + Against: Enemy + Types: Vehicle, Tank, Infantry, Defense + Attractiveness: -10 + TargetMetric: None + CheckRadius: 10c0 + SupportPowerDecision@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 BuildingCommonNames: @@ -119,6 +158,45 @@ Player: ca: 10% pt: 10% SquadSize: 40 + SupportPowerDecision@Airstrike: + OrderName: AirstrikePowerInfoOrder + MinimumAttractiveness: 1 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 5c0 + SupportPowerDecision@Paratroopers: + OrderName: ParatroopersPowerInfoOrder + MinimumAttractiveness: 5 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 10c0 + Consideration@2: + Against: Enemy + Types: Vehicle, Tank, Infantry, Defense + Attractiveness: -10 + TargetMetric: None + CheckRadius: 10c0 + SupportPowerDecision@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 BuildingCommonNames: @@ -185,6 +263,45 @@ Player: ca: 10% pt: 10% SquadSize: 10 + SupportPowerDecision@Airstrike: + OrderName: AirstrikePowerInfoOrder + MinimumAttractiveness: 1 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 5c0 + SupportPowerDecision@Paratroopers: + OrderName: ParatroopersPowerInfoOrder + MinimumAttractiveness: 5 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 10c0 + Consideration@2: + Against: Enemy + Types: Vehicle, Tank, Infantry, Defense + Attractiveness: -10 + TargetMetric: None + CheckRadius: 10c0 + SupportPowerDecision@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 BuildingCommonNames: @@ -238,4 +355,43 @@ Player: ca: 20% pt: 10% SquadSize: 1 + SupportPowerDecision@Airstrike: + OrderName: AirstrikePowerInfoOrder + MinimumAttractiveness: 1 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 5c0 + SupportPowerDecision@Paratroopers: + OrderName: ParatroopersPowerInfoOrder + MinimumAttractiveness: 5 + Consideration@1: + Against: Enemy + Types: Structure + Attractiveness: 1 + TargetMetric: None + CheckRadius: 10c0 + Consideration@2: + Against: Enemy + Types: Vehicle, Tank, Infantry, Defense + Attractiveness: -10 + TargetMetric: None + CheckRadius: 10c0 + SupportPowerDecision@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 diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 1fe2145be0..9e91d32f1b 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -15,7 +15,7 @@ Selectable: Voice: VehicleVoice TargetableUnit: - TargetTypes: Ground, C4, Repair + TargetTypes: Ground, C4, Repair, Vehicle Repairable: Chronoshiftable: Passenger: @@ -73,7 +73,7 @@ Selectable: Voice: VehicleVoice TargetableUnit: - TargetTypes: Ground, C4, Repair + TargetTypes: Ground, C4, Repair, Tank Repairable: Chronoshiftable: Passenger: @@ -268,7 +268,7 @@ Selectable: Priority: 3 TargetableBuilding: - TargetTypes: Ground, C4, DetonateAttack + TargetTypes: Ground, C4, DetonateAttack, Structure Building: Dimensions: 1,1 Footprint: x @@ -310,6 +310,11 @@ LuaScriptEvents: Demolishable: ScriptTriggers: + +^Defense: + Inherits: ^Building + TargetableBuilding: + TargetTypes: Ground, C4, DetonateAttack, Structure, Defense ^Wall: AppearsOnRadar: diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index 444307353c..33c12c72e6 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -276,7 +276,7 @@ PDOX: Amount: -200 TSLA: - Inherits: ^Building + Inherits: ^Defense Buildable: Queue: Defense BuildPaletteOrder: 80 @@ -319,7 +319,7 @@ TSLA: Amount: -150 AGUN: - Inherits: ^Building + Inherits: ^Defense Buildable: Queue: Defense BuildPaletteOrder: 90 @@ -400,7 +400,7 @@ DOME: Amount: -40 PBOX: - Inherits: ^Building + Inherits: ^Defense Tooltip: Name: Pillbox Description: Static defense with a fireport for a\ngarrisoned soldier. @@ -445,7 +445,7 @@ PBOX: Amount: -15 HBOX: - Inherits: ^Building + Inherits: ^Defense Tooltip: Name: Camo Pillbox Description: Camouflaged static defense with a fireport\nfor a garrisoned soldier. @@ -493,7 +493,7 @@ HBOX: Amount: -15 GUN: - Inherits: ^Building + Inherits: ^Defense Buildable: Queue: Defense BuildPaletteOrder: 70 @@ -533,7 +533,7 @@ GUN: Amount: -40 FTUR: - Inherits: ^Building + Inherits: ^Defense Buildable: Queue: Defense BuildPaletteOrder: 60 @@ -571,7 +571,7 @@ FTUR: Amount: -20 SAM: - Inherits: ^Building + Inherits: ^Defense Buildable: Queue: Defense BuildPaletteOrder: 100