상속: ITick, IBot, INotifyDamage
예제 #1
0
        public Squad(HackyAI bot, SquadType type, Actor target)
        {
            Bot               = bot;
            World             = bot.World;
            Random            = bot.Random;
            Type              = type;
            Target            = Target.FromActor(target);
            FuzzyStateMachine = new StateMachine();

            switch (type)
            {
            case SquadType.Assault:
            case SquadType.Rush:
                FuzzyStateMachine.ChangeState(this, new GroundUnitsIdleState(), true);
                break;

            case SquadType.Air:
                FuzzyStateMachine.ChangeState(this, new AirIdleState(), true);
                break;

            case SquadType.Protection:
                FuzzyStateMachine.ChangeState(this, new UnitsForProtectionIdleState(), true);
                break;
            }
        }
예제 #2
0
 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;
 }
예제 #3
0
 public AIHarvesterManager(HackyAI ai, Player p)
 {
     this.ai     = ai;
     world       = p.World;
     pathfinder  = world.WorldActor.Trait <IPathFinder>();
     domainIndex = world.WorldActor.Trait <DomainIndex>();
     resLayer    = world.WorldActor.TraitOrDefault <ResourceLayer>();
     claimLayer  = world.WorldActor.TraitOrDefault <ResourceClaimLayer>();
 }
예제 #4
0
 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;
 }
예제 #5
0
		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;
		}
예제 #6
0
 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;
 }
예제 #7
0
        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);
        }
예제 #8
0
 public AISupportPowerManager(HackyAI ai, Player p)
 {
     this.ai             = ai;
     world               = p.World;
     player              = p;
     frozenLayer         = p.PlayerActor.Trait <FrozenActorLayer>();
     supportPowerManager = p.PlayerActor.TraitOrDefault <SupportPowerManager>();
     foreach (var decision in ai.Info.SupportPowerDecisions)
     {
         powerDecisions.Add(decision.OrderName, decision);
     }
 }
예제 #9
0
        public RenderDebugState(Actor self, RenderDebugStateInfo info)
        {
            var buildingInfo = self.Info.TraitInfoOrDefault<BuildingInfo>();
            var yOffset = buildingInfo == null ? 1 : buildingInfo.Dimensions.Y;
            offset = new WVec(0, 512 * yOffset, 0);

            this.self = self;
            color = GetColor();
            font = Game.Renderer.Fonts[info.Font];

            var localPlayer = self.World.LocalPlayer;
            devMode = localPlayer != null ? localPlayer.PlayerActor.Trait<DeveloperMode>() : null;
            ai = self.Owner.PlayerActor.TraitsImplementing<HackyAI>().FirstOrDefault(x => x.IsEnabled);
        }
예제 #10
0
        public void Tick(List <Actor> activeUnits)
        {
            if (resLayer == null || resLayer.IsResourceLayerEmpty)
            {
                return;
            }

            // Find idle harvesters and give them orders:
            foreach (var harvester in activeUnits)
            {
                var harv = harvester.TraitOrDefault <Harvester>();
                if (harv == null)
                {
                    continue;
                }

                if (!harv.IsEmpty)
                {
                    continue;
                }

                if (!harvester.IsIdle)
                {
                    var act = harvester.CurrentActivity;
                    if (!harv.LastSearchFailed || act.NextActivity == null || act.NextActivity.GetType() != typeof(FindResources))
                    {
                        continue;
                    }
                }

                var para = harvester.TraitOrDefault <Parachutable>();
                if (para != null && para.IsInAir)
                {
                    continue;
                }

                // Tell the idle harvester to quit slacking:
                var newSafeResourcePatch = FindNextResource(harvester, harv);
                HackyAI.BotDebug("AI: Harvester {0} is idle. Ordering to {1} in search for new resources.".F(harvester, newSafeResourcePatch));
                ai.QueueOrder(new Order("Harvest", harvester, Target.FromCell(world, newSafeResourcePatch), false));
            }
        }
