1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
4  * This file is part of OpenRA, which is free software. It is made
5  * available to you under the terms of the GNU General Public License
6  * as published by the Free Software Foundation, either version 3 of
7  * the License, or (at your option) any later version. For more
8  * information, see COPYING.
9  */
10 #endregion
11 
12 using System.Linq;
13 using OpenRA.Traits;
14 
15 namespace OpenRA.Mods.Common.Traits.BotModules.Squads
16 {
17 	abstract class GroundStateBase : StateBase
18 	{
ShouldFlee(Squad owner)19 		protected virtual bool ShouldFlee(Squad owner)
20 		{
21 			return ShouldFlee(owner, enemies => !AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemies));
22 		}
23 
FindClosestEnemy(Squad owner)24 		protected Actor FindClosestEnemy(Squad owner)
25 		{
26 			return owner.SquadManager.FindClosestEnemy(owner.Units.First().CenterPosition);
27 		}
28 	}
29 
30 	class GroundUnitsIdleState : GroundStateBase, IState
31 	{
Activate(Squad owner)32 		public void Activate(Squad owner) { }
33 
Tick(Squad owner)34 		public void Tick(Squad owner)
35 		{
36 			if (!owner.IsValid)
37 				return;
38 
39 			if (!owner.IsTargetValid)
40 			{
41 				var closestEnemy = FindClosestEnemy(owner);
42 				if (closestEnemy == null)
43 					return;
44 
45 				owner.TargetActor = closestEnemy;
46 			}
47 
48 			var enemyUnits = owner.World.FindActorsInCircle(owner.TargetActor.CenterPosition, WDist.FromCells(owner.SquadManager.Info.IdleScanRadius))
49 				.Where(owner.SquadManager.IsEnemyUnit).ToList();
50 
51 			if (enemyUnits.Count == 0)
52 				return;
53 
54 			if (AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemyUnits))
55 			{
56 				foreach (var u in owner.Units)
57 					owner.Bot.QueueOrder(new Order("AttackMove", u, Target.FromCell(owner.World, owner.TargetActor.Location), false));
58 
59 				// We have gathered sufficient units. Attack the nearest enemy unit.
60 				owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackMoveState(), true);
61 			}
62 			else
63 				owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
64 		}
65 
Deactivate(Squad owner)66 		public void Deactivate(Squad owner) { }
67 	}
68 
69 	class GroundUnitsAttackMoveState : GroundStateBase, IState
70 	{
Activate(Squad owner)71 		public void Activate(Squad owner) { }
72 
Tick(Squad owner)73 		public void Tick(Squad owner)
74 		{
75 			if (!owner.IsValid)
76 				return;
77 
78 			if (!owner.IsTargetValid)
79 			{
80 				var closestEnemy = FindClosestEnemy(owner);
81 				if (closestEnemy != null)
82 					owner.TargetActor = closestEnemy;
83 				else
84 				{
85 					owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
86 					return;
87 				}
88 			}
89 
90 			var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition);
91 			if (leader == null)
92 				return;
93 
94 			var ownUnits = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Units.Count) / 3)
95 				.Where(a => a.Owner == owner.Units.First().Owner && owner.Units.Contains(a)).ToHashSet();
96 
97 			if (ownUnits.Count < owner.Units.Count)
98 			{
99 				// Since units have different movement speeds, they get separated while approaching the target.
100 				// Let them regroup into tighter formation.
101 				owner.Bot.QueueOrder(new Order("Stop", leader, false));
102 				foreach (var unit in owner.Units.Where(a => !ownUnits.Contains(a)))
103 					owner.Bot.QueueOrder(new Order("AttackMove", unit, Target.FromCell(owner.World, leader.Location), false));
104 			}
105 			else
106 			{
107 				var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.SquadManager.Info.AttackScanRadius))
108 					.Where(owner.SquadManager.IsEnemyUnit);
109 				var target = enemies.ClosestTo(leader.CenterPosition);
110 				if (target != null)
111 				{
112 					owner.TargetActor = target;
113 					owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackState(), true);
114 				}
115 				else
116 					foreach (var a in owner.Units)
117 						owner.Bot.QueueOrder(new Order("AttackMove", a, Target.FromCell(owner.World, owner.TargetActor.Location), false));
118 			}
119 
120 			if (ShouldFlee(owner))
121 				owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
122 		}
123 
Deactivate(Squad owner)124 		public void Deactivate(Squad owner) { }
125 	}
126 
127 	class GroundUnitsAttackState : GroundStateBase, IState
128 	{
Activate(Squad owner)129 		public void Activate(Squad owner) { }
130 
Tick(Squad owner)131 		public void Tick(Squad owner)
132 		{
133 			if (!owner.IsValid)
134 				return;
135 
136 			if (!owner.IsTargetValid)
137 			{
138 				var closestEnemy = FindClosestEnemy(owner);
139 				if (closestEnemy != null)
140 					owner.TargetActor = closestEnemy;
141 				else
142 				{
143 					owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
144 					return;
145 				}
146 			}
147 
148 			foreach (var a in owner.Units)
149 				if (!BusyAttack(a))
150 					owner.Bot.QueueOrder(new Order("Attack", a, Target.FromActor(owner.TargetActor), false));
151 
152 			if (ShouldFlee(owner))
153 				owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
154 		}
155 
Deactivate(Squad owner)156 		public void Deactivate(Squad owner) { }
157 	}
158 
159 	class GroundUnitsFleeState : GroundStateBase, IState
160 	{
Activate(Squad owner)161 		public void Activate(Squad owner) { }
162 
Tick(Squad owner)163 		public void Tick(Squad owner)
164 		{
165 			if (!owner.IsValid)
166 				return;
167 
168 			GoToRandomOwnBuilding(owner);
169 			owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsIdleState(), true);
170 		}
171 
Deactivate(Squad owner)172 		public void Deactivate(Squad owner) { owner.Units.Clear(); }
173 	}
174 }
175