Files
OpenRA/OpenRA.Mods.Common/AI/BaseBuilder.cs
2015-02-09 16:57:29 +02:00

233 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));
world.IssueOrder(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));
world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1));
}
else
{
world.IssueOrder(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 < 0)
{
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 < 0 || 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;
}
}
}