예제 #11
0
        /// <summary>Scans the map in chunks, evaluating all actors in each.</summary>
        CPos?FindCoarseAttackLocationToSupportPower(SupportPowerInstance readyPower)
        {
            CPos?bestLocation       = null;
            var  bestAttractiveness = 0;
            var  powerDecision      = powerDecisions[readyPower.Info.OrderName];

            if (powerDecision == null)
            {
                HackyAI.BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", readyPower.Info.OrderName);
                return(null);
            }

            var map         = world.Map;
            var checkRadius = powerDecision.CoarseScanRadius;

            for (var i = 0; i < map.MapSize.X; i += checkRadius)
            {
                for (var j = 0; j < map.MapSize.Y; j += checkRadius)
                {
                    var tl     = new MPos(i, j);
                    var br     = new MPos(i + checkRadius, j + checkRadius);
                    var region = new CellRegion(map.Grid.Type, tl, br);

                    // HACK: The AI code should not be messing with raw coordinate transformations
                    var wtl     = world.Map.CenterOfCell(tl.ToCPos(map));
                    var wbr     = world.Map.CenterOfCell(br.ToCPos(map));
                    var targets = world.ActorMap.ActorsInBox(wtl, wbr);

                    var frozenTargets            = frozenLayer.FrozenActorsInRegion(region);
                    var consideredAttractiveness = powerDecision.GetAttractiveness(targets, player) + powerDecision.GetAttractiveness(frozenTargets, player);
                    if (consideredAttractiveness <= bestAttractiveness || consideredAttractiveness < powerDecision.MinimumAttractiveness)
                    {
                        continue;
                    }

                    bestAttractiveness = consideredAttractiveness;
                    bestLocation       = new MPos(i, j).ToCPos(map);
                }
            }

            return(bestLocation);
        }
예제 #12
0
        /// <summary>Detail scans an area, evaluating positions.</summary>
        CPos?FindFineAttackLocationToSupportPower(SupportPowerInstance readyPower, CPos checkPos, int extendedRange = 1)
        {
            CPos?bestLocation       = null;
            var  bestAttractiveness = 0;
            var  powerDecision      = powerDecisions[readyPower.Info.OrderName];

            if (powerDecision == null)
            {
                HackyAI.BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", readyPower.Info.OrderName);
                return(null);
            }

            var checkRadius = powerDecision.CoarseScanRadius;
            var fineCheck   = powerDecision.FineScanRadius;

            for (var i = 0 - extendedRange; i <= (checkRadius + extendedRange); i += fineCheck)
            {
                var x = checkPos.X + i;

                for (var j = 0 - extendedRange; j <= (checkRadius + extendedRange); j += fineCheck)
                {
                    var y   = checkPos.Y + j;
                    var pos = world.Map.CenterOfCell(new CPos(x, y));
                    var consideredAttractiveness = 0;
                    consideredAttractiveness += powerDecision.GetAttractiveness(pos, player, frozenLayer);

                    if (consideredAttractiveness <= bestAttractiveness || consideredAttractiveness < powerDecision.MinimumAttractiveness)
                    {
                        continue;
                    }

                    bestAttractiveness = consideredAttractiveness;
                    bestLocation       = new CPos(x, y);
                }
            }

            return(bestLocation);
        }
예제 #13
0
파일: Squad.cs 프로젝트: Roger-luo/OpenRA
		public Squad(HackyAI bot, SquadType type, Actor target)
		{
			Bot = bot;
			World = bot.World;
			Random = bot.Random;
			Type = type;
			Target = Target.FromActor(target);
			FuzzyStateMachine = new StateMachine();

			switch (type)
			{
				case SquadType.Assault:
				case SquadType.Rush:
					FuzzyStateMachine.ChangeState(this, new GroundUnitsIdleState(), true);
					break;
				case SquadType.Air:
					FuzzyStateMachine.ChangeState(this, new AirIdleState(), true);
					break;
				case SquadType.Protection:
					FuzzyStateMachine.ChangeState(this, new UnitsForProtectionIdleState(), true);
					break;
			}
		}
예제 #14
0
 public int GetNextScanTime(HackyAI ai)
 {
     return ai.Random.Next(MinimumScanTimeInterval, MaximumScanTimeInterval);
 }
예제 #15
0
파일: Squad.cs 프로젝트: Roger-luo/OpenRA
		public Squad(HackyAI bot, SquadType type) : this(bot, type, null) { }
