Split off CaptureManagerBotModule
from HackyAI. Note: This isn't used in any official mod right know, and known to be bugged on bleed already.
This commit is contained in:
committed by
Oliver Brakmann
parent
d823d38e8c
commit
6db27b1839
@@ -119,27 +119,6 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
[FieldLoader.LoadUsing("LoadBuildingCategories", true)]
|
[FieldLoader.LoadUsing("LoadBuildingCategories", true)]
|
||||||
public readonly BuildingCategories BuildingCommonNames;
|
public readonly BuildingCategories BuildingCommonNames;
|
||||||
|
|
||||||
[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");
|
||||||
@@ -206,8 +185,6 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
int assignRolesTicks;
|
int assignRolesTicks;
|
||||||
int attackForceTicks;
|
int attackForceTicks;
|
||||||
int minAttackForceDelayTicks;
|
int minAttackForceDelayTicks;
|
||||||
int minCaptureDelayTicks;
|
|
||||||
readonly int maximumCaptureTargetOptions;
|
|
||||||
|
|
||||||
public HackyAI(HackyAIInfo info, ActorInitializer init)
|
public HackyAI(HackyAIInfo info, ActorInitializer init)
|
||||||
{
|
{
|
||||||
@@ -223,8 +200,6 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
&& unit.Info.HasTraitInfo<ITargetableInfo>();
|
&& unit.Info.HasTraitInfo<ITargetableInfo>();
|
||||||
|
|
||||||
unitCannotBeOrdered = a => a.Owner != Player || a.IsDead || !a.IsInWorld;
|
unitCannotBeOrdered = a => a.Owner != Player || a.IsDead || !a.IsInWorld;
|
||||||
|
|
||||||
maximumCaptureTargetOptions = Math.Max(1, Info.MaximumCaptureTargetOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the host's player creation code
|
// Called by the host's player creation code
|
||||||
@@ -246,7 +221,6 @@ 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBot.QueueOrder(Order order)
|
void IBot.QueueOrder(Order order)
|
||||||
@@ -459,96 +433,6 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
minAttackForceDelayTicks = Info.MinimumAttackForceDelay;
|
minAttackForceDelayTicks = Info.MinimumAttackForceDelay;
|
||||||
CreateAttackForce();
|
CreateAttackForce();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (--minCaptureDelayTicks <= 0)
|
|
||||||
{
|
|
||||||
minCaptureDelayTicks = Info.MinimumCaptureDelay;
|
|
||||||
QueueCaptureOrders();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<Actor> GetVisibleActorsBelongingToPlayer(Player owner)
|
|
||||||
{
|
|
||||||
foreach (var actor in GetActorsThatCanBeOrderedByPlayer(owner))
|
|
||||||
if (actor.CanBeViewedByPlayer(Player))
|
|
||||||
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))
|
|
||||||
.Select(a => new TraitPair<CaptureManager>(a, a.TraitOrDefault<CaptureManager>()))
|
|
||||||
.Where(tp => tp.Trait != null)
|
|
||||||
.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 =>
|
|
||||||
{
|
|
||||||
if (target.Info == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var captureManager = target.Actor.TraitOrDefault<CaptureManager>();
|
|
||||||
if (captureManager == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return capturers.Any(tp => captureManager.CanBeTargetedBy(target.Actor, tp.Actor, tp.Trait));
|
|
||||||
})
|
|
||||||
.OrderByDescending(target => target.Actor.GetSellValue())
|
|
||||||
.Take(maximumCaptureTargetOptions);
|
|
||||||
|
|
||||||
if (Info.CapturableActorTypes.Any())
|
|
||||||
capturableTargetOptions = capturableTargetOptions.Where(target => Info.CapturableActorTypes.Contains(target.Actor.Info.Name.ToLowerInvariant()));
|
|
||||||
|
|
||||||
if (!capturableTargetOptions.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
var capturesCapturers = capturers.Where(tp => tp.Actor.Info.HasTraitInfo<CapturesInfo>());
|
|
||||||
foreach (var tp in capturesCapturers)
|
|
||||||
QueueCaptureOrderFor(tp.Actor, GetCapturerTargetClosestToOrDefault(tp.Actor, capturableTargetOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
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, Target.FromActor(target.Actor), true));
|
|
||||||
AIUtils.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FindNewUnits(Actor self)
|
void FindNewUnits(Actor self)
|
||||||
|
|||||||
@@ -128,6 +128,7 @@
|
|||||||
<Compile Include="Traits\BotModules\SupportPowerBotModule.cs" />
|
<Compile Include="Traits\BotModules\SupportPowerBotModule.cs" />
|
||||||
<Compile Include="Traits\BotModules\BaseBuilderBotModule.cs" />
|
<Compile Include="Traits\BotModules\BaseBuilderBotModule.cs" />
|
||||||
<Compile Include="Traits\BotModules\BuildingRepairBotModule.cs" />
|
<Compile Include="Traits\BotModules\BuildingRepairBotModule.cs" />
|
||||||
|
<Compile Include="Traits\BotModules\CaptureManagerBotModule.cs" />
|
||||||
<Compile Include="AI\Squad.cs" />
|
<Compile Include="AI\Squad.cs" />
|
||||||
<Compile Include="AI\StateMachine.cs" />
|
<Compile Include="AI\StateMachine.cs" />
|
||||||
<Compile Include="AI\States\AirStates.cs" />
|
<Compile Include="AI\States\AirStates.cs" />
|
||||||
|
|||||||
192
OpenRA.Mods.Common/Traits/BotModules/CaptureManagerBotModule.cs
Normal file
192
OpenRA.Mods.Common/Traits/BotModules/CaptureManagerBotModule.cs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||||
|
* 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenRA.Mods.Common.AI;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
|
||||||
|
namespace OpenRA.Mods.Common.Traits
|
||||||
|
{
|
||||||
|
[Desc("Manages AI capturing logic.")]
|
||||||
|
public class CaptureManagerBotModuleInfo : ConditionalTraitInfo
|
||||||
|
{
|
||||||
|
[Desc("Actor types that can capture other actors (via `Captures`).",
|
||||||
|
"Leave this empty to disable capturing.")]
|
||||||
|
public readonly HashSet<string> CapturingActorTypes = new HashSet<string>();
|
||||||
|
|
||||||
|
[Desc("Actor types that can be targeted for capturing.",
|
||||||
|
"Leave this empty to include all actors.")]
|
||||||
|
public readonly 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;
|
||||||
|
|
||||||
|
public override object Create(ActorInitializer init) { return new CaptureManagerBotModule(init.Self, this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CaptureManagerBotModule : ConditionalTrait<CaptureManagerBotModuleInfo>, IBotTick
|
||||||
|
{
|
||||||
|
readonly World world;
|
||||||
|
readonly Player player;
|
||||||
|
readonly Func<Actor, bool> isEnemyUnit;
|
||||||
|
readonly Predicate<Actor> unitCannotBeOrdered;
|
||||||
|
readonly int maximumCaptureTargetOptions;
|
||||||
|
int minCaptureDelayTicks;
|
||||||
|
|
||||||
|
// Units that the bot already knows about and has given a capture order. Any unit not on this list needs to be given a new order.
|
||||||
|
List<Actor> activeCapturers = new List<Actor>();
|
||||||
|
|
||||||
|
public CaptureManagerBotModule(Actor self, CaptureManagerBotModuleInfo info)
|
||||||
|
: base(info)
|
||||||
|
{
|
||||||
|
world = self.World;
|
||||||
|
player = self.Owner;
|
||||||
|
|
||||||
|
if (world.Type == WorldType.Editor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isEnemyUnit = unit =>
|
||||||
|
player.Stances[unit.Owner] == Stance.Enemy
|
||||||
|
&& !unit.Info.HasTraitInfo<HuskInfo>()
|
||||||
|
&& unit.Info.HasTraitInfo<ITargetableInfo>();
|
||||||
|
|
||||||
|
unitCannotBeOrdered = a => a.Owner != player || a.IsDead || !a.IsInWorld;
|
||||||
|
|
||||||
|
maximumCaptureTargetOptions = Math.Max(1, Info.MaximumCaptureTargetOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void TraitEnabled(Actor self)
|
||||||
|
{
|
||||||
|
// Avoid all AIs reevaluating assignments on the same tick, randomize their initial evaluation delay.
|
||||||
|
minCaptureDelayTicks = world.LocalRandom.Next(0, Info.MinimumCaptureDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IBotTick.BotTick(IBot bot)
|
||||||
|
{
|
||||||
|
if (--minCaptureDelayTicks <= 0)
|
||||||
|
{
|
||||||
|
minCaptureDelayTicks = Info.MinimumCaptureDelay;
|
||||||
|
QueueCaptureOrders(bot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
if (actor.CanBeViewedByPlayer(player))
|
||||||
|
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(IBot bot)
|
||||||
|
{
|
||||||
|
if (!Info.CapturingActorTypes.Any() || player.WinState != WinState.Undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activeCapturers.RemoveAll(unitCannotBeOrdered);
|
||||||
|
|
||||||
|
var newUnits = world.ActorsHavingTrait<IPositionable>()
|
||||||
|
.Where(a => a.Owner == player && !activeCapturers.Contains(a));
|
||||||
|
|
||||||
|
var capturers = newUnits
|
||||||
|
.Where(a => a.IsIdle && Info.CapturingActorTypes.Contains(a.Info.Name))
|
||||||
|
.Select(a => new TraitPair<CaptureManager>(a, a.TraitOrDefault<CaptureManager>()))
|
||||||
|
.Where(tp => tp.Trait != null)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (capturers.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var randPlayer = world.Players.Where(p => !p.Spectating
|
||||||
|
&& Info.CapturableStances.HasStance(player.Stances[p])).Random(world.LocalRandom);
|
||||||
|
|
||||||
|
var targetOptions = Info.CheckCaptureTargetsForVisibility
|
||||||
|
? GetVisibleActorsBelongingToPlayer(randPlayer)
|
||||||
|
: GetActorsThatCanBeOrderedByPlayer(randPlayer);
|
||||||
|
|
||||||
|
var capturableTargetOptions = targetOptions
|
||||||
|
.Select(a => new CaptureTarget<CapturableInfo>(a, "CaptureActor"))
|
||||||
|
.Where(target =>
|
||||||
|
{
|
||||||
|
if (target.Info == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var captureManager = target.Actor.TraitOrDefault<CaptureManager>();
|
||||||
|
if (captureManager == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return capturers.Any(tp => captureManager.CanBeTargetedBy(target.Actor, tp.Actor, tp.Trait));
|
||||||
|
})
|
||||||
|
.OrderByDescending(target => target.Actor.GetSellValue())
|
||||||
|
.Take(maximumCaptureTargetOptions);
|
||||||
|
|
||||||
|
if (Info.CapturableActorTypes.Any())
|
||||||
|
capturableTargetOptions = capturableTargetOptions.Where(target => Info.CapturableActorTypes.Contains(target.Actor.Info.Name.ToLowerInvariant()));
|
||||||
|
|
||||||
|
if (!capturableTargetOptions.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var capturesCapturers = capturers.Where(tp => tp.Actor.Info.HasTraitInfo<CapturesInfo>());
|
||||||
|
foreach (var tp in capturesCapturers)
|
||||||
|
QueueCaptureOrderFor(bot, tp.Actor, GetCapturerTargetClosestToOrDefault(tp.Actor, capturableTargetOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueueCaptureOrderFor<TTargetType>(IBot bot, Actor capturer, CaptureTarget<TTargetType> target) where TTargetType : class, ITraitInfoInterface
|
||||||
|
{
|
||||||
|
if (capturer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (target.Actor == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bot.QueueOrder(new Order(target.OrderString, capturer, Target.FromActor(target.Actor), true));
|
||||||
|
AIUtils.BotDebug("AI ({0}): Ordered {1} to capture {2}", player.ClientIndex, capturer, target.Actor);
|
||||||
|
activeCapturers.Add(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -453,6 +453,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
[RequireExplicitImplementation]
|
[RequireExplicitImplementation]
|
||||||
public interface IBotTick { void BotTick(IBot bot); }
|
public interface IBotTick { void BotTick(IBot bot); }
|
||||||
|
|
||||||
|
[RequireExplicitImplementation]
|
||||||
public interface IBotRespondToAttack { void RespondToAttack(IBot bot, Actor self, AttackInfo e); }
|
public interface IBotRespondToAttack { void RespondToAttack(IBot bot, Actor self, AttackInfo e); }
|
||||||
|
|
||||||
[RequireExplicitImplementation]
|
[RequireExplicitImplementation]
|
||||||
|
|||||||
@@ -74,6 +74,16 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules
|
|||||||
"MaxBaseRadius",
|
"MaxBaseRadius",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
readonly string[] captureManagerFields =
|
||||||
|
{
|
||||||
|
"CapturingActorTypes",
|
||||||
|
"CapturableActorTypes",
|
||||||
|
"MinimumCaptureDelay",
|
||||||
|
"MaximumCaptureTargetOptions",
|
||||||
|
"CheckCaptureTargetsForVisibility",
|
||||||
|
"CapturableStances",
|
||||||
|
};
|
||||||
|
|
||||||
public override IEnumerable<string> AfterUpdate(ModData modData)
|
public override IEnumerable<string> AfterUpdate(ModData modData)
|
||||||
{
|
{
|
||||||
if (!messageShown)
|
if (!messageShown)
|
||||||
@@ -245,6 +255,21 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules
|
|||||||
requiresConditionNode.ReplaceValue(oldValue + " || " + conditionString);
|
requiresConditionNode.ReplaceValue(oldValue + " || " + conditionString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (captureManagerFields.Any(f => hackyAINode.ChildrenMatching(f).Any()))
|
||||||
|
{
|
||||||
|
var node = new MiniYamlNode("CaptureManagerBotModule@" + aiType, "");
|
||||||
|
node.AddNode(new MiniYamlNode("RequiresCondition", conditionString));
|
||||||
|
|
||||||
|
foreach (var field in captureManagerFields)
|
||||||
|
{
|
||||||
|
var fieldNode = hackyAINode.LastChildMatching(field);
|
||||||
|
if (fieldNode != null)
|
||||||
|
fieldNode.MoveNode(hackyAINode, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
addNodes.Add(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add module if any bot is using/enabling it.
|
// Only add module if any bot is using/enabling it.
|
||||||
|
|||||||
Reference in New Issue
Block a user