Improve AI squad pathing and regrouping behavior.
Ensure the target location can be pathed to by all units in the squad, so the squad won't get stuck if some units can't make it. Improve the choice of leader for the squad. We attempt to a choose a leader whose locomotor is the most restrictive in terms of passable terrain. This maximises the chance that the squad will be able to follow the leader along the path to the target. We also keep this choice of leader as the squad advances, this avoids the squad constantly switching leaders and regrouping backwards in some cases.
This commit is contained in:
@@ -153,10 +153,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return false;
|
||||
|
||||
var targetTypes = a.GetEnabledTargetTypes();
|
||||
return !targetTypes.IsEmpty && !targetTypes.Overlaps(Info.IgnoredEnemyTargetTypes);
|
||||
if (targetTypes.IsEmpty || targetTypes.Overlaps(Info.IgnoredEnemyTargetTypes))
|
||||
return false;
|
||||
|
||||
return IsNotHiddenUnit(a);
|
||||
}
|
||||
|
||||
public bool IsNotHiddenUnit(Actor a)
|
||||
bool IsNotHiddenUnit(Actor a)
|
||||
{
|
||||
var hasModifier = false;
|
||||
var visModifiers = a.TraitsImplementing<IVisibilityModifier>();
|
||||
@@ -244,7 +247,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// 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))
|
||||
.Where(IsPreferredEnemyUnit)
|
||||
.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)))))
|
||||
@@ -462,7 +465,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var protectSq = GetSquadOfType(SquadType.Protection);
|
||||
protectSq ??= RegisterNewSquad(bot, SquadType.Protection, (attacker, WVec.Zero));
|
||||
|
||||
if (protectSq.IsValid && !protectSq.IsTargetValid())
|
||||
if (protectSq.IsValid && !protectSq.IsTargetValid(protectSq.CenterUnit()))
|
||||
protectSq.SetActorToTarget((attacker, WVec.Zero));
|
||||
|
||||
if (!protectSq.IsValid)
|
||||
|
||||
@@ -95,12 +95,12 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
/// <summary>
|
||||
/// Checks the target is still valid, and updates the <see cref="Target"/> location if it is still valid.
|
||||
/// </summary>
|
||||
public bool IsTargetValid()
|
||||
public bool IsTargetValid(Actor squadUnit)
|
||||
{
|
||||
var valid =
|
||||
TargetActor != null &&
|
||||
TargetActor.IsInWorld &&
|
||||
TargetActor.IsTargetableBy(Units.FirstOrDefault()) &&
|
||||
Units.Any(Target.IsValidFor) &&
|
||||
!TargetActor.Info.HasTraitInfo<HuskInfo>();
|
||||
if (!valid)
|
||||
return false;
|
||||
@@ -113,7 +113,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
// 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();
|
||||
var target = SquadManager.FindEnemies(new[] { TargetActor }, squadUnit).FirstOrDefault();
|
||||
SetActorToTarget(target);
|
||||
return target.Actor != null;
|
||||
}
|
||||
@@ -122,7 +122,16 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
TargetActor != null &&
|
||||
TargetActor.CanBeViewedByPlayer(Bot.Player);
|
||||
|
||||
public WPos CenterPosition { get { return Units.Select(u => u.CenterPosition).Average(); } }
|
||||
public WPos CenterPosition()
|
||||
{
|
||||
return Units.Select(a => a.CenterPosition).Average();
|
||||
}
|
||||
|
||||
public Actor CenterUnit()
|
||||
{
|
||||
var centerPosition = CenterPosition();
|
||||
return Units.MinByOrDefault(a => (a.CenterPosition - centerPosition).LengthSquared);
|
||||
}
|
||||
|
||||
public MiniYaml Serialize()
|
||||
{
|
||||
|
||||
@@ -147,10 +147,10 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid())
|
||||
var leader = owner.CenterUnit();
|
||||
if (!owner.IsTargetValid(leader))
|
||||
{
|
||||
var a = owner.Units.Random(owner.Random);
|
||||
var closestEnemy = owner.SquadManager.FindClosestEnemy(a);
|
||||
var closestEnemy = owner.SquadManager.FindClosestEnemy(leader);
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
{
|
||||
|
||||
@@ -17,21 +17,59 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
{
|
||||
abstract class GroundStateBase : StateBase
|
||||
{
|
||||
Actor leader;
|
||||
|
||||
/// <summary>
|
||||
/// Elects a unit to lead the squad, other units in the squad will regroup to the leader if they start to spread out.
|
||||
/// The leader remains the same unless a new one is forced or the leader is no longer part of the squad.
|
||||
/// </summary>
|
||||
protected Actor Leader(Squad owner)
|
||||
{
|
||||
if (leader == null || !owner.Units.Contains(leader))
|
||||
leader = NewLeader(owner);
|
||||
return leader;
|
||||
}
|
||||
|
||||
static Actor NewLeader(Squad owner)
|
||||
{
|
||||
IEnumerable<Actor> units = owner.Units;
|
||||
|
||||
// Identify the Locomotor with the most restrictive passable terrain list. For squads with mixed
|
||||
// locomotors, we hope to choose the most restrictive option. This means we won't nominate a leader who has
|
||||
// more options. This avoids situations where we would nominate a hovercraft as the leader and tanks would
|
||||
// fail to follow it because they can't go over water. By forcing us to choose a unit with limited movement
|
||||
// options, we maximise the chance other units will be able to follow it. We could still be screwed if the
|
||||
// squad has a mix of units with disparate movement, e.g. land units and naval units. We must trust the
|
||||
// squad has been formed from a set of units that don't suffer this problem.
|
||||
var leastCommonDenominator = units
|
||||
.Select(a => a.TraitOrDefault<Mobile>()?.Locomotor)
|
||||
.Where(l => l != null)
|
||||
.MinByOrDefault(l => l.Info.TerrainSpeeds.Count)
|
||||
?.Info.TerrainSpeeds.Count;
|
||||
if (leastCommonDenominator != null)
|
||||
units = units.Where(a => a.TraitOrDefault<Mobile>()?.Locomotor.Info.TerrainSpeeds.Count == leastCommonDenominator).ToList();
|
||||
|
||||
// Choosing a unit in the center reduces the need for an immediate regroup.
|
||||
var centerPosition = units.Select(a => a.CenterPosition).Average();
|
||||
return units.MinBy(a => (a.CenterPosition - centerPosition).LengthSquared);
|
||||
}
|
||||
|
||||
protected virtual bool ShouldFlee(Squad owner)
|
||||
{
|
||||
return ShouldFlee(owner, enemies => !AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemies));
|
||||
}
|
||||
|
||||
protected static (Actor Actor, WVec Offset) FindClosestEnemy(Squad owner)
|
||||
protected (Actor Actor, WVec Offset) NewLeaderAndFindClosestEnemy(Squad owner)
|
||||
{
|
||||
return owner.SquadManager.FindClosestEnemy(owner.Units.First());
|
||||
leader = null; // Force a new leader to be elected, useful if we are targeting a new enemy.
|
||||
return owner.SquadManager.FindClosestEnemy(Leader(owner));
|
||||
}
|
||||
|
||||
protected static IEnumerable<(Actor Actor, WVec Offset)> FindEnemies(Squad owner, IEnumerable<Actor> actors)
|
||||
protected IEnumerable<(Actor Actor, WVec Offset)> FindEnemies(Squad owner, IEnumerable<Actor> actors)
|
||||
{
|
||||
return owner.SquadManager.FindEnemies(
|
||||
actors,
|
||||
owner.Units.First());
|
||||
Leader(owner));
|
||||
}
|
||||
|
||||
protected static Actor ClosestToEnemy(Squad owner)
|
||||
@@ -49,9 +87,9 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid())
|
||||
if (!owner.IsTargetValid(Leader(owner)))
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
var closestEnemy = NewLeaderAndFindClosestEnemy(owner);
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
return;
|
||||
@@ -93,9 +131,9 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid())
|
||||
if (!owner.IsTargetValid(Leader(owner)))
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
var closestEnemy = NewLeaderAndFindClosestEnemy(owner);
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
{
|
||||
@@ -104,10 +142,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
}
|
||||
}
|
||||
|
||||
var leader = ClosestToEnemy(owner);
|
||||
if (leader == null)
|
||||
return;
|
||||
|
||||
var leader = Leader(owner);
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
@@ -130,7 +165,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
}
|
||||
|
||||
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();
|
||||
.Where(owner.Units.Contains).ToHashSet();
|
||||
|
||||
if (ownUnits.Count < owner.Units.Count)
|
||||
{
|
||||
@@ -173,9 +208,9 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid())
|
||||
if (!owner.IsTargetValid(Leader(owner)))
|
||||
{
|
||||
var closestEnemy = FindClosestEnemy(owner);
|
||||
var closestEnemy = NewLeaderAndFindClosestEnemy(owner);
|
||||
owner.SetActorToTarget(closestEnemy);
|
||||
if (closestEnemy.Actor == null)
|
||||
{
|
||||
@@ -184,10 +219,10 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
}
|
||||
}
|
||||
|
||||
var leader = ClosestToEnemy(owner);
|
||||
if (leader?.Location != lastLeaderLocation)
|
||||
var leader = Leader(owner);
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader?.Location;
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,10 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!owner.IsValid)
|
||||
return;
|
||||
|
||||
if (!owner.IsTargetValid())
|
||||
var leader = Leader(owner);
|
||||
if (!owner.IsTargetValid(leader))
|
||||
{
|
||||
var target = owner.SquadManager.FindClosestEnemy(owner.Units.First(), WDist.FromCells(owner.SquadManager.Info.ProtectionScanRadius));
|
||||
var target = owner.SquadManager.FindClosestEnemy(leader, WDist.FromCells(owner.SquadManager.Info.ProtectionScanRadius));
|
||||
owner.SetActorToTarget(target);
|
||||
if (target.Actor == null)
|
||||
{
|
||||
|
||||
@@ -85,9 +85,8 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (!squad.IsValid)
|
||||
return false;
|
||||
|
||||
var randomSquadUnit = squad.Units.Random(squad.Random);
|
||||
var dangerRadius = squad.SquadManager.Info.DangerScanRadius;
|
||||
var units = squad.World.FindActorsInCircle(randomSquadUnit.CenterPosition, WDist.FromCells(dangerRadius)).ToList();
|
||||
var units = squad.World.FindActorsInCircle(squad.CenterPosition(), WDist.FromCells(dangerRadius)).ToList();
|
||||
|
||||
// If there are any own buildings within the DangerRadius, don't flee
|
||||
// PERF: Avoid LINQ
|
||||
|
||||
Reference in New Issue
Block a user