diff --git a/OpenRA.Game/DomainIndex.cs b/OpenRA.Game/DomainIndex.cs new file mode 100644 index 0000000000..0207556e69 --- /dev/null +++ b/OpenRA.Game/DomainIndex.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +using OpenRA.Effects; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Network; +using OpenRA.Orders; +using OpenRA.Support; +using OpenRA.Traits; +using XRandom = OpenRA.Thirdparty.Random; + +namespace OpenRA +{ + public class DomainIndex + { + Rectangle bounds; + int[,] domains; + + public DomainIndex(World world) + { + bounds = world.Map.Bounds; + domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)]; + + BuildDomains(world); + } + + public int GetDomainOf(CPos p) + { + return domains[p.X, p.Y]; + } + + public bool IsCrossDomain(CPos p1, CPos p2) + { + return GetDomainOf(p1) != GetDomainOf(p2); + } + + public void SetDomain(CPos p, int domain) + { + domains[p.X, p.Y] = domain; + } + + void BuildDomains(World world) + { + Map map = world.Map; + + int i = 1; + HashSet unassigned = new HashSet(); + + // Fill up our set of yet-unassigned map cells + for (int x = map.Bounds.Left; x < bounds.Right; x += 1) + { + for (int y = bounds.Top; y < bounds.Bottom; y += 1) + { + unassigned.Add(new CPos(x, y)); + } + } + + while (unassigned.Count != 0) + { + var start = unassigned.First(); + unassigned.Remove(start); + + // Wander around looking for water transitions + bool inWater = WorldUtils.GetTerrainInfo(world, start).IsWater; + Queue toProcess = new Queue(); + HashSet seen = new HashSet(); + toProcess.Enqueue(start); + + do + { + CPos p = toProcess.Dequeue(); + if (seen.Contains(p)) continue; + seen.Add(p); + + TerrainTypeInfo cellInfo = WorldUtils.GetTerrainInfo(world, p); + bool isWater = cellInfo.IsWater; + + // Check if we're still in one contiguous domain + if (inWater == isWater) + { + SetDomain(p, i); + unassigned.Remove(p); + + // Visit our neighbors, if we haven't already + foreach (var d in CVec.directions) + { + CPos nextPos = p + d; + if (nextPos.X >= map.Bounds.Left && nextPos.Y >= map.Bounds.Top && + nextPos.X < map.Bounds.Right && nextPos.Y < map.Bounds.Bottom) + { + if (!seen.Contains(nextPos)) toProcess.Enqueue(nextPos); + } + } + } + } while (toProcess.Count != 0); + + i += 1; + } + + Log.Write("debug", "{0}: Found {1} domains", map.Title, i-1); + } + } +} diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 0aca544852..2c5c9cebbf 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -85,6 +85,7 @@ + diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 7c2d6177a6..d1ea2004a0 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -111,6 +111,7 @@ namespace OpenRA } } + public DomainIndex WorldDomains; internal World(Manifest manifest, Map map, OrderManager orderManager, bool isShellmap) { IsShellmap = isShellmap; @@ -121,6 +122,9 @@ namespace OpenRA TileSet = Rules.TileSets[Map.Tileset]; TileSet.LoadTiles(); + // Identify untraversable regions of the map for faster pathfinding, especially with AI + WorldDomains = new DomainIndex(this); + SharedRandom = new XRandom(orderManager.LobbyInfo.GlobalSettings.RandomSeed); WorldActor = CreateActor( "World", new TypeDictionary() ); diff --git a/OpenRA.Mods.RA/Move/PathFinder.cs b/OpenRA.Mods.RA/Move/PathFinder.cs index 5345bb0702..40d3e84fcd 100755 --- a/OpenRA.Mods.RA/Move/PathFinder.cs +++ b/OpenRA.Mods.RA/Move/PathFinder.cs @@ -44,6 +44,9 @@ namespace OpenRA.Mods.RA.Move { using (new PerfSample("Pathfinder")) { + // If a water-land transition is required, bail early + if (world.WorldDomains.IsCrossDomain(from, target)) return new List(0); + var cached = CachedPaths.FirstOrDefault(p => p.from == from && p.to == target && p.actor == self); if (cached != null) { @@ -72,6 +75,13 @@ namespace OpenRA.Mods.RA.Move { using (new PerfSample("Pathfinder")) { + // As with FindUnitPath, avoid trying to traverse domain transitions. + // In this case it's pretty sketchy; path false-negatives are possible. + if(world.WorldDomains.IsCrossDomain(src, target.ToCPos())) + { + return new List(0); + } + var mi = self.Info.Traits.Get(); var targetCell = target.ToCPos(); var rangeSquared = range.Range*range.Range;