Add helper methods to locate actors that can be reached via a path.
Previously, the ClosestTo and PositionClosestTo existed to perform a simple distance based check to choose the closest location from a choice of locations to a single other location. For some functions this is sufficient, but for many functions we want to then move between the locations. If the location selected is in fact unreachable (e.g. on another island) then we would not want to consider it. We now introduce ClosestToIgnoringPath for checks where we don't care about a path existing, e.g. weapons hitting nearby targets. When we do care about paths, we introduce ClosestToWithPathFrom and ClosestToWithPathTo which will check that a path exists. The PathFrom check will make sure one of the actors from the list can make it to the single target location. The PathTo check will make sure the single actor can make it to one of the target locations. This difference allows us to specify which actor will be doing the moving. This is important as a path might exists for one actor, but not another. Consider two islands with a hovercraft on one and a tank on the other. The hovercraft can path to the tank, but the tank cannot path to the hovercraft. We also introduce WithPathFrom and WithPathTo. These will perform filtering by checking for valid paths, but won't select the closest location. By employing the new methods that filter for paths, we fix various behaviour that would cause actors to get confused. Imagine an islands map, by checking for paths we ensure logic will locate reachable locations on the island, rather than considering a location on a nearby island that is physically closer but unreachable. This fixes AI squad automation, and other automated behaviours such as rearming.
This commit is contained in:
@@ -479,8 +479,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
case BuildingType.Defense:
|
||||
|
||||
// Build near the closest enemy structure
|
||||
var closestEnemy = world.ActorsHavingTrait<Building>().Where(a => !a.Disposed && player.RelationshipWith(a.Owner) == PlayerRelationship.Enemy)
|
||||
.ClosestTo(world.Map.CenterOfCell(baseBuilder.DefenseCenter));
|
||||
var closestEnemy = world.ActorsHavingTrait<Building>()
|
||||
.Where(a => !a.Disposed && player.RelationshipWith(a.Owner) == PlayerRelationship.Enemy)
|
||||
.ClosestToIgnoringPath(world.Map.CenterOfCell(baseBuilder.DefenseCenter));
|
||||
|
||||
var targetCell = closestEnemy != null ? closestEnemy.Location : baseCenter;
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
readonly World world;
|
||||
readonly Player player;
|
||||
readonly Func<Actor, bool> isEnemyUnit;
|
||||
readonly Predicate<Actor> unitCannotBeOrderedOrIsIdle;
|
||||
readonly int maximumCaptureTargetOptions;
|
||||
int minCaptureDelayTicks;
|
||||
@@ -64,11 +63,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (world.Type == WorldType.Editor)
|
||||
return;
|
||||
|
||||
isEnemyUnit = unit =>
|
||||
player.RelationshipWith(unit.Owner) == PlayerRelationship.Enemy
|
||||
&& !unit.Info.HasTraitInfo<HuskInfo>()
|
||||
&& unit.Info.HasTraitInfo<ITargetableInfo>();
|
||||
|
||||
unitCannotBeOrderedOrIsIdle = a => a.Owner != player || a.IsDead || !a.IsInWorld || a.IsIdle;
|
||||
|
||||
maximumCaptureTargetOptions = Math.Max(1, Info.MaximumCaptureTargetOptions);
|
||||
@@ -89,16 +83,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
internal Actor FindClosestEnemy(WPos pos)
|
||||
{
|
||||
return world.Actors.Where(isEnemyUnit).ClosestTo(pos);
|
||||
}
|
||||
|
||||
internal Actor FindClosestEnemy(WPos pos, WDist radius)
|
||||
{
|
||||
return world.FindActorsInCircle(pos, radius).Where(isEnemyUnit).ClosestTo(pos);
|
||||
}
|
||||
|
||||
IEnumerable<Actor> GetVisibleActorsBelongingToPlayer(Player owner)
|
||||
{
|
||||
foreach (var actor in GetActorsThatCanBeOrderedByPlayer(owner))
|
||||
@@ -160,7 +144,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
foreach (var capturer in capturers)
|
||||
{
|
||||
var targetActor = capturableTargetOptionsList.MinByOrDefault(target => (target.CenterPosition - capturer.Actor.CenterPosition).LengthSquared);
|
||||
var targetActor = capturableTargetOptionsList.ClosestToWithPathFrom(capturer.Actor);
|
||||
if (targetActor == null)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
readonly List<Actor> unitsHangingAroundTheBase = new();
|
||||
|
||||
// Units that the bot already knows about. Any unit not on this list needs to be given a role.
|
||||
readonly List<Actor> activeUnits = new();
|
||||
readonly HashSet<Actor> activeUnits = new();
|
||||
|
||||
public List<Squad> Squads = new();
|
||||
|
||||
@@ -195,22 +195,105 @@ namespace OpenRA.Mods.Common.Traits
|
||||
AssignRolesToIdleUnits(bot);
|
||||
}
|
||||
|
||||
internal Actor FindClosestEnemy(WPos pos)
|
||||
internal static Actor ClosestTo(IEnumerable<Actor> ownActors, Actor targetActor)
|
||||
{
|
||||
var units = World.Actors.Where(IsPreferredEnemyUnit).ToList();
|
||||
return units.Where(IsNotHiddenUnit).ClosestTo(pos) ?? units.ClosestTo(pos);
|
||||
// Return actors that can get within weapons range of the target.
|
||||
// First, let's determine the max weapons range for each of the actors.
|
||||
var target = Target.FromActor(targetActor);
|
||||
var ownActorsAndTheirAttackRanges = ownActors
|
||||
.Select(a => (Actor: a, AttackBases: a.TraitsImplementing<AttackBase>().Where(Exts.IsTraitEnabled)
|
||||
.Where(ab => ab.HasAnyValidWeapons(target)).ToList()))
|
||||
.Where(x => x.AttackBases.Count > 0)
|
||||
.Select(x => (x.Actor, Range: x.AttackBases.Max(ab => ab.GetMaximumRangeVersusTarget(target))))
|
||||
.ToDictionary(x => x.Actor, x => x.Range);
|
||||
|
||||
// Now determine if each actor can either path directly to the target,
|
||||
// or if it can path to a nearby location at the edge of its weapon range to the target
|
||||
// A thorough check would check each position within the circle, but for performance
|
||||
// we'll only check 8 positions around the edge of the circle.
|
||||
// We need to account for the weapons range here to account for units such as boats.
|
||||
// They can't path directly to a land target,
|
||||
// but might be able to get close enough to shore to attack the target from range.
|
||||
return ownActorsAndTheirAttackRanges.Keys
|
||||
.ClosestToWithPathToAny(targetActor.World, a =>
|
||||
{
|
||||
var range = ownActorsAndTheirAttackRanges[a].Length;
|
||||
var rangeDiag = Exts.MultiplyBySqrtTwoOverTwo(range);
|
||||
return new[]
|
||||
{
|
||||
targetActor.CenterPosition,
|
||||
targetActor.CenterPosition + new WVec(range, 0, 0),
|
||||
targetActor.CenterPosition + new WVec(-range, 0, 0),
|
||||
targetActor.CenterPosition + new WVec(0, range, 0),
|
||||
targetActor.CenterPosition + new WVec(0, -range, 0),
|
||||
targetActor.CenterPosition + new WVec(rangeDiag, rangeDiag, 0),
|
||||
targetActor.CenterPosition + new WVec(-rangeDiag, rangeDiag, 0),
|
||||
targetActor.CenterPosition + new WVec(-rangeDiag, -rangeDiag, 0),
|
||||
targetActor.CenterPosition + new WVec(rangeDiag, -rangeDiag, 0),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
internal Actor FindClosestEnemy(WPos pos, WDist radius)
|
||||
internal IEnumerable<(Actor Actor, WVec Offset)> FindEnemies(IEnumerable<Actor> actors, Actor sourceActor)
|
||||
{
|
||||
return World.FindActorsInCircle(pos, radius).Where(a => IsPreferredEnemyUnit(a) && IsNotHiddenUnit(a)).ClosestTo(pos);
|
||||
// Check units are in fact enemies and not hidden.
|
||||
// Then check which are in weapons range of the source.
|
||||
var activeAttackBases = sourceActor.TraitsImplementing<AttackBase>().Where(Exts.IsTraitEnabled).ToArray();
|
||||
var enemiesAndSourceAttackRanges = actors
|
||||
.Where(a => IsPreferredEnemyUnit(a) && IsNotHiddenUnit(a))
|
||||
.Select(a => (Actor: a, AttackBases: activeAttackBases.Where(ab => ab.HasAnyValidWeapons(Target.FromActor(a))).ToList()))
|
||||
.Where(x => x.AttackBases.Count > 0)
|
||||
.Select(x => (x.Actor, Range: x.AttackBases.Max(ab => ab.GetMaximumRangeVersusTarget(Target.FromActor(x.Actor)))))
|
||||
.ToDictionary(x => x.Actor, x => x.Range);
|
||||
|
||||
// Now determine if the source actor can path directly to the target,
|
||||
// or if it can path to a nearby location at the edge of its weapon range to the target
|
||||
// A thorough check would check each position within the circle, but for performance
|
||||
// we'll only check 8 positions around the edge of the circle.
|
||||
// We need to account for the weapons range here to account for units such as boats.
|
||||
// They can't path directly to a land target,
|
||||
// but might be able to get close enough to shore to attack the target from range.
|
||||
return enemiesAndSourceAttackRanges.Keys
|
||||
.WithPathFrom(sourceActor, a =>
|
||||
{
|
||||
var range = enemiesAndSourceAttackRanges[a].Length;
|
||||
var rangeDiag = Exts.MultiplyBySqrtTwoOverTwo(range);
|
||||
return new[]
|
||||
{
|
||||
WVec.Zero,
|
||||
new WVec(range, 0, 0),
|
||||
new WVec(-range, 0, 0),
|
||||
new WVec(0, range, 0),
|
||||
new WVec(0, -range, 0),
|
||||
new WVec(rangeDiag, rangeDiag, 0),
|
||||
new WVec(-rangeDiag, rangeDiag, 0),
|
||||
new WVec(-rangeDiag, -rangeDiag, 0),
|
||||
new WVec(rangeDiag, -rangeDiag, 0),
|
||||
};
|
||||
})
|
||||
.Select(x => (x.Actor, x.ReachableOffsets.MinBy(o => o.LengthSquared)));
|
||||
}
|
||||
|
||||
internal (Actor Actor, WVec Offset) FindClosestEnemy(Actor sourceActor)
|
||||
{
|
||||
return FindClosestEnemy(World.Actors, sourceActor);
|
||||
}
|
||||
|
||||
internal (Actor Actor, WVec Offset) FindClosestEnemy(Actor sourceActor, WDist radius)
|
||||
{
|
||||
return FindClosestEnemy(World.FindActorsInCircle(sourceActor.CenterPosition, radius), sourceActor);
|
||||
}
|
||||
|
||||
(Actor Actor, WVec Offset) FindClosestEnemy(IEnumerable<Actor> actors, Actor sourceActor)
|
||||
{
|
||||
return WorldUtils.ClosestToIgnoringPath(FindEnemies(actors, sourceActor), x => x.Actor, sourceActor);
|
||||
}
|
||||
|
||||
void CleanSquads()
|
||||
{
|
||||
Squads.RemoveAll(s => !s.IsValid);
|
||||
foreach (var s in Squads)
|
||||
s.Units.RemoveAll(unitCannotBeOrdered);
|
||||
s.Units.RemoveWhere(unitCannotBeOrdered);
|
||||
Squads.RemoveAll(s => !s.IsValid);
|
||||
}
|
||||
|
||||
// HACK: Use of this function requires that there is one squad of this type.
|
||||
@@ -219,18 +302,28 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return Squads.FirstOrDefault(s => s.Type == type);
|
||||
}
|
||||
|
||||
Squad RegisterNewSquad(IBot bot, SquadType type, Actor target = null)
|
||||
Squad RegisterNewSquad(IBot bot, SquadType type, (Actor Actor, WVec Offset) target = default)
|
||||
{
|
||||
var ret = new Squad(bot, this, type, target);
|
||||
Squads.Add(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal void UnregisterSquad(Squad squad)
|
||||
{
|
||||
activeUnits.ExceptWith(squad.Units);
|
||||
squad.Units.Clear();
|
||||
|
||||
// CleanSquads will remove the squad from the Squads list.
|
||||
// We can't do that here as this is designed to be called from within Squad.Update
|
||||
// and thus would mutate the Squads list we are iterating over.
|
||||
}
|
||||
|
||||
void AssignRolesToIdleUnits(IBot bot)
|
||||
{
|
||||
CleanSquads();
|
||||
|
||||
activeUnits.RemoveAll(unitCannotBeOrdered);
|
||||
activeUnits.RemoveWhere(unitCannotBeOrdered);
|
||||
unitsHangingAroundTheBase.RemoveAll(unitCannotBeOrdered);
|
||||
foreach (var n in notifyIdleBaseUnits)
|
||||
n.UpdatedIdleBaseUnits(unitsHangingAroundTheBase);
|
||||
@@ -305,8 +398,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
var attackForce = RegisterNewSquad(bot, SquadType.Assault);
|
||||
|
||||
foreach (var a in unitsHangingAroundTheBase)
|
||||
attackForce.Units.Add(a);
|
||||
attackForce.Units.UnionWith(unitsHangingAroundTheBase);
|
||||
|
||||
unitsHangingAroundTheBase.Clear();
|
||||
foreach (var n in notifyIdleBaseUnits)
|
||||
@@ -316,29 +408,45 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void TryToRushAttack(IBot bot)
|
||||
{
|
||||
var allEnemyBaseBuilder = AIUtils.FindEnemiesByCommonName(Info.ConstructionYardTypes, Player);
|
||||
|
||||
var ownUnits = activeUnits
|
||||
.Where(unit => unit.IsIdle && unit.Info.HasTraitInfo<AttackBaseInfo>()
|
||||
&& !Info.AirUnitsTypes.Contains(unit.Info.Name) && !Info.NavalUnitsTypes.Contains(unit.Info.Name) && !Info.ExcludeFromSquadsTypes.Contains(unit.Info.Name)).ToList();
|
||||
.Where(unit =>
|
||||
unit.IsIdle
|
||||
&& unit.Info.HasTraitInfo<AttackBaseInfo>()
|
||||
&& !Info.AirUnitsTypes.Contains(unit.Info.Name)
|
||||
&& !Info.NavalUnitsTypes.Contains(unit.Info.Name)
|
||||
&& !Info.ExcludeFromSquadsTypes.Contains(unit.Info.Name))
|
||||
.ToList();
|
||||
|
||||
if (ownUnits.Count < Info.SquadSize)
|
||||
return;
|
||||
|
||||
var allEnemyBaseBuilder = FindEnemies(
|
||||
World.Actors.Where(a => Info.ConstructionYardTypes.Contains(a.Info.Name)),
|
||||
ownUnits.First())
|
||||
.ToList();
|
||||
|
||||
if (allEnemyBaseBuilder.Count == 0 || ownUnits.Count < Info.SquadSize)
|
||||
return;
|
||||
|
||||
foreach (var b in allEnemyBaseBuilder)
|
||||
foreach (var enemyBaseBuilder in allEnemyBaseBuilder)
|
||||
{
|
||||
// Don't rush enemy aircraft!
|
||||
var enemies = World.FindActorsInCircle(b.CenterPosition, WDist.FromCells(Info.RushAttackScanRadius))
|
||||
.Where(unit => IsPreferredEnemyUnit(unit) && unit.Info.HasTraitInfo<AttackBaseInfo>() && !Info.AirUnitsTypes.Contains(unit.Info.Name) && !Info.NavalUnitsTypes.Contains(unit.Info.Name)).ToList();
|
||||
var enemies = FindEnemies(
|
||||
World.FindActorsInCircle(enemyBaseBuilder.Actor.CenterPosition, WDist.FromCells(Info.RushAttackScanRadius))
|
||||
.Where(unit =>
|
||||
unit.Info.HasTraitInfo<AttackBaseInfo>()
|
||||
&& !Info.AirUnitsTypes.Contains(unit.Info.Name)
|
||||
&& !Info.NavalUnitsTypes.Contains(unit.Info.Name)),
|
||||
ownUnits.First())
|
||||
.ToList();
|
||||
|
||||
if (AttackOrFleeFuzzy.Rush.CanAttack(ownUnits, enemies))
|
||||
if (AttackOrFleeFuzzy.Rush.CanAttack(ownUnits, enemies.Select(x => x.Actor).ToList()))
|
||||
{
|
||||
var target = enemies.Count > 0 ? enemies.Random(World.LocalRandom) : b;
|
||||
var target = enemies.Count > 0 ? enemies.Random(World.LocalRandom) : enemyBaseBuilder;
|
||||
var rush = GetSquadOfType(SquadType.Rush);
|
||||
rush ??= RegisterNewSquad(bot, SquadType.Rush, target);
|
||||
|
||||
foreach (var a3 in ownUnits)
|
||||
rush.Units.Add(a3);
|
||||
rush.Units.UnionWith(ownUnits);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -348,18 +456,21 @@ namespace OpenRA.Mods.Common.Traits
|
||||
void ProtectOwn(IBot bot, Actor attacker)
|
||||
{
|
||||
var protectSq = GetSquadOfType(SquadType.Protection);
|
||||
protectSq ??= RegisterNewSquad(bot, SquadType.Protection, attacker);
|
||||
protectSq ??= RegisterNewSquad(bot, SquadType.Protection, (attacker, WVec.Zero));
|
||||
|
||||
if (!protectSq.IsTargetValid)
|
||||
protectSq.TargetActor = attacker;
|
||||
if (protectSq.IsValid && !protectSq.IsTargetValid())
|
||||
protectSq.SetActorToTarget((attacker, WVec.Zero));
|
||||
|
||||
if (!protectSq.IsValid)
|
||||
{
|
||||
var ownUnits = World.FindActorsInCircle(World.Map.CenterOfCell(GetRandomBaseCenter()), WDist.FromCells(Info.ProtectUnitScanRadius))
|
||||
.Where(unit => unit.Owner == Player && !Info.ProtectionTypes.Contains(unit.Info.Name) && unit.Info.HasTraitInfo<AttackBaseInfo>());
|
||||
.Where(unit =>
|
||||
unit.Owner == Player
|
||||
&& !Info.ProtectionTypes.Contains(unit.Info.Name)
|
||||
&& unit.Info.HasTraitInfo<AttackBaseInfo>())
|
||||
.WithPathTo(World, attacker.CenterPosition);
|
||||
|
||||
foreach (var a in ownUnits)
|
||||
protectSq.Units.Add(a);
|
||||
protectSq.Units.UnionWith(ownUnits);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +540,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (activeUnitsNode != null)
|
||||
{
|
||||
activeUnits.Clear();
|
||||
activeUnits.AddRange(FieldLoader.GetValue<uint[]>("ActiveUnits", activeUnitsNode.Value.Value)
|
||||
activeUnits.UnionWith(FieldLoader.GetValue<uint[]>("ActiveUnits", activeUnitsNode.Value.Value)
|
||||
.Select(a => self.World.GetActorById(a)).Where(a => a != null));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,34 +20,44 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
|
||||
public class Squad
|
||||
{
|
||||
public List<Actor> Units = new();
|
||||
public HashSet<Actor> Units = new();
|
||||
public SquadType Type;
|
||||
|
||||
internal IBot Bot;
|
||||
internal World World;
|
||||
internal SquadManagerBotModule SquadManager;
|
||||
internal MersenneTwister Random;
|
||||
|
||||
internal Target Target;
|
||||
internal StateMachine FuzzyStateMachine;
|
||||
|
||||
public Squad(IBot bot, SquadManagerBotModule squadManager, SquadType type)
|
||||
: this(bot, squadManager, type, null) { }
|
||||
/// <summary>
|
||||
/// Target location to attack. This will be either the targeted actor,
|
||||
/// or a position close to that actor sufficient to get within weapons range.
|
||||
/// </summary>
|
||||
internal Target Target { get; set; }
|
||||
|
||||
public Squad(IBot bot, SquadManagerBotModule squadManager, SquadType type, Actor target)
|
||||
/// <summary>
|
||||
/// Actor that is targeted, for any actor based checks. Use <see cref="Target"/> for a targeting location.
|
||||
/// </summary>
|
||||
internal Actor TargetActor;
|
||||
|
||||
public Squad(IBot bot, SquadManagerBotModule squadManager, SquadType type)
|
||||
: this(bot, squadManager, type, default) { }
|
||||
|
||||
public Squad(IBot bot, SquadManagerBotModule squadManager, SquadType type, (Actor Actor, WVec Offset) target)
|
||||
{
|
||||
Bot = bot;
|
||||
SquadManager = squadManager;
|
||||
World = bot.Player.PlayerActor.World;
|
||||
Random = World.LocalRandom;
|
||||
Type = type;
|
||||
Target = Target.FromActor(target);
|
||||
SetActorToTarget(target);
|
||||
FuzzyStateMachine = new StateMachine();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SquadType.Assault:
|
||||
case SquadType.Rush:
|
||||
case SquadType.Naval:
|
||||
FuzzyStateMachine.ChangeState(this, new GroundUnitsIdleState(), true);
|
||||
break;
|
||||
case SquadType.Air:
|
||||
@@ -56,9 +66,6 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
case SquadType.Protection:
|
||||
FuzzyStateMachine.ChangeState(this, new UnitsForProtectionIdleState(), true);
|
||||
break;
|
||||
case SquadType.Naval:
|
||||
FuzzyStateMachine.ChangeState(this, new NavyUnitsIdleState(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,15 +77,50 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
|
||||
public bool IsValid => Units.Count > 0;
|
||||
|
||||
public Actor TargetActor
|
||||
public void SetActorToTarget((Actor Actor, WVec Offset) target)
|
||||
{
|
||||
get => Target.Actor;
|
||||
set => Target = Target.FromActor(value);
|
||||
TargetActor = target.Actor;
|
||||
if (TargetActor == null)
|
||||
{
|
||||
Target = Target.Invalid;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.Offset == WVec.Zero)
|
||||
Target = Target.FromActor(TargetActor);
|
||||
else
|
||||
Target = Target.FromPos(TargetActor.CenterPosition + target.Offset);
|
||||
}
|
||||
|
||||
public bool IsTargetValid => Target.IsValidFor(Units.FirstOrDefault()) && !Target.Actor.Info.HasTraitInfo<HuskInfo>();
|
||||
/// <summary>
|
||||
/// Checks the target is still valid, and updates the <see cref="Target"/> location if it is still valid.
|
||||
/// </summary>
|
||||
public bool IsTargetValid()
|
||||
{
|
||||
var valid =
|
||||
TargetActor != null &&
|
||||
TargetActor.IsInWorld &&
|
||||
TargetActor.IsTargetableBy(Units.FirstOrDefault()) &&
|
||||
!TargetActor.Info.HasTraitInfo<HuskInfo>();
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
public bool IsTargetVisible => TargetActor.CanBeViewedByPlayer(Bot.Player);
|
||||
// Refresh the target location.
|
||||
// If the actor moved out of reach then we'll mark it invalid.
|
||||
// e.g. a ship targeting a land unit that moves inland out of weapons range.
|
||||
// or the target crossed a bridge which is then destroyed.
|
||||
// If it is still in range but we have to target a nearby location, we can update that location.
|
||||
// e.g. a ship targeting a land unit, but the land unit moved north.
|
||||
// We need to update our location to move north as well.
|
||||
// If we can reach the actor directly, we'll just target it directly.
|
||||
var target = SquadManager.FindEnemies(new[] { TargetActor }, Units.First()).FirstOrDefault();
|
||||
SetActorToTarget(target);
|
||||
return target.Actor != null;
|
||||
}
|
||||
|
||||
public bool IsTargetVisible =>
|
||||
TargetActor != null &&
|
||||
TargetActor.CanBeViewedByPlayer(Bot.Player);
|
||||
|
||||
public WPos CenterPosition { get { return Units.Select(u => u.CenterPosition).Average(); } }
|
||||
|
||||
@@ -87,10 +129,14 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
var nodes = new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("Type", FieldSaver.FormatValue(Type)),
|
||||
new MiniYamlNode("Units", FieldSaver.FormatValue(Units.Select(a => a.ActorID).ToArray())),
|
||||
new MiniYamlNode("Units", FieldSaver.FormatValue(Units.Select(a => a.ActorID).ToArray()))
|
||||
};
|
||||
if (Target.Type == TargetType.Actor)
|
||||
nodes.Add(new MiniYamlNode("Target", FieldSaver.FormatValue(Target.Actor.ActorID)));
|
||||
|
||||
if (Target != Target.Invalid)
|
||||
{
|
||||
nodes.Add(new MiniYamlNode("ActorToTarget", FieldSaver.FormatValue(TargetActor.ActorID)));
|
||||
nodes.Add(new MiniYamlNode("TargetOffset", FieldSaver.FormatValue(Target.CenterPosition - TargetActor.CenterPosition)));
|
||||
}
|
||||
|
||||
return new MiniYaml("", nodes);
|
||||
}
|
||||
@@ -98,21 +144,26 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
public static Squad Deserialize(IBot bot, SquadManagerBotModule squadManager, MiniYaml yaml)
|
||||
{
|
||||
var type = SquadType.Rush;
|
||||
Actor targetActor = null;
|
||||
var target = ((Actor)null, WVec.Zero);
|
||||
|
||||
var typeNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Type");
|
||||
if (typeNode != null)
|
||||
type = FieldLoader.GetValue<SquadType>("Type", typeNode.Value.Value);
|
||||
|
||||
var targetNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Target");
|
||||
if (targetNode != null)
|
||||
targetActor = squadManager.World.GetActorById(FieldLoader.GetValue<uint>("ActiveUnits", targetNode.Value.Value));
|
||||
var actorToTargetNode = yaml.Nodes.FirstOrDefault(n => n.Key == "ActorToTarget");
|
||||
var targetOffsetNode = yaml.Nodes.FirstOrDefault(n => n.Key == "TargetOffset");
|
||||
if (actorToTargetNode != null && targetOffsetNode != null)
|
||||
{
|
||||
var actorToTarget = squadManager.World.GetActorById(FieldLoader.GetValue<uint>("ActorToTarget", actorToTargetNode.Value.Value));
|
||||
var targetOffset = FieldLoader.GetValue<WVec>("TargetOffset", targetOffsetNode.Value.Value);
|
||||
target = (actorToTarget, targetOffset);
|
||||
}
|
||||
|
||||
var squad = new Squad(bot, squadManager, type, targetActor);
|
||||
var squad = new Squad(bot, squadManager, type, target);
|
||||
|
||||
var unitsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Units");
|
||||
if (unitsNode != null)
|
||||
squad.Units.AddRange(FieldLoader.GetValue<uint[]>("Units", unitsNode.Value.Value)
|
||||
squad.Units.UnionWith(FieldLoader.GetValue<uint[]>("Units", unitsNode.Value.Value)
|
||||
.Select(a => squadManager.World.GetActorById(a)));
|
||||
|
||||
return squad;
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (e == null)
|
||||
return;
|
||||
|
||||
owner.TargetActor = e;
|
||||
owner.SetActorToTarget((e, WVec.Zero));
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new AirAttackState(), true);
|
||||
}
|
||||
|
||||
@@ -150,20 +150,19 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
if (!owner.IsTargetValid())
|
||||
{
|
||||
var a = owner.Units.Random(owner.Random);
|
||||
var closestEnemy = owner.SquadManager.FindClosestEnemy(a.CenterPosition);
|
||||
if (closestEnemy != null)
|
||||
owner.TargetActor = closestEnemy;
|
||||
else
|
||||
var closestEnemy = owner.SquadManager.FindClosestEnemy(a);
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new AirFleeState(), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!NearToPosSafely(owner, owner.TargetActor.CenterPosition))
|
||||
if (!NearToPosSafely(owner, owner.Target.CenterPosition))
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new AirFleeState(), true);
|
||||
return;
|
||||
@@ -188,7 +187,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
}
|
||||
|
||||
if (CanAttackTarget(a, owner.TargetActor))
|
||||
owner.Bot.QueueOrder(new Order("Attack", a, Target.FromActor(owner.TargetActor), false));
|
||||
owner.Bot.QueueOrder(new Order("Attack", a, owner.Target, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -21,9 +22,21 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
return ShouldFlee(owner, enemies => !AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemies));
|
||||
}
|
||||
|
||||
protected static Actor FindClosestEnemy(Squad owner)
|
||||
protected static (Actor Actor, WVec Offset) FindClosestEnemy(Squad owner)
|
||||
{
|
||||
return owner.SquadManager.FindClosestEnemy(owner.Units.First().CenterPosition);
|
||||
return owner.SquadManager.FindClosestEnemy(owner.Units.First());
|
||||
}
|
||||
|
||||
protected static IEnumerable<(Actor Actor, WVec Offset)> FindEnemies(Squad owner, IEnumerable<Actor> actors)
|
||||
{
|
||||
return owner.SquadManager.FindEnemies(
|
||||
actors,
|
||||
owner.Units.First());
|
||||
}
|
||||
|
||||
protected static Actor ClosestToEnemy(Squad owner)
|
||||
{
|
||||
return SquadManagerBotModule.ClosestTo(owner.Units, owner.TargetActor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,24 +49,26 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
if (!owner.IsTargetValid())
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
if (closestEnemy == null)
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
return;
|
||||
|
||||
owner.TargetActor = closestEnemy;
|
||||
}
|
||||
|
||||
var enemyUnits = owner.World.FindActorsInCircle(owner.TargetActor.CenterPosition, WDist.FromCells(owner.SquadManager.Info.IdleScanRadius))
|
||||
.Where(owner.SquadManager.IsPreferredEnemyUnit).ToList();
|
||||
var enemyUnits =
|
||||
FindEnemies(owner,
|
||||
owner.World.FindActorsInCircle(owner.Target.CenterPosition, WDist.FromCells(owner.SquadManager.Info.IdleScanRadius)))
|
||||
.Select(x => x.Actor)
|
||||
.ToList();
|
||||
|
||||
if (enemyUnits.Count == 0)
|
||||
return;
|
||||
|
||||
if (AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemyUnits))
|
||||
{
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, owner.TargetActor.Location), false, groupedActors: owner.Units.ToArray()));
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, owner.Target, false, groupedActors: owner.Units.ToArray()));
|
||||
|
||||
// We have gathered sufficient units. Attack the nearest enemy unit.
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackMoveState(), true);
|
||||
@@ -78,19 +93,18 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
if (!owner.IsTargetValid())
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
if (closestEnemy != null)
|
||||
owner.TargetActor = closestEnemy;
|
||||
else
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition);
|
||||
var leader = ClosestToEnemy(owner);
|
||||
if (leader == null)
|
||||
return;
|
||||
|
||||
@@ -129,16 +143,14 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
}
|
||||
else
|
||||
{
|
||||
var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.SquadManager.Info.AttackScanRadius))
|
||||
.Where(owner.SquadManager.IsPreferredEnemyUnit);
|
||||
var target = enemies.ClosestTo(leader.CenterPosition);
|
||||
if (target != null)
|
||||
var target = owner.SquadManager.FindClosestEnemy(leader, WDist.FromCells(owner.SquadManager.Info.AttackScanRadius));
|
||||
if (target.Actor != null)
|
||||
{
|
||||
owner.TargetActor = target;
|
||||
owner.SetActorToTarget(target);
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackState(), true);
|
||||
}
|
||||
else
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, owner.TargetActor.Location), false, groupedActors: owner.Units.ToArray()));
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, owner.Target, false, groupedActors: owner.Units.ToArray()));
|
||||
}
|
||||
|
||||
if (ShouldFlee(owner))
|
||||
@@ -161,22 +173,21 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
if (!owner.IsTargetValid())
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
if (closestEnemy != null)
|
||||
owner.TargetActor = closestEnemy;
|
||||
else
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition);
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
var leader = ClosestToEnemy(owner);
|
||||
if (leader?.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastLeaderLocation = leader?.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
@@ -197,7 +208,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
|
||||
foreach (var a in owner.Units)
|
||||
if (!BusyAttack(a))
|
||||
owner.Bot.QueueOrder(new Order("Attack", a, Target.FromActor(owner.TargetActor), false));
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", a, owner.Target, false));
|
||||
|
||||
if (ShouldFlee(owner))
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true);
|
||||
@@ -219,6 +230,6 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsIdleState(), true);
|
||||
}
|
||||
|
||||
public void Deactivate(Squad owner) { owner.Units.Clear(); }
|
||||
public void Deactivate(Squad owner) { owner.SquadManager.UnregisterSquad(owner); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
{
|
||||
abstract class NavyStateBase : StateBase
|
||||
{
|
||||
protected virtual bool ShouldFlee(Squad owner)
|
||||
{
|
||||
return ShouldFlee(owner, enemies => !AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemies));
|
||||
}
|
||||
|
||||
protected static Actor FindClosestEnemy(Squad owner)
|
||||
{
|
||||
var first = owner.Units.First();
|
||||
|
||||
// Navy squad AI can exploit enemy naval production to find path, if any.
|
||||
// (Way better than finding a nearest target which is likely to be on Ground)
|
||||
// You might be tempted to move these lookups into Activate() but that causes null reference exception.
|
||||
var mobile = first.Trait<Mobile>();
|
||||
|
||||
var navalProductions = owner.World.ActorsHavingTrait<Building>().Where(a
|
||||
=> owner.SquadManager.Info.NavalProductionTypes.Contains(a.Info.Name)
|
||||
&& mobile.PathFinder.PathExistsForLocomotor(mobile.Locomotor, first.Location, a.Location)
|
||||
&& a.AppearsHostileTo(first));
|
||||
|
||||
var nearest = navalProductions.ClosestTo(first);
|
||||
if (nearest != null)
|
||||
{
|
||||
// Return nearest when it is FAR enough.
|
||||
// If the naval production is within MaxBaseRadius, it implies that
|
||||
// this squad is close to enemy territory and they should expect a naval combat;
|
||||
// closest enemy makes more sense in that case.
|
||||
if ((nearest.Location - first.Location).LengthSquared > owner.SquadManager.Info.MaxBaseRadius * owner.SquadManager.Info.MaxBaseRadius)
|
||||
return nearest;
|
||||
}
|
||||
|
||||
return owner.SquadManager.FindClosestEnemy(first.CenterPosition);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class NavyUnitsIdleState : NavyStateBase, IState
|
||||
{
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
{
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
if (closestEnemy == null)
|
||||
return;
|
||||
|
||||
owner.TargetActor = closestEnemy;
|
||||
}
|
||||
|
||||
var enemyUnits = owner.World.FindActorsInCircle(owner.TargetActor.CenterPosition, WDist.FromCells(owner.SquadManager.Info.IdleScanRadius))
|
||||
.Where(owner.SquadManager.IsPreferredEnemyUnit).ToList();
|
||||
|
||||
if (enemyUnits.Count == 0)
|
||||
return;
|
||||
|
||||
if (AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemyUnits))
|
||||
{
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, owner.TargetActor.Location), false, groupedActors: owner.Units.ToArray()));
|
||||
|
||||
// We have gathered sufficient units. Attack the nearest enemy unit.
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsAttackMoveState(), true);
|
||||
}
|
||||
else
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true);
|
||||
}
|
||||
|
||||
public void Deactivate(Squad owner) { }
|
||||
}
|
||||
|
||||
sealed class NavyUnitsAttackMoveState : NavyStateBase, IState
|
||||
{
|
||||
int lastUpdatedTick;
|
||||
CPos? lastLeaderLocation;
|
||||
Actor lastTarget;
|
||||
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
{
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
if (closestEnemy != null)
|
||||
owner.TargetActor = closestEnemy;
|
||||
else
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition);
|
||||
if (leader == null)
|
||||
return;
|
||||
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
if (owner.TargetActor != lastTarget)
|
||||
{
|
||||
lastTarget = owner.TargetActor;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
// HACK: Drop back to the idle state if we haven't moved in 2.5 seconds
|
||||
// This works around the squad being stuck trying to attack-move to a location
|
||||
// that they cannot path to, generating expensive pathfinding calls each tick.
|
||||
if (owner.World.WorldTick > lastUpdatedTick + 63)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsIdleState(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
var ownUnits = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Units.Count) / 3)
|
||||
.Where(a => a.Owner == owner.Units.First().Owner && owner.Units.Contains(a)).ToHashSet();
|
||||
|
||||
if (ownUnits.Count < owner.Units.Count)
|
||||
{
|
||||
// Since units have different movement speeds, they get separated while approaching the target.
|
||||
// Let them regroup into tighter formation.
|
||||
owner.Bot.QueueOrder(new Order("Stop", leader, false));
|
||||
|
||||
var units = owner.Units.Where(a => !ownUnits.Contains(a)).ToArray();
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, leader.Location), false, groupedActors: units));
|
||||
}
|
||||
else
|
||||
{
|
||||
var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.SquadManager.Info.AttackScanRadius))
|
||||
.Where(owner.SquadManager.IsPreferredEnemyUnit);
|
||||
var target = enemies.ClosestTo(leader.CenterPosition);
|
||||
if (target != null)
|
||||
{
|
||||
owner.TargetActor = target;
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsAttackState(), true);
|
||||
}
|
||||
else
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, owner.TargetActor.Location), false, groupedActors: owner.Units.ToArray()));
|
||||
}
|
||||
|
||||
if (ShouldFlee(owner))
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true);
|
||||
}
|
||||
|
||||
public void Deactivate(Squad owner) { }
|
||||
}
|
||||
|
||||
sealed class NavyUnitsAttackState : NavyStateBase, IState
|
||||
{
|
||||
int lastUpdatedTick;
|
||||
CPos? lastLeaderLocation;
|
||||
Actor lastTarget;
|
||||
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
{
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
if (closestEnemy != null)
|
||||
owner.TargetActor = closestEnemy;
|
||||
else
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition);
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
if (owner.TargetActor != lastTarget)
|
||||
{
|
||||
lastTarget = owner.TargetActor;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
// HACK: Drop back to the idle state if we haven't moved in 2.5 seconds
|
||||
// This works around the squad being stuck trying to attack-move to a location
|
||||
// that they cannot path to, generating expensive pathfinding calls each tick.
|
||||
if (owner.World.WorldTick > lastUpdatedTick + 63)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsIdleState(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var a in owner.Units)
|
||||
if (!BusyAttack(a))
|
||||
owner.Bot.QueueOrder(new Order("Attack", a, Target.FromActor(owner.TargetActor), false));
|
||||
|
||||
if (ShouldFlee(owner))
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true);
|
||||
}
|
||||
|
||||
public void Deactivate(Squad owner) { }
|
||||
}
|
||||
|
||||
sealed class NavyUnitsFleeState : NavyStateBase, IState
|
||||
{
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
{
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
GoToRandomOwnBuilding(owner);
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsIdleState(), true);
|
||||
}
|
||||
|
||||
public void Deactivate(Squad owner) { owner.Units.Clear(); }
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Traits;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
{
|
||||
@@ -32,11 +32,11 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid)
|
||||
if (!owner.IsTargetValid())
|
||||
{
|
||||
owner.TargetActor = owner.SquadManager.FindClosestEnemy(owner.CenterPosition, WDist.FromCells(owner.SquadManager.Info.ProtectionScanRadius));
|
||||
|
||||
if (owner.TargetActor == null)
|
||||
var target = owner.SquadManager.FindClosestEnemy(owner.Units.First(), WDist.FromCells(owner.SquadManager.Info.ProtectionScanRadius));
|
||||
owner.SetActorToTarget(target);
|
||||
if (target.Actor == null)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new UnitsForProtectionFleeState(), true);
|
||||
return;
|
||||
@@ -55,7 +55,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
Backoff--;
|
||||
}
|
||||
else
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, owner.TargetActor.Location), false, groupedActors: owner.Units.ToArray()));
|
||||
owner.Bot.QueueOrder(new Order("AttackMove", null, owner.Target, false, groupedActors: owner.Units.ToArray()));
|
||||
}
|
||||
|
||||
public void Deactivate(Squad owner) { }
|
||||
@@ -74,6 +74,6 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new UnitsForProtectionIdleState(), true);
|
||||
}
|
||||
|
||||
public void Deactivate(Squad owner) { owner.Units.Clear(); }
|
||||
public void Deactivate(Squad owner) { owner.SquadManager.UnregisterSquad(owner); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user