If not enough water space can be found inside the base perimeter, stop the AI from trying to build naval production buildings permanently until it deploys another construction yard. If enough water is available within the base perimeter, check whether any building that provides buildable area (for adjacency) is close enough to water, otherwise don't even start producing this naval structure.
311 lines
10 KiB
C#
311 lines
10 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;
|
|
bool waterAvailable;
|
|
bool checkedWater;
|
|
int failCount;
|
|
int failRetryTicks;
|
|
int cachedBases;
|
|
|
|
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;
|
|
failRetryTicks = ai.Info.StructureProductionResumeDelay;
|
|
}
|
|
|
|
public void Tick()
|
|
{
|
|
// If failed to place something N consecutive times, wait M ticks until resuming building production
|
|
if (failCount >= ai.Info.MaximumFailedPlacementAttempts && --failRetryTicks <= 0)
|
|
failCount = 0;
|
|
|
|
// Only update once per second or so
|
|
if (--waitTicks > 0)
|
|
return;
|
|
|
|
if (!checkedWater)
|
|
{
|
|
waterAvailable = ai.EnoughWaterToBuildNaval();
|
|
checkedWater = true;
|
|
}
|
|
|
|
if (!waterAvailable)
|
|
{
|
|
var currentBases = world.ActorsWithTrait<BaseBuilding>()
|
|
.Where(a => a.Actor.Owner == player)
|
|
.Count();
|
|
|
|
if (currentBases > cachedBases)
|
|
{
|
|
cachedBases = currentBases;
|
|
checkedWater = false;
|
|
}
|
|
}
|
|
|
|
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 && failCount < ai.Info.MaximumFailedPlacementAttempts)
|
|
{
|
|
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
|
|
// TODO: Derive this from BuildingCommonNames instead
|
|
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));
|
|
failCount += failCount;
|
|
}
|
|
else
|
|
{
|
|
failCount = 0;
|
|
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);
|
|
}
|
|
|
|
bool HasSufficientPowerForActor(ActorInfo actorInfo)
|
|
{
|
|
return (actorInfo.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1)
|
|
.Sum(p => p.Amount) + playerPower.ExcessPower) >= ai.Info.MinimumExcessPower;
|
|
}
|
|
|
|
ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
|
|
{
|
|
var buildableThings = queue.BuildableItems();
|
|
|
|
// This gets used quite a bit, so let's cache it here
|
|
var power = GetProducibleBuilding("Power", buildableThings,
|
|
a => a.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount));
|
|
|
|
// First priority is to get out of a low power situation
|
|
if (playerPower.ExcessPower < ai.Info.MinimumExcessPower)
|
|
{
|
|
if (power != null && power.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount) > 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 && HasSufficientPowerForActor(refinery))
|
|
{
|
|
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (refinery)", queue.Actor.Owner, refinery.Name);
|
|
return refinery;
|
|
}
|
|
|
|
if (power != null && refinery != null && !HasSufficientPowerForActor(refinery))
|
|
{
|
|
HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
|
|
return power;
|
|
}
|
|
}
|
|
|
|
// Make sure that we 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 && HasSufficientPowerForActor(production))
|
|
{
|
|
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (production)", queue.Actor.Owner, production.Name);
|
|
return production;
|
|
}
|
|
|
|
if (power != null && production != null && !HasSufficientPowerForActor(production))
|
|
{
|
|
HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
|
|
return power;
|
|
}
|
|
}
|
|
|
|
// Only consider building this if there is enough water inside the base perimeter and there are close enough adjacent buildings
|
|
if (waterAvailable && ai.CloseEnoughToWater()
|
|
&& ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold)
|
|
{
|
|
var navalproduction = GetProducibleBuilding("NavalProduction", buildableThings);
|
|
if (navalproduction != null && HasSufficientPowerForActor(navalproduction))
|
|
{
|
|
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (navalproduction)", queue.Actor.Owner, navalproduction.Name);
|
|
return navalproduction;
|
|
}
|
|
|
|
if (power != null && navalproduction != null && !HasSufficientPowerForActor(navalproduction))
|
|
{
|
|
HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
|
|
return power;
|
|
}
|
|
}
|
|
|
|
// Create some head room for resource storage if we really need it
|
|
if (playerResources.AlertSilo)
|
|
{
|
|
var silo = GetProducibleBuilding("Silo", buildableThings);
|
|
if (silo != null && HasSufficientPowerForActor(silo))
|
|
{
|
|
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (silo)", queue.Actor.Owner, silo.Name);
|
|
return silo;
|
|
}
|
|
|
|
if (power != null && silo != null && !HasSufficientPowerForActor(silo))
|
|
{
|
|
HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
|
|
return power;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
// If we're considering to build a naval structure, check whether there is enough water inside the base perimeter
|
|
// and any structure providing buildable area close enough to that water.
|
|
// TODO: Extend this check to cover any naval structure, not just production.
|
|
if (ai.Info.BuildingCommonNames.ContainsKey("NavalProduction")
|
|
&& ai.Info.BuildingCommonNames["NavalProduction"].Contains(name)
|
|
&& (!waterAvailable || !ai.CloseEnoughToWater()))
|
|
continue;
|
|
|
|
// Will this put us into low power?
|
|
var actor = world.Map.Rules.Actors[name];
|
|
if (playerPower.ExcessPower < ai.Info.MinimumExcessPower || !HasSufficientPowerForActor(actor))
|
|
{
|
|
// Try building a power plant instead
|
|
if (power != null && power.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount) > 0)
|
|
{
|
|
if (playerPower.PowerOutageRemainingTicks > 0)
|
|
HackyAI.BotDebug("{0} decided to build {1}: Priority override (is low power)", 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;
|
|
}
|
|
}
|
|
}
|