diff --git a/OpenRA.Mods.RA/AI/HackyAI.cs b/OpenRA.Mods.RA/AI/HackyAI.cs index 7b64fa2795..7a6fb10719 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,16 +12,15 @@ 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; @@ -82,610 +81,11 @@ namespace OpenRA.Mods.RA.AI 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 List units = new List(); - public SquadType type; - - 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 + public class HackyAI : ITick, IBot, INotifyDamage { bool enabled; public int ticks; diff --git a/OpenRA.Mods.RA/AI/Squad.cs b/OpenRA.Mods.RA/AI/Squad.cs new file mode 100644 index 0000000000..b97459781c --- /dev/null +++ b/OpenRA.Mods.RA/AI/Squad.cs @@ -0,0 +1,85 @@ +#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.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 Actor 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 = 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; } + set { target = value; } + } + + public bool TargetIsValid + { + get { return (target != null && !target.IsDead() && !target.Destroyed + && target.IsInWorld && !target.HasTrait()); } + } + } +} diff --git a/OpenRA.Mods.RA/AI/States/AirStates.cs b/OpenRA.Mods.RA/AI/States/AirStates.cs new file mode 100644 index 0000000000..71afee7d00 --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/AirStates.cs @@ -0,0 +1,251 @@ +#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 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) { } + } +} diff --git a/OpenRA.Mods.RA/AI/States/GroundStates.cs b/OpenRA.Mods.RA/AI/States/GroundStates.cs new file mode 100644 index 0000000000..7f2c2b47a6 --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/GroundStates.cs @@ -0,0 +1,172 @@ +#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 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(); } + } +} diff --git a/OpenRA.Mods.RA/AI/States/ProtectionStates.cs b/OpenRA.Mods.RA/AI/States/ProtectionStates.cs new file mode 100644 index 0000000000..dae3b62214 --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/ProtectionStates.cs @@ -0,0 +1,64 @@ +#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 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(); } + } +} diff --git a/OpenRA.Mods.RA/AI/States/StateBase.cs b/OpenRA.Mods.RA/AI/States/StateBase.cs new file mode 100644 index 0000000000..78d63e82e6 --- /dev/null +++ b/OpenRA.Mods.RA/AI/States/StateBase.cs @@ -0,0 +1,107 @@ +#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 +{ + //********************************************************************************** + // 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; + } + } +} 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)" + + +