diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 0731e10ace..db04199195 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -768,6 +768,8 @@
+
+
diff --git a/OpenRA.Mods.Common/Traits/ExitsDebugOverlay.cs b/OpenRA.Mods.Common/Traits/ExitsDebugOverlay.cs
new file mode 100644
index 0000000000..de4cd55d0c
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/ExitsDebugOverlay.cs
@@ -0,0 +1,105 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2016 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, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System.Drawing;
+using System.Linq;
+using OpenRA.Graphics;
+using OpenRA.Mods.Common.Graphics;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ [Desc("Displays `Exit` data for factories.")]
+ public class ExitsDebugOverlayInfo : ITraitInfo, Requires
+ {
+ [Desc("Should cell vectors be drawn for each perimeter cell?")]
+ public readonly bool DrawPerimiterCellVectors = true;
+
+ [Desc("Should cell vectors be drawn for each exit cell?")]
+ public readonly bool DrawExitCellVectors = true;
+
+ [Desc("Should lines be drawn for each exit (from spawn offset to the center of the exit cell)?")]
+ public readonly bool DrawSpawnOffsetLines = true;
+
+ object ITraitInfo.Create(ActorInitializer init) { return new ExitsDebugOverlay(init.Self, this); }
+ }
+
+ public class ExitsDebugOverlay : IPostRender
+ {
+ readonly ExitsDebugOverlayManager manager;
+ readonly ExitsDebugOverlayInfo info;
+ readonly RgbaColorRenderer rgbaRenderer;
+ readonly ExitInfo[] exits;
+
+ CPos[] exitCells;
+ WPos[] spawnPositions;
+ CPos[] perimeterCells;
+
+ public ExitsDebugOverlay(Actor self, ExitsDebugOverlayInfo info)
+ {
+ this.info = info;
+ manager = self.World.WorldActor.TraitOrDefault();
+ rgbaRenderer = Game.Renderer.WorldRgbaColorRenderer;
+ exits = self.Info.TraitInfos().ToArray();
+ }
+
+ void IPostRender.RenderAfterWorld(WorldRenderer wr, Actor self)
+ {
+ if (manager == null || !manager.Enabled)
+ return;
+
+ exitCells = exits.Select(e => self.Location + e.ExitCell).ToArray();
+
+ if (info.DrawExitCellVectors)
+ {
+ foreach (var exitCell in exitCells)
+ {
+ var color = self.Owner.Color.RGB;
+ var vec = exitCell - self.Location;
+ var center = wr.World.Map.CenterOfCell(exitCell);
+ new TextRenderable(manager.Font, center, 0, color, vec.ToString()).Render(wr);
+ }
+ }
+
+ if (info.DrawPerimiterCellVectors)
+ {
+ var occupiedCells = self.OccupiesSpace.OccupiedCells().Select(p => p.First).ToArray();
+ perimeterCells = Util.ExpandFootprint(occupiedCells, true).Except(occupiedCells).ToArray();
+
+ foreach (var perimCell in perimeterCells)
+ {
+ var color = Color.Gray;
+ if (exitCells.Contains(perimCell))
+ continue;
+
+ var vec = perimCell - self.Location;
+ var center = wr.World.Map.CenterOfCell(perimCell);
+ new TextRenderable(manager.Font, center, 0, color, vec.ToString()).Render(wr);
+ }
+ }
+
+ if (info.DrawSpawnOffsetLines)
+ {
+ spawnPositions = exits.Select(e => self.CenterPosition + e.SpawnOffset).ToArray();
+
+ for (var i = 0; i < spawnPositions.Length; i++)
+ {
+ var spawnPos = spawnPositions[i];
+ if (spawnPos == self.CenterPosition)
+ continue;
+
+ var exitCellCenter = self.World.Map.CenterOfCell(exitCells[i]);
+ rgbaRenderer.DrawLine(wr.ScreenPosition(spawnPos), wr.ScreenPosition(exitCellCenter), 1f, self.Owner.Color.RGB);
+ }
+ }
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/Traits/World/ExitsDebugOverlayManager.cs b/OpenRA.Mods.Common/Traits/World/ExitsDebugOverlayManager.cs
new file mode 100644
index 0000000000..5d48a5f5ae
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/World/ExitsDebugOverlayManager.cs
@@ -0,0 +1,65 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2016 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, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using OpenRA.Graphics;
+using OpenRA.Mods.Common.Commands;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ public class ExitsDebugOverlayManagerInfo : ITraitInfo
+ {
+ [Desc("The font used to draw cell vectors. Should match the value as-is in the Fonts section of the mod manifest (do not convert to lowercase).")]
+ public readonly string Font = "TinyBold";
+
+ object ITraitInfo.Create(ActorInitializer init) { return new ExitsDebugOverlayManager(init.Self, this); }
+ }
+
+ public class ExitsDebugOverlayManager : IWorldLoaded, IChatCommand
+ {
+ const string CommandName = "exits-overlay";
+ const string CommandHelp = "Displays exits for factories.";
+
+ public readonly SpriteFont Font;
+ public readonly ExitsDebugOverlayManagerInfo Info;
+
+ public bool Enabled;
+
+ readonly Actor self;
+
+ public ExitsDebugOverlayManager(Actor self, ExitsDebugOverlayManagerInfo info)
+ {
+ this.self = self;
+ Info = info;
+
+ if (!Game.Renderer.Fonts.TryGetValue(info.Font, out Font))
+ throw new YamlException("Could not find font '{0}'".F(info.Font));
+ }
+
+ void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr)
+ {
+ var console = self.TraitOrDefault();
+ var help = self.TraitOrDefault();
+
+ if (console == null || help == null)
+ return;
+
+ console.RegisterCommand(CommandName, this);
+ help.RegisterHelp(CommandName, CommandHelp);
+ }
+
+ void IChatCommand.InvokeCommand(string command, string arg)
+ {
+ if (command == CommandName)
+ Enabled ^= true;
+ }
+ }
+}