예제 #16
0
        public void TryToUseSupportPower(Actor self)
        {
            if (supportPowerManager == null)
            {
                return;
            }

            foreach (var sp in supportPowerManager.Powers.Values)
            {
                if (sp.Disabled)
                {
                    continue;
                }

                // Add power to dictionary if not in delay dictionary yet
                if (!waitingPowers.ContainsKey(sp))
                {
                    waitingPowers.Add(sp, 0);
                }

                if (waitingPowers[sp] > 0)
                {
                    waitingPowers[sp]--;
                }

                // If we have recently tried and failed to find a use location for a power, then do not try again until later
                var isDelayed = waitingPowers[sp] > 0;
                if (sp.Ready && !isDelayed && powerDecisions.ContainsKey(sp.Info.OrderName))
                {
                    var powerDecision = powerDecisions[sp.Info.OrderName];
                    if (powerDecision == null)
                    {
                        HackyAI.BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", sp.Info.OrderName);
                        continue;
                    }

                    var attackLocation = FindCoarseAttackLocationToSupportPower(sp);
                    if (attackLocation == null)
                    {
                        HackyAI.BotDebug("AI: {1} can't find suitable coarse attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, player.PlayerName);
                        waitingPowers[sp] += powerDecision.GetNextScanTime(ai);

                        continue;
                    }

                    // Found a target location, check for precise target
                    attackLocation = FindFineAttackLocationToSupportPower(sp, (CPos)attackLocation);
                    if (attackLocation == null)
                    {
                        HackyAI.BotDebug("AI: {1} can't find suitable final attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, player.PlayerName);
                        waitingPowers[sp] += powerDecision.GetNextScanTime(ai);

                        continue;
                    }

                    // Valid target found, delay by a few ticks to avoid rescanning before power fires via order
                    HackyAI.BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, player.PlayerName);
                    waitingPowers[sp] += 10;
                    ai.QueueOrder(new Order(sp.Key, supportPowerManager.Self, Target.FromCell(world, attackLocation.Value), false)
                    {
                        SuppressVisualFeedback = true
                    });
                }
            }
        }
예제 #17
0
        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);
        }
예제 #18
0
 public int GetNextScanTime(HackyAI ai)
 {
     return(ai.Random.Next(MinimumScanTimeInterval, MaximumScanTimeInterval));
 }
예제 #19
0
        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);
                }

                ai.QueueOrder(Order.StartProduction(queue.Actor, item.Name, 1));
            }
            else if (currentBuilding != null && 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].HasTraitInfo <AttackBaseInfo>())
                {
                    type = BuildingType.Defense;
                }
                else if (world.Map.Rules.Actors[currentBuilding.Item].HasTraitInfo <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;

                    // If we just reached the maximum fail count, cache the number of current structures
                    if (failCount == ai.Info.MaximumFailedPlacementAttempts)
                    {
                        cachedBuildings = world.ActorsHavingTrait <Building>().Count(a => a.Owner == player);
                        cachedBases     = world.ActorsHavingTrait <BaseProvider>().Count(a => a.Owner == player);
                    }
                }
                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);
        }
예제 #20
0
 public Squad(HackyAI bot, SquadType type) : this(bot, type, null)
 {
 }
예제 #21
0
        ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
        {
            var buildableThings = queue.BuildableItems();

            // This gets used quite a bit, so let's cache it here
            var power = GetProducibleBuilding(ai.Info.BuildingCommonNames.Power, buildableThings,
                                              a => a.TraitInfos <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.TraitInfos <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(ai.Info.BuildingCommonNames.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(ai.Info.BuildingCommonNames.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 (waterState == Water.EnoughWater && ai.Info.NewProductionCashThreshold > 0 &&
                playerResources.Resources > ai.Info.NewProductionCashThreshold &&
                ai.CloseEnoughToWater())
            {
                var navalproduction = GetProducibleBuilding(ai.Info.BuildingCommonNames.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.Resources > 0.8 * playerResources.ResourceCapacity)
            {
                var silo = GetProducibleBuilding(ai.Info.BuildingCommonNames.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.NavalProduction.Contains(name) &&
                    (waterState == Water.NotEnoughWater || !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.TraitInfos <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);
        }