Convert AIHarvesterManager into *Module

This commit is contained in:
reaperrr
2018-11-03 04:31:19 +01:00
committed by abcdefg30
parent 04c69efc30
commit 927b6cd561
15 changed files with 284 additions and 107 deletions

View File

@@ -1,94 +0,0 @@
#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.Activities;
using OpenRA.Mods.Common.Pathfinder;
using OpenRA.Mods.Common.Traits;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.AI
{
class AIHarvesterManager
{
readonly HackyAI ai;
readonly World world;
readonly IPathFinder pathfinder;
readonly DomainIndex domainIndex;
readonly ResourceLayer resLayer;
readonly ResourceClaimLayer claimLayer;
public AIHarvesterManager(HackyAI ai, Player p)
{
this.ai = ai;
world = p.World;
pathfinder = world.WorldActor.Trait<IPathFinder>();
domainIndex = world.WorldActor.Trait<DomainIndex>();
resLayer = world.WorldActor.TraitOrDefault<ResourceLayer>();
claimLayer = world.WorldActor.TraitOrDefault<ResourceClaimLayer>();
}
CPos FindNextResource(Actor actor, Harvester harv)
{
var locomotorInfo = actor.Info.TraitInfo<MobileInfo>().LocomotorInfo;
Func<CPos, bool> isValidResource = cell =>
domainIndex.IsPassable(actor.Location, cell, locomotorInfo) &&
harv.CanHarvestCell(actor, cell) &&
claimLayer.CanClaimCell(actor, cell);
var path = pathfinder.FindPath(
PathSearch.Search(world, locomotorInfo, actor, true, isValidResource)
.WithCustomCost(loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), ai.Info.HarvesterEnemyAvoidanceRadius)
.Where(u => !u.IsDead && actor.Owner.Stances[u.Owner] == Stance.Enemy)
.Sum(u => Math.Max(WDist.Zero.Length, ai.Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length)))
.FromPoint(actor.Location));
if (path.Count == 0)
return CPos.Zero;
return path[0];
}
public void Tick(List<Actor> harvesters)
{
if (resLayer == null || resLayer.IsResourceLayerEmpty)
return;
// Find idle harvesters and give them orders:
foreach (var harvester in harvesters)
{
var harv = harvester.Trait<Harvester>();
if (!harv.IsEmpty)
continue;
if (!harvester.IsIdle)
{
var act = harvester.CurrentActivity;
if (!harv.LastSearchFailed || act.NextActivity == null || act.NextActivity.GetType() != typeof(FindResources))
continue;
}
var para = harvester.TraitOrDefault<Parachutable>();
if (para != null && para.IsInAir)
continue;
// Tell the idle harvester to quit slacking:
var newSafeResourcePatch = FindNextResource(harvester, harv);
HackyAI.BotDebug("AI: Harvester {0} is idle. Ordering to {1} in search for new resources.".F(harvester, newSafeResourcePatch));
ai.QueueOrder(new Order("Harvest", harvester, Target.FromCell(world, newSafeResourcePatch), false));
}
}
}
}

View File

