HackyAI: Add basic capturing
This commit is contained in:
@@ -16,12 +16,28 @@ using System.Linq;
|
|||||||
using OpenRA.Mods.Common.Activities;
|
using OpenRA.Mods.Common.Activities;
|
||||||
using OpenRA.Mods.Common.Pathfinder;
|
using OpenRA.Mods.Common.Pathfinder;
|
||||||
using OpenRA.Mods.Common.Traits;
|
using OpenRA.Mods.Common.Traits;
|
||||||
using OpenRA.Primitives;
|
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common.AI
|
namespace OpenRA.Mods.Common.AI
|
||||||
{
|
{
|
||||||
|
class CaptureTarget<TInfoType> where TInfoType : class, ITraitInfoInterface
|
||||||
|
{
|
||||||
|
internal readonly Actor Actor;
|
||||||
|
internal readonly TInfoType Info;
|
||||||
|
|
||||||
|
/// <summary>The order string given to the capturer so they can capture this actor.</summary>
|
||||||
|
/// <example>ExternalCaptureActor</example>
|
||||||
|
internal readonly string OrderString;
|
||||||
|
|
||||||
|
internal CaptureTarget(Actor actor, string orderString)
|
||||||
|
{
|
||||||
|
Actor = actor;
|
||||||
|
Info = actor.Info.TraitInfoOrDefault<TInfoType>();
|
||||||
|
OrderString = orderString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class HackyAIInfo : IBotInfo, ITraitInfo
|
public sealed class HackyAIInfo : IBotInfo, ITraitInfo
|
||||||
{
|
{
|
||||||
public class UnitCategories
|
public class UnitCategories
|
||||||
@@ -174,6 +190,27 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
[FieldLoader.LoadUsing("LoadDecisions")]
|
[FieldLoader.LoadUsing("LoadDecisions")]
|
||||||
public readonly List<SupportPowerDecision> PowerDecisions = new List<SupportPowerDecision>();
|
public readonly List<SupportPowerDecision> PowerDecisions = new List<SupportPowerDecision>();
|
||||||
|
|
||||||
|
[Desc("Actor types that can capture other actors (via `Captures` or `ExternalCaptures`).",
|
||||||
|
"Leave this empty to disable capturing.")]
|
||||||
|
public HashSet<string> CapturingActorTypes = new HashSet<string>();
|
||||||
|
|
||||||
|
[Desc("Actor types that can be targeted for capturing.",
|
||||||
|
"Leave this empty to include all actors.")]
|
||||||
|
public HashSet<string> CapturableActorTypes = new HashSet<string>();
|
||||||
|
|
||||||
|
[Desc("Minimum delay (in ticks) between trying to capture with CapturingActorTypes.")]
|
||||||
|
public readonly int MinimumCaptureDelay = 375;
|
||||||
|
|
||||||
|
[Desc("Maximum number of options to consider for capturing.",
|
||||||
|
"If a value less than 1 is given 1 will be used instead.")]
|
||||||
|
public readonly int MaximumCaptureTargetOptions = 10;
|
||||||
|
|
||||||
|
[Desc("Should visibility (Shroud, Fog, Cloak, etc) be considered when searching for capturable targets?")]
|
||||||
|
public readonly bool CheckCaptureTargetsForVisibility = true;
|
||||||
|
|
||||||
|
[Desc("Player stances that capturers should attempt to target.")]
|
||||||
|
public readonly Stance CapturableStances = Stance.Enemy | Stance.Neutral;
|
||||||
|
|
||||||
static object LoadUnitCategories(MiniYaml yaml)
|
static object LoadUnitCategories(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var categories = yaml.Nodes.First(n => n.Key == "UnitsCommonNames");
|
var categories = yaml.Nodes.First(n => n.Key == "UnitsCommonNames");
|
||||||
@@ -254,6 +291,8 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
int assignRolesTicks;
|
int assignRolesTicks;
|
||||||
int attackForceTicks;
|
int attackForceTicks;
|
||||||
int minAttackForceDelayTicks;
|
int minAttackForceDelayTicks;
|
||||||
|
int minCaptureDelayTicks;
|
||||||
|
readonly int maximumCaptureTargetOptions;
|
||||||
|
|
||||||
readonly Queue<Order> orders = new Queue<Order>();
|
readonly Queue<Order> orders = new Queue<Order>();
|
||||||
|
|
||||||
@@ -279,6 +318,8 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
|
|
||||||
foreach (var decision in info.PowerDecisions)
|
foreach (var decision in info.PowerDecisions)
|
||||||
powerDecisions.Add(decision.OrderName, decision);
|
powerDecisions.Add(decision.OrderName, decision);
|
||||||
|
|
||||||
|
maximumCaptureTargetOptions = Math.Max(1, Info.MaximumCaptureTargetOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BotDebug(string s, params object[] args)
|
public static void BotDebug(string s, params object[] args)
|
||||||
@@ -311,6 +352,7 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
assignRolesTicks = Random.Next(0, Info.AssignRolesInterval);
|
assignRolesTicks = Random.Next(0, Info.AssignRolesInterval);
|
||||||
attackForceTicks = Random.Next(0, Info.AttackForceInterval);
|
attackForceTicks = Random.Next(0, Info.AttackForceInterval);
|
||||||
minAttackForceDelayTicks = Random.Next(0, Info.MinimumAttackForceDelay);
|
minAttackForceDelayTicks = Random.Next(0, Info.MinimumAttackForceDelay);
|
||||||
|
minCaptureDelayTicks = Random.Next(0, Info.MinimumCaptureDelay);
|
||||||
|
|
||||||
var tileset = World.Map.Rules.TileSet;
|
var tileset = World.Map.Rules.TileSet;
|
||||||
resourceTypeIndices = new BitArray(tileset.TerrainInfo.Length); // Big enough
|
resourceTypeIndices = new BitArray(tileset.TerrainInfo.Length); // Big enough
|
||||||
@@ -631,6 +673,95 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
}
|
}
|
||||||
|
|
||||||
FindAndDeployBackupMcv(self);
|
FindAndDeployBackupMcv(self);
|
||||||
|
|
||||||
|
if (--minCaptureDelayTicks <= 0)
|
||||||
|
{
|
||||||
|
minCaptureDelayTicks = Info.MinimumCaptureDelay;
|
||||||
|
QueueCaptureOrders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Actor> GetVisibleActorsBelongingToPlayer(Player owner)
|
||||||
|
{
|
||||||
|
foreach (var actor in GetActorsThatCanBeOrderedByPlayer(owner))
|
||||||
|
if (actor.CanBeViewedByPlayer(owner))
|
||||||
|
yield return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Actor> GetActorsThatCanBeOrderedByPlayer(Player owner)
|
||||||
|
{
|
||||||
|
foreach (var actor in World.Actors)
|
||||||
|
if (actor.Owner == owner && !actor.IsDead && actor.IsInWorld)
|
||||||
|
yield return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueueCaptureOrders()
|
||||||
|
{
|
||||||
|
if (!Info.CapturingActorTypes.Any() || Player.WinState != WinState.Undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var capturers = unitsHangingAroundTheBase.Where(a => a.IsIdle && Info.CapturingActorTypes.Contains(a.Info.Name)).ToArray();
|
||||||
|
if (capturers.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var randPlayer = World.Players.Where(p => !p.Spectating
|
||||||
|
&& Info.CapturableStances.HasStance(Player.Stances[p])).Random(Random);
|
||||||
|
|
||||||
|
var targetOptions = Info.CheckCaptureTargetsForVisibility
|
||||||
|
? GetVisibleActorsBelongingToPlayer(randPlayer)
|
||||||
|
: GetActorsThatCanBeOrderedByPlayer(randPlayer);
|
||||||
|
|
||||||
|
var capturableTargetOptions = targetOptions
|
||||||
|
.Select(a => new CaptureTarget<CapturableInfo>(a, "CaptureActor"))
|
||||||
|
.Where(target => target.Info != null && capturers.Any(capturer => target.Info.CanBeTargetedBy(capturer, target.Actor.Owner)))
|
||||||
|
.OrderByDescending(target => target.Actor.GetSellValue())
|
||||||
|
.Take(maximumCaptureTargetOptions);
|
||||||
|
|
||||||
|
var externalCapturableTargetOptions = targetOptions
|
||||||
|
.Select(a => new CaptureTarget<ExternalCapturableInfo>(a, "ExternalCaptureActor"))
|
||||||
|
.Where(target => target.Info != null && capturers.Any(capturer => target.Info.CanBeTargetedBy(capturer, target.Actor.Owner)))
|
||||||
|
.OrderByDescending(target => target.Actor.GetSellValue())
|
||||||
|
.Take(maximumCaptureTargetOptions);
|
||||||
|
|
||||||
|
if (Info.CapturableActorTypes.Any())
|
||||||
|
{
|
||||||
|
capturableTargetOptions = capturableTargetOptions.Where(target => Info.CapturableActorTypes.Contains(target.Actor.Info.Name.ToLowerInvariant()));
|
||||||
|
externalCapturableTargetOptions = externalCapturableTargetOptions.Where(target => Info.CapturableActorTypes.Contains(target.Actor.Info.Name.ToLowerInvariant()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!capturableTargetOptions.Any() && !externalCapturableTargetOptions.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var capturesCapturers = capturers.Where(a => a.Info.HasTraitInfo<CapturesInfo>());
|
||||||
|
var externalCapturers = capturers.Except(capturesCapturers).Where(a => a.Info.HasTraitInfo<ExternalCapturesInfo>());
|
||||||
|
|
||||||
|
foreach (var capturer in capturesCapturers)
|
||||||
|
QueueCaptureOrderFor(capturer, GetCapturerTargetClosestToOrDefault(capturer, capturableTargetOptions));
|
||||||
|
|
||||||
|
foreach (var capturer in externalCapturers)
|
||||||
|
QueueCaptureOrderFor(capturer, GetCapturerTargetClosestToOrDefault(capturer, externalCapturableTargetOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueueCaptureOrderFor<TTargetType>(Actor capturer, CaptureTarget<TTargetType> target) where TTargetType : class, ITraitInfoInterface
|
||||||
|
{
|
||||||
|
if (capturer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (target.Actor == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QueueOrder(new Order(target.OrderString, capturer, true) { TargetActor = target.Actor });
|
||||||
|
BotDebug("AI ({0}): Ordered {1} to capture {2}", Player.ClientIndex, capturer, target.Actor);
|
||||||
|
activeUnits.Remove(capturer);
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureTarget<TTargetType> GetCapturerTargetClosestToOrDefault<TTargetType>(Actor capturer, IEnumerable<CaptureTarget<TTargetType>> targets)
|
||||||
|
where TTargetType : class, ITraitInfoInterface
|
||||||
|
{
|
||||||
|
return targets.MinByOrDefault(target => (target.Actor.CenterPosition - capturer.CenterPosition).LengthSquared);
|
||||||
}
|
}
|
||||||
|
|
||||||
CPos FindNextResource(Actor harvester)
|
CPos FindNextResource(Actor harvester)
|
||||||
|
|||||||
Reference in New Issue
Block a user