diff --git a/OpenRA.Game/DomainIndex.cs b/OpenRA.Game/DomainIndex.cs deleted file mode 100644 index 0207556e69..0000000000 --- a/OpenRA.Game/DomainIndex.cs +++ /dev/null @@ -1,106 +0,0 @@ -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 2c5c9cebbf..0aca544852 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -85,7 +85,6 @@ - diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index d1ea2004a0..7c2d6177a6 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -111,7 +111,6 @@ namespace OpenRA } } - public DomainIndex WorldDomains; internal World(Manifest manifest, Map map, OrderManager orderManager, bool isShellmap) { IsShellmap = isShellmap; @@ -122,9 +121,6 @@ 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 38d22bfe47..92d7e15112 100755 --- a/OpenRA.Mods.RA/Move/PathFinder.cs +++ b/OpenRA.Mods.RA/Move/PathFinder.cs @@ -44,9 +44,6 @@ 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) { @@ -58,6 +55,15 @@ namespace OpenRA.Mods.RA.Move var mi = self.Info.Traits.Get(); + // If a water-land transition is required, bail early + var domainIndex = self.World.WorldActor.TraitOrDefault(); + if (domainIndex != null) + { + var passable = mi.GetMovementClass(world.TileSet); + if (!domainIndex.IsPassable(from, target, (uint)passable)) + return new List(0); + } + var pb = FindBidiPath( PathSearch.FromPoint(world, mi, self, target, from, true), PathSearch.FromPoint(world, mi, self, from, target, true).InReverse() @@ -87,12 +93,17 @@ namespace OpenRA.Mods.RA.Move // This assumes that the SubCell does not change during the path traversal var tilesInRange = world.FindTilesInCircle(targetCell, range.Range / 1024 + 1) .Where(t => (t.CenterPosition - target).LengthSquared <= rangeSquared - && mi.CanEnterCell(self.World, self, t, null, true, true) - && !world.WorldDomains.IsCrossDomain(src, t)); + && mi.CanEnterCell(self.World, self, t, null, true, true)); - if(tilesInRange.Count() == 0) + // See if there is any cell within range that does not involve a cross-domain request + // Really, we only need to check the circle perimeter, but it's not clear that would be a performance win + var domainIndex = self.World.WorldActor.TraitOrDefault(); + if (domainIndex != null) { - return new List(0); + var passable = mi.GetMovementClass(world.TileSet); + tilesInRange = new List(tilesInRange.Where(t => domainIndex.IsPassable(src, t, (uint)passable))); + if (tilesInRange.Count() == 0) + return new List(0); } var path = FindBidiPath( diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 6713cdc609..249cf6de26 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -457,6 +457,7 @@ + diff --git a/OpenRA.Mods.RA/World/DomainIndex.cs b/OpenRA.Mods.RA/World/DomainIndex.cs new file mode 100644 index 0000000000..9ce98e000c --- /dev/null +++ b/OpenRA.Mods.RA/World/DomainIndex.cs @@ -0,0 +1,146 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.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 OpenRA.Mods.RA.Move; +using XRandom = OpenRA.Thirdparty.Random; + +namespace OpenRA.Mods.RA +{ + // Identify untraversable regions of the map for faster pathfinding, especially with AI + class DomainIndexInfo : TraitInfo {} + + public class DomainIndex : IWorldLoaded + { + Dictionary domains; + + public void WorldLoaded(World world) + { + domains = new Dictionary(); + var movementClasses = new HashSet( + Rules.Info.Where(ai => ai.Value.Traits.Contains()) + .Select(ai => (uint)ai.Value.Traits.Get().GetMovementClass(world.TileSet))); + + foreach(var mc in movementClasses) domains[mc] = new MovementClassDomainIndex(world, mc); + } + + public bool IsPassable(CPos p1, CPos p2, uint movementClass) + { + return domains[movementClass].IsPassable(p1, p2); + } + } + + class MovementClassDomainIndex + { + Rectangle bounds; + uint movementClass; + int[,] domains; + + public MovementClassDomainIndex(World world, uint movementClass) + { + this.movementClass = movementClass; + 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 IsPassable(CPos p1, CPos p2) + { + return domains[p1.X, p1.Y] == domains[p2.X, p2.Y]; + } + + public void SetDomain(CPos p, int domain) + { + domains[p.X, p.Y] = domain; + } + + + void BuildDomains(World world) + { + Map map = world.Map; + + int i = 1; + var 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 + string currentTileType = WorldUtils.GetTerrainType(world, start); + int terrainOffset = world.TileSet.Terrain.OrderBy(t => t.Key).ToList().FindIndex(x => x.Key == currentTileType); + bool currentPassable = (movementClass & (1 << terrainOffset)) > 0; + + var toProcess = new Queue(); + var seen = new HashSet(); + toProcess.Enqueue(start); + + do + { + CPos p = toProcess.Dequeue(); + if (seen.Contains(p)) continue; + seen.Add(p); + + string candidateTileType = WorldUtils.GetTerrainType(world, p); + int candidateTerrainOffset = world.TileSet.Terrain.OrderBy(t => t.Key).ToList().FindIndex(x => x.Key == candidateTileType); + bool candidatePassable = (movementClass & (1 << candidateTerrainOffset)) > 0; + + // Check if we're still in one contiguous domain + if (currentPassable == candidatePassable) + { + 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/mods/cnc/rules/system.yaml b/mods/cnc/rules/system.yaml index 1445012741..1f2920bce5 100644 --- a/mods/cnc/rules/system.yaml +++ b/mods/cnc/rules/system.yaml @@ -158,7 +158,7 @@ Player: silo: 1 BuildingFractions: proc: 17% - nuke: 10% + nuke: 10% pyle: 7% hand: 9% hq: 1% @@ -273,6 +273,7 @@ World: ProductionQueueFromSelection: ProductionTabsWidget: PRODUCTION_TABS BibLayer: + DomainIndex: ResourceLayer: ResourceClaimLayer: ResourceType@green-tib: diff --git a/mods/d2k/rules/system.yaml b/mods/d2k/rules/system.yaml index b6bbe67e14..79b5c49606 100644 --- a/mods/d2k/rules/system.yaml +++ b/mods/d2k/rules/system.yaml @@ -354,6 +354,7 @@ World: BibLayer: BibTypes: bib3x, bib2x BibWidths: 3, 2 + DomainIndex: ResourceLayer: ResourceClaimLayer: ResourceType@Spice: diff --git a/mods/ra/rules/system.yaml b/mods/ra/rules/system.yaml index 05ac3233c5..4953c644ec 100644 --- a/mods/ra/rules/system.yaml +++ b/mods/ra/rules/system.yaml @@ -604,6 +604,7 @@ World: Name: Soviet Race: soviet BibLayer: + DomainIndex: ResourceLayer: ResourceClaimLayer: ResourceType@ore: