Files
OpenRA/OpenRA.Mods.Common/AI/BaseBuilder.cs
RoosterDragon 942ce6b5a6 Throttle the issuing of orders by the AI.
To prevent the AI generating lag spikes by issuing too many orders in a single tick, it is now limited in how many orders can be issued per tick. Extra orders are queued. This allows the performance hit of issuing multiple orders to be spread out over several ticks.
2015-07-26 12:57:19 +01:00

235 lines
7.7 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.AI
{
class BaseBuilder
{
readonly string category;
readonly HackyAI ai;
readonly World world;
readonly Player player;
readonly PowerManager playerPower;
readonly PlayerResources playerResources;
int waitTicks;
Actor[] playerBuildings;
public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr)
{
this.ai = ai;
world = p.World;
player = p;
playerPower = pm;
playerResources = pr;
this.category = category;
}
public void Tick()
{
// Only update once per second or so
if (--waitTicks > 0)
return;
playerBuildings = world.ActorsWithTrait<Building>()
.Where(a => a.Actor.Owner == player)
.Select(a => a.Actor)
.ToArray();
var active = false;
foreach (var queue in ai.FindQueues(category))
if (TickQueue(queue))
active = true;
waitTicks = active ? ai.Info.StructureProductionActiveDelay : ai.Info.StructureProductionInactiveDelay;
}
bool TickQueue(ProductionQueue queue)
{
var currentBuilding = queue.CurrentItem();
// Waiting to build something
if (currentBuilding == null)
{
var item = ChooseBuildingToBuild(queue);
if (item == null)
return false;
HackyAI.BotDebug("AI: {0} is starting production of {1}".F(player, item.Name));
ai.QueueOrder(Order.StartProduction(queue.Actor, item.Name, 1));
}
else if (currentBuilding.Done)
{
// Production is complete
// Choose the placement logic
// HACK: HACK HACK HACK
var type = BuildingType.Building;
if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains<AttackBaseInfo>())
type = BuildingType.Defense;
else if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains<RefineryInfo>())
type = BuildingType.Refinery;
var location = ai.ChooseBuildLocation(currentBuilding.Item, true, type);
if (location == null)
{
HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item));
ai.QueueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1));
}
else
{
ai.QueueOrder(new Order("PlaceBuilding", player.PlayerActor, false)
{
TargetLocation = location.Value,
TargetString = currentBuilding.Item,
TargetActor = queue.Actor,
SuppressVisualFeedback = true
});
return true;
}
}
return true;
}
ActorInfo GetProducibleBuilding(string commonName, IEnumerable<ActorInfo> buildables, Func<ActorInfo, int> orderBy = null)
{
string[] actors;
if (!ai.Info.BuildingCommonNames.TryGetValue(commonName, out actors))
throw new InvalidOperationException("Can't find {0} in the HackyAI BuildingCommonNames definition.".F(commonName));
var available = buildables.Where(actor =>
{
// Are we able to build this?
if (!actors.Contains(actor.Name))
return false;
if (!ai.Info.BuildingLimits.ContainsKey(actor.Name))
return true;
return playerBuildings.Count(a => a.Info.Name == actor.Name) <= ai.Info.BuildingLimits[actor.Name];
});
if (orderBy != null)
return available.MaxByOrDefault(orderBy);
return available.RandomOrDefault(ai.Random);
}
ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
{
var buildableThings = queue.BuildableItems();
// First priority is to get out of a low power situation
if (playerPower.ExcessPower < ai.Info.MinimumExcessPower)
{
var power = GetProducibleBuilding("Power", buildableThings, a => a.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount));
if (power != null && power.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount) > 0)
{
// TODO: Handle the case when of when we actually do need a power plant because we don't have enough but are also suffering from a power outage
if (playerPower.PowerOutageRemainingTicks <= 0)
{
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name);
return power;
}
}
}
// Next is to build up a strong economy
if (!ai.HasAdequateProc() || !ai.HasMinimumProc())
{
var refinery = GetProducibleBuilding("Refinery", buildableThings);
if (refinery != null)
{
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (refinery)", queue.Actor.Owner, refinery.Name);
return refinery;
}
}
// Make sure that we can can spend as fast as we are earning
if (ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold)
{
var production = GetProducibleBuilding("Production", buildableThings);
if (production != null)
{
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (production)", queue.Actor.Owner, production.Name);
return production;
}
}
// Create some head room for resource storage if we really need it
if (playerResources.AlertSilo)
{
var silo = GetProducibleBuilding("Silo", buildableThings);
if (silo != null)
{
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (silo)", queue.Actor.Owner, silo.Name);
return silo;
}
}
// Build everything else
foreach (var frac in ai.Info.BuildingFractions.Shuffle(ai.Random))
{
var name = frac.Key;
// Can we build this structure?
if (!buildableThings.Any(b => b.Name == name))
continue;
// Do we want to build this structure?
var count = playerBuildings.Count(a => a.Info.Name == name);
if (count > frac.Value * playerBuildings.Length)
continue;
if (ai.Info.BuildingLimits.ContainsKey(name) && ai.Info.BuildingLimits[name] <= count)
continue;
// Will this put us into low power?
var actor = world.Map.Rules.Actors[frac.Key];
var pis = actor.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1);
if (playerPower.ExcessPower < ai.Info.MinimumExcessPower || playerPower.ExcessPower < pis.Sum(pi => pi.Amount))
{
// Try building a power plant instead
var power = GetProducibleBuilding("Power",
buildableThings, a => a.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount));
if (power != null && power.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount) > 0)
{
// TODO: Handle the case when of when we actually do need a power plant because we don't have enough but are also suffering from a power outage
if (playerPower.PowerOutageRemainingTicks > 0)
HackyAI.BotDebug("AI: {0} is suffering from a power outage; not going to build {1}", queue.Actor.Owner, power.Name);
else
{
HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
return power;
}
}
}
// Lets build this
HackyAI.BotDebug("{0} decided to build {1}: Desired is {2} ({3} / {4}); current is {5} / {4}",
queue.Actor.Owner, name, frac.Value, frac.Value * playerBuildings.Length, playerBuildings.Length, count);
return actor;
}
// Too spammy to keep enabled all the time, but very useful when debugging specific issues.
// HackyAI.BotDebug("{0} couldn't decide what to build for queue {1}.", queue.Actor.Owner, queue.Info.Group);
return null;
}
}
}