diff --git a/OpenRA.Mods.RA/Bridge.cs b/OpenRA.Mods.RA/Bridge.cs index b889b4e8c9..ea51b80d62 100644 --- a/OpenRA.Mods.RA/Bridge.cs +++ b/OpenRA.Mods.RA/Bridge.cs @@ -218,6 +218,12 @@ namespace OpenRA.Mods.RA foreach (var c in TileSprites[currentTemplate].Keys) self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c); + // If this bridge repair operation connects two pathfinding domains, + // update the domain index. + var domainIndex = self.World.WorldActor.TraitOrDefault(); + if (domainIndex != null) + domainIndex.UpdateCells(self.World, TileSprites[currentTemplate].Keys); + if (LongBridgeSegmentIsDead() && !killedUnits) { killedUnits = true; diff --git a/OpenRA.Mods.RA/World/DomainIndex.cs b/OpenRA.Mods.RA/World/DomainIndex.cs index 9ce98e000c..99d89d012c 100644 --- a/OpenRA.Mods.RA/World/DomainIndex.cs +++ b/OpenRA.Mods.RA/World/DomainIndex.cs @@ -13,15 +13,9 @@ 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 { @@ -30,53 +24,147 @@ namespace OpenRA.Mods.RA public class DomainIndex : IWorldLoaded { - Dictionary domains; + Dictionary domainIndexes; public void WorldLoaded(World world) { - domains = new Dictionary(); + domainIndexes = 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); + foreach (var mc in movementClasses) domainIndexes[mc] = new MovementClassDomainIndex(world, mc); } public bool IsPassable(CPos p1, CPos p2, uint movementClass) { - return domains[movementClass].IsPassable(p1, p2); + return domainIndexes[movementClass].IsPassable(p1, p2); + } + + /// Regenerate the domain index for a group of cells + public void UpdateCells(World world, IEnumerable cells) + { + var dirty = new HashSet(cells); + foreach (var index in domainIndexes) index.Value.UpdateCells(world, dirty); } } class MovementClassDomainIndex { Rectangle bounds; + uint movementClass; int[,] domains; + Dictionary> transientConnections; public MovementClassDomainIndex(World world, uint movementClass) { - this.movementClass = movementClass; bounds = world.Map.Bounds; + this.movementClass = movementClass; domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)]; - BuildDomains(world); - } + transientConnections = new Dictionary>(); - public int GetDomainOf(CPos p) - { - return domains[p.X, p.Y]; + BuildDomains(world); } public bool IsPassable(CPos p1, CPos p2) { - return domains[p1.X, p1.Y] == domains[p2.X, p2.Y]; + if (domains[p1.X, p1.Y] == domains[p2.X, p2.Y]) return true; + + // Even though p1 and p2 are in different domains, it's possible + // that some dynamic terrain (i.e. bridges) may connect them. + return HasConnection(GetDomainOf(p1), GetDomainOf(p2)); } - public void SetDomain(CPos p, int domain) + public void UpdateCells(World world, HashSet dirtyCells) + { + var neighborDomains = new List(); + + foreach (var cell in dirtyCells) + { + // Select all neighbors inside the map boundries + var neighbors = CVec.directions.Select(d => d + cell) + .Where(c => bounds.Contains(c.X, c.Y)); + + bool found = false; + foreach (var neighbor in neighbors) + { + if (!dirtyCells.Contains(neighbor)) + { + int neighborDomain = GetDomainOf(neighbor); + + bool match = CanTraverseTile(world, neighbor); + if (match) neighborDomains.Add(neighborDomain); + + // Set ourselves to the first non-dirty neighbor we find. + if (!found) + { + SetDomain(cell, neighborDomain); + found = true; + } + } + } + } + + foreach (var c1 in neighborDomains) + { + foreach (var c2 in neighborDomains) + { + CreateConnection(c1, c2); + } + } + } + + int GetDomainOf(CPos p) + { + return domains[p.X, p.Y]; + } + + void SetDomain(CPos p, int domain) { domains[p.X, p.Y] = domain; } + bool HasConnection(int d1, int d2) + { + // Search our connections graph for a possible route + var visited = new HashSet(); + var toProcess = new Stack(); + toProcess.Push(d1); + + int i = 0; + while (toProcess.Count() > 0) + { + int current = toProcess.Pop(); + if (!transientConnections.ContainsKey(current)) continue; + foreach (int neighbor in transientConnections[current]) + { + if (neighbor == d2) return true; + if (!visited.Contains(neighbor)) toProcess.Push(neighbor); + } + + visited.Add(current); + i += 1; + } + + return false; + } + + void CreateConnection(int d1, int d2) + { + if (!transientConnections.ContainsKey(d1)) transientConnections[d1] = new HashSet(); + if (!transientConnections.ContainsKey(d2)) transientConnections[d2] = new HashSet(); + + transientConnections[d1].Add(d2); + transientConnections[d2].Add(d1); + } + + bool CanTraverseTile(World world, CPos p) + { + string currentTileType = WorldUtils.GetTerrainType(world, p); + int terrainOffset = world.TileSet.Terrain.OrderBy(t => t.Key).ToList().FindIndex(x => x.Key == currentTileType); + return (movementClass & (1 << terrainOffset)) > 0; + } void BuildDomains(World world) { @@ -100,9 +188,7 @@ namespace OpenRA.Mods.RA 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; + bool currentPassable = CanTraverseTile(world, start); var toProcess = new Queue(); var seen = new HashSet(); @@ -114,9 +200,7 @@ namespace OpenRA.Mods.RA 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; + bool candidatePassable = CanTraverseTile(world, p); // Check if we're still in one contiguous domain if (currentPassable == candidatePassable)