diff --git a/OpenRA.Mods.Common/AI/AISupportPowerManager.cs b/OpenRA.Mods.Common/AI/AISupportPowerManager.cs new file mode 100644 index 0000000000..8a71abe193 --- /dev/null +++ b/OpenRA.Mods.Common/AI/AISupportPowerManager.cs @@ -0,0 +1,175 @@ +#region Copyright & License Information +/* + * Copyright 2007-2018 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Mods.Common.Activities; +using OpenRA.Mods.Common.Pathfinder; +using OpenRA.Mods.Common.Traits; +using OpenRA.Support; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.AI +{ + class AISupportPowerManager + { + readonly HackyAI ai; + readonly World world; + readonly Player player; + readonly FrozenActorLayer frozenLayer; + readonly SupportPowerManager supportPowerManager; + Dictionary waitingPowers = new Dictionary(); + Dictionary powerDecisions = new Dictionary(); + + public AISupportPowerManager(HackyAI ai, Player p) + { + this.ai = ai; + world = p.World; + player = p; + frozenLayer = p.PlayerActor.Trait(); + supportPowerManager = p.PlayerActor.TraitOrDefault(); + foreach (var decision in ai.Info.PowerDecisions) + powerDecisions.Add(decision.OrderName, decision); + } + + 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 }); + } + } + } + + /// Scans the map in chunks, evaluating all actors in each. + 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; + } + + /// Detail scans an area, evaluating positions. + 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; + } + } +} diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index 795692ba97..a14b6e47e3 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -267,19 +267,16 @@ namespace OpenRA.Mods.Common.AI readonly Func isEnemyUnit; readonly Predicate unitCannotBeOrdered; - Dictionary waitingPowers = new Dictionary(); - Dictionary powerDecisions = new Dictionary(); CPos initialBaseCenter; PowerManager playerPower; - SupportPowerManager supportPowerMngr; PlayerResources playerResource; - FrozenActorLayer frozenLayer; int ticks; BitArray resourceTypeIndices; AIHarvesterManager harvManager; + AISupportPowerManager supportPowerManager; List builders = new List(); @@ -318,9 +315,6 @@ namespace OpenRA.Mods.Common.AI unitCannotBeOrdered = a => a.Owner != Player || a.IsDead || !a.IsInWorld; - foreach (var decision in info.PowerDecisions) - powerDecisions.Add(decision.OrderName, decision); - maximumCaptureTargetOptions = Math.Max(1, Info.MaximumCaptureTargetOptions); } @@ -336,11 +330,10 @@ namespace OpenRA.Mods.Common.AI Player = p; IsEnabled = true; playerPower = p.PlayerActor.Trait(); - supportPowerMngr = p.PlayerActor.Trait(); playerResource = p.PlayerActor.Trait(); - frozenLayer = p.PlayerActor.Trait(); harvManager = new AIHarvesterManager(this, p); + supportPowerManager = new AISupportPowerManager(this, p); foreach (var building in Info.BuildingQueues) builders.Add(new BaseBuilder(this, building, p, playerPower, playerResource)); @@ -551,7 +544,7 @@ namespace OpenRA.Mods.Common.AI AssignRolesToIdleUnits(self); SetRallyPointsForNewProductionBuildings(self); - TryToUseSupportPower(self); + supportPowerManager.TryToUseSupportPower(self); foreach (var b in builders) b.Tick(); @@ -918,137 +911,6 @@ namespace OpenRA.Mods.Common.AI return mcv; } - void TryToUseSupportPower(Actor self) - { - if (supportPowerMngr == null) - return; - - foreach (var sp in supportPowerMngr.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) - { - BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", sp.Info.OrderName); - continue; - } - - var attackLocation = FindCoarseAttackLocationToSupportPower(sp); - if (attackLocation == null) - { - 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(this); - - continue; - } - - // Found a target location, check for precise target - attackLocation = FindFineAttackLocationToSupportPower(sp, (CPos)attackLocation); - if (attackLocation == null) - { - 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(this); - - continue; - } - - // Valid target found, delay by a few ticks to avoid rescanning before power fires via order - BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, Player.PlayerName); - waitingPowers[sp] += 10; - QueueOrder(new Order(sp.Key, supportPowerMngr.Self, Target.FromCell(World, attackLocation.Value), false) { SuppressVisualFeedback = true }); - } - } - } - - /// Scans the map in chunks, evaluating all actors in each. - CPos? FindCoarseAttackLocationToSupportPower(SupportPowerInstance readyPower) - { - CPos? bestLocation = null; - var bestAttractiveness = 0; - var powerDecision = powerDecisions[readyPower.Info.OrderName]; - if (powerDecision == null) - { - 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; - } - - /// Detail scans an area, evaluating positions. - CPos? FindFineAttackLocationToSupportPower(SupportPowerInstance readyPower, CPos checkPos, int extendedRange = 1) - { - CPos? bestLocation = null; - var bestAttractiveness = 0; - var powerDecision = powerDecisions[readyPower.Info.OrderName]; - if (powerDecision == null) - { - 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; - } - internal IEnumerable FindQueues(string category) { return World.ActorsWithTrait() diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index f73bed4878..b5b134033a 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -127,6 +127,7 @@ +