@@ -281,7 +281,6 @@ namespace OpenRA.Mods.Common.AI
BitArray resourceTypeIndices; BitArray resourceTypeIndices;
AIHarvesterManager harvManager;
AISupportPowerManager supportPowerManager; AISupportPowerManager supportPowerManager;
List<BaseBuilder> builders = new List<BaseBuilder>(); List<BaseBuilder> builders = new List<BaseBuilder>();
@@ -291,10 +290,6 @@ namespace OpenRA.Mods.Common.AI
// Units that the ai already knows about. Any unit not on this list needs to be given a role. // Units that the ai already knows about. Any unit not on this list needs to be given a role.
List<Actor> activeUnits = new List<Actor>(); List<Actor> activeUnits = new List<Actor>();
// Harvesters are usually listed under ExcludeFromSquads, so they're not included in the activeUnits list, but still needed in AIHarvesterManager.
// TODO: Consider adding an explicit UnitsCommonNames.Harvester category.
List<Actor> harvesters = new List<Actor>();
public const int FeedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag. public const int FeedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag.
public readonly World World; public readonly World World;
@@ -335,7 +330,6 @@ namespace OpenRA.Mods.Common.AI
playerResource = p.PlayerActor.Trait<PlayerResources>(); playerResource = p.PlayerActor.Trait<PlayerResources>();
botOrderManager = p.PlayerActor.Trait<BotOrderManager>(); botOrderManager = p.PlayerActor.Trait<BotOrderManager>();
harvManager = new AIHarvesterManager(this, p);
supportPowerManager = new AISupportPowerManager(this, p); supportPowerManager = new AISupportPowerManager(this, p);
foreach (var building in Info.BuildingQueues) foreach (var building in Info.BuildingQueues)
@@ -600,7 +594,6 @@ namespace OpenRA.Mods.Common.AI
activeUnits.RemoveAll(unitCannotBeOrdered); activeUnits.RemoveAll(unitCannotBeOrdered);
unitsHangingAroundTheBase.RemoveAll(unitCannotBeOrdered); unitsHangingAroundTheBase.RemoveAll(unitCannotBeOrdered);
harvesters.RemoveAll(unitCannotBeOrdered);
if (--rushTicks <= 0) if (--rushTicks <= 0)
{ {
@@ -619,7 +612,6 @@ namespace OpenRA.Mods.Common.AI
{ {
assignRolesTicks = Info.AssignRolesInterval; assignRolesTicks = Info.AssignRolesInterval;
FindNewUnits(self); FindNewUnits(self);
harvManager.Tick(harvesters);
InitializeBase(self, true); InitializeBase(self, true);
} }
@@ -723,13 +715,10 @@ namespace OpenRA.Mods.Common.AI
void FindNewUnits(Actor self) void FindNewUnits(Actor self)
{ {
var newUnits = self.World.ActorsHavingTrait<IPositionable>() var newUnits = self.World.ActorsHavingTrait<IPositionable>()
.Where(a => a.Owner == Player && !activeUnits.Contains(a) && !harvesters.Contains(a)); .Where(a => a.Owner == Player && !activeUnits.Contains(a));
foreach (var a in newUnits) foreach (var a in newUnits)
{ {
if (a.Info.HasTraitInfo<HarvesterInfo>())
harvesters.Add(a);
if (Info.UnitsCommonNames.Mcv.Contains(a.Info.Name) || Info.UnitsCommonNames.ExcludeFromSquads.Contains(a.Info.Name)) if (Info.UnitsCommonNames.Mcv.Contains(a.Info.Name) || Info.UnitsCommonNames.ExcludeFromSquads.Contains(a.Info.Name))
continue; continue;

View File

@@ -125,7 +125,7 @@
<Compile Include="AI\BaseBuilder.cs" /> <Compile Include="AI\BaseBuilder.cs" />
<Compile Include="AI\BotOrderManager.cs" /> <Compile Include="AI\BotOrderManager.cs" />
<Compile Include="AI\HackyAI.cs" /> <Compile Include="AI\HackyAI.cs" />
<Compile Include="AI\AIHarvesterManager.cs" /> <Compile Include="Traits\BotModules\HarvesterBotModule.cs" />
<Compile Include="AI\AISupportPowerManager.cs" /> <Compile Include="AI\AISupportPowerManager.cs" />
<Compile Include="AI\Squad.cs" /> <Compile Include="AI\Squad.cs" />
<Compile Include="AI\StateMachine.cs" /> <Compile Include="AI\StateMachine.cs" />
@@ -937,6 +937,7 @@
<Compile Include="UpdateRules\Rules\20180923\AddRearmable.cs" /> <Compile Include="UpdateRules\Rules\20180923\AddRearmable.cs" />
<Compile Include="UpdateRules\Rules\20180923\MergeAttackPlaneAndHeli.cs" /> <Compile Include="UpdateRules\Rules\20180923\MergeAttackPlaneAndHeli.cs" />
<Compile Include="UpdateRules\Rules\20180923\AddBotOrderManager.cs" /> <Compile Include="UpdateRules\Rules\20180923\AddBotOrderManager.cs" />
<Compile Include="UpdateRules\Rules\20180923\AddHarvesterBotModule.cs" />
<Compile Include="Traits\Player\PlayerResources.cs" /> <Compile Include="Traits\Player\PlayerResources.cs" />
<Compile Include="UtilityCommands\DumpSequenceSheetsCommand.cs" /> <Compile Include="UtilityCommands\DumpSequenceSheetsCommand.cs" />
<Compile Include="Traits\Render\WithBuildingRepairDecoration.cs" /> <Compile Include="Traits\Render\WithBuildingRepairDecoration.cs" />

View File

@@ -0,0 +1,133 @@
#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.Activities;
using OpenRA.Mods.Common.AI;
using OpenRA.Mods.Common.Pathfinder;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Put this on the Player actor. Manages bot harvesters to ensure they always continue harvesting as long as there are resources on the map.")]
public class HarvesterBotModuleInfo : ConditionalTraitInfo, Requires<BotOrderManagerInfo>
{
[Desc("Interval (in ticks) between giving out orders to idle harvesters.")]
public readonly int ScanForIdleHarvestersInterval = 20;
[Desc("Avoid enemy actors nearby when searching for a new resource patch. Should be somewhere near the max weapon range.")]
public readonly WDist HarvesterEnemyAvoidanceRadius = WDist.FromCells(8);
public override object Create(ActorInitializer init) { return new HarvesterBotModule(init.Self, this); }
}
public class HarvesterBotModule : ConditionalTrait<HarvesterBotModuleInfo>, ITick
{
readonly World world;
readonly Player player;
readonly Predicate<Actor> unitCannotBeOrdered;
IPathFinder pathfinder;
DomainIndex domainIndex;
ResourceLayer resLayer;
ResourceClaimLayer claimLayer;
BotOrderManager botOrderManager;
List<Actor> harvesters = new List<Actor>();
int scanForIdleHarvestersTicks;
public HarvesterBotModule(Actor self, HarvesterBotModuleInfo info)
: base(info)
{
world = self.World;
player = self.Owner;
unitCannotBeOrdered = a => a.Owner != self.Owner || a.IsDead || !a.IsInWorld;
}
protected override void TraitEnabled(Actor self)
{
pathfinder = world.WorldActor.Trait<IPathFinder>();
domainIndex = world.WorldActor.Trait<DomainIndex>();
resLayer = world.WorldActor.TraitOrDefault<ResourceLayer>();
claimLayer = world.WorldActor.TraitOrDefault<ResourceClaimLayer>();
botOrderManager = self.Owner.PlayerActor.Trait<BotOrderManager>();
scanForIdleHarvestersTicks = Info.ScanForIdleHarvestersInterval;
}
void ITick.Tick(Actor self)
{
if (IsTraitDisabled)
return;
if (resLayer == null || resLayer.IsResourceLayerEmpty)
return;
if (--scanForIdleHarvestersTicks > 0)
return;
harvesters.RemoveAll(unitCannotBeOrdered);
scanForIdleHarvestersTicks = Info.ScanForIdleHarvestersInterval;
// Find new harvesters
// TODO: Look for a more performance-friendly way to update this list
var newHarvesters = world.ActorsHavingTrait<Harvester>().Where(a => a.Owner == player && !harvesters.Contains(a));
foreach (var a in newHarvesters)
harvesters.Add(a);
// Find idle harvesters and give them orders:
foreach (var harvester in harvesters)
{
var harv = harvester.Trait<Harvester>();
if (!harv.IsEmpty)
continue;
if (!harvester.IsIdle)
{
var act = harvester.CurrentActivity;
if (!harv.LastSearchFailed || act.NextActivity == null || act.NextActivity.GetType() != typeof(FindResources))
continue;
}
var para = harvester.TraitOrDefault<Parachutable>();
if (para != null && para.IsInAir)
continue;
// Tell the idle harvester to quit slacking:
var newSafeResourcePatch = FindNextResource(harvester, harv);
AIUtils.BotDebug("AI: Harvester {0} is idle. Ordering to {1} in search for new resources.".F(harvester, newSafeResourcePatch));
botOrderManager.QueueOrder(new Order("Harvest", harvester, Target.FromCell(world, newSafeResourcePatch), false));
}
}
CPos FindNextResource(Actor actor, Harvester harv)
{
var locomotorInfo = actor.Info.TraitInfo<MobileInfo>().LocomotorInfo;
Func<CPos, bool> isValidResource = cell =>
domainIndex.IsPassable(actor.Location, cell, locomotorInfo) &&
harv.CanHarvestCell(actor, cell) &&
claimLayer.CanClaimCell(actor, cell);
var path = pathfinder.FindPath(
PathSearch.Search(world, locomotorInfo, actor, true, isValidResource)
.WithCustomCost(loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), Info.HarvesterEnemyAvoidanceRadius)
.Where(u => !u.IsDead && actor.Owner.Stances[u.Owner] == Stance.Enemy)
.Sum(u => Math.Max(WDist.Zero.Length, Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length)))
.FromPoint(actor.Location));
if (path.Count == 0)
return CPos.Zero;
return path[0];
}
}
}

View File

@@ -0,0 +1,122 @@
#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;
namespace OpenRA.Mods.Common.UpdateRules.Rules
{
public class AddHarvesterBotModule : UpdateRule
{
public override string Name { get { return "Split HackyAI harvester handling to HarvesterBotModule"; } }
public override string Description
{
get
{
return "Some properties and all harvester handling have been moved from HackyAI\n" +
"to the new HarvesterBotModule.";
}
}
bool messageShown;
readonly string[] harvesterFields =
{
"HarvesterEnemyAvoidanceRadius", "AssignRolesInterval"
};
public override IEnumerable<string> AfterUpdate(ModData modData)
{
var message = "You may want to check your AI yamls for possible redundant module entries.\n" +
"Additionally, make sure the Player actor has the ConditionManager trait and add it manually if it doesn't.";
if (!messageShown)
yield return message;
messageShown = true;
}
public override IEnumerable<string> UpdateActorNode(ModData modData, MiniYamlNode actorNode)
{
if (actorNode.Key != "Player")
yield break;
var hackyAIs = actorNode.ChildrenMatching("HackyAI");
if (!hackyAIs.Any())
yield break;
var addNodes = new List<MiniYamlNode>();
// We add a 'default' HarvesterBotModule in any case,
// and only add more for AIs that define custom values for one of its fields.
var defaultHarvNode = new MiniYamlNode("HarvesterBotModule", "");
foreach (var hackyAINode in hackyAIs)
{
// HackyAIInfo.Name might contain spaces, so Type is better suited to be used as condition name
var aiType = hackyAINode.LastChildMatching("Type").NodeValue<string>();
var conditionString = "enable-" + aiType + "-ai";
var requiresCondition = new MiniYamlNode("RequiresCondition", conditionString);
var conditionNode = hackyAINode.LastChildMatching("Condition");
if (conditionNode == null)
{
var enableModule = new MiniYamlNode("Condition", conditionString);
hackyAINode.AddNode(enableModule);
}
if (harvesterFields.Any(f => hackyAINode.ChildrenMatching(f).Any()))
{
var harvNode = new MiniYamlNode("HarvesterBotModule@" + aiType, "");
harvNode.AddNode(requiresCondition);
foreach (var hf in harvesterFields)
{
var fieldNode = hackyAINode.LastChildMatching(hf);
if (fieldNode != null)
{
if (hf == "AssignRolesInterval")
fieldNode.MoveAndRenameNode(hackyAINode, harvNode, "ScanForIdleHarvestersInterval");
else
fieldNode.MoveNode(hackyAINode, harvNode);
}
}
addNodes.Add(harvNode);
}
else
{
// We want the default module to be enabled for every AI that didn't customise one of its fields,
// so we need to update RequiresCondition to be enabled on any of the conditions granted by these AIs,
// but only if the condition hasn't been added yet.
var requiresConditionNode = defaultHarvNode.LastChildMatching("RequiresCondition");
if (requiresConditionNode == null)
defaultHarvNode.AddNode(requiresCondition);
else
{
var oldValue = requiresConditionNode.NodeValue<string>();
if (oldValue.Contains(conditionString))
continue;
requiresConditionNode.ReplaceValue(oldValue + " || " + conditionString);
}
}
}
addNodes.Add(defaultHarvNode);
foreach (var node in addNodes)
actorNode.AddNode(node);
yield break;
}
}
}

View File

@@ -106,6 +106,7 @@ namespace OpenRA.Mods.Common.UpdateRules
new RequireProductionType(), new RequireProductionType(),
new CloakRequiresConditionToPause(), new CloakRequiresConditionToPause(),
new AddBotOrderManager(), new AddBotOrderManager(),
new AddHarvesterBotModule(),
}) })
}; };

