diff --git a/OpenRA.Game/Graphics/QuadRenderer.cs b/OpenRA.Game/Graphics/QuadRenderer.cs new file mode 100644 index 0000000000..83ecf2886c --- /dev/null +++ b/OpenRA.Game/Graphics/QuadRenderer.cs @@ -0,0 +1,67 @@ +#region Copyright & License Information +/* + * Copyright 2007-2011 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.Drawing; +using OpenRA.FileFormats.Graphics; + +namespace OpenRA.Graphics +{ + public class QuadRenderer : Renderer.IBatchRenderer + { + Renderer renderer; + IShader shader; + + Vertex[] vertices = new Vertex[Renderer.TempBufferSize]; + int nv = 0; + + public QuadRenderer(Renderer renderer, IShader shader) + { + this.renderer = renderer; + this.shader = shader; + } + + public void Flush() + { + if (nv > 0) + { + shader.Render(() => + { + var vb = renderer.GetTempVertexBuffer(); + vb.SetData(vertices, nv); + renderer.DrawBatch(vb, 0, nv, PrimitiveType.QuadList); + }); + + nv = 0; + } + } + + public void FillRect(RectangleF r, Color color) + { + Renderer.CurrentBatchRenderer = this; + + if (nv + 4 > Renderer.TempBufferSize) + Flush(); + + vertices[nv] = new Vertex(new float2(r.Left, r.Top), new float2(color.R / 255.0f, color.G / 255.0f), new float2(color.B / 255.0f, color.A / 255.0f)); + vertices[nv + 1] = new Vertex(new float2(r.Right, r.Top), new float2(color.R / 255.0f, color.G / 255.0f), new float2(color.B / 255.0f, color.A / 255.0f)); + vertices[nv + 2] = new Vertex(new float2(r.Right, r.Bottom), new float2(color.R / 255.0f, color.G / 255.0f), new float2(color.B / 255.0f, color.A / 255.0f)); + vertices[nv + 3] = new Vertex(new float2(r.Left, r.Bottom), new float2(color.R / 255.0f, color.G / 255.0f), new float2(color.B / 255.0f, color.A / 255.0f)); + + nv += 4; + } + + public void SetShaderParams(ITexture palette, Size screen, float zoom, float2 scroll) + { + shader.SetVec("Scroll", (int)scroll.X, (int)scroll.Y); + shader.SetVec("r1", zoom*2f/screen.Width, -zoom*2f/screen.Height); + shader.SetVec("r2", -1, 1); + } + } +} diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs index 8be5040802..443ad08844 100644 --- a/OpenRA.Game/Graphics/Renderer.cs +++ b/OpenRA.Game/Graphics/Renderer.cs @@ -28,6 +28,7 @@ namespace OpenRA.Graphics internal static int TempBufferCount; public SpriteRenderer WorldSpriteRenderer { get; private set; } + public QuadRenderer WorldQuadRenderer { get; private set; } public LineRenderer WorldLineRenderer { get; private set; } public LineRenderer LineRenderer { get; private set; } public SpriteRenderer RgbaSpriteRenderer { get; private set; } @@ -48,6 +49,7 @@ namespace OpenRA.Graphics WorldSpriteRenderer = new SpriteRenderer(this, device.CreateShader("shp")); WorldLineRenderer = new LineRenderer(this, device.CreateShader("line")); LineRenderer = new LineRenderer(this, device.CreateShader("line")); + WorldQuadRenderer = new QuadRenderer(this, device.CreateShader("line")); RgbaSpriteRenderer = new SpriteRenderer(this, device.CreateShader("rgba")); SpriteRenderer = new SpriteRenderer(this, device.CreateShader("shp")); @@ -67,6 +69,7 @@ namespace OpenRA.Graphics device.Clear(); WorldSpriteRenderer.SetShaderParams(PaletteTexture, Resolution, zoom, scroll); WorldLineRenderer.SetShaderParams(PaletteTexture, Resolution, zoom, scroll); + WorldQuadRenderer.SetShaderParams(PaletteTexture, Resolution, zoom, scroll); SpriteRenderer.SetShaderParams(PaletteTexture, Resolution, 1f, float2.Zero); LineRenderer.SetShaderParams(PaletteTexture, Resolution, 1f, float2.Zero); RgbaSpriteRenderer.SetShaderParams(PaletteTexture, Resolution, 1f, float2.Zero); diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 890b04c2ca..e1d4cc465c 100755 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -80,6 +80,7 @@ + diff --git a/OpenRA.Mods.RA/Move/Move.cs b/OpenRA.Mods.RA/Move/Move.cs index 4806593020..b86f2278cc 100755 --- a/OpenRA.Mods.RA/Move/Move.cs +++ b/OpenRA.Mods.RA/Move/Move.cs @@ -49,8 +49,9 @@ namespace OpenRA.Mods.RA.Move { this.getPath = (self,mobile) => self.World.WorldActor.Trait().FindPath( - PathSearch.FromPoint( self.World, mobile.Info, self, mobile.toCell, destination, false ) - .WithIgnoredBuilding( ignoreBuilding )); + PathSearch.FromPoint(self.World, mobile.Info, self, mobile.toCell, destination, false) + .WithIgnoredBuilding(ignoreBuilding) + ); this.destination = destination; this.nearEnough = 0; diff --git a/OpenRA.Mods.RA/Move/PathFinder.cs b/OpenRA.Mods.RA/Move/PathFinder.cs index 8705463283..2a7556f1d8 100755 --- a/OpenRA.Mods.RA/Move/PathFinder.cs +++ b/OpenRA.Mods.RA/Move/PathFinder.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using OpenRA.FileFormats; using OpenRA.Support; using OpenRA.Traits; @@ -89,13 +90,29 @@ namespace OpenRA.Mods.RA.Move using (new PerfSample("Pathfinder")) { using (search) + { + List path = null; + while (!search.queue.Empty) { var p = search.Expand(world); if (search.heuristic(p) == 0) - return MakePath(search.cellInfo, p); + { + path = MakePath(search.cellInfo, p); + break; + } } + var dbg = world.WorldActor.TraitOrDefault(); + if (dbg != null) + { + dbg.AddLayer(search.considered.Select(p => new Pair(p, search.cellInfo[p.X, p.Y].MinCost)), search.maxCost, search.owner); + } + + if (path != null) + return path; + } + // no path exists return new List(0); } @@ -125,21 +142,43 @@ namespace OpenRA.Mods.RA.Move { using (fromSrc) using (fromDest) + { + List path = null; + while (!fromSrc.queue.Empty && !fromDest.queue.Empty) { /* make some progress on the first search */ var p = fromSrc.Expand(world); - if (fromDest.cellInfo[p.X, p.Y].Seen && fromDest.cellInfo[p.X, p.Y].MinCost < float.PositiveInfinity) - return MakeBidiPath(fromSrc, fromDest, p); + if (fromDest.cellInfo[p.X, p.Y].Seen && + fromDest.cellInfo[p.X, p.Y].MinCost < float.PositiveInfinity) + { + path = MakeBidiPath(fromSrc, fromDest, p); + break; + } /* make some progress on the second search */ var q = fromDest.Expand(world); - if (fromSrc.cellInfo[q.X, q.Y].Seen && fromSrc.cellInfo[q.X, q.Y].MinCost < float.PositiveInfinity) - return MakeBidiPath(fromSrc, fromDest, q); + if (fromSrc.cellInfo[q.X, q.Y].Seen && + fromSrc.cellInfo[q.X, q.Y].MinCost < float.PositiveInfinity) + { + path = MakeBidiPath(fromSrc, fromDest, q); + break; + } } + var dbg = world.WorldActor.TraitOrDefault(); + if (dbg != null) + { + dbg.AddLayer(fromSrc.considered.Select(p => new Pair(p, fromSrc.cellInfo[p.X, p.Y].MinCost)), fromSrc.maxCost, fromSrc.owner); + dbg.AddLayer(fromDest.considered.Select(p => new Pair(p, fromDest.cellInfo[p.X, p.Y].MinCost)), fromDest.maxCost, fromDest.owner); + } + + if (path != null) + return path; + } + return new List(0); } } diff --git a/OpenRA.Mods.RA/Move/PathSearch.cs b/OpenRA.Mods.RA/Move/PathSearch.cs index 8261f807b7..2775e3a3de 100755 --- a/OpenRA.Mods.RA/Move/PathSearch.cs +++ b/OpenRA.Mods.RA/Move/PathSearch.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; +using System.Linq; using OpenRA.FileFormats; using OpenRA.Traits; @@ -26,7 +27,9 @@ namespace OpenRA.Mods.RA.Move public bool checkForBlocked; public Actor ignoreBuilding; public bool inReverse; - + public HashSet considered; + public int maxCost; + Pair[] nextDirections; MobileInfo mobileInfo; Actor self; public Player owner { get { return self.Owner; } } @@ -39,6 +42,9 @@ namespace OpenRA.Mods.RA.Move this.self = self; customCost = null; queue = new PriorityQueue(); + considered = new HashSet(); + maxCost = 0; + nextDirections = directions.Select(d => new Pair(d, 0)).ToArray(); } public PathSearch InReverse() @@ -108,14 +114,23 @@ namespace OpenRA.Mods.RA.Move return p.Location; } - foreach( CVec d in directions ) + // This current cell is ok; check all immediate directions: + considered.Add(p.Location); + + for (int i = 0; i < nextDirections.Length; ++i) { + CVec d = nextDirections[i].First; + nextDirections[i].Second = int.MaxValue; + CPos newHere = p.Location + d; - if (!world.Map.IsInMap(newHere.X, newHere.Y)) continue; + // Is this direction flat-out unusable or already seen? + if (!world.Map.IsInMap(newHere.X, newHere.Y)) + continue; if (cellInfo[newHere.X, newHere.Y].Seen) continue; + // Now we may seriously consider this direction using heuristics: var costHere = mobileInfo.MovementCostForCell(world, newHere); if (costHere == int.MaxValue) @@ -134,8 +149,12 @@ namespace OpenRA.Mods.RA.Move int cellCost = costHere; if (d.X * d.Y != 0) cellCost = (cellCost * 34) / 24; + int userCost = 0; if (customCost != null) - cellCost += customCost(newHere); + { + userCost = customCost(newHere); + cellCost += userCost; + } // directional bonuses for smoother flow! if (LaneBias != 0) @@ -151,14 +170,23 @@ namespace OpenRA.Mods.RA.Move int newCost = cellInfo[p.Location.X, p.Location.Y].MinCost + cellCost; - if (newCost >= cellInfo[newHere.X, newHere.Y].MinCost) + // Cost is even higher; next direction: + if (newCost > cellInfo[newHere.X, newHere.Y].MinCost) continue; cellInfo[newHere.X, newHere.Y].Path = p.Location; cellInfo[newHere.X, newHere.Y].MinCost = newCost; + nextDirections[i].Second = newCost + est; queue.Add(new PathDistance(newCost + est, newHere)); + + if (newCost > maxCost) maxCost = newCost; + considered.Add(newHere); } + + // Sort to prefer the cheaper direction: + //Array.Sort(nextDirections, (a, b) => a.Second.CompareTo(b.Second)); + return p.Location; } @@ -263,10 +291,11 @@ namespace OpenRA.Mods.RA.Move { return here => { - CVec d = (here - destination).Abs(); - int diag = Math.Min(d.X, d.Y); - int straight = Math.Abs(d.X - d.Y); - return (3400 * diag / 24) + (100 * straight); + int diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y)); + int straight = (Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y)); + int h = (3400 * diag / 24) + 100 * (straight - (2 * diag)); + h = (int)(h * 1.001); + return h; }; } diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 00c0d47f48..735cae25ae 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -403,6 +403,7 @@ + diff --git a/OpenRA.Mods.RA/Widgets/Logic/CheatsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/CheatsLogic.cs index 8d97a5173f..c42ceaca0f 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/CheatsLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/CheatsLogic.cs @@ -59,6 +59,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic widget.Get("RESET_EXPLORATION").OnClick = () => world.IssueOrder(new Order("DevResetExploration", world.LocalPlayer.PlayerActor, false)); + var dbgOverlay = world.WorldActor.TraitOrDefault(); + var showAstarCostCheckbox = widget.Get("SHOW_ASTAR"); + showAstarCostCheckbox.IsChecked = () => dbgOverlay != null ? dbgOverlay.Visible : false; + showAstarCostCheckbox.OnClick = () => { if (dbgOverlay != null) dbgOverlay.Visible ^= true; }; + widget.Get("CLOSE").OnClick = () => { Ui.CloseWindow(); onExit(); }; } diff --git a/OpenRA.Mods.RA/World/DebugOverlay.cs b/OpenRA.Mods.RA/World/DebugOverlay.cs new file mode 100644 index 0000000000..8aa63d0038 --- /dev/null +++ b/OpenRA.Mods.RA/World/DebugOverlay.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + class DebugOverlayInfo : Traits.TraitInfo + { + } + + class DebugOverlay : IRenderOverlay, IWorldLoaded + { + Dictionary layers; + int refreshTick; + World world; + public bool Visible; + + public void WorldLoaded(World w) + { + this.world = w; + this.refreshTick = 0; + this.layers = new Dictionary(8); + // Enabled via Cheats menu + this.Visible = false; + } + + public void AddLayer(IEnumerable> cellWeights, int maxWeight, Player pl) + { + if (maxWeight == 0) return; + + int[,] layer; + if (!layers.TryGetValue(pl, out layer)) + { + layer = new int[world.Map.MapSize.X, world.Map.MapSize.Y]; + layers.Add(pl, layer); + } + + foreach (var p in cellWeights) + layer[p.First.X, p.First.Y] = Math.Min(128, (layer[p.First.X, p.First.Y]) + ((maxWeight - p.Second) * 64 / maxWeight)); + } + + public void Render(WorldRenderer wr) + { + if (!Visible) return; + + var qr = Game.Renderer.WorldQuadRenderer; + bool doDim = refreshTick - world.FrameNumber <= 0; + if (doDim) refreshTick = world.FrameNumber + 20; + + var viewBounds = Game.viewport.WorldBounds(world); + var mapBounds = world.Map.Bounds; + + foreach (var pair in layers) + { + Color c = (pair.Key != null) ? pair.Key.ColorRamp.GetColor(0f) : Color.PaleTurquoise; + var layer = pair.Value; + + for (int j = mapBounds.Top; j <= mapBounds.Bottom; ++j) + for (int i = mapBounds.Left; i <= mapBounds.Right; ++i) + { + if (layer[i, j] <= 0) continue; + + var w = Math.Max(0, Math.Min(layer[i, j], 128)); + if (doDim) + { + layer[i, j] = layer[i, j] * 5 / 6; + } + + if (!viewBounds.Contains(i, j)) continue; + + // Only render quads in viewing range: + var ploc = new CPos(i, j).ToPPos(); + qr.FillRect(new RectangleF(ploc.X, ploc.Y, Game.CellSize, Game.CellSize), Color.FromArgb(w, c)); + } + } + } + } +} diff --git a/mods/ra/chrome/cheats.yaml b/mods/ra/chrome/cheats.yaml index d93e09a360..0539423ca2 100644 --- a/mods/ra/chrome/cheats.yaml +++ b/mods/ra/chrome/cheats.yaml @@ -3,7 +3,7 @@ Background@CHEATS_PANEL: X:(WINDOW_RIGHT - WIDTH)/2 Y:(WINDOW_BOTTOM - HEIGHT)/2 Width:350 - Height:420 + Height:450 Visible:true Children: Label@LABEL_TITLE: @@ -75,6 +75,12 @@ Background@CHEATS_PANEL: Width:PARENT_RIGHT - 30 Height:20 Text:Build Anywhere + Checkbox@SHOW_ASTAR: + X:30 + Y:320 + Width:PARENT_RIGHT - 30 + Height:20 + Text:Show A* Cost Button@CLOSE: X:30 Y:360 diff --git a/mods/ra/rules/system.yaml b/mods/ra/rules/system.yaml index 0999f4ce4e..6b2f3ae00b 100644 --- a/mods/ra/rules/system.yaml +++ b/mods/ra/rules/system.yaml @@ -455,7 +455,7 @@ Player: BaseAttackNotifier: PlayerStatistics: -World: +World: OpenWidgetAtGameStart: Widget: INGAME_ROOT ObserverWidget: OBSERVER_ROOT @@ -562,6 +562,7 @@ World: Type:Crater Types:cr1,cr2,cr3,cr4,cr5,cr6 Depths:5,5,5,5,5,5 + DebugOverlay: SpawnMapActors: CreateMPPlayers: MPStartLocations: