diff --git a/OpenRA.FileFormats/WPos.cs b/OpenRA.FileFormats/WPos.cs index 32ec012970..7ccc4d22f2 100644 --- a/OpenRA.FileFormats/WPos.cs +++ b/OpenRA.FileFormats/WPos.cs @@ -9,6 +9,8 @@ #endregion using System; +using System.Collections.Generic; +using System.Linq; using System.Drawing; namespace OpenRA @@ -50,28 +52,6 @@ namespace OpenRA return new WPos(ret.X, ret.Y, ret.Z + offset); } - public static WPos Average(params WPos[] list) - { - if (list == null || list.Length == 0) - return WPos.Zero; - - var x = 0; - var y = 0; - var z = 0; - foreach(var pos in list) - { - x += pos.X; - y += pos.Y; - z += pos.Z; - } - - x /= list.Length; - y /= list.Length; - z /= list.Length; - - return new WPos(x,y,z); - } - public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); } public override bool Equals(object obj) @@ -85,4 +65,30 @@ namespace OpenRA public override string ToString() { return "{0},{1},{2}".F(X, Y, Z); } } + + public static class IEnumerableExtensions + { + public static WPos Average(this IEnumerable source) + { + var length = source.Count(); + if (length == 0) + return WPos.Zero; + + var x = 0L; + var y = 0L; + var z = 0L; + foreach (var pos in source) + { + x += pos.X; + y += pos.Y; + z += pos.Z; + } + + x /= length; + y /= length; + z /= length; + + return new WPos((int)x, (int)y, (int)z); + } + } } diff --git a/OpenRA.Game/Graphics/ContrailRenderable.cs b/OpenRA.Game/Graphics/ContrailRenderable.cs index ef428044e0..b433aeff2e 100644 --- a/OpenRA.Game/Graphics/ContrailRenderable.cs +++ b/OpenRA.Game/Graphics/ContrailRenderable.cs @@ -68,7 +68,7 @@ namespace OpenRA.Graphics for (var i = 0; i < length - skip - 4; i++) { var j = next - skip - i - 2; - var nextPos = WPos.Average(trail[idx(j)], trail[idx(j-1)], trail[idx(j-2)], trail[idx(j-3)]); + var nextPos = Average(trail[idx(j)], trail[idx(j-1)], trail[idx(j-2)], trail[idx(j-3)]); var nextCell = nextPos.ToCPos(); var nextColor = Exts.ColorLerp(i * 1f / (length - 4), color, Color.Transparent); @@ -90,6 +90,11 @@ namespace OpenRA.Graphics return j < 0 ? j + trail.Length : j; } + WPos Average(params WPos[] list) + { + return list.Average(); + } + public void Update(WPos pos) { trail[next] = pos; diff --git a/OpenRA.Mods.RA/AI/AttackOrFleeFuzzy.cs b/OpenRA.Mods.RA/AI/AttackOrFleeFuzzy.cs index 3f6786bb5c..0e61d7a633 100644 --- a/OpenRA.Mods.RA/AI/AttackOrFleeFuzzy.cs +++ b/OpenRA.Mods.RA/AI/AttackOrFleeFuzzy.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2012 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2013 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, @@ -11,63 +11,55 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using AI.Fuzzy.Library; using OpenRA.Mods.RA.Move; using OpenRA.Traits; -using AI.Fuzzy.Library; namespace OpenRA.Mods.RA.AI { class AttackOrFleeFuzzy { - protected MamdaniFuzzySystem fuzzyEngine; - protected Dictionary result; - - public bool CanAttack - { - get - { - //not sure that this will happen (NaN), it's for the safety of - if (result[fuzzyEngine.OutputByName("AttackOrFlee")].ToString() != "NaN") - return result[fuzzyEngine.OutputByName("AttackOrFlee")] < 30.0; - return false; - } - } + MamdaniFuzzySystem fuzzyEngine; public AttackOrFleeFuzzy() { InitializateFuzzyVariables(); } + protected void AddFuzzyRule(string rule) + { + fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule(rule)); + } + protected void InitializateFuzzyVariables() { fuzzyEngine = new MamdaniFuzzySystem(); - FuzzyVariable playerHealthFuzzy = new FuzzyVariable("OwnHealth", 0.0, 100.0); + var playerHealthFuzzy = new FuzzyVariable("OwnHealth", 0.0, 100.0); playerHealthFuzzy.Terms.Add(new FuzzyTerm("NearDead", new TrapezoidMembershipFunction(0, 0, 20, 40))); playerHealthFuzzy.Terms.Add(new FuzzyTerm("Injured", new TrapezoidMembershipFunction(30, 50, 50, 70))); playerHealthFuzzy.Terms.Add(new FuzzyTerm("Normal", new TrapezoidMembershipFunction(50, 80, 100, 100))); fuzzyEngine.Input.Add(playerHealthFuzzy); - FuzzyVariable enemyHealthFuzzy = new FuzzyVariable("EnemyHealth", 0.0, 100.0); + var enemyHealthFuzzy = new FuzzyVariable("EnemyHealth", 0.0, 100.0); enemyHealthFuzzy.Terms.Add(new FuzzyTerm("NearDead", new TrapezoidMembershipFunction(0, 0, 20, 40))); enemyHealthFuzzy.Terms.Add(new FuzzyTerm("Injured", new TrapezoidMembershipFunction(30, 50, 50, 70))); enemyHealthFuzzy.Terms.Add(new FuzzyTerm("Normal", new TrapezoidMembershipFunction(50, 80, 100, 100))); fuzzyEngine.Input.Add(enemyHealthFuzzy); - FuzzyVariable relativeAttackPowerFuzzy = new FuzzyVariable("RelativeAttackPower", 0.0, 1000.0); + var relativeAttackPowerFuzzy = new FuzzyVariable("RelativeAttackPower", 0.0, 1000.0); relativeAttackPowerFuzzy.Terms.Add(new FuzzyTerm("Weak", new TrapezoidMembershipFunction(0, 0, 70, 90))); relativeAttackPowerFuzzy.Terms.Add(new FuzzyTerm("Equal", new TrapezoidMembershipFunction(85, 100, 100, 115))); relativeAttackPowerFuzzy.Terms.Add(new FuzzyTerm("Strong", new TrapezoidMembershipFunction(110, 150, 150, 1000))); fuzzyEngine.Input.Add(relativeAttackPowerFuzzy); - FuzzyVariable relativeSpeedFuzzy = new FuzzyVariable("RelativeSpeed", 0.0, 1000.0); + var relativeSpeedFuzzy = new FuzzyVariable("RelativeSpeed", 0.0, 1000.0); relativeSpeedFuzzy.Terms.Add(new FuzzyTerm("Slow", new TrapezoidMembershipFunction(0, 0, 70, 90))); relativeSpeedFuzzy.Terms.Add(new FuzzyTerm("Equal", new TrapezoidMembershipFunction(85, 100, 100, 115))); relativeSpeedFuzzy.Terms.Add(new FuzzyTerm("Fast", new TrapezoidMembershipFunction(110, 150, 150, 1000))); fuzzyEngine.Input.Add(relativeSpeedFuzzy); - FuzzyVariable attackOrFleeFuzzy = new FuzzyVariable("AttackOrFlee", 0.0, 50.0); + var attackOrFleeFuzzy = new FuzzyVariable("AttackOrFlee", 0.0, 50.0); attackOrFleeFuzzy.Terms.Add(new FuzzyTerm("Attack", new TrapezoidMembershipFunction(0, 15, 15, 30))); attackOrFleeFuzzy.Terms.Add(new FuzzyTerm("Flee", new TrapezoidMembershipFunction(25, 35, 35, 50))); fuzzyEngine.Output.Add(attackOrFleeFuzzy); @@ -79,123 +71,124 @@ namespace OpenRA.Mods.RA.AI protected virtual void AddingRulesForNormalOwnHealth() { - //1 OwnHealth is Normal - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Normal) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack")); + AddFuzzyRule("if ((OwnHealth is Normal) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack"); } protected virtual void AddingRulesForInjuredOwnHealth() { - //OwnHealth is Injured - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Injured) " + - "and (EnemyHealth is NearDead) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack")); + AddFuzzyRule("if ((OwnHealth is Injured) " + + "and (EnemyHealth is NearDead) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack"); - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack")); + AddFuzzyRule("if ((OwnHealth is Injured) " + + "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack"); - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and (RelativeAttackPower is Weak) " + - "and (RelativeSpeed is Slow)) " + - "then AttackOrFlee is Attack")); + AddFuzzyRule("if ((OwnHealth is Injured) " + + "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and (RelativeAttackPower is Weak) " + + "and (RelativeSpeed is Slow)) " + + "then AttackOrFlee is Attack"); - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee")); + AddFuzzyRule("if ((OwnHealth is Injured) " + + "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee"); - //2 - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and (RelativeSpeed is Slow)) " + - "then AttackOrFlee is Attack")); + AddFuzzyRule("if ((OwnHealth is Injured) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and (RelativeSpeed is Slow)) " + + "then AttackOrFlee is Attack"); } protected virtual void AddingRulesForNearDeadOwnHealth() { - //3 OwnHealth is NearDead - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is Injured) " + - "and (RelativeAttackPower is Equal) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal))) " + - "then AttackOrFlee is Attack")); - //4 - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is NearDead) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee")); - //5 - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is Injured) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee")); + AddFuzzyRule("if ((OwnHealth is NearDead) " + + "and (EnemyHealth is Injured) " + + "and (RelativeAttackPower is Equal) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal))) " + + "then AttackOrFlee is Attack"); - //6 - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is Normal) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee")); + AddFuzzyRule("if ((OwnHealth is NearDead) " + + "and (EnemyHealth is NearDead) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee"); - //7 - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if (OwnHealth is NearDead) " + - "and (EnemyHealth is Normal) " + - "and (RelativeAttackPower is Equal) " + - "and (RelativeSpeed is Fast) " + - "then AttackOrFlee is Flee")); - //8 - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if (OwnHealth is NearDead) " + - "and (EnemyHealth is Normal) " + - "and (RelativeAttackPower is Strong) " + - "and (RelativeSpeed is Fast) " + - "then AttackOrFlee is Flee")); + AddFuzzyRule("if ((OwnHealth is NearDead) " + + "and (EnemyHealth is Injured) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee"); - //9 - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if (OwnHealth is NearDead) " + - "and (EnemyHealth is Injured) " + - "and (RelativeAttackPower is Equal) " + - "and (RelativeSpeed is Fast) " + - "then AttackOrFlee is Flee")); + AddFuzzyRule("if ((OwnHealth is NearDead) " + + "and (EnemyHealth is Normal) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee"); + + AddFuzzyRule("if (OwnHealth is NearDead) " + + "and (EnemyHealth is Normal) " + + "and (RelativeAttackPower is Equal) " + + "and (RelativeSpeed is Fast) " + + "then AttackOrFlee is Flee"); + + AddFuzzyRule("if (OwnHealth is NearDead) " + + "and (EnemyHealth is Normal) " + + "and (RelativeAttackPower is Strong) " + + "and (RelativeSpeed is Fast) " + + "then AttackOrFlee is Flee"); + + AddFuzzyRule("if (OwnHealth is NearDead) " + + "and (EnemyHealth is Injured) " + + "and (RelativeAttackPower is Equal) " + + "and (RelativeSpeed is Fast) " + + "then AttackOrFlee is Flee"); } - public void CalculateFuzzy(List ownUnits, List enemyUnits) + + public bool CanAttack(IEnumerable ownUnits, IEnumerable enemyUnits) { - Dictionary inputValues = new Dictionary(); + var inputValues = new Dictionary(); inputValues.Add(fuzzyEngine.InputByName("OwnHealth"), (double)NormalizedHealth(ownUnits, 100)); inputValues.Add(fuzzyEngine.InputByName("EnemyHealth"), (double)NormalizedHealth(enemyUnits, 100)); inputValues.Add(fuzzyEngine.InputByName("RelativeAttackPower"), (double)RelativePower(ownUnits, enemyUnits)); inputValues.Add(fuzzyEngine.InputByName("RelativeSpeed"), (double)RelativeSpeed(ownUnits, enemyUnits)); - result = fuzzyEngine.Calculate(inputValues); + var result = fuzzyEngine.Calculate(inputValues); + var attackChance = result[fuzzyEngine.OutputByName("AttackOrFlee")]; + return !double.IsNaN(attackChance) && attackChance < 30.0; } - protected float NormalizedHealth(List actors, float normalizeByValue) + protected float NormalizedHealth(IEnumerable actors, float normalizeByValue) { - int sumOfMaxHp = 0; - int sumOfHp = 0; + var sumOfMaxHp = 0; + var sumOfHp = 0; foreach (var a in actors) + { if (a.HasTrait()) { sumOfMaxHp += a.Trait().MaxHP; sumOfHp += a.Trait().HP; } - if (sumOfMaxHp == 0) return 0.0f; + } + + if (sumOfMaxHp == 0) + return 0.0f; + return (sumOfHp * normalizeByValue) / sumOfMaxHp; } - protected float RelativePower(List own, List enemy) + protected float RelativePower(IEnumerable own, IEnumerable enemy) { return RelativeValue(own, enemy, 100, SumOfValues, (Actor a) => { @@ -208,43 +201,50 @@ namespace OpenRA.Mods.RA.AI }); } - protected float RelativeSpeed(List own, List enemy) + protected float RelativeSpeed(IEnumerable own, IEnumerable enemy) { return RelativeValue(own, enemy, 100, Average, (Actor a) => a.Trait().Info.Speed); } - protected float RelativeValue(List own, List enemy, float normalizeByValue, - Func, Func, float> relativeFunc, Func getValue) + protected float RelativeValue(IEnumerable own, IEnumerable enemy, float normalizeByValue, + Func, Func, float> relativeFunc, Func getValue) { - if (enemy.Count == 0) + if (!enemy.Any()) return 999.0f; - if (own.Count == 0) + + if (!own.Any()) return 0.0f; - float relative = (relativeFunc(own, getValue) / relativeFunc(enemy, getValue)) * normalizeByValue; + var relative = (relativeFunc(own, getValue) / relativeFunc(enemy, getValue)) * normalizeByValue; return relative.Clamp(0.0f, 999.0f); } - protected float SumOfValues(List actors, Func getValue) + protected float SumOfValues(IEnumerable actors, Func getValue) { - int sum = 0; + var sum = 0; foreach (var a in actors) if (a.HasTrait()) sum += getValue(a); + return sum; } - protected float Average(List actors, Func getValue) + protected float Average(IEnumerable actors, Func getValue) { - int sum = 0; - int countActors = 0; + var sum = 0; + var countActors = 0; foreach (var a in actors) + { if (a.HasTrait()) { sum += getValue(a); countActors++; } - if (countActors == 0) return 0.0f; + } + + if (countActors == 0) + return 0.0f; + return sum / countActors; } } diff --git a/OpenRA.Mods.RA/AI/BaseBuilder.cs b/OpenRA.Mods.RA/AI/BaseBuilder.cs index 312870e9b0..fd7d153fd9 100644 --- a/OpenRA.Mods.RA/AI/BaseBuilder.cs +++ b/OpenRA.Mods.RA/AI/BaseBuilder.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2013 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, @@ -9,13 +9,7 @@ #endregion using System; -using System.Collections.Generic; using System.Linq; -using OpenRA.FileFormats; -using OpenRA.Mods.RA.Buildings; -using OpenRA.Traits; -using OpenRA.Mods.RA.Activities; -using XRandom = OpenRA.Thirdparty.Random; namespace OpenRA.Mods.RA.AI { @@ -39,70 +33,69 @@ namespace OpenRA.Mods.RA.AI public void Tick() { // Pick a free queue - var queue = ai.FindQueues( category ).FirstOrDefault(); + var queue = ai.FindQueues(category).FirstOrDefault(); if (queue == null) return; var currentBuilding = queue.CurrentItem(); switch (state) { - case BuildState.ChooseItem: + case BuildState.ChooseItem: + var item = chooseItem(queue); + if (item == null) + { + state = BuildState.WaitForFeedback; + lastThinkTick = ai.ticks; + } + else + { + HackyAI.BotDebug("AI: Starting production of {0}".F(item.Name)); + state = BuildState.WaitForProduction; + ai.world.IssueOrder(Order.StartProduction(queue.self, item.Name, 1)); + } + break; + + case BuildState.WaitForProduction: + if (currentBuilding == null) + return; + + if (currentBuilding.Paused) + ai.world.IssueOrder(Order.PauseProduction(queue.self, currentBuilding.Item, false)); + else if (currentBuilding.Done) + { + state = BuildState.WaitForFeedback; + lastThinkTick = ai.ticks; + + // Place the building + var type = BuildingType.Building; + if (Rules.Info[currentBuilding.Item].Traits.Contains()) + type = BuildingType.Defense; + else if (Rules.Info[currentBuilding.Item].Traits.Contains()) + type = BuildingType.Refinery; + + var location = ai.ChooseBuildLocation(currentBuilding.Item, type); + if (location == null) { - var item = chooseItem(queue); - if (item == null) - { - state = BuildState.WaitForFeedback; - lastThinkTick = ai.ticks; - } - else - { - HackyAI.BotDebug("AI: Starting production of {0}".F(item.Name)); - state = BuildState.WaitForProduction; - ai.world.IssueOrder(Order.StartProduction(queue.self, item.Name, 1)); - } + HackyAI.BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item)); + ai.world.IssueOrder(Order.CancelProduction(queue.self, currentBuilding.Item, 1)); } - break; - - case BuildState.WaitForProduction: - if (currentBuilding == null) return; /* let it happen.. */ - - else if (currentBuilding.Paused) - ai.world.IssueOrder(Order.PauseProduction(queue.self, currentBuilding.Item, false)); - else if (currentBuilding.Done) + else { - state = BuildState.WaitForFeedback; - lastThinkTick = ai.ticks; - - /* place the building */ - BuildingType type = BuildingType.Building; - if(Rules.Info[currentBuilding.Item].Traits.Contains()) - type = BuildingType.Defense; - else if(Rules.Info[currentBuilding.Item].Traits.Contains()) - type = BuildingType.Refinery; - - var location = ai.ChooseBuildLocation(currentBuilding.Item, type); - if (location == null) + ai.world.IssueOrder(new Order("PlaceBuilding", ai.p.PlayerActor, false) { - HackyAI.BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item)); - ai.world.IssueOrder(Order.CancelProduction(queue.self, currentBuilding.Item, 1)); - } - else - { - ai.world.IssueOrder(new Order("PlaceBuilding", ai.p.PlayerActor, false) - { - TargetLocation = location.Value, - TargetString = currentBuilding.Item - }); - } + TargetLocation = location.Value, + TargetString = currentBuilding.Item + }); } - break; + } - case BuildState.WaitForFeedback: - if (ai.ticks - lastThinkTick > HackyAI.feedbackTime) - state = BuildState.ChooseItem; - break; + break; + + case BuildState.WaitForFeedback: + if (ai.ticks - lastThinkTick > HackyAI.feedbackTime) + state = BuildState.ChooseItem; + break; } } } } - diff --git a/OpenRA.Mods.RA/AI/HackyAI.cs b/OpenRA.Mods.RA/AI/HackyAI.cs index 7b64fa2795..535d6cd579 100644 --- a/OpenRA.Mods.RA/AI/HackyAI.cs +++ b/OpenRA.Mods.RA/AI/HackyAI.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2013 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, @@ -12,26 +12,25 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.FileFormats; +using OpenRA.Mods.RA.Air; using OpenRA.Mods.RA.Buildings; using OpenRA.Mods.RA.Move; -using OpenRA.Mods.RA.Effects; -using OpenRA.Mods.RA.Air; using OpenRA.Traits; using XRandom = OpenRA.Thirdparty.Random; namespace OpenRA.Mods.RA.AI { - class HackyAIInfo : IBotInfo, ITraitInfo + public class HackyAIInfo : IBotInfo, ITraitInfo { public readonly string Name = "Unnamed Bot"; public readonly int SquadSize = 8; - //intervals public readonly int AssignRolesInterval = 20; public readonly int RushInterval = 600; public readonly int AttackForceInterval = 30; - public readonly string RallypointTestBuilding = "fact"; // temporary hack to maintain previous rallypoint behavior. + // Temporary hack to maintain previous rallypoint behavior. + public readonly string RallypointTestBuilding = "fact"; public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" }; public readonly bool ShouldRepairBuildings = true; @@ -52,16 +51,6 @@ namespace OpenRA.Mods.RA.AI [FieldLoader.LoadUsing("LoadBuildingLimits")] public readonly Dictionary BuildingLimits = null; - static object LoadActorList(MiniYaml y, string field) - { - return LoadList(y, field); - } - - static object LoadListList(MiniYaml y, string field) - { - return LoadList(y,field); - } - static object LoadList(MiniYaml y, string field) { return y.NodesDict.ContainsKey(field) @@ -71,622 +60,25 @@ namespace OpenRA.Mods.RA.AI : new Dictionary(); } - static object LoadUnits(MiniYaml y) { return LoadActorList(y, "UnitsToBuild"); } - static object LoadBuildings(MiniYaml y) { return LoadActorList(y, "BuildingFractions"); } + static object LoadUnits(MiniYaml y) { return LoadList(y, "UnitsToBuild"); } + static object LoadBuildings(MiniYaml y) { return LoadList(y, "BuildingFractions"); } - static object LoadUnitsCommonNames(MiniYaml y) { return LoadListList(y, "UnitsCommonNames"); } - static object LoadBuildingsCommonNames(MiniYaml y) { return LoadListList(y, "BuildingCommonNames"); } + static object LoadUnitsCommonNames(MiniYaml y) { return LoadList(y, "UnitsCommonNames"); } + static object LoadBuildingsCommonNames(MiniYaml y) { return LoadList(y, "BuildingCommonNames"); } static object LoadBuildingLimits(MiniYaml y) { return LoadList(y, "BuildingLimits"); } public object Create(ActorInitializer init) { return new HackyAI(this); } } - /* a pile of hacks, which control a local player on the host. */ + public class Enemy { public int Aggro; } - class Enemy { public int Aggro; } + public enum BuildingType { Building, Defense, Refinery } - enum SquadType { Assault, Air, Rush, Protection } - - enum BuildingType { Building, Defense, Refinery } - - class Squad + public class HackyAI : ITick, IBot, INotifyDamage { - public List units = new List(); - public SquadType type; + static readonly List NoActors = new List(); - World world; - HackyAI bot; - XRandom random; - - Target target; - StateMachine fsm; - - //fuzzy - AttackOrFleeFuzzy attackOrFleeFuzzy = new AttackOrFleeFuzzy(); - - public Squad(HackyAI bot, SquadType type) : this(bot, type, null) { } - - public Squad(HackyAI bot, SquadType type, Actor target) - { - this.bot = bot; - this.world = bot.world; - this.random = bot.random; - this.type = type; - this.target = Traits.Target.FromActor(target); - - fsm = new StateMachine(this); - - switch (type) - { - case SquadType.Assault: - case SquadType.Rush: - fsm.ChangeState(new GroundUnitsIdleState(), true); - break; - case SquadType.Air: - fsm.ChangeState(new AirIdleState(), true); - break; - case SquadType.Protection: - fsm.ChangeState(new UnitsForProtectionIdleState(), true); - break; - } - } - - public void Update() - { - if (IsEmpty) return; - fsm.UpdateFsm(); - } - - public bool IsEmpty - { - get { return !units.Any(); } - } - - public Actor Target - { - get { return target.Actor; } - set { target = Traits.Target.FromActor(value); } - } - - public bool TargetIsValid - { - get { return target.IsValidFor(units.FirstOrDefault()) && !target.Actor.HasTrait(); } - } - - //********************************************************************************** - // Squad AI States - - /* Include general functional for all states */ - - abstract class StateBase - { - protected const int dangerRadius = 10; - - protected virtual bool MayBeFlee(Squad owner, Func, bool> flee) - { - if (owner.IsEmpty) return false; - var u = owner.units.Random(owner.random); - - var units = owner.world.FindActorsInCircle(u.CenterPosition, WRange.FromCells(dangerRadius)).ToList(); - var ownBaseBuildingAround = units.Where(unit => unit.Owner == owner.bot.p && unit.HasTrait()).ToList(); - if (ownBaseBuildingAround.Count > 0) return false; - - var enemyAroundUnit = units.Where(unit => owner.bot.p.Stances[unit.Owner] == Stance.Enemy && unit.HasTrait()).ToList(); - if (!enemyAroundUnit.Any()) return false; - - return flee(enemyAroundUnit); - } - - protected static CPos? AverageUnitsPosition(List units) - { - int x = 0; - int y = 0; - int countUnits = 0; - foreach (var u in units) - { - x += u.Location.X; - y += u.Location.Y; - countUnits++; - } - x = x / countUnits; - y = y / countUnits; - return (x != 0 && y != 0) ? new CPos?(new CPos(x, y)) : null; - } - - protected static void GoToRandomOwnBuilding(Squad owner) - { - var loc = RandomBuildingLocation(owner); - foreach (var a in owner.units) - owner.world.IssueOrder(new Order("Move", a, false) { TargetLocation = loc }); - } - - protected static CPos RandomBuildingLocation(Squad owner) - { - var location = owner.bot.baseCenter; - var buildings = owner.world.ActorsWithTrait() - .Where(a => a.Actor.Owner == owner.bot.p).Select(a => a.Actor).ToArray(); - if (buildings.Length > 0) - location = buildings.Random(owner.random).Location; - return location; - } - - protected static bool BusyAttack(Actor a) - { - if (!a.IsIdle) - if (a.GetCurrentActivity().GetType() == typeof(OpenRA.Mods.RA.Activities.Attack) || - a.GetCurrentActivity().GetType() == typeof(FlyAttack) || - (a.GetCurrentActivity().NextActivity != null && - (a.GetCurrentActivity().NextActivity.GetType() == typeof(OpenRA.Mods.RA.Activities.Attack) || - a.GetCurrentActivity().NextActivity.GetType() == typeof(FlyAttack)) ) - ) - return true; - return false; - } - - protected static bool CanAttackTarget(Actor a, Actor target) - { - if (!a.HasTrait()) - return false; - - var targetable = target.TraitOrDefault(); - if (targetable == null) - return false; - - var arms = a.TraitsImplementing(); - foreach (var arm in arms) - if (arm.Weapon.ValidTargets.Intersect(targetable.TargetTypes) != null) - return true; - - return false; - } - } - - /* States for air units */ - - abstract class AirStateBase : StateBase - { - protected const int missileUnitsMultiplier = 3; - - protected static int CountAntiAirUnits(List units) - { - int missileUnitsCount = 0; - foreach (var unit in units) - if (unit != null && unit.HasTrait() && !unit.HasTrait() - && !unit.IsDisabled()) - { - var arms = unit.TraitsImplementing(); - foreach (var a in arms) - if (a.Weapon.ValidTargets.Contains("Air")) - { - missileUnitsCount++; - break; - } - } - return missileUnitsCount; - } - - //checks the number of anti air enemies around units - protected virtual bool MayBeFlee(Squad owner) - { - return base.MayBeFlee(owner, (enemyAroundUnit) => - { - int missileUnitsCount = 0; - if (enemyAroundUnit.Count > 0) - missileUnitsCount = CountAntiAirUnits(enemyAroundUnit); - - if (missileUnitsCount * missileUnitsMultiplier > owner.units.Count) - return true; - - return false; - }); - } - - protected static Actor FindDefenselessTarget(Squad owner) - { - Actor target = null; - FindSafePlace(owner, out target, true); - - return target == null ? null : target; - } - - protected static CPos? FindSafePlace(Squad owner, out Actor detectedEnemyTarget, bool needTarget) - { - World world = owner.world; - detectedEnemyTarget = null; - int x = (world.Map.MapSize.X % dangerRadius) == 0 ? world.Map.MapSize.X : world.Map.MapSize.X + dangerRadius; - int y = (world.Map.MapSize.Y % dangerRadius) == 0 ? world.Map.MapSize.Y : world.Map.MapSize.Y + dangerRadius; - - for (int i = 0; i < x; i += dangerRadius * 2) - for (int j = 0; j < y; j += dangerRadius * 2) - { - CPos pos = new CPos(i, j); - if (NearToPosSafely(owner, pos.CenterPosition, out detectedEnemyTarget)) - { - if (needTarget) - { - if (detectedEnemyTarget == null) - continue; - else - return pos; - } - return pos; - } - } - return null; - } - - protected static bool NearToPosSafely(Squad owner, WPos loc) - { - Actor a; - return NearToPosSafely(owner, loc, out a); - } - - protected static bool NearToPosSafely(Squad owner, WPos loc, out Actor detectedEnemyTarget) - { - detectedEnemyTarget = null; - var unitsAroundPos = owner.world.FindActorsInCircle(loc, WRange.FromCells(dangerRadius)) - .Where(unit => owner.bot.p.Stances[unit.Owner] == Stance.Enemy).ToList(); - - int missileUnitsCount = 0; - if (unitsAroundPos.Count > 0) - { - missileUnitsCount = CountAntiAirUnits(unitsAroundPos); - if (missileUnitsCount * missileUnitsMultiplier < owner.units.Count) - { - detectedEnemyTarget = unitsAroundPos.Random(owner.random); - return true; - } - else - return false; - } - return true; - } - - protected static bool FullAmmo(Actor a) - { - var limitedAmmo = a.TraitOrDefault(); - return (limitedAmmo != null && limitedAmmo.FullAmmo()); - } - - protected static bool HasAmmo(Actor a) - { - var limitedAmmo = a.TraitOrDefault(); - return (limitedAmmo != null && limitedAmmo.HasAmmo()); - } - - protected static bool IsReloadable(Actor a) - { - return a.HasTrait(); - } - - protected static bool IsRearm(Actor a) - { - if (a.GetCurrentActivity() == null) return false; - if (a.GetCurrentActivity().GetType() == typeof(OpenRA.Mods.RA.Activities.Rearm) || - a.GetCurrentActivity().GetType() == typeof(ResupplyAircraft) || - (a.GetCurrentActivity().NextActivity != null && - (a.GetCurrentActivity().NextActivity.GetType() == typeof(OpenRA.Mods.RA.Activities.Rearm) || - a.GetCurrentActivity().NextActivity.GetType() == typeof(ResupplyAircraft))) - ) - return true; - return false; - } - } - - class AirIdleState : AirStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - - if (MayBeFlee(owner)) - { - owner.fsm.ChangeState(new AirFleeState(), true); - return; - } - - var e = FindDefenselessTarget(owner); - if (e == null) - return; - else - { - owner.Target = e; - owner.fsm.ChangeState(new AirAttackState(), true); - } - } - - public void Exit(Squad owner) { } - } - - class AirAttackState : AirStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - - if (!owner.TargetIsValid) - { - var a = owner.units.Random(owner.random); - var closestEnemy = owner.bot.FindClosestEnemy(a.CenterPosition); - if (closestEnemy != null) - owner.Target = closestEnemy; - else - { - owner.fsm.ChangeState(new AirFleeState(), true); - return; - } - } - - if (!NearToPosSafely(owner, owner.Target.CenterPosition)) - { - owner.fsm.ChangeState(new AirFleeState(), true); - return; - } - - foreach (var a in owner.units) - { - if (BusyAttack(a)) - continue; - if (!IsReloadable(a)) - { - if (!HasAmmo(a)) - { - if (IsRearm(a)) - continue; - owner.world.IssueOrder(new Order("ReturnToBase", a, false)); - continue; - } - if (IsRearm(a)) - continue; - } - if (owner.Target.HasTrait() && CanAttackTarget(a, owner.Target)) - owner.world.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.Target }); - } - } - - public void Exit(Squad owner) { } - } - - class AirFleeState : AirStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - - foreach (var a in owner.units) - { - if (!IsReloadable(a)) - if (!FullAmmo(a)) - { - if (IsRearm(a)) - continue; - owner.world.IssueOrder(new Order("ReturnToBase", a, false)); - continue; - } - owner.world.IssueOrder(new Order("Move", a, false) { TargetLocation = RandomBuildingLocation(owner) }); - } - owner.fsm.ChangeState(new AirIdleState(), true); - } - - public void Exit(Squad owner) { } - } - - /* States for ground units */ - - abstract class GroundStateBase : StateBase - { - protected virtual bool MayBeFlee(Squad owner) - { - return base.MayBeFlee(owner, (enemyAroundUnit) => - { - owner.attackOrFleeFuzzy.CalculateFuzzy(owner.units, enemyAroundUnit); - if (!owner.attackOrFleeFuzzy.CanAttack) - return true; - - return false; - }); - } - } - - class GroundUnitsIdleState : GroundStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - if (!owner.TargetIsValid) - { - var t = owner.bot.FindClosestEnemy(owner.units.FirstOrDefault().CenterPosition); - if (t == null) return; - owner.Target = t; - } - - var enemyUnits = owner.world.FindActorsInCircle(owner.Target.CenterPosition, WRange.FromCells(10)) - .Where(unit => owner.bot.p.Stances[unit.Owner] == Stance.Enemy).ToList(); - if (enemyUnits.Any()) - - { - owner.attackOrFleeFuzzy.CalculateFuzzy(owner.units, enemyUnits); - if (owner.attackOrFleeFuzzy.CanAttack) - { - foreach(var u in owner.units) - owner.world.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.Target.CenterPosition.ToCPos() }); - // We have gathered sufficient units. Attack the nearest enemy unit. - owner.fsm.ChangeState(new GroundUnitsAttackMoveState(), true); - return; - } - else - owner.fsm.ChangeState(new GroundUnitsFleeState(), true); - } - } - - public void Exit(Squad owner) { } - } - - class GroundUnitsAttackMoveState : GroundStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - - if (!owner.TargetIsValid) - { - var closestEnemy = owner.bot.FindClosestEnemy(owner.units.Random(owner.random).CenterPosition); - if (closestEnemy != null) - owner.Target = closestEnemy; - else - { - owner.fsm.ChangeState(new GroundUnitsFleeState(), true); - return; - } - } - - Actor leader = owner.units.ClosestTo(owner.Target.CenterPosition); - if (leader == null) - return; - var ownUnits = owner.world.FindActorsInCircle(leader.CenterPosition, WRange.FromCells(owner.units.Count) / 3) - .Where(a => a.Owner == owner.units.FirstOrDefault().Owner && owner.units.Contains(a)).ToList(); - if (ownUnits.Count < owner.units.Count) - { - owner.world.IssueOrder(new Order("Stop", leader, false)); - foreach (var unit in owner.units.Where(a => !ownUnits.Contains(a))) - owner.world.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.CenterPosition.ToCPos() }); - } - else - { - var enemys = owner.world.FindActorsInCircle(leader.CenterPosition, WRange.FromCells(12)) - .Where(a1 => !a1.Destroyed && !a1.IsDead()).ToList(); - var enemynearby = enemys.Where(a1 => a1.HasTrait() && leader.Owner.Stances[a1.Owner] == Stance.Enemy).ToList(); - if (enemynearby.Any()) - { - owner.Target = enemynearby.ClosestTo(leader.CenterPosition); - owner.fsm.ChangeState(new GroundUnitsAttackState(), true); - return; - } - else - foreach (var a in owner.units) - owner.world.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.Target.Location }); - } - - if (MayBeFlee(owner)) - { - owner.fsm.ChangeState(new GroundUnitsFleeState(), true); - return; - } - } - - public void Exit(Squad owner) { } - } - - class GroundUnitsAttackState : GroundStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - - if (!owner.TargetIsValid) - { - var closestEnemy = owner.bot.FindClosestEnemy(owner.units.Random(owner.random).CenterPosition); - if (closestEnemy != null) - owner.Target = closestEnemy; - else - { - owner.fsm.ChangeState(new GroundUnitsFleeState(), true); - return; - } - } - foreach (var a in owner.units) - if (!BusyAttack(a)) - owner.world.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.bot.FindClosestEnemy(a.CenterPosition) }); - - if (MayBeFlee(owner)) - { - owner.fsm.ChangeState(new GroundUnitsFleeState(), true); - return; - } - } - - public void Exit(Squad owner) { } - } - - class GroundUnitsFleeState : GroundStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - - GoToRandomOwnBuilding(owner); - owner.fsm.ChangeState(new GroundUnitsIdleState(), true); - } - - public void Exit(Squad owner) { owner.units.Clear(); } - } - - class UnitsForProtectionIdleState : GroundStateBase, IState - { - public void Enter(Squad owner) { } - public void Execute(Squad owner) { owner.fsm.ChangeState(new UnitsForProtectionAttackState(), true); } - public void Exit(Squad owner) { } - } - - class UnitsForProtectionAttackState : GroundStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - if (!owner.TargetIsValid) - { - var circaPostion = AverageUnitsPosition(owner.units); - if (circaPostion == null) return; - owner.Target = owner.bot.FindClosestEnemy(circaPostion.Value.CenterPosition, WRange.FromCells(8)); - - if (owner.Target == null) - { - owner.fsm.ChangeState(new UnitsForProtectionFleeState(), true); - return; - } - } - foreach (var a in owner.units) - owner.world.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.Target.Location }); - } - - public void Exit(Squad owner) { } - } - - class UnitsForProtectionFleeState : GroundStateBase, IState - { - public void Enter(Squad owner) { } - - public void Execute(Squad owner) - { - if (owner.IsEmpty) return; - - GoToRandomOwnBuilding(owner); - owner.fsm.ChangeState(new UnitsForProtectionIdleState(), true); - } - - public void Exit(Squad owner) { owner.units.Clear(); } - } - } - - class HackyAI : ITick, IBot, INotifyDamage - { bool enabled; public int ticks; public Player p; @@ -695,27 +87,34 @@ namespace OpenRA.Mods.RA.AI PowerManager playerPower; SupportPowerManager supportPowerMngr; PlayerResources playerResource; - readonly BuildingInfo rallypointTestBuilding; // temporary hack + BuildingInfo rallypointTestBuilding; internal readonly HackyAIInfo Info; string[] resourceTypes; RushFuzzy rushFuzzy = new RushFuzzy(); - Cache aggro = new Cache( _ => new Enemy() ); + Cache aggro = new Cache(_ => new Enemy()); BaseBuilder[] builders; + List squads = new List(); + List unitsHangingAroundTheBase = new List(); + + // Units that the ai already knows about. Any unit not on this list needs to be given a role. + List activeUnits = new List(); + const int MaxBaseDistance = 40; public const int feedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag. public World world { get { return p.PlayerActor.World; } } IBotInfo IBot.Info { get { return this.Info; } } - public HackyAI(HackyAIInfo Info) + public HackyAI(HackyAIInfo info) { - this.Info = Info; - // temporary hack. - this.rallypointTestBuilding = Rules.Info[Info.RallypointTestBuilding].Traits.Get(); + Info = info; + + // Temporary hack. + rallypointTestBuilding = Rules.Info[Info.RallypointTestBuilding].Traits.Get(); } public static void BotDebug(string s, params object[] args) @@ -724,7 +123,7 @@ namespace OpenRA.Mods.RA.AI Game.Debug(s, args); } - /* called by the host's player creation code */ + // Called by the host's player creation code public void Activate(Player p) { this.p = p; @@ -733,8 +132,9 @@ namespace OpenRA.Mods.RA.AI supportPowerMngr = p.PlayerActor.Trait(); playerResource = p.PlayerActor.Trait(); builders = new BaseBuilder[] { - new BaseBuilder( this, "Building", q => ChooseBuildingToBuild(q, false) ), - new BaseBuilder( this, "Defense", q => ChooseBuildingToBuild(q, true) ) }; + new BaseBuilder(this, "Building", q => ChooseBuildingToBuild(q, false)), + new BaseBuilder(this, "Defense", q => ChooseBuildingToBuild(q, true)) + }; random = new XRandom((int)p.PlayerActor.ActorID); @@ -745,24 +145,24 @@ namespace OpenRA.Mods.RA.AI int GetPowerProvidedBy(ActorInfo building) { var bi = building.Traits.GetOrDefault(); - if (bi == null) return 0; - return bi.Power; + return bi != null ? bi.Power : 0; } ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); - if (!buildableThings.Any()) return null; + if (!buildableThings.Any()) + return null; + var unit = buildableThings.ElementAtOrDefault(random.Next(buildableThings.Count())); - if (HasAdequateAirUnits(unit)) - return unit; - return null; + return HasAdequateAirUnits(unit) ? unit : null; } ActorInfo ChooseUnitToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); - if (!buildableThings.Any()) return null; + if (!buildableThings.Any()) + return null; var myUnits = p.World .ActorsWithTrait() @@ -780,26 +180,33 @@ namespace OpenRA.Mods.RA.AI int CountBuilding(string frac, Player owner) { - return world.ActorsWithTrait().Where(a => a.Actor.Owner == owner && a.Actor.Info.Name == frac).Count(); + return world.ActorsWithTrait() + .Where(a => a.Actor.Owner == owner && a.Actor.Info.Name == frac) + .Count(); } int CountUnits(string unit, Player owner) { - return world.ActorsWithTrait().Where(a => a.Actor.Owner == owner && a.Actor.Info.Name == unit).Count(); + return world.ActorsWithTrait() + .Where(a => a.Actor.Owner == owner && a.Actor.Info.Name == unit) + .Count(); } int? CountBuildingByCommonName(string commonName, Player owner) { - if(Info.BuildingCommonNames.ContainsKey(commonName)) - return world.ActorsWithTrait() - .Where(a => a.Actor.Owner == owner && Info.BuildingCommonNames[commonName].Contains(a.Actor.Info.Name)).Count(); - return null; + if (!Info.BuildingCommonNames.ContainsKey(commonName)) + return null; + + return world.ActorsWithTrait() + .Where(a => a.Actor.Owner == owner && Info.BuildingCommonNames[commonName].Contains(a.Actor.Info.Name)) + .Count(); } ActorInfo GetBuildingInfoByCommonName(string commonName, Player owner) { if (commonName == "ConstructionYard") return Rules.Info.Where(k => Info.BuildingCommonNames[commonName].Contains(k.Key)).Random(random).Value; + return GetInfoByCommonName(Info.BuildingCommonNames, commonName, owner); } @@ -810,52 +217,52 @@ namespace OpenRA.Mods.RA.AI ActorInfo GetInfoByCommonName(Dictionary names, string commonName, Player owner) { - if (!names.Any() || !names.ContainsKey(commonName)) return null; + if (!names.Any() || !names.ContainsKey(commonName)) + return null; + return Rules.Info.Where(k => names[commonName].Contains(k.Key) && - k.Value.Traits.Get().Owner.Contains(owner.Country.Race)).Random(random).Value; //random is shit + k.Value.Traits.Get().Owner.Contains(owner.Country.Race)).Random(random).Value; } bool HasAdequatePower() { - /* note: CNC `fact` provides a small amount of power. don't get jammed because of that. */ + // note: CNC `fact` provides a small amount of power. don't get jammed because of that. return playerPower.PowerProvided > 50 && playerPower.PowerProvided > playerPower.PowerDrained * 1.2; } bool HasAdequateFact() { - if (CountBuildingByCommonName("ConstructionYard", p) == 0 && CountBuildingByCommonName("VehiclesFactory", p) > 0) - return false; - return true; + // Require at least one construction yard, unless we have no vehicles factory (can't build it). + return CountBuildingByCommonName("ConstructionYard", p) > 0 || + CountBuildingByCommonName("VehiclesFactory", p) == 0; } bool HasAdequateProc() { - if (CountBuildingByCommonName("Refinery", p) == 0 && CountBuildingByCommonName("Power", p) > 0) - return false; - return true; + // Require at least one refinery, unless we have no power (can't build it). + return CountBuildingByCommonName("Refinery", p) > 0 || + CountBuildingByCommonName("Power", p) == 0; } bool HasMinimumProc() { - if (CountBuildingByCommonName("Refinery", p) < 2 && CountBuildingByCommonName("Power", p) > 0 && - CountBuildingByCommonName("Barracks",p) > 0) - return false; - return true; + // Require at least two refineries, unless we have no power (can't build it) + // or barracks (higher priority?) + return CountBuildingByCommonName("Refinery", p) >= 2 || + CountBuildingByCommonName("Power", p) == 0 || + CountBuildingByCommonName("Barracks", p) == 0; } bool HasAdequateNumber(string frac, Player owner) { if (Info.BuildingLimits.ContainsKey(frac)) - if (CountBuilding(frac, owner) < Info.BuildingLimits[frac]) - return true; - else - return false; + return CountBuilding(frac, owner) < Info.BuildingLimits[frac]; return true; } - //for mods like RA (number of building must match the number of aircraft) + // For mods like RA (number of building must match the number of aircraft) bool HasAdequateAirUnits(ActorInfo actorInfo) { if (!actorInfo.Traits.Contains() && actorInfo.Traits.Contains() @@ -866,6 +273,7 @@ namespace OpenRA.Mods.RA.AI if (countOwnAir >= countBuildings) return false; } + return true; } @@ -875,8 +283,8 @@ namespace OpenRA.Mods.RA.AI if (!isDefense) { - if (!HasAdequatePower()) /* try to maintain 20% excess power */ - /* find the best thing we can build which produces power */ + // Try to maintain 20% excess power + if (!HasAdequatePower()) return buildableThings.Where(a => GetPowerProvidedBy(a) > 0) .OrderByDescending(a => GetPowerProvidedBy(a)).FirstOrDefault(); @@ -886,10 +294,12 @@ namespace OpenRA.Mods.RA.AI if (!HasAdequateProc() || !HasMinimumProc()) return GetBuildingInfoByCommonName("Refinery", p); } + var myBuildings = p.World .ActorsWithTrait() - .Where( a => a.Actor.Owner == p ) - .Select(a => a.Actor.Info.Name).ToArray(); + .Where(a => a.Actor.Owner == p) + .Select(a => a.Actor.Info.Name) + .ToArray(); foreach (var frac in Info.BuildingFractions) if (buildableThings.Any(b => b.Name == frac.Key)) @@ -914,25 +324,28 @@ namespace OpenRA.Mods.RA.AI public CPos? ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, int maxBaseDistance, BuildingType type) { - var bi = Rules.Info[actorType].Traits.Get(); - if (bi == null) return null; + var bi = Rules.Info[actorType].Traits.GetOrDefault(); + if (bi == null) + return null; - Func findPos = (WPos pos, CPos center) => + Func findPos = (pos, center) => { for (var k = MaxBaseDistance; k >= 0; k--) { var tlist = world.FindTilesInCircle(center, k) .OrderBy(a => (a.CenterPosition - pos).LengthSquared); + foreach (var t in tlist) if (world.CanPlaceBuilding(actorType, bi, t, null)) if (bi.IsCloseEnoughToBase(world, p, actorType, t)) if (NoBuildingsUnder(Util.ExpandFootprint(FootprintUtils.Tiles(actorType, bi, t), false))) return t; } + return null; }; - switch(type) + switch (type) { case BuildingType.Defense: Actor enemyBase = FindEnemyBuildingClosestToPos(baseCenter.CenterPosition); @@ -946,19 +359,25 @@ namespace OpenRA.Mods.RA.AI case BuildingType.Building: for (var k = 0; k < maxBaseDistance; k++) + { foreach (var t in world.FindTilesInCircle(baseCenter, k)) + { if (world.CanPlaceBuilding(actorType, bi, t, null)) { - if (distanceToBaseIsImportant) - if (!bi.IsCloseEnoughToBase(world, p, actorType, t)) - continue; + if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, p, actorType, t)) + continue; + if (NoBuildingsUnder(Util.ExpandFootprint(FootprintUtils.Tiles(actorType, bi, t), false))) return t; } + } + } + break; } - return null; // i don't know where to put it. + // Can't find a build location + return null; } public void Tick(Actor self) @@ -969,7 +388,7 @@ namespace OpenRA.Mods.RA.AI ticks++; if (ticks == 1) - DeployMcv(self); + InitializeBase(self); if (ticks % feedbackTime == 0) ProductionUnits(self); @@ -984,9 +403,11 @@ namespace OpenRA.Mods.RA.AI internal Actor ChooseEnemyTarget() { + if (p.WinState != WinState.Undefined) + return null; + var liveEnemies = world.Players - .Where(q => p != q && p.Stances[q] == Stance.Enemy) - .Where(q => p.WinState == WinState.Undefined && q.WinState == WinState.Undefined); + .Where(q => p != q && p.Stances[q] == Stance.Enemy && q.WinState == WinState.Undefined); if (!liveEnemies.Any()) return null; @@ -996,19 +417,13 @@ namespace OpenRA.Mods.RA.AI .OrderByDescending(g => g.Key) .FirstOrDefault(); - Player enemy; - if (leastLikedEnemies == null) - enemy = liveEnemies.FirstOrDefault(); - else - enemy = leastLikedEnemies.Random(random); + var enemy = (leastLikedEnemies != null) ? + leastLikedEnemies.Random(random) : liveEnemies.FirstOrDefault(); - /* pick something worth attacking owned by that player */ - var targets = world.Actors - .Where(a => a.Owner == enemy && a.HasTrait()); - Actor target = null; - - if (targets.Any()) - target = targets.ClosestTo(baseCenter.CenterPosition); + // Pick something worth attacking owned by that player + var target = world.Actors + .Where(a => a.Owner == enemy && a.HasTrait()) + .ClosestTo(baseCenter.CenterPosition); if (target == null) { @@ -1019,7 +434,7 @@ namespace OpenRA.Mods.RA.AI return null; } - /* bump the aggro slightly to avoid changing our mind */ + // Bump the aggro slightly to avoid changing our mind if (leastLikedEnemies.Count() > 1) aggro[enemy].Aggro++; @@ -1034,17 +449,19 @@ namespace OpenRA.Mods.RA.AI if (allEnemyUnits.Count > 0) return allEnemyUnits.ClosestTo(pos); + return null; } internal Actor FindClosestEnemy(WPos pos, WRange radius) { var enemyUnits = world.FindActorsInCircle(pos, radius) - .Where(unit => p.Stances[unit.Owner] == Stance.Enemy && - !unit.HasTrait() && unit.HasTrait()).ToList(); + .Where(unit => p.Stances[unit.Owner] == Stance.Enemy && + !unit.HasTrait() && unit.HasTrait()).ToList(); if (enemyUnits.Count > 0) return enemyUnits.ClosestTo(pos); + return null; } @@ -1052,30 +469,26 @@ namespace OpenRA.Mods.RA.AI { var bases = world.Actors.Where(a => p.Stances[a.Owner] == Stance.Enemy && !a.Destroyed && a.HasTrait() && !a.HasTrait()).ToList(); - return bases != null ? bases : new List(); + + return bases ?? NoActors; } Actor FindEnemyBuildingClosestToPos(WPos pos) { var closestBuilding = world.Actors.Where(a => p.Stances[a.Owner] == Stance.Enemy && !a.Destroyed && a.HasTrait()).ClosestTo(pos); + return closestBuilding; } - List squads = new List(); - - List unitsHangingAroundTheBase = new List(); - //Units that the ai already knows about. Any unit not on this list needs to be given a role. - List activeUnits = new List(); - void CleanSquads() { - squads.RemoveAll(s => s.IsEmpty); - foreach (Squad squad in squads) - squad.units.RemoveAll(a => a.Destroyed || a.IsDead()); + squads.RemoveAll(s => !s.IsValid); + foreach (var s in squads) + s.units.RemoveAll(a => a.Destroyed || a.IsDead()); } - //use of this function requires that one squad of this type. Hence it is a piece of shit + // Use of this function requires that one squad of this type. Hence it is a piece of shit Squad GetSquadOfType(SquadType type) { return squads.Where(s => s.type == type).FirstOrDefault(); @@ -1113,13 +526,13 @@ namespace OpenRA.Mods.RA.AI if (--assignRolesTicks > 0) return; - else - assignRolesTicks = Info.AssignRolesInterval; + + assignRolesTicks = Info.AssignRolesInterval; GiveOrdersToIdleHarvesters(); FindNewUnits(self); CreateAttackForce(); - FindAndDeployMcv(self); + FindAndDeployBackupMcv(self); } void GiveOrdersToIdleHarvesters() @@ -1128,17 +541,21 @@ namespace OpenRA.Mods.RA.AI foreach (var a in activeUnits) { var harv = a.TraitOrDefault(); - if (harv == null) continue; + if (harv == null) + continue; if (!a.IsIdle) { - Activity act = a.GetCurrentActivity(); + var act = a.GetCurrentActivity(); + // A Wait activity is technically idle: if ((act.GetType() != typeof(OpenRA.Mods.RA.Activities.Wait)) && (act.NextActivity == null || act.NextActivity.GetType() != typeof(OpenRA.Mods.RA.Activities.FindResources))) continue; } - if (!harv.IsEmpty) continue; + + if (!harv.IsEmpty) + continue; // Tell the idle harvester to quit slacking: world.IssueOrder(new Order("Harvest", a, false)); @@ -1149,8 +566,8 @@ namespace OpenRA.Mods.RA.AI { var newUnits = self.World.ActorsWithTrait() .Where(a => a.Actor.Owner == p && !a.Actor.HasTrait() - && !activeUnits.Contains(a.Actor)) - .Select(a => a.Actor).ToArray(); + && !activeUnits.Contains(a.Actor)) + .Select(a => a.Actor); foreach (var a in newUnits) { @@ -1159,6 +576,7 @@ namespace OpenRA.Mods.RA.AI world.IssueOrder(new Order("Harvest", a, false)); else unitsHangingAroundTheBase.Add(a); + if (a.HasTrait() && a.HasTrait()) { var air = GetSquadOfType(SquadType.Air); @@ -1167,14 +585,15 @@ namespace OpenRA.Mods.RA.AI air.units.Add(a); } + activeUnits.Add(a); } } void CreateAttackForce() { - /* Create an attack force when we have enough units around our base. */ - // (don't bother leaving any behind for defense.) + // Create an attack force when we have enough units around our base. + // (don't bother leaving any behind for defense) var randomizedSquadSize = Info.SquadSize + random.Next(30); if (unitsHangingAroundTheBase.Count >= randomizedSquadSize) @@ -1184,6 +603,7 @@ namespace OpenRA.Mods.RA.AI foreach (var a in unitsHangingAroundTheBase) if (!a.HasTrait()) attackForce.units.Add(a); + unitsHangingAroundTheBase.Clear(); } } @@ -1193,14 +613,16 @@ namespace OpenRA.Mods.RA.AI var allEnemyBaseBuilder = FindEnemyConstructionYards(); var ownUnits = activeUnits .Where(unit => unit.HasTrait() && !unit.HasTrait() && unit.IsIdle).ToList(); - if (!allEnemyBaseBuilder.Any() || (ownUnits.Count < Info.SquadSize)) return; + + if (!allEnemyBaseBuilder.Any() || (ownUnits.Count < Info.SquadSize)) + return; + foreach (var b in allEnemyBaseBuilder) { var enemys = world.FindActorsInCircle(b.CenterPosition, WRange.FromCells(15)) .Where(unit => p.Stances[unit.Owner] == Stance.Enemy && unit.HasTrait()).ToList(); - rushFuzzy.CalculateFuzzy(ownUnits, enemys); - if (rushFuzzy.CanAttack) + if (rushFuzzy.CanAttack(ownUnits, enemys)) { var target = enemys.Any() ? enemys.Random(random) : b; var rush = GetSquadOfType(SquadType.Rush); @@ -1223,11 +645,13 @@ namespace OpenRA.Mods.RA.AI if (!protectSq.TargetIsValid) protectSq.Target = attacker; - if (protectSq.IsEmpty) + + if (!protectSq.IsValid) { var ownUnits = world.FindActorsInCircle(baseCenter.CenterPosition, WRange.FromCells(15)) - .Where(unit => unit.Owner == p && !unit.HasTrait() - && unit.HasTrait()).ToList(); + .Where(unit => unit.Owner == p && !unit.HasTrait() + && unit.HasTrait()).ToList(); + foreach (var a in ownUnits) protectSq.units.Add(a); } @@ -1235,8 +659,8 @@ namespace OpenRA.Mods.RA.AI bool IsRallyPointValid(CPos x) { - // this is actually WRONG as soon as HackyAI is building units with a variety of - // movement capabilities. (has always been wrong) + // This is actually WRONG as soon as HackyAI is building units with + // a variety of movement capabilities. (has always been wrong) return world.IsCellBuildable(x, rallypointTestBuilding); } @@ -1251,17 +675,16 @@ namespace OpenRA.Mods.RA.AI p.PlayerName, buildings.Length); foreach (var a in buildings) - { - CPos newRallyPoint = ChooseRallyLocationNear(a.Actor.Location); - world.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = newRallyPoint }); - } + world.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor.Location) }); } - //won't work for shipyards... + // Won't work for shipyards... CPos ChooseRallyLocationNear(CPos startPos) { - var possibleRallyPoints = world.FindTilesInCircle(startPos, 8).Where(IsRallyPointValid).ToArray(); - if (possibleRallyPoints.Length == 0) + var possibleRallyPoints = world.FindTilesInCircle(startPos, 8) + .Where(IsRallyPointValid); + + if (!possibleRallyPoints.Any()) { BotDebug("Bot Bug: No possible rallypoint near {0}", startPos); return startPos; @@ -1270,9 +693,9 @@ namespace OpenRA.Mods.RA.AI return possibleRallyPoints.Random(random); } - void DeployMcv(Actor self) + void InitializeBase(Actor self) { - /* find our mcv and deploy it */ + // Find and deploy our mcv var mcv = self.World.Actors .FirstOrDefault(a => a.Owner == p && a.HasTrait()); @@ -1280,7 +703,9 @@ namespace OpenRA.Mods.RA.AI { baseCenter = mcv.Location; defenseCenter = baseCenter; - //Don't transform the mcv if it is a fact + + // Don't transform the mcv if it is a fact + // HACK: This needs to query against MCVs directly if (mcv.HasTrait()) world.IssueOrder(new Order("DeployTransform", mcv, false)); } @@ -1288,42 +713,52 @@ namespace OpenRA.Mods.RA.AI BotDebug("AI: Can't find BaseBuildUnit."); } - void FindAndDeployMcv(Actor self) + // Find any newly constructed MCVs and deploy them at a sensible + // backup location within the main base. + void FindAndDeployBackupMcv(Actor self) { - var mcvs = self.World.Actors.Where(a => a.Owner == p && a.HasTrait()).ToArray(); + var maxBaseDistance = Math.Max(world.Map.MapSize.X, world.Map.MapSize.Y); + + // HACK: Assumes all MCVs deploy into the same construction yard footprint + var mcvInfo = GetUnitInfoByCommonName("Mcv", p); + if (mcvInfo == null) + return; + + var factType = mcvInfo.Traits.Get().IntoActor; + + // HACK: This needs to query against MCVs directly + var mcvs = self.World.Actors.Where(a => a.Owner == p && a.HasTrait() && a.HasTrait()); if (!mcvs.Any()) return; - else - foreach (var mcv in mcvs) - if (mcv != null) - //Don't transform the mcv if it is a fact - if (mcv.HasTrait()) - { - if (mcv.IsMoving()) return; - var maxBaseDistance = world.Map.MapSize.X > world.Map.MapSize.Y ? world.Map.MapSize.X : world.Map.MapSize.Y; - ActorInfo aInfo = GetUnitInfoByCommonName("Mcv",p); - if (aInfo == null) return; - string intoActor = aInfo.Traits.Get().IntoActor; - var desiredLocation = ChooseBuildLocation(intoActor, false, maxBaseDistance, BuildingType.Building); - if (desiredLocation == null) - return; - world.IssueOrder(new Order("Move", mcv, false) { TargetLocation = desiredLocation.Value }); - world.IssueOrder(new Order("DeployTransform", mcv, false)); - } + + foreach (var mcv in mcvs) + { + if (mcv.IsMoving()) + continue; + + var desiredLocation = ChooseBuildLocation(factType, false, maxBaseDistance, BuildingType.Building); + if (desiredLocation == null) + continue; + + world.IssueOrder(new Order("Move", mcv, false) { TargetLocation = desiredLocation.Value }); + world.IssueOrder(new Order("DeployTransform", mcv, false)); + } } void TryToUseSupportPower(Actor self) { - if (supportPowerMngr == null) return; - var powers = supportPowerMngr.Powers.Where(p => !p.Value.Disabled); + if (supportPowerMngr == null) + return; + var powers = supportPowerMngr.Powers.Where(p => !p.Value.Disabled); foreach (var kv in powers) { var sp = kv.Value; if (sp.Ready) { var attackLocation = FindAttackLocationToSupportPower(5); - if (attackLocation == null) return; + if (attackLocation == null) + return; world.IssueOrder(new Order(sp.Info.OrderName, supportPowerMngr.self, false) { TargetLocation = attackLocation.Value }); } @@ -1333,27 +768,31 @@ namespace OpenRA.Mods.RA.AI CPos? FindAttackLocationToSupportPower(int radiusOfPower) { CPos? resLoc = null; - int countUnits = 0; + var countUnits = 0; - int x = (world.Map.MapSize.X % radiusOfPower) == 0 ? world.Map.MapSize.X : world.Map.MapSize.X + radiusOfPower; - int y = (world.Map.MapSize.Y % radiusOfPower) == 0 ? world.Map.MapSize.Y : world.Map.MapSize.Y + radiusOfPower; + 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 (int i = 0; i < x; i += radiusOfPower * 2) + { for (int j = 0; j < y; j += radiusOfPower * 2) { - CPos pos = new CPos(i, j); + var pos = new CPos(i, j); var targets = world.FindActorsInCircle(pos.CenterPosition, WRange.FromCells(radiusOfPower)).ToList(); var enemys = 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(); if (enemys.Count < ally.Count || !enemys.Any()) continue; + if (enemys.Count > countUnits) { countUnits = enemys.Count; resLoc = enemys.Random(random).Location; } } + } + return resLoc; } @@ -1366,34 +805,28 @@ namespace OpenRA.Mods.RA.AI void ProductionUnits(Actor self) { - if (!HasAdequateProc()) /* Stop building until economy is back on */ + // Stop building until economy is restored + if (!HasAdequateProc()) return; - if (!HasAdequateFact()) - if (!self.World.Actors.Where(a => a.Owner == p && a.HasTrait() && a.HasTrait()).Any()) - BuildUnit("Vehicle", GetUnitInfoByCommonName("Mcv",p).Name); + + // No construction yards - Build a new MCV + if (!HasAdequateFact() && !self.World.Actors.Where(a => a.Owner == p && a.HasTrait() && a.HasTrait()).Any()) + BuildUnit("Vehicle", GetUnitInfoByCommonName("Mcv", p).Name); + foreach (var q in Info.UnitQueues) - { - if (unitsHangingAroundTheBase.Count < 12) - { - BuildUnit(q, true); - continue; - } - BuildUnit(q, false); - } + BuildUnit(q, unitsHangingAroundTheBase.Count < 12); } void BuildUnit(string category, bool buildRandom) { // Pick a free queue - var queue = FindQueues(category).FirstOrDefault( q => q.CurrentItem() == null ); + var queue = FindQueues(category).FirstOrDefault(q => q.CurrentItem() == null); if (queue == null) return; - ActorInfo unit; - if(buildRandom) - unit = ChooseRandomUnitToBuild(queue); - else - unit = ChooseUnitToBuild(queue); + var unit = buildRandom ? + ChooseRandomUnitToBuild(queue) : + ChooseUnitToBuild(queue); if (unit != null && Info.UnitsToBuild.Any(u => u.Key == unit.Name)) world.IssueOrder(Order.StartProduction(queue.self, unit.Name, 1)); @@ -1401,19 +834,25 @@ namespace OpenRA.Mods.RA.AI void BuildUnit(string category, string name) { - var queue = FindQueues(category).FirstOrDefault( q => q.CurrentItem() == null ); - if (queue == null) return; - if(Rules.Info[name] != null) + var queue = FindQueues(category).FirstOrDefault(q => q.CurrentItem() == null); + if (queue == null) + return; + + if (Rules.Info[name] != null) world.IssueOrder(Order.StartProduction(queue.self, name, 1)); } public void Damaged(Actor self, AttackInfo e) { - if (!enabled) return; - if (e.Attacker.Destroyed) return; - if (!e.Attacker.HasTrait()) return; + // TODO: Surely we want to do this even if their destroyer died? + if (!enabled || e.Attacker.Destroyed) + return; + + if (!e.Attacker.HasTrait()) + return; if (Info.ShouldRepairBuildings && self.HasTrait()) + { if (e.DamageState > DamageState.Light && e.PreviousDamageState <= DamageState.Light) { BotDebug("Bot noticed damage {0} {1}->{2}, repairing.", @@ -1421,11 +860,12 @@ namespace OpenRA.Mods.RA.AI world.IssueOrder(new Order("RepairBuilding", self.Owner.PlayerActor, false) { TargetActor = self }); } + } if (e.Attacker != null && e.Damage > 0) aggro[e.Attacker.Owner].Aggro += e.Damage; - //protected harvesters or building + // Protected harvesters or building if ((self.HasTrait() || self.HasTrait()) && p.Stances[e.Attacker.Owner] == Stance.Enemy) { diff --git a/OpenRA.Mods.RA/AI/RushFuzzy.cs b/OpenRA.Mods.RA/AI/RushFuzzy.cs index 5d6925763d..afe43e8624 100644 --- a/OpenRA.Mods.RA/AI/RushFuzzy.cs +++ b/OpenRA.Mods.RA/AI/RushFuzzy.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2013 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, @@ -8,12 +8,6 @@ */ #endregion -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using AI.Fuzzy.Library; - namespace OpenRA.Mods.RA.AI { class RushFuzzy : AttackOrFleeFuzzy @@ -22,18 +16,17 @@ namespace OpenRA.Mods.RA.AI protected override void AddingRulesForNormalOwnHealth() { - //1 OwnHealth is Normal - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Normal) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and (RelativeAttackPower is Strong) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack")); + AddFuzzyRule("if ((OwnHealth is Normal) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and (RelativeAttackPower is Strong) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack"); - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule("if ((OwnHealth is Normal) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee")); + AddFuzzyRule("if ((OwnHealth is Normal) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee"); } } } diff --git a/OpenRA.Mods.RA/AI/Squad.cs b/OpenRA.Mods.RA/AI/Squad.cs new file mode 100644 index 0000000000..1ea59d8c45 --- /dev/null +++ b/OpenRA.Mods.RA/AI/Squad.cs @@ -0,0 +1,84 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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 OpenRA.FileFormats; +using OpenRA.Traits; +using XRandom = OpenRA.Thirdparty.Random; + +namespace OpenRA.Mods.RA.AI +{ + public enum SquadType { Assault, Air, Rush, Protection } + + public class Squad + { + public List units = new List(); + public SquadType type; + + internal World world; + internal HackyAI bot; + internal XRandom random; + + internal Target target; + internal StateMachine fsm; + + //fuzzy + internal AttackOrFleeFuzzy attackOrFleeFuzzy = new AttackOrFleeFuzzy(); + + public Squad(HackyAI bot, SquadType type) : this(bot, type, null) { } + + public Squad(HackyAI bot, SquadType type, Actor target) + { + this.bot = bot; + this.world = bot.world; + this.random = bot.random; + this.type = type; + this.target = Traits.Target.FromActor(target); + fsm = new StateMachine(); + + switch (type) + { + case SquadType.Assault: + case SquadType.Rush: + fsm.ChangeState(this, new GroundUnitsIdleState(), true); + break; + case SquadType.Air: + fsm.ChangeState(this, new AirIdleState(), true); + break; + case SquadType.Protection: + fsm.ChangeState(this, new UnitsForProtectionIdleState(), true); + break; + } + } + + public void Update() + { + if (IsValid) + fsm.Update(this); + } + + public bool IsValid { get { return units.Any(); } } + + public Actor Target + { + get { return target.Actor; } + set { target = Traits.Target.FromActor(value); } + } + + public bool TargetIsValid + { + get { return target.IsValidFor(units.FirstOrDefault()) && !target.Actor.HasTrait(); } + } + + public WPos CenterPosition { get { return units.Select(u => u.CenterPosition).Average(); } } + } +} diff --git a/OpenRA.Mods.RA/AI/StateMachine.cs b/OpenRA.Mods.RA/AI/StateMachine.cs index f3ac9007f1..0c1857b6ea 100644 --- a/OpenRA.Mods.RA/AI/StateMachine.cs +++ b/OpenRA.Mods.RA/AI/StateMachine.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2013 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, @@ -12,68 +12,38 @@ namespace OpenRA.Mods.RA.AI { class StateMachine { - //a pointer to the agent that owns this instance - private Squad owner; + IState currentState; + IState previousState; - private IState currentState; - - //a record of the last state the agent was in - private IState previousState; - - public StateMachine(Squad owner) + public void Update(Squad squad) { - this.owner = owner; + currentState.Tick(squad); } - public IState CurrentState + public void ChangeState(Squad squad, IState newState, bool rememberPrevious) { - get { return currentState; } - set { currentState = value; } - } - - public IState PreviousState - { - get { return previousState; } - set { previousState = value; } - } - - //call this to update the FSM - public void UpdateFsm() - { - currentState.Execute(owner); - } - - //change to a new state - //boolean variable isSaveCurrentState respons on save or not current state - public void ChangeState(IState newState, bool saveCurrentState) - { - if (saveCurrentState) - //keep a record of the previous state + if (rememberPrevious) previousState = currentState; - //call the exit method of the existing state - if(currentState != null) - currentState.Exit(owner); + if (currentState != null) + currentState.Deactivate(squad); - //change state to the new state if (newState != null) currentState = newState; - //call the entry method of the new state - currentState.Enter(owner); + currentState.Activate(squad); } - //change state back to the previous state - public void RevertToPreviousState(bool saveCurrentState) + public void RevertToPreviousState(Squad squad, bool saveCurrentState) { - ChangeState(previousState, saveCurrentState); + ChangeState(squad, previousState, saveCurrentState); } } interface IState { - void Enter(Squad bot); - void Execute(Squad bot); - void Exit(Squad bot); + void Activate(Squad bot); + void Tick(Squad bot); + void Deactivate(Squad bot); } } diff --git a/OpenRA.Mods.RA/AI/States/AirStates.cs b/OpenRA.Mods.RA/AI/States/AirStates.cs new file mode 100644 index 0000000000..99d7a7dcb1 --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/AirStates.cs @@ -0,0 +1,258 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Collections.Generic; +using System.Linq; +using OpenRA.Mods.RA.Air; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.AI +{ + abstract class AirStateBase : StateBase + { + protected const int MissileUnitMultiplier = 3; + + protected static int CountAntiAirUnits(IEnumerable units) + { + if (!units.Any()) + return 0; + + var missileUnitsCount = 0; + foreach (var unit in units) + { + if (unit != null && unit.HasTrait() && !unit.HasTrait() + && !unit.IsDisabled()) + { + var arms = unit.TraitsImplementing(); + foreach (var a in arms) + { + if (a.Weapon.ValidTargets.Contains("Air")) + { + missileUnitsCount++; + break; + } + } + } + } + + return missileUnitsCount; + } + + protected static Actor FindDefenselessTarget(Squad owner) + { + Actor target = null; + FindSafePlace(owner, out target, true); + return target; + } + + protected static CPos? FindSafePlace(Squad owner, out Actor detectedEnemyTarget, bool needTarget) + { + var world = owner.world; + detectedEnemyTarget = null; + var x = (world.Map.MapSize.X % DangerRadius) == 0 ? world.Map.MapSize.X : world.Map.MapSize.X + DangerRadius; + var y = (world.Map.MapSize.Y % DangerRadius) == 0 ? world.Map.MapSize.Y : world.Map.MapSize.Y + DangerRadius; + + for (var i = 0; i < x; i += DangerRadius * 2) + { + for (var j = 0; j < y; j += DangerRadius * 2) + { + var pos = new CPos(i, j); + if (NearToPosSafely(owner, pos.CenterPosition, out detectedEnemyTarget)) + { + if (needTarget && detectedEnemyTarget == null) + continue; + + return pos; + } + } + } + + return null; + } + + protected static bool NearToPosSafely(Squad owner, WPos loc) + { + Actor a; + return NearToPosSafely(owner, loc, out a); + } + + protected static bool NearToPosSafely(Squad owner, WPos loc, out Actor detectedEnemyTarget) + { + detectedEnemyTarget = null; + var unitsAroundPos = owner.world.FindActorsInCircle(loc, WRange.FromCells(DangerRadius)) + .Where(unit => owner.bot.p.Stances[unit.Owner] == Stance.Enemy).ToList(); + + if (!unitsAroundPos.Any()) + return true; + + if (CountAntiAirUnits(unitsAroundPos) * MissileUnitMultiplier < owner.units.Count) + { + detectedEnemyTarget = unitsAroundPos.Random(owner.random); + return true; + } + + return false; + } + + protected static bool FullAmmo(Actor a) + { + var limitedAmmo = a.TraitOrDefault(); + return limitedAmmo != null && limitedAmmo.FullAmmo(); + } + + protected static bool HasAmmo(Actor a) + { + var limitedAmmo = a.TraitOrDefault(); + return limitedAmmo != null && limitedAmmo.HasAmmo(); + } + + protected static bool ReloadsAutomatically(Actor a) + { + return a.HasTrait(); + } + + protected static bool IsRearm(Actor a) + { + var activity = a.GetCurrentActivity(); + if (activity == null) + return false; + + var type = activity.GetType(); + if (type == typeof(OpenRA.Mods.RA.Activities.Rearm) || type == typeof(ResupplyAircraft)) + return true; + + var next = activity.NextActivity; + if (next == null) + return false; + + var nextType = next.GetType(); + if (nextType == typeof(OpenRA.Mods.RA.Activities.Rearm) || nextType == typeof(ResupplyAircraft)) + return true; + + return false; + } + + // Checks the number of anti air enemies around units + protected virtual bool ShouldFlee(Squad owner) + { + return base.ShouldFlee(owner, enemies => CountAntiAirUnits(enemies) * MissileUnitMultiplier > owner.units.Count); + } + } + + class AirIdleState : AirStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + if (ShouldFlee(owner)) + { + owner.fsm.ChangeState(owner, new AirFleeState(), true); + return; + } + + var e = FindDefenselessTarget(owner); + if (e == null) + return; + + owner.Target = e; + owner.fsm.ChangeState(owner, new AirAttackState(), true); + } + + public void Deactivate(Squad owner) { } + } + + class AirAttackState : AirStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + if (!owner.TargetIsValid) + { + var a = owner.units.Random(owner.random); + var closestEnemy = owner.bot.FindClosestEnemy(a.CenterPosition); + if (closestEnemy != null) + owner.Target = closestEnemy; + else + { + owner.fsm.ChangeState(owner, new AirFleeState(), true); + return; + } + } + + if (!NearToPosSafely(owner, owner.Target.CenterPosition)) + { + owner.fsm.ChangeState(owner, new AirFleeState(), true); + return; + } + + foreach (var a in owner.units) + { + if (BusyAttack(a)) + continue; + + if (!ReloadsAutomatically(a)) + { + if (!HasAmmo(a)) + { + if (IsRearm(a)) + continue; + owner.world.IssueOrder(new Order("ReturnToBase", a, false)); + continue; + } + + if (IsRearm(a)) + continue; + } + + if (owner.Target.HasTrait() && CanAttackTarget(a, owner.Target)) + owner.world.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.Target }); + } + } + + public void Deactivate(Squad owner) { } + } + + class AirFleeState : AirStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + foreach (var a in owner.units) + { + if (!ReloadsAutomatically(a) && !FullAmmo(a)) + { + if (IsRearm(a)) + continue; + + owner.world.IssueOrder(new Order("ReturnToBase", a, false)); + continue; + } + + owner.world.IssueOrder(new Order("Move", a, false) { TargetLocation = RandomBuildingLocation(owner) }); + } + + owner.fsm.ChangeState(owner, new AirIdleState(), true); + } + + public void Deactivate(Squad owner) { } + } +} diff --git a/OpenRA.Mods.RA/AI/States/GroundStates.cs b/OpenRA.Mods.RA/AI/States/GroundStates.cs new file mode 100644 index 0000000000..1846c9758b --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/GroundStates.cs @@ -0,0 +1,171 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Collections.Generic; +using System.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.AI +{ + abstract class GroundStateBase : StateBase + { + protected virtual bool ShouldFlee(Squad owner) + { + return base.ShouldFlee(owner, enemies => !owner.attackOrFleeFuzzy.CanAttack(owner.units, enemies)); + } + } + + class GroundUnitsIdleState : GroundStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + if (!owner.TargetIsValid) + { + var t = owner.bot.FindClosestEnemy(owner.units.FirstOrDefault().CenterPosition); + if (t == null) return; + owner.Target = t; + } + + var enemyUnits = owner.world.FindActorsInCircle(owner.Target.CenterPosition, WRange.FromCells(10)) + .Where(unit => owner.bot.p.Stances[unit.Owner] == Stance.Enemy).ToList(); + + if (enemyUnits.Any()) + { + if (owner.attackOrFleeFuzzy.CanAttack(owner.units, enemyUnits)) + { + foreach (var u in owner.units) + owner.world.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.Target.CenterPosition.ToCPos() }); + + // We have gathered sufficient units. Attack the nearest enemy unit. + owner.fsm.ChangeState(owner, new GroundUnitsAttackMoveState(), true); + return; + } + else + owner.fsm.ChangeState(owner, new GroundUnitsFleeState(), true); + } + } + + public void Deactivate(Squad owner) { } + } + + class GroundUnitsAttackMoveState : GroundStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + if (!owner.TargetIsValid) + { + var closestEnemy = owner.bot.FindClosestEnemy(owner.units.Random(owner.random).CenterPosition); + if (closestEnemy != null) + owner.Target = closestEnemy; + else + { + owner.fsm.ChangeState(owner, new GroundUnitsFleeState(), true); + return; + } + } + + Actor leader = owner.units.ClosestTo(owner.Target.CenterPosition); + if (leader == null) + return; + var ownUnits = owner.world.FindActorsInCircle(leader.CenterPosition, WRange.FromCells(owner.units.Count) / 3) + .Where(a => a.Owner == owner.units.FirstOrDefault().Owner && owner.units.Contains(a)).ToList(); + if (ownUnits.Count < owner.units.Count) + { + owner.world.IssueOrder(new Order("Stop", leader, false)); + foreach (var unit in owner.units.Where(a => !ownUnits.Contains(a))) + owner.world.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.CenterPosition.ToCPos() }); + } + else + { + var enemys = owner.world.FindActorsInCircle(leader.CenterPosition, WRange.FromCells(12)) + .Where(a1 => !a1.Destroyed && !a1.IsDead()).ToList(); + var enemynearby = enemys.Where(a1 => a1.HasTrait() && leader.Owner.Stances[a1.Owner] == Stance.Enemy).ToList(); + if (enemynearby.Any()) + { + owner.Target = enemynearby.ClosestTo(leader.CenterPosition); + owner.fsm.ChangeState(owner, new GroundUnitsAttackState(), true); + return; + } + else + foreach (var a in owner.units) + owner.world.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.Target.Location }); + } + + if (ShouldFlee(owner)) + { + owner.fsm.ChangeState(owner, new GroundUnitsFleeState(), true); + return; + } + } + + public void Deactivate(Squad owner) { } + } + + class GroundUnitsAttackState : GroundStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + if (!owner.TargetIsValid) + { + var closestEnemy = owner.bot.FindClosestEnemy(owner.units.Random(owner.random).CenterPosition); + if (closestEnemy != null) + owner.Target = closestEnemy; + else + { + owner.fsm.ChangeState(owner, new GroundUnitsFleeState(), true); + return; + } + } + + foreach (var a in owner.units) + if (!BusyAttack(a)) + owner.world.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.bot.FindClosestEnemy(a.CenterPosition) }); + + if (ShouldFlee(owner)) + { + owner.fsm.ChangeState(owner, new GroundUnitsFleeState(), true); + return; + } + } + + public void Deactivate(Squad owner) { } + } + + class GroundUnitsFleeState : GroundStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + GoToRandomOwnBuilding(owner); + owner.fsm.ChangeState(owner, new GroundUnitsIdleState(), true); + } + + public void Deactivate(Squad owner) { owner.units.Clear(); } + } +} diff --git a/OpenRA.Mods.RA/AI/States/ProtectionStates.cs b/OpenRA.Mods.RA/AI/States/ProtectionStates.cs new file mode 100644 index 0000000000..ff40ec8219 --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/ProtectionStates.cs @@ -0,0 +1,66 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Collections.Generic; +using System.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.AI +{ + class UnitsForProtectionIdleState : GroundStateBase, IState + { + public void Activate(Squad owner) { } + public void Tick(Squad owner) { owner.fsm.ChangeState(owner, new UnitsForProtectionAttackState(), true); } + public void Deactivate(Squad owner) { } + } + + class UnitsForProtectionAttackState : GroundStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + if (!owner.TargetIsValid) + { + owner.Target = owner.bot.FindClosestEnemy(owner.CenterPosition, WRange.FromCells(8)); + + if (owner.Target == null) + { + owner.fsm.ChangeState(owner, new UnitsForProtectionFleeState(), true); + return; + } + } + + foreach (var a in owner.units) + owner.world.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.Target.Location }); + } + + public void Deactivate(Squad owner) { } + } + + class UnitsForProtectionFleeState : GroundStateBase, IState + { + public void Activate(Squad owner) { } + + public void Tick(Squad owner) + { + if (!owner.IsValid) + return; + + GoToRandomOwnBuilding(owner); + owner.fsm.ChangeState(owner, new UnitsForProtectionIdleState(), true); + } + + public void Deactivate(Squad owner) { owner.units.Clear(); } + } +} diff --git a/OpenRA.Mods.RA/AI/States/StateBase.cs b/OpenRA.Mods.RA/AI/States/StateBase.cs new file mode 100644 index 0000000000..97a330aab8 --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/StateBase.cs @@ -0,0 +1,96 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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 OpenRA.Mods.RA.Air; +using OpenRA.Mods.RA.Buildings; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.AI +{ + abstract class StateBase + { + protected const int DangerRadius = 10; + + protected static void GoToRandomOwnBuilding(Squad squad) + { + var loc = RandomBuildingLocation(squad); + foreach (var a in squad.units) + squad.world.IssueOrder(new Order("Move", a, false) { TargetLocation = loc }); + } + + protected static CPos RandomBuildingLocation(Squad squad) + { + var location = squad.bot.baseCenter; + var buildings = squad.world.ActorsWithTrait() + .Where(a => a.Actor.Owner == squad.bot.p).Select(a => a.Actor).ToArray(); + if (buildings.Length > 0) + location = buildings.Random(squad.random).Location; + return location; + } + + protected static bool BusyAttack(Actor a) + { + if (a.IsIdle) + return false; + + var type = a.GetCurrentActivity().GetType(); + if (type == typeof(OpenRA.Mods.RA.Activities.Attack) || type == typeof(FlyAttack)) + return true; + + var next = a.GetCurrentActivity().NextActivity; + if (next == null) + return false; + + var nextType = a.GetCurrentActivity().NextActivity.GetType(); + if (nextType == typeof(OpenRA.Mods.RA.Activities.Attack) || nextType == typeof(FlyAttack)) + return true; + + return false; + } + + protected static bool CanAttackTarget(Actor a, Actor target) + { + if (!a.HasTrait()) + return false; + + var targetable = target.TraitOrDefault(); + if (targetable == null) + return false; + + var arms = a.TraitsImplementing(); + foreach (var arm in arms) + if (arm.Weapon.ValidTargets.Intersect(targetable.TargetTypes) != null) + return true; + + return false; + } + + protected virtual bool ShouldFlee(Squad squad, Func, bool> flee) + { + if (!squad.IsValid) + return false; + + var u = squad.units.Random(squad.random); + var units = squad.world.FindActorsInCircle(u.CenterPosition, WRange.FromCells(DangerRadius)).ToList(); + var ownBaseBuildingAround = units.Where(unit => unit.Owner == squad.bot.p && unit.HasTrait()); + if (ownBaseBuildingAround.Any()) + return false; + + var enemyAroundUnit = units.Where(unit => squad.bot.p.Stances[unit.Owner] == Stance.Enemy && unit.HasTrait()); + if (!enemyAroundUnit.Any()) + return false; + + return flee(enemyAroundUnit); + } + } +} diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 4fc02caee2..b19714e39f 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -466,6 +466,11 @@ + + + + + @@ -519,4 +524,7 @@ cd "$(SolutionDir)thirdparty/" copy "FuzzyLogicLibrary.dll" "$(SolutionDir)" cd "$(SolutionDir)" + + +