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