View File

@@ -131,6 +131,7 @@ Player:
Attractiveness: -10 Attractiveness: -10
TargetMetric: Value TargetMetric: Value
CheckRadius: 7c0 CheckRadius: 7c0
Condition: enable-cabal-ai
HackyAI@Watson: HackyAI@Watson:
Name: Watson Name: Watson
Type: watson Type: watson
@@ -262,6 +263,7 @@ Player:
Attractiveness: -10 Attractiveness: -10
TargetMetric: Value TargetMetric: Value
CheckRadius: 7c0 CheckRadius: 7c0
Condition: enable-watson-ai
HackyAI@HAL9001: HackyAI@HAL9001:
Name: HAL 9001 Name: HAL 9001
Type: hal9001 Type: hal9001
@@ -395,3 +397,6 @@ Player:
Attractiveness: -10 Attractiveness: -10
TargetMetric: Value TargetMetric: Value
CheckRadius: 7c0 CheckRadius: 7c0
Condition: enable-hal9001-ai
HarvesterBotModule:
RequiresCondition: enable-cabal-ai || enable-watson-ai || enable-hal9001-ai

View File

@@ -58,3 +58,4 @@ Player:
GrantConditionOnPrerequisiteManager: GrantConditionOnPrerequisiteManager:
ResourceStorageWarning: ResourceStorageWarning:
PlayerExperience: PlayerExperience:
ConditionManager:

