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.
235 lines
7.7 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|