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 NavyStateBase : 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 var first = owner.Units.First(); 27 28 // Navy squad AI can exploit enemy naval production to find path, if any. 29 // (Way better than finding a nearest target which is likely to be on Ground) 30 // You might be tempted to move these lookups into Activate() but that causes null reference exception. 31 var domainIndex = first.World.WorldActor.Trait<DomainIndex>(); 32 var locomotorInfo = first.Info.TraitInfo<MobileInfo>().LocomotorInfo; 33 34 var navalProductions = owner.World.ActorsHavingTrait<Building>().Where(a 35 => owner.SquadManager.Info.NavalProductionTypes.Contains(a.Info.Name) 36 && domainIndex.IsPassable(first.Location, a.Location, locomotorInfo) 37 && a.AppearsHostileTo(first)); 38 39 if (navalProductions.Any()) 40 { 41 var nearest = navalProductions.ClosestTo(first); 42 43 // Return nearest when it is FAR enough. 44 // If the naval production is within MaxBaseRadius, it implies that 45 // this squad is close to enemy territory and they should expect a naval combat; 46 // closest enemy makes more sense in that case. 47 if ((nearest.Location - first.Location).LengthSquared > owner.SquadManager.Info.MaxBaseRadius * owner.SquadManager.Info.MaxBaseRadius) 48 return nearest; 49 } 50 51 return owner.SquadManager.FindClosestEnemy(first.CenterPosition); 52 } 53 } 54 55 class NavyUnitsIdleState : NavyStateBase, IState 56 { Activate(Squad owner)57 public void Activate(Squad owner) { } 58 Tick(Squad owner)59 public void Tick(Squad owner) 60 { 61 if (!owner.IsValid) 62 return; 63 64 if (!owner.IsTargetValid) 65 { 66 var closestEnemy = FindClosestEnemy(owner); 67 if (closestEnemy == null) 68 return; 69 70 owner.TargetActor = closestEnemy; 71 } 72 73 var enemyUnits = owner.World.FindActorsInCircle(owner.TargetActor.CenterPosition, WDist.FromCells(owner.SquadManager.Info.IdleScanRadius)) 74 .Where(owner.SquadManager.IsEnemyUnit).ToList(); 75 76 if (enemyUnits.Count == 0) 77 return; 78 79 if (AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemyUnits)) 80 { 81 foreach (var u in owner.Units) 82 owner.Bot.QueueOrder(new Order("AttackMove", u, Target.FromCell(owner.World, owner.TargetActor.Location), false)); 83 84 // We have gathered sufficient units. Attack the nearest enemy unit. 85 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsAttackMoveState(), true); 86 } 87 else 88 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); 89 } 90 Deactivate(Squad owner)91 public void Deactivate(Squad owner) { } 92 } 93 94 class NavyUnitsAttackMoveState : NavyStateBase, IState 95 { Activate(Squad owner)96 public void Activate(Squad owner) { } 97 Tick(Squad owner)98 public void Tick(Squad owner) 99 { 100 if (!owner.IsValid) 101 return; 102 103 if (!owner.IsTargetValid) 104 { 105 var closestEnemy = FindClosestEnemy(owner); 106 if (closestEnemy != null) 107 owner.TargetActor = closestEnemy; 108 else 109 { 110 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); 111 return; 112 } 113 } 114 115 var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition); 116 if (leader == null) 117 return; 118 119 var ownUnits = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Units.Count) / 3) 120 .Where(a => a.Owner == owner.Units.First().Owner && owner.Units.Contains(a)).ToHashSet(); 121 122 if (ownUnits.Count < owner.Units.Count) 123 { 124 // Since units have different movement speeds, they get separated while approaching the target. 125 // Let them regroup into tighter formation. 126 owner.Bot.QueueOrder(new Order("Stop", leader, false)); 127 foreach (var unit in owner.Units.Where(a => !ownUnits.Contains(a))) 128 owner.Bot.QueueOrder(new Order("AttackMove", unit, Target.FromCell(owner.World, leader.Location), false)); 129 } 130 else 131 { 132 var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.SquadManager.Info.AttackScanRadius)) 133 .Where(owner.SquadManager.IsEnemyUnit); 134 var target = enemies.ClosestTo(leader.CenterPosition); 135 if (target != null) 136 { 137 owner.TargetActor = target; 138 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsAttackState(), true); 139 } 140 else 141 foreach (var a in owner.Units) 142 owner.Bot.QueueOrder(new Order("AttackMove", a, Target.FromCell(owner.World, owner.TargetActor.Location), false)); 143 } 144 145 if (ShouldFlee(owner)) 146 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); 147 } 148 Deactivate(Squad owner)149 public void Deactivate(Squad owner) { } 150 } 151 152 class NavyUnitsAttackState : NavyStateBase, IState 153 { Activate(Squad owner)154 public void Activate(Squad owner) { } 155 Tick(Squad owner)156 public void Tick(Squad owner) 157 { 158 if (!owner.IsValid) 159 return; 160 161 if (!owner.IsTargetValid) 162 { 163 var closestEnemy = FindClosestEnemy(owner); 164 if (closestEnemy != null) 165 owner.TargetActor = closestEnemy; 166 else 167 { 168 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); 169 return; 170 } 171 } 172 173 foreach (var a in owner.Units) 174 if (!BusyAttack(a)) 175 owner.Bot.QueueOrder(new Order("Attack", a, Target.FromActor(owner.TargetActor), false)); 176 177 if (ShouldFlee(owner)) 178 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); 179 } 180 Deactivate(Squad owner)181 public void Deactivate(Squad owner) { } 182 } 183 184 class NavyUnitsFleeState : NavyStateBase, IState 185 { Activate(Squad owner)186 public void Activate(Squad owner) { } 187 Tick(Squad owner)188 public void Tick(Squad owner) 189 { 190 if (!owner.IsValid) 191 return; 192 193 GoToRandomOwnBuilding(owner); 194 owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsIdleState(), true); 195 } 196 Deactivate(Squad owner)197 public void Deactivate(Squad owner) { owner.Units.Clear(); } 198 } 199 } 200