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:
209
OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs
Normal file
209
OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
#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.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Controls AI unit production.")]
|
||||
public class UnitBuilderBotModuleInfo : ConditionalTraitInfo
|
||||
{
|
||||
// TODO: Investigate whether this might the (or at least one) reason why bots occasionally get into a state of doing nothing.
|
||||
// Reason: If this is less than SquadSize, the bot might get stuck between not producing more units due to this,
|
||||
// but also not creating squads since there aren't enough idle units.
|
||||
[Desc("Only produce units as long as there are less than this amount of units idling inside the base.")]
|
||||
public readonly int IdleBaseUnitsMaximum = 12;
|
||||
|
||||
[Desc("Production queues AI uses for producing units.")]
|
||||
public readonly HashSet<string> UnitQueues = new HashSet<string> { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" };
|
||||
|
||||
[Desc("What units to the AI should build.", "What relative share of the total army must be this type of unit.")]
|
||||
public readonly Dictionary<string, int> UnitsToBuild = null;
|
||||
|
||||
[Desc("What units should the AI have a maximum limit to train.")]
|
||||
public readonly Dictionary<string, int> UnitLimits = null;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new UnitBuilderBotModule(init.Self, this); }
|
||||
}
|
||||
|
||||
public class UnitBuilderBotModule : ConditionalTrait<UnitBuilderBotModuleInfo>, IBotTick, IBotNotifyIdleBaseUnits, IBotRequestUnitProduction
|
||||
{
|
||||
public const int FeedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag.
|
||||
|
||||
readonly World world;
|
||||
readonly Player player;
|
||||
|
||||
readonly List<string> queuedBuildRequests = new List<string>();
|
||||
|
||||
IBotRequestPauseUnitProduction[] requestPause;
|
||||
|
||||
List<Actor> idleUnits = new List<Actor>();
|
||||
|
||||
int ticks;
|
||||
|
||||
public UnitBuilderBotModule(Actor self, UnitBuilderBotModuleInfo info)
|
||||
: base(info)
|
||||
{
|
||||
world = self.World;
|
||||
player = self.Owner;
|
||||
}
|
||||
|
||||
protected override void TraitEnabled(Actor self)
|
||||
{
|
||||
requestPause = player.PlayerActor.TraitsImplementing<IBotRequestPauseUnitProduction>().ToArray();
|
||||
}
|
||||
|
||||
void IBotNotifyIdleBaseUnits.UpdatedIdleBaseUnits(List<Actor> idleUnits)
|
||||
{
|
||||
this.idleUnits = idleUnits;
|
||||
}
|
||||
|
||||
void IBotTick.BotTick(IBot bot)
|
||||
{
|
||||
if (requestPause.Any(rp => rp.PauseUnitProduction))
|
||||
return;
|
||||
|
||||
ticks++;
|
||||
|
||||
if (ticks % FeedbackTime == 0)
|
||||
{
|
||||
var buildRequest = queuedBuildRequests.FirstOrDefault();
|
||||
if (buildRequest != null)
|
||||
{
|
||||
BuildUnit(bot, buildRequest);
|
||||
queuedBuildRequests.Remove(buildRequest);
|
||||
}
|
||||
|
||||
foreach (var q in Info.UnitQueues)
|
||||
BuildUnit(bot, q, idleUnits.Count < Info.IdleBaseUnitsMaximum);
|
||||
}
|
||||
}
|
||||
|
||||
void IBotRequestUnitProduction.RequestUnitProduction(IBot bot, string requestedActor)
|
||||
{
|
||||
queuedBuildRequests.Add(requestedActor);
|
||||
}
|
||||
|
||||
void BuildUnit(IBot bot, string category, bool buildRandom)
|
||||
{
|
||||
// Pick a free queue
|
||||
var queue = AIUtils.FindQueues(player, category).FirstOrDefault(q => !q.AllQueued().Any());
|
||||
if (queue == null)
|
||||
return;
|
||||
|
||||
var unit = buildRandom ?
|
||||
ChooseRandomUnitToBuild(queue) :
|
||||
ChooseUnitToBuild(queue);
|
||||
|
||||
if (unit == null)
|
||||
return;
|
||||
|
||||
var name = unit.Name;
|
||||
|
||||
if (Info.UnitsToBuild != null && !Info.UnitsToBuild.ContainsKey(name))
|
||||
return;
|
||||
|
||||
if (Info.UnitLimits != null &&
|
||||
Info.UnitLimits.ContainsKey(name) &&
|
||||
world.Actors.Count(a => a.Owner == player && a.Info.Name == name) >= Info.UnitLimits[name])
|
||||
return;
|
||||
|
||||
bot.QueueOrder(Order.StartProduction(queue.Actor, name, 1));
|
||||
}
|
||||
|
||||
void BuildUnit(IBot bot, string category, string name)
|
||||
{
|
||||
var queue = AIUtils.FindQueues(player, category).FirstOrDefault(q => !q.AllQueued().Any());
|
||||
if (queue == null)
|
||||
return;
|
||||
|
||||
if (world.Map.Rules.Actors[name] != null)
|
||||
bot.QueueOrder(Order.StartProduction(queue.Actor, name, 1));
|
||||
}
|
||||
|
||||
// In cases where we want to build a specific unit but don't know the queue name (because there's more than one possibility)
|
||||
void BuildUnit(IBot bot, string name)
|
||||
{
|
||||
var actorInfo = world.Map.Rules.Actors[name];
|
||||
if (actorInfo == null)
|
||||
return;
|
||||
|
||||
var buildableInfo = actorInfo.TraitInfoOrDefault<BuildableInfo>();
|
||||
if (buildableInfo == null)
|
||||
return;
|
||||
|
||||
ProductionQueue queue = null;
|
||||
foreach (var pq in buildableInfo.Queue)
|
||||
{
|
||||
queue = AIUtils.FindQueues(player, pq).FirstOrDefault(q => !q.AllQueued().Any());
|
||||
if (queue != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (queue != null)
|
||||
bot.QueueOrder(Order.StartProduction(queue.Actor, name, 1));
|
||||
}
|
||||
|
||||
ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue)
|
||||
{
|
||||
var buildableThings = queue.BuildableItems();
|
||||
if (!buildableThings.Any())
|
||||
return null;
|
||||
|
||||
var unit = buildableThings.Random(world.LocalRandom);
|
||||
return HasAdequateAirUnitReloadBuildings(unit) ? unit : null;
|
||||
}
|
||||
|
||||
ActorInfo ChooseUnitToBuild(ProductionQueue queue)
|
||||
{
|
||||
var buildableThings = queue.BuildableItems();
|
||||
if (!buildableThings.Any())
|
||||
return null;
|
||||
|
||||
var myUnits = player.World
|
||||
.ActorsHavingTrait<IPositionable>()
|
||||
.Where(a => a.Owner == player)
|
||||
.Select(a => a.Info.Name).ToList();
|
||||
|
||||
foreach (var unit in Info.UnitsToBuild.Shuffle(world.LocalRandom))
|
||||
if (buildableThings.Any(b => b.Name == unit.Key))
|
||||
if (myUnits.Count(a => a == unit.Key) * 100 < unit.Value * myUnits.Count)
|
||||
if (HasAdequateAirUnitReloadBuildings(world.Map.Rules.Actors[unit.Key]))
|
||||
return world.Map.Rules.Actors[unit.Key];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// For mods like RA (number of RearmActors must match the number of aircraft)
|
||||
bool HasAdequateAirUnitReloadBuildings(ActorInfo actorInfo)
|
||||
{
|
||||
var aircraftInfo = actorInfo.TraitInfoOrDefault<AircraftInfo>();
|
||||
if (aircraftInfo == null)
|
||||
return true;
|
||||
|
||||
// If actor isn't Rearmable, it doesn't need a RearmActor to reload
|
||||
var rearmableInfo = actorInfo.TraitInfoOrDefault<RearmableInfo>();
|
||||
if (rearmableInfo == null)
|
||||
return true;
|
||||
|
||||
var countOwnAir = AIUtils.CountActorsWithTrait<IPositionable>(actorInfo.Name, player);
|
||||
var countBuildings = rearmableInfo.RearmActors.Sum(b => AIUtils.CountActorsWithTrait<Building>(b, player));
|
||||
if (countOwnAir >= countBuildings)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user