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: