Split off last bot modules
And dissolve AI namespace. There would have been so little left in Common.AI, that keeping it made no sense anymore.
This commit is contained in:
222
OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs
Normal file
222
OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Manages AI MCVs.")]
|
||||
public class McvManagerBotModuleInfo : ConditionalTraitInfo
|
||||
{
|
||||
[Desc("Actor types that are considered MCVs (deploy into base builders).")]
|
||||
public readonly HashSet<string> McvTypes = new HashSet<string>();
|
||||
|
||||
[Desc("Actor types that are considered construction yards (base builders).")]
|
||||
public readonly HashSet<string> ConstructionYardTypes = new HashSet<string>();
|
||||
|
||||
[Desc("Actor types that are able to produce MCVs.")]
|
||||
public readonly HashSet<string> McvFactoryTypes = new HashSet<string>();
|
||||
|
||||
[Desc("Try to maintain at least this many ConstructionYardTypes, build an MCV if number is below this.")]
|
||||
public readonly int MinimumConstructionYardCount = 1;
|
||||
|
||||
[Desc("Delay (in ticks) between looking for and giving out orders to new MCVs.")]
|
||||
public readonly int ScanForNewMcvInterval = 20;
|
||||
|
||||
[Desc("Minimum distance in cells from center of the base when checking for MCV deployment location.")]
|
||||
public readonly int MinBaseRadius = 2;
|
||||
|
||||
[Desc("Maximum distance in cells from center of the base when checking for MCV deployment location.",
|
||||
"Only applies if RestrictMCVDeploymentFallbackToBase is enabled and there's at least one construction yard.")]
|
||||
public readonly int MaxBaseRadius = 20;
|
||||
|
||||
[Desc("Should deployment of additional MCVs be restricted to MaxBaseRadius if explicit deploy locations are missing or occupied?")]
|
||||
public readonly bool RestrictMCVDeploymentFallbackToBase = true;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new McvManagerBotModule(init.Self, this); }
|
||||
}
|
||||
|
||||
public class McvManagerBotModule : ConditionalTrait<McvManagerBotModuleInfo>, IBotTick, IBotPositionsUpdated
|
||||
{
|
||||
public CPos GetRandomBaseCenter()
|
||||
{
|
||||
var randomConstructionYard = world.Actors.Where(a => a.Owner == player &&
|
||||
Info.ConstructionYardTypes.Contains(a.Info.Name))
|
||||
.RandomOrDefault(world.LocalRandom);
|
||||
|
||||
return randomConstructionYard != null ? randomConstructionYard.Location : initialBaseCenter;
|
||||
}
|
||||
|
||||
readonly World world;
|
||||
readonly Player player;
|
||||
|
||||
readonly Predicate<Actor> unitCannotBeOrdered;
|
||||
|
||||
IBotPositionsUpdated[] notifyPositionsUpdated;
|
||||
IBotRequestUnitProduction[] requestUnitProduction;
|
||||
|
||||
CPos initialBaseCenter;
|
||||
int scanInterval;
|
||||
int ticks;
|
||||
|
||||
// MCVs that the bot already knows about. Any MCV not on this list needs to be given an order.
|
||||
List<Actor> activeMCVs = new List<Actor>();
|
||||
|
||||
public McvManagerBotModule(Actor self, McvManagerBotModuleInfo info)
|
||||
: base(info)
|
||||
{
|
||||
world = self.World;
|
||||
player = self.Owner;
|
||||
unitCannotBeOrdered = a => a.Owner != player || a.IsDead || !a.IsInWorld;
|
||||
}
|
||||
|
||||
protected override void TraitEnabled(Actor self)
|
||||
{
|
||||
notifyPositionsUpdated = player.PlayerActor.TraitsImplementing<IBotPositionsUpdated>().ToArray();
|
||||
requestUnitProduction = player.PlayerActor.TraitsImplementing<IBotRequestUnitProduction>().ToArray();
|
||||
|
||||
// Avoid all AIs reevaluating assignments on the same tick, randomize their initial evaluation delay.
|
||||
scanInterval = world.LocalRandom.Next(Info.ScanForNewMcvInterval, Info.ScanForNewMcvInterval * 2);
|
||||
}
|
||||
|
||||
void IBotPositionsUpdated.UpdatedBaseCenter(CPos newLocation)
|
||||
{
|
||||
initialBaseCenter = newLocation;
|
||||
}
|
||||
|
||||
void IBotPositionsUpdated.UpdatedDefenseCenter(CPos newLocation) { }
|
||||
|
||||
void IBotTick.BotTick(IBot bot)
|
||||
{
|
||||
ticks++;
|
||||
|
||||
if (ticks == 1)
|
||||
DeployMcvs(bot, false);
|
||||
|
||||
if (--scanInterval <= 0)
|
||||
{
|
||||
scanInterval = Info.ScanForNewMcvInterval;
|
||||
DeployMcvs(bot, true);
|
||||
|
||||
// No construction yards - Build a new MCV
|
||||
if (ShouldBuildMCV())
|
||||
{
|
||||
var unitBuilder = requestUnitProduction.FirstOrDefault(Exts.IsTraitEnabled);
|
||||
if (unitBuilder != null)
|
||||
{
|
||||
var mcvInfo = AIUtils.GetInfoByCommonName(Info.McvTypes, player);
|
||||
unitBuilder.RequestUnitProduction(bot, mcvInfo.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldBuildMCV()
|
||||
{
|
||||
// Only build MCV if we don't already have one in the field.
|
||||
var allowedToBuildMCV = AIUtils.CountActorByCommonName(Info.McvTypes, player) == 0;
|
||||
if (!allowedToBuildMCV)
|
||||
return false;
|
||||
|
||||
// Build MCV if we don't have the desired number of construction yards, unless we have no factory (can't build it).
|
||||
return AIUtils.CountBuildingByCommonName(Info.ConstructionYardTypes, player) < Info.MinimumConstructionYardCount &&
|
||||
AIUtils.CountBuildingByCommonName(Info.McvFactoryTypes, player) > 0;
|
||||
}
|
||||
|
||||
void DeployMcvs(IBot bot, bool chooseLocation)
|
||||
{
|
||||
activeMCVs.RemoveAll(unitCannotBeOrdered);
|
||||
|
||||
var newMCVs = world.ActorsHavingTrait<Transforms>()
|
||||
.Where(a => a.Owner == player &&
|
||||
a.IsIdle &&
|
||||
Info.McvTypes.Contains(a.Info.Name) &&
|
||||
!activeMCVs.Contains(a));
|
||||
|
||||
foreach (var a in newMCVs)
|
||||
activeMCVs.Add(a);
|
||||
|
||||
foreach (var mcv in activeMCVs)
|
||||
DeployMcv(bot, mcv, chooseLocation);
|
||||
}
|
||||
|
||||
// Find any MCV and deploy them at a sensible location.
|
||||
void DeployMcv(IBot bot, Actor mcv, bool move)
|
||||
{
|
||||
if (move)
|
||||
{
|
||||
// If we lack a base, we need to make sure we don't restrict deployment of the MCV to the base!
|
||||
var restrictToBase = Info.RestrictMCVDeploymentFallbackToBase && AIUtils.CountBuildingByCommonName(Info.ConstructionYardTypes, player) > 0;
|
||||
|
||||
var transformsInfo = mcv.Info.TraitInfo<TransformsInfo>();
|
||||
var desiredLocation = ChooseMcvDeployLocation(transformsInfo.IntoActor, transformsInfo.Offset, restrictToBase);
|
||||
if (desiredLocation == null)
|
||||
return;
|
||||
|
||||
bot.QueueOrder(new Order("Move", mcv, Target.FromCell(world, desiredLocation.Value), true));
|
||||
}
|
||||
|
||||
// If the MCV has to move first, we can't be sure it reaches the destination alive, so we only
|
||||
// update base and defense center if the MCV is deployed immediately (i.e. at game start).
|
||||
// TODO: This could be adressed via INotifyTransform.
|
||||
foreach (var n in notifyPositionsUpdated)
|
||||
{
|
||||
n.UpdatedBaseCenter(mcv.Location);
|
||||
n.UpdatedDefenseCenter(mcv.Location);
|
||||
}
|
||||
|
||||
bot.QueueOrder(new Order("DeployTransform", mcv, true));
|
||||
}
|
||||
|
||||
CPos? ChooseMcvDeployLocation(string actorType, CVec offset, bool distanceToBaseIsImportant)
|
||||
{
|
||||
var actorInfo = world.Map.Rules.Actors[actorType];
|
||||
var bi = actorInfo.TraitInfoOrDefault<BuildingInfo>();
|
||||
if (bi == null)
|
||||
return null;
|
||||
|
||||
// Find the buildable cell that is closest to pos and centered around center
|
||||
Func<CPos, CPos, int, int, CPos?> findPos = (center, target, minRange, maxRange) =>
|
||||
{
|
||||
var cells = world.Map.FindTilesInAnnulus(center, minRange, maxRange);
|
||||
|
||||
// Sort by distance to target if we have one
|
||||
if (center != target)
|
||||
cells = cells.OrderBy(c => (c - target).LengthSquared);
|
||||
else
|
||||
cells = cells.Shuffle(world.LocalRandom);
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (!world.CanPlaceBuilding(cell + offset, actorInfo, bi, null))
|
||||
continue;
|
||||
|
||||
if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, player, actorInfo, cell))
|
||||
continue;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
var baseCenter = GetRandomBaseCenter();
|
||||
|
||||
return findPos(baseCenter, baseCenter, Info.MinBaseRadius,
|
||||
distanceToBaseIsImportant ? Info.MaxBaseRadius : world.Map.Grid.MaximumTileSearchRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user