diff --git a/OpenRa.DataStructures/int2.cs b/OpenRa.DataStructures/int2.cs index 8cf08c2444..1083173118 100644 --- a/OpenRa.DataStructures/int2.cs +++ b/OpenRa.DataStructures/int2.cs @@ -27,6 +27,9 @@ namespace OpenRa public int Length { get { return (int)Math.Sqrt(LengthSquared); } } public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); } + public static int2 Max(int2 a, int2 b) { return new int2(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)); } + public static int2 Min(int2 a, int2 b) { return new int2(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y)); } + public override bool Equals(object obj) { if (obj == null) diff --git a/OpenRa.Game/BuildingInfluenceMap.cs b/OpenRa.Game/BuildingInfluenceMap.cs index c8360e7d75..b8adf9fe6c 100644 --- a/OpenRa.Game/BuildingInfluenceMap.cs +++ b/OpenRa.Game/BuildingInfluenceMap.cs @@ -3,33 +3,94 @@ using System.Collections.Generic; using System.Linq; using System.Text; using OpenRa.Game.GameRules; +using IjwFramework.Types; +using IjwFramework.Collections; namespace OpenRa.Game { class BuildingInfluenceMap { - Actor[,] influence = new Actor[128, 128]; + Pair[,] influence = new Pair[128, 128]; + readonly int maxDistance; /* clip limit for voronoi cells */ + static readonly Pair NoClaim = Pair.New((Actor)null, float.MaxValue); - public BuildingInfluenceMap(World world, Player player) + public BuildingInfluenceMap(World world, int maxDistance) { + this.maxDistance = maxDistance; + + for (int j = 0; j < 128; j++) + for (int i = 0; i < 128; i++) + influence[i, j] = NoClaim; + world.ActorAdded += - a => { if (a.traits.Contains() && a.Owner == player) AddInfluence(a); }; + a => { if (a.traits.Contains()) AddInfluence(a); }; world.ActorRemoved += - a => { if (a.traits.Contains() && a.Owner == player) RemoveInfluence(a); }; + a => { if (a.traits.Contains()) RemoveInfluence(a); }; } void AddInfluence(Actor a) { - foreach (var t in Footprint.UnpathableTiles(a.unitInfo, a.Location)) + var tiles = Footprint.UnpathableTiles(a.unitInfo, a.Location).ToArray(); + var min = int2.Max(new int2(0, 0), + tiles.Aggregate(int2.Min) - new int2(maxDistance, maxDistance)); + var max = int2.Min(new int2(128, 128), + tiles.Aggregate(int2.Max) + new int2(maxDistance, maxDistance)); + + var pq = new PriorityQueue(); + + var initialTileCount = 0; + + foreach (var t in tiles) if (IsValid(t)) - influence[t.X, t.Y] = a; + { + pq.Add(new Cell { location = t, distance = 0, actor=a }); + ++initialTileCount; + } + + Log.Write("Recalculating voronoi region for {{ {0} ({1},{2}) }}: {3} initial tiles", + a.unitInfo.Name, a.Location.X, a.Location.Y, initialTileCount); + + var updatedCells = 0; + + while (!pq.Empty) + { + var c = pq.Pop(); + + if (influence[c.location.X, c.location.Y].Second <= c.distance) + continue; + + influence[c.location.X, c.location.Y].First = c.actor; + influence[c.location.X, c.location.Y].Second = c.distance; + + ++updatedCells; + + if (c.distance + 1 > maxDistance) continue; + + foreach (var d in PathFinder.directions) + { + var e = c.location + d; + if (e.X < min.X || e.Y < min.Y || e.X > max.X || e.Y > max.Y) + continue; + + pq.Add(new Cell + { + location = e, + distance = c.distance + ((d.X * d.Y != 0) ? 1.414f : 1f), + actor = c.actor + }); + } + } + + Log.Write("Finished recalculating region. {0} cells updated.", updatedCells); } void RemoveInfluence(Actor a) { foreach (var t in Footprint.UnpathableTiles(a.unitInfo, a.Location)) if (IsValid(t)) - influence[t.X, t.Y] = null; + influence[t.X, t.Y] = NoClaim; + + /* todo: fix everything that was in this region! doesnt matter yet, since we cant destroy buildings */ } bool IsValid(int2 t) @@ -37,6 +98,35 @@ namespace OpenRa.Game return !(t.X < 0 || t.Y < 0 || t.X >= 128 || t.Y >= 128); } - public Actor this[int2 cell] { get { return IsValid(cell) ? influence[cell.X, cell.Y] : null; } } + public Actor GetBuildingAt(int2 cell) + { + if (!IsValid(cell) || influence[cell.X, cell.Y].Second != 0) + return null; + return influence[cell.X, cell.Y].First; + } + + public Actor GetNearestBuilding(int2 cell) + { + if (!IsValid(cell)) return null; + return influence[cell.X, cell.Y].First; + } + + public int GetDistanceToBuilding(int2 cell) + { + if (!IsValid(cell)) return int.MaxValue; + return (int)influence[cell.X, cell.Y].Second; + } + + struct Cell : IComparable + { + public int2 location; + public float distance; + public Actor actor; + + public int CompareTo(Cell other) + { + return distance.CompareTo(other.distance); + } + } } } diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index da37156139..46020f4f96 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -28,7 +28,7 @@ namespace OpenRa.Game public static Dictionary players = new Dictionary(); public static Player LocalPlayer { get { return players[localPlayerIndex]; } } - public static BuildingInfluenceMap LocalPlayerBuildings; + public static BuildingInfluenceMap BuildingInfluence; static ISoundEngine soundEngine; @@ -55,9 +55,9 @@ namespace OpenRa.Game LoadMapBuildings( mapFile ); LoadMapUnits( mapFile ); - LocalPlayerBuildings = new BuildingInfluenceMap(world, LocalPlayer); + BuildingInfluence = new BuildingInfluenceMap(world, 8); - pathFinder = new PathFinder(map, terrain.tileSet, LocalPlayerBuildings); + pathFinder = new PathFinder(map, terrain.tileSet, BuildingInfluence); network = new Network(); @@ -124,7 +124,7 @@ namespace OpenRa.Game public static bool IsCellBuildable(int2 a, UnitMovementType umt) { - if (LocalPlayerBuildings[a] != null) return false; + if (BuildingInfluence.GetBuildingAt(a) != null) return false; a += map.Offset; @@ -159,6 +159,15 @@ namespace OpenRa.Game { var q = FindUnits(a, a); return q.Where(x => x.traits.Contains()).Concat(q).Take(1); + } + + public static int GetDistanceToBase(int2 b, Player p) + { + var building = BuildingInfluence.GetNearestBuilding(b); + if (building == null || building.Owner != p) + return int.MaxValue; + + return BuildingInfluence.GetDistanceToBuilding(b); } } } diff --git a/OpenRa.Game/MainWindow.cs b/OpenRa.Game/MainWindow.cs index 8a63e725e5..ee82338c86 100755 --- a/OpenRa.Game/MainWindow.cs +++ b/OpenRa.Game/MainWindow.cs @@ -54,7 +54,7 @@ namespace OpenRa.Game SequenceProvider.ForcePrecache(); - Game.world.Add( new Actor( "mcv", new int2( 5, 5 ), Game.players[ 3 ]) ); + Game.world.Add( new Actor( "mcv", new int2( 5, 5 ), Game.players[ 1 ]) ); Game.world.Add( new Actor( "mcv", new int2( 7, 5 ), Game.players[ 2 ] ) ); Game.world.Add( new Actor( "mcv", new int2( 9, 5 ), Game.players[ 0 ] ) ); var jeep = new Actor( "jeep", new int2( 9, 15 ), Game.players[ 1 ] ); diff --git a/OpenRa.Game/PathFinder.cs b/OpenRa.Game/PathFinder.cs index ac8f42f9af..903f9ce0c3 100644 --- a/OpenRa.Game/PathFinder.cs +++ b/OpenRa.Game/PathFinder.cs @@ -64,7 +64,7 @@ namespace OpenRa.Game continue; if( passableCost[ newHere.X, newHere.Y ] == double.PositiveInfinity ) continue; - if (bim[newHere - offset] != null) + if (bim.GetBuildingAt(newHere - offset) != null) continue; double cellCost = ( ( d.X * d.Y != 0 ) ? 1.414213563 : 1.0 ) * passableCost[ newHere.X, newHere.Y ]; @@ -98,7 +98,7 @@ namespace OpenRa.Game return ret; } - static readonly int2[] directions = + public static readonly int2[] directions = new int2[] { new int2( -1, -1 ), new int2( -1, 0 ), diff --git a/OpenRa.Game/PlaceBuilding.cs b/OpenRa.Game/PlaceBuilding.cs index 8ddbe02eb6..e93dcc052a 100644 --- a/OpenRa.Game/PlaceBuilding.cs +++ b/OpenRa.Game/PlaceBuilding.cs @@ -26,6 +26,11 @@ namespace OpenRa.Game bi.WaterBound ? UnitMovementType.Float : UnitMovementType.Wheel))) yield break; + var maxDistance = bi.Adjacent + 2; /* real-ra is weird. this is 1 GAP. */ + if (!Footprint.Tiles(bi, xy).Any( + t => Game.GetDistanceToBase(t, Owner) < maxDistance)) + yield break; + yield return new PlaceBuildingOrder(this, xy); } diff --git a/OpenRa.Game/UiOverlay.cs b/OpenRa.Game/UiOverlay.cs index 39c4f739fd..873ae5cfa3 100644 --- a/OpenRa.Game/UiOverlay.cs +++ b/OpenRa.Game/UiOverlay.cs @@ -1,7 +1,8 @@ using System.Drawing; using OpenRa.Game.Graphics; using System; -using OpenRa.Game.GameRules; +using OpenRa.Game.GameRules; +using System.Linq; namespace OpenRa.Game { @@ -32,12 +33,16 @@ namespace OpenRa.Game public void Draw() { - if (!hasOverlay) - return; + if (!hasOverlay) return; var bi = (UnitInfo.BuildingInfo)Rules.UnitInfo[name]; + + var maxDistance = bi.Adjacent + 2; /* real-ra is weird. this is 1 GAP. */ + var tooFarFromBase = !Footprint.Tiles(bi, position).Any( + t => Game.GetDistanceToBase(t, Game.LocalPlayer) < maxDistance); + foreach( var t in Footprint.Tiles( bi, position ) ) - spriteRenderer.DrawSprite( Game.IsCellBuildable( t, bi.WaterBound + spriteRenderer.DrawSprite( !tooFarFromBase && Game.IsCellBuildable( t, bi.WaterBound ? UnitMovementType.Float : UnitMovementType.Wheel ) ? buildOk : buildBlocked, Game.CellSize * t, 0 );