#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() .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()) type = BuildingType.Defense; else if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains()) 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 buildables, Func 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().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount)); if (power != null && power.Traits.WithInterface().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().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().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount)); if (power != null && power.Traits.WithInterface().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; } } }