View File

@@ -125,6 +125,7 @@ Player:
OrderName: ProduceActorPower.Fremen OrderName: ProduceActorPower.Fremen
Consideration@1: Consideration@1:
Against: Ally Against: Ally
Condition: enable-omnius-ai
HackyAI@Vidius: HackyAI@Vidius:
Name: Vidious Name: Vidious
Type: vidious Type: vidious
@@ -250,6 +251,7 @@ Player:
OrderName: ProduceActorPower.Fremen OrderName: ProduceActorPower.Fremen
Consideration@1: Consideration@1:
Against: Ally Against: Ally
Condition: enable-vidious-ai
HackyAI@Gladius: HackyAI@Gladius:
Name: Gladius Name: Gladius
Type: gladius Type: gladius
@@ -374,3 +376,6 @@ Player:
OrderName: ProduceActorPower.Fremen OrderName: ProduceActorPower.Fremen
Consideration@1: Consideration@1:
Against: Ally Against: Ally
Condition: enable-gladius-ai
HarvesterBotModule:
RequiresCondition: enable-omnius-ai || enable-vidious-ai || enable-gladius-ai

View File

@@ -144,3 +144,4 @@ Player:
ResourceStorageWarning: ResourceStorageWarning:
AdviceInterval: 26 AdviceInterval: 26
PlayerExperience: PlayerExperience:
ConditionManager:

View File

@@ -105,6 +105,8 @@ Player:
DefaultCashDropdownLocked: True DefaultCashDropdownLocked: True
DefaultCashDropdownVisible: False DefaultCashDropdownVisible: False
DefaultCash: 50 DefaultCash: 50
-ConditionManager:
-HarvesterBotModule:
-HackyAI@RushAI: -HackyAI@RushAI:
-HackyAI@NormalAI: -HackyAI@NormalAI:
-HackyAI@NavalAI: -HackyAI@NavalAI:

View File

@@ -121,6 +121,7 @@ Player:
Attractiveness: -10 Attractiveness: -10
TargetMetric: Value TargetMetric: Value
CheckRadius: 7c0 CheckRadius: 7c0
Condition: enable-rush-ai
HackyAI@NormalAI: HackyAI@NormalAI:
Name: Normal AI Name: Normal AI
Type: normal Type: normal
@@ -261,6 +262,7 @@ Player:
Attractiveness: -10 Attractiveness: -10
TargetMetric: Value TargetMetric: Value
CheckRadius: 7c0 CheckRadius: 7c0
Condition: enable-normal-ai
HackyAI@TurtleAI: HackyAI@TurtleAI:
Name: Turtle AI Name: Turtle AI
Type: turtle Type: turtle
@@ -402,6 +404,7 @@ Player:
Attractiveness: -10 Attractiveness: -10
TargetMetric: Value TargetMetric: Value
CheckRadius: 7c0 CheckRadius: 7c0
Condition: enable-turtle-ai
HackyAI@NavalAI: HackyAI@NavalAI:
Name: Naval AI Name: Naval AI
Type: naval Type: naval
@@ -518,3 +521,6 @@ Player:
Attractiveness: -10 Attractiveness: -10
TargetMetric: Value TargetMetric: Value
CheckRadius: 7c0 CheckRadius: 7c0
Condition: enable-naval-ai
HarvesterBotModule:
RequiresCondition: enable-rush-ai || enable-normal-ai || enable-turtle-ai || enable-naval-ai

View File

@@ -143,3 +143,4 @@ Player:
Sequence: veteran Sequence: veteran
ResourceStorageWarning: ResourceStorageWarning:
PlayerExperience: PlayerExperience:
ConditionManager:

View File

@@ -78,3 +78,6 @@ Player:
medic: 3 medic: 3
repair: 3 repair: 3
SquadSize: 20 SquadSize: 20
Condition: enable-test-ai
HarvesterBotModule:
RequiresCondition: enable-test-ai

View File

@@ -114,3 +114,4 @@ Player:
Id: unrestricted Id: unrestricted
ResourceStorageWarning: ResourceStorageWarning:
PlayerExperience: PlayerExperience:
ConditionManager: