From 3beaade48e283080acb6db1e3ce19255e1f5e11b Mon Sep 17 00:00:00 2001
From: MaxED <j.maxed@gmail.com>
Date: Mon, 17 Sep 2012 15:41:18 +0000
Subject: [PATCH] Order, in which patrol points and interpolation points are
 connected can now be shown in Things and Visual modes.

---
 Source/Core/Builder.csproj                    |   2 +
 Source/Core/Config/ProgramConfiguration.cs    |   4 +
 Source/Core/GZBuilder/Data/LinkHelper.cs      | 164 ++++++++++++++++++
 Source/Core/GZBuilder/GZGeneral.cs            |   8 +-
 Source/Core/GZBuilder/Geometry/Line3D.cs      |  28 +++
 Source/Core/Rendering/ColorCollection.cs      |   6 +-
 Source/Core/Rendering/Renderer3D.cs           |  56 ++++++
 Source/Core/Resources/Actions.cfg             |  13 +-
 .../Core/Windows/PreferencesForm.Designer.cs  |  27 ++-
 Source/Core/Windows/PreferencesForm.cs        |   2 +
 Source/Core/Windows/ThingEditForm.cs          |  11 +-
 .../BuilderModes/ClassicModes/ThingsMode.cs   |  18 ++
 12 files changed, 325 insertions(+), 14 deletions(-)
 create mode 100644 Source/Core/GZBuilder/Data/LinkHelper.cs
 create mode 100644 Source/Core/GZBuilder/Geometry/Line3D.cs

diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index bc91b73a0..a8f0331a9 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -705,6 +705,7 @@
     <Compile Include="GZBuilder\Data\BoundingBox.cs" />
     <Compile Include="GZBuilder\Data\GameType.cs" />
     <Compile Include="GZBuilder\Data\GZDoomLight.cs" />
+    <Compile Include="GZBuilder\Data\LinkHelper.cs" />
     <Compile Include="GZBuilder\Data\MapInfo.cs" />
     <Compile Include="GZBuilder\Data\ModeldefEntry.cs" />
     <Compile Include="GZBuilder\Data\ScriptItem.cs" />
@@ -713,6 +714,7 @@
     <Compile Include="GZBuilder\Data\TextureData.cs" />
     <Compile Include="GZBuilder\Data\ThingBoundingBox.cs" />
     <Compile Include="GZBuilder\Data\ThingCopyData.cs" />
+    <Compile Include="GZBuilder\Geometry\Line3D.cs" />
     <Compile Include="GZBuilder\GZDoom\DecorateParserSE.cs" />
     <Compile Include="GZBuilder\GZDoom\GldefsParser.cs" />
     <Compile Include="GZBuilder\GZDoom\MapinfoParser.cs" />
diff --git a/Source/Core/Config/ProgramConfiguration.cs b/Source/Core/Config/ProgramConfiguration.cs
index ffd2f603a..fbe4550c7 100644
--- a/Source/Core/Config/ProgramConfiguration.cs
+++ b/Source/Core/Config/ProgramConfiguration.cs
@@ -103,6 +103,7 @@ namespace CodeImp.DoomBuilder.Config
         private bool gzDrawFog;
         private bool gzToolbarGZDoom;
         private bool gzSynchCameras;
+        private bool gzShowEventLines;
         private int gzMaxDynamicLights;
         private float gzDynamicLightRadius;
         private float gzDynamicLightIntensity;
@@ -181,6 +182,7 @@ namespace CodeImp.DoomBuilder.Config
         public bool GZDrawFog { get { return gzDrawFog; } internal set { gzDrawFog = value; } }
         public bool GZToolbarGZDoom { get { return gzToolbarGZDoom; } internal set { gzToolbarGZDoom = value; } }
         public bool GZSynchCameras { get { return gzSynchCameras; } internal set { gzSynchCameras = value; } }
+        public bool GZShowEventLines { get { return gzShowEventLines; } internal set { gzShowEventLines = value; } }
         public bool GZTestFromCurrentPosition { get { return gzTestFromCurrentPosition; } internal set { gzTestFromCurrentPosition = value; } }
         public int GZMaxDynamicLights { get { return gzMaxDynamicLights; } internal set { gzMaxDynamicLights = value; } }
         public float GZDynamicLightRadius { get { return gzDynamicLightRadius; } internal set { gzDynamicLightRadius = value; } }
@@ -276,6 +278,7 @@ namespace CodeImp.DoomBuilder.Config
                 gzDrawFog = cfg.ReadSetting("gzdrawfog", false);
                 gzToolbarGZDoom = cfg.ReadSetting("gztoolbargzdoom", true);
                 gzSynchCameras = cfg.ReadSetting("gzsynchcameras", true);
+                gzShowEventLines = cfg.ReadSetting("gzshoweventlines", true);
                 gzMaxDynamicLights = cfg.ReadSetting("gzmaxdynamiclights", 16);
                 gzDynamicLightRadius = cfg.ReadSetting("gzdynamiclightradius", 1.0f);
                 gzDynamicLightIntensity = cfg.ReadSetting("gzdynamiclightintensity", 1.0f);
@@ -351,6 +354,7 @@ namespace CodeImp.DoomBuilder.Config
             cfg.WriteSetting("gzanimatelights", gzAnimateLights);
             cfg.WriteSetting("gzdrawfog", gzDrawFog);
             cfg.WriteSetting("gzsynchcameras", gzSynchCameras);
+            cfg.WriteSetting("gzshoweventlines", gzShowEventLines);
             cfg.WriteSetting("gztoolbargzdoom", gzToolbarGZDoom);
             cfg.WriteSetting("gzmaxdynamiclights", gzMaxDynamicLights);
             cfg.WriteSetting("gzdynamiclightradius", gzDynamicLightRadius);
diff --git a/Source/Core/GZBuilder/Data/LinkHelper.cs b/Source/Core/GZBuilder/Data/LinkHelper.cs
new file mode 100644
index 000000000..2bea3c3b6
--- /dev/null
+++ b/Source/Core/GZBuilder/Data/LinkHelper.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+//using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.VisualModes;
+using CodeImp.DoomBuilder.GZBuilder.Geometry;
+using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.Rendering;
+using CodeImp.DoomBuilder.Config;
+
+namespace CodeImp.DoomBuilder.GZBuilder.Data {
+    
+    public static class LinksCollector {
+        private struct ThingsCheckResult {
+            public bool ProcessPathNodes;
+            public bool ProcessInterpolationPoints;
+            public bool ProcessThingsWithGoal;
+            public bool ProcessCameras;
+        }
+        
+        public static List<Line3D> GetThingLinks(ICollection<VisualThing> visualThings) {
+            List<Thing> things = new List<Thing>();
+            foreach (VisualThing vt in visualThings) things.Add(vt.Thing);
+
+            ThingsCheckResult result = checkThings(things);
+            if (result.ProcessPathNodes || result.ProcessInterpolationPoints || result.ProcessThingsWithGoal || result.ProcessCameras)
+                return getThingLinks(result, true);
+            return new List<Line3D>();
+        }
+        
+        public static List<Line3D> GetThingLinks(ICollection<Thing> things) {
+            ThingsCheckResult result = checkThings(things);
+            if (result.ProcessPathNodes || result.ProcessInterpolationPoints || result.ProcessThingsWithGoal || result.ProcessCameras)
+                return getThingLinks(result, false);
+            return new List<Line3D>();
+        }
+
+        private static ThingsCheckResult checkThings(ICollection<Thing> things) {
+            ThingsCheckResult result = new ThingsCheckResult();
+
+            foreach (Thing t in things) {
+                if (t.Type == 9024) //zdoom path node
+                    result.ProcessPathNodes = true;
+                else if (t.Type == 9070) //zdoom camera interpolation point
+                    result.ProcessInterpolationPoints = true;
+                else if (t.Action == 229 && t.Args[1] != 0) //Thing_SetGoal
+                    result.ProcessThingsWithGoal = true;
+                else if (t.Type == 9072 && (t.Args[0] != 0 || t.Args[1] != 0)) //camera with a path
+                    result.ProcessCameras = true;
+            }
+
+            return result;
+        }
+
+        private static List<Line3D> getThingLinks(ThingsCheckResult result, bool correctHeight) {
+            List<Line3D> lines = new List<Line3D>();
+            Dictionary<int, Thing> pathNodes = new Dictionary<int, Thing>();
+            Dictionary<int, Thing> interpolationPoints = new Dictionary<int, Thing>();
+            List<Thing> thingsWithGoal = new List<Thing>();
+            List<Thing> cameras = new List<Thing>();
+
+            bool getPathNodes = result.ProcessPathNodes || result.ProcessThingsWithGoal;
+            bool getInterpolationPoints = result.ProcessInterpolationPoints || result.ProcessCameras;
+
+            //collect relevant things
+            foreach (Thing t in General.Map.Map.Things) {
+                if (getPathNodes && t.Type == 9024) {
+                    pathNodes[t.Tag] = t;
+                }
+                if (getInterpolationPoints && t.Type == 9070) {
+                    interpolationPoints[t.Tag] = t;
+                }
+                if (result.ProcessThingsWithGoal && t.Action == 229 && t.Args[1] != 0) {
+                    thingsWithGoal.Add(t);
+                }
+                if (result.ProcessCameras && t.Type == 9072 && (t.Args[0] != 0 || t.Args[1] != 0)) {
+                    cameras.Add(t);
+                }
+            }
+
+            Vector3D start = new Vector3D();
+            Vector3D end = new Vector3D();
+
+            //process path nodes
+            if (result.ProcessPathNodes) {
+                foreach (KeyValuePair<int, Thing> group in pathNodes) {
+                    if (group.Value.Args[0] == 0) continue; //no goal
+                    if (pathNodes.ContainsKey(group.Value.Args[0])) {
+                        start = group.Value.Position;
+                        if (correctHeight) start.z += getCorrectHeight(group.Value);
+
+                        end = pathNodes[group.Value.Args[0]].Position;
+                        if (correctHeight) end.z += getCorrectHeight(pathNodes[group.Value.Args[0]]);
+
+                        lines.Add(new Line3D(start, end));
+                    }
+                }
+            }
+
+            //process things with Thing_SetGoal
+            if (result.ProcessThingsWithGoal) {
+                foreach (Thing t in thingsWithGoal) {
+                    if (pathNodes.ContainsKey(t.Args[1])) {
+                        if (t.Args[0] == 0 || t.Args[0] == t.Tag) {
+                            start = t.Position;
+                            if (correctHeight) start.z += getCorrectHeight(t);
+
+                            end = pathNodes[t.Args[1]].Position;
+                            if (correctHeight) end.z += getCorrectHeight(pathNodes[t.Args[1]]);
+
+                            lines.Add(new Line3D(start, end));
+                        }
+                    }
+                }
+            }
+
+            //process interpolation points
+            if (result.ProcessInterpolationPoints) {
+                foreach (KeyValuePair<int, Thing> group in interpolationPoints) {
+                    int targetTag = group.Value.Args[3] + group.Value.Args[4] * 256;
+                    if (targetTag == 0) continue; //no goal
+
+                    if (interpolationPoints.ContainsKey(targetTag)) {
+                        start = group.Value.Position;
+                        if (correctHeight) start.z += getCorrectHeight(group.Value);
+
+                        end = interpolationPoints[targetTag].Position;
+                        if (correctHeight) end.z += getCorrectHeight(interpolationPoints[targetTag]);
+
+                        lines.Add(new Line3D(start, end));
+                    }
+                }
+            }
+
+            //process cameras
+            if (result.ProcessCameras) {
+                foreach (Thing t in cameras) {
+                    int targetTag = t.Args[0] + t.Args[1] * 256;
+                    if (targetTag == 0) continue; //no goal
+
+                    if (interpolationPoints.ContainsKey(targetTag)) {
+                            start = t.Position;
+                            if (correctHeight) start.z += getCorrectHeight(t);
+
+                            end = interpolationPoints[targetTag].Position;
+                            if (correctHeight) end.z += getCorrectHeight(interpolationPoints[targetTag]);
+
+                        lines.Add(new Line3D(start, end));
+                    }
+                }
+            }
+
+            return lines;
+        }
+
+        private static float getCorrectHeight(Thing thing) {
+            ThingTypeInfo tti = General.Map.Data.GetThingInfo(thing.Type);
+            float height = tti.Height / 2f;
+            if (thing.Sector != null) height += thing.Sector.FloorHeight;
+            return height;
+        }
+    }
+}
diff --git a/Source/Core/GZBuilder/GZGeneral.cs b/Source/Core/GZBuilder/GZGeneral.cs
index 3287937b4..8d2432f1f 100644
--- a/Source/Core/GZBuilder/GZGeneral.cs
+++ b/Source/Core/GZBuilder/GZGeneral.cs
@@ -33,7 +33,7 @@ namespace CodeImp.DoomBuilder.GZBuilder
 
         //version
         public const float Version = 1.12f;
-        public const char Revision = 'h';
+        public const char Revision = 'i';
 
         //debug console
 #if DEBUG
@@ -149,6 +149,12 @@ namespace CodeImp.DoomBuilder.GZBuilder
             General.MainWindow.UpdateGZDoomPannel();
         }
 
+        [BeginAction("gztoggleeventlines")]
+        private static void toggleEventLines() {
+            General.Settings.GZShowEventLines = !General.Settings.GZShowEventLines;
+            General.MainWindow.DisplayStatus(StatusType.Action, "Event lines are " + (General.Settings.GZShowEventLines ? "ENABLED" : "DISABLED"));
+        }
+
         //main menu actions
         [BeginAction("gzreloadmodeldef")]
         private static void reloadModeldef() {
diff --git a/Source/Core/GZBuilder/Geometry/Line3D.cs b/Source/Core/GZBuilder/Geometry/Line3D.cs
new file mode 100644
index 000000000..b7f595b61
--- /dev/null
+++ b/Source/Core/GZBuilder/Geometry/Line3D.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using CodeImp.DoomBuilder.Geometry;
+
+namespace CodeImp.DoomBuilder.GZBuilder.Geometry {
+    public class Line3D {
+        
+        // Coordinates
+        public Vector3D v1;
+        public Vector3D v2;
+
+        // Constructor
+		public Line3D(Vector3D v1, Vector3D v2)	{
+			this.v1 = v1;
+			this.v2 = v2;
+		}
+
+        public Vector3D GetDelta() { return v2 - v1; }
+
+        // This calculates the angle
+        public float GetAngle() {
+            // Calculate and return the angle
+            Vector2D d = GetDelta();
+            return -(float)Math.Atan2(-d.y, d.x) + (float)Math.PI * 0.5f;
+        }
+    }
+}
diff --git a/Source/Core/Rendering/ColorCollection.cs b/Source/Core/Rendering/ColorCollection.cs
index 4f6ad664c..0c421a080 100644
--- a/Source/Core/Rendering/ColorCollection.cs
+++ b/Source/Core/Rendering/ColorCollection.cs
@@ -44,7 +44,7 @@ namespace CodeImp.DoomBuilder.Rendering
 		private const float DARK_ADDITION = -0.2f;
 
 		// Palette size
-		private const int NUM_COLORS = 41;
+		private const int NUM_COLORS = 42;
 		public const int NUM_THING_COLORS = 20;
 		public const int THING_COLORS_OFFSET = 20;
 
@@ -91,6 +91,7 @@ namespace CodeImp.DoomBuilder.Rendering
         public const int THINGCOLOR19 = 39;
         //mxd
         public const int MODELWIRECOLOR = 40;
+        public const int INFOLINECOLOR = 41;
 		
 		#endregion
 
@@ -125,6 +126,7 @@ namespace CodeImp.DoomBuilder.Rendering
 
         //mxd
         public PixelColor ModelWireframe { get { return colors[MODELWIRECOLOR]; } internal set { colors[MODELWIRECOLOR] = value; } }
+        public PixelColor InfoLine { get { return colors[INFOLINECOLOR]; } internal set { colors[INFOLINECOLOR] = value; } }
 		
 		public PixelColor Crosshair3D { get { return colors[CROSSHAIR3D]; } internal set { colors[CROSSHAIR3D] = value; } }
 		public PixelColor Highlight3D { get { return colors[HIGHLIGHT3D]; } internal set { colors[HIGHLIGHT3D] = value; } }
@@ -154,7 +156,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			for(int i = 0; i < NUM_COLORS; i++)
 			{
 				// Read color
-				colors[i] = PixelColor.FromInt(cfg.ReadSetting("colors.color" + i.ToString(CultureInfo.InvariantCulture), 0));
+                colors[i] = PixelColor.FromInt(cfg.ReadSetting("colors.color" + i.ToString(CultureInfo.InvariantCulture), -16711872)); //mxd. changed default color from transparent to light-green
 			}
 
 			// Set new colors
diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs
index 608d7fa49..367729d04 100644
--- a/Source/Core/Rendering/Renderer3D.cs
+++ b/Source/Core/Rendering/Renderer3D.cs
@@ -36,6 +36,7 @@ using CodeImp.DoomBuilder.Map;
 //mxd
 using CodeImp.DoomBuilder.GZBuilder.MD3;
 using CodeImp.DoomBuilder.GZBuilder.Data;
+using CodeImp.DoomBuilder.GZBuilder.Geometry;
 
 #endregion
 
@@ -555,6 +556,14 @@ namespace CodeImp.DoomBuilder.Rendering
             // THINGS
 			if(renderthingcages) RenderThingCages();
 
+            //mxd. LINKS
+            if (General.Settings.GZShowEventLines) {
+                //mxd. gather links
+                List<Line3D> lines = GZBuilder.Data.LinksCollector.GetThingLinks(thingsbydistance);
+                if (lines.Count > 0)
+                    renderLinks(lines);
+            }
+
 			// ADDITIVE PASS
 			world = Matrix.Identity;
 			ApplyMatrices3D();
@@ -655,6 +664,53 @@ namespace CodeImp.DoomBuilder.Rendering
                 graphics.Device.DrawPrimitives(PrimitiveType.LineList, 0, 5);
             }
 
+            // Done
+            graphics.Shaders.World3D.EndPass();
+            graphics.Shaders.World3D.SetModulateColor(-1);
+            graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
+        }
+
+        //mxd
+        private void renderLinks(List<Line3D> lines) {
+            //create vertices
+            WorldVertex[] verts = new WorldVertex[lines.Count * 6];
+            for (int i = 0; i < lines.Count; i++) {
+                WorldVertex endPoint = new WorldVertex(lines[i].v2);
+                float angle = lines[i].GetAngle();
+                verts[i * 6] = new WorldVertex(lines[i].v1);
+                verts[i * 6 + 1] = endPoint;
+                verts[i * 6 + 2] = endPoint;
+                verts[i * 6 + 3] = new WorldVertex(new Vector3D(lines[i].v2.x - 20f * (float)Math.Sin(angle - 0.52f), lines[i].v2.y + 20f * (float)Math.Cos(angle - 0.52f), lines[i].v2.z));
+                verts[i * 6 + 4] = endPoint;
+                verts[i * 6 + 5] = new WorldVertex(new Vector3D(lines[i].v2.x - 20f * (float)Math.Sin(angle + 0.52f), lines[i].v2.y + 20f * (float)Math.Cos(angle + 0.52f), lines[i].v2.z));
+            }
+
+            VertexBuffer vb = new VertexBuffer(General.Map.Graphics.Device, WorldVertex.Stride * verts.Length, Usage.WriteOnly | Usage.Dynamic, VertexFormat.None, Pool.Default);
+            DataStream s = vb.Lock(0, WorldVertex.Stride * verts.Length, LockFlags.Discard);
+            s.WriteRange<WorldVertex>(verts);
+            vb.Unlock();
+            s.Dispose();
+            
+            //begin rendering
+            graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, true);
+            graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false);
+            graphics.Device.SetRenderState(RenderState.ZWriteEnable, false);
+            graphics.Device.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha);
+            graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.SourceAlpha);
+
+            graphics.Shaders.World3D.BeginPass(16);
+
+            world = Matrix.Identity;
+            ApplyMatrices3D();
+
+            // Setup color
+            graphics.Shaders.World3D.VertexColor = General.Colors.InfoLine.ToColorValue();
+
+            //render
+            graphics.Shaders.World3D.ApplySettings();
+            graphics.Device.SetStreamSource(0, vb, 0, WorldVertex.Stride);
+            graphics.Device.DrawPrimitives(PrimitiveType.LineList, 0, lines.Count * 3);
+
             // Done
             graphics.Shaders.World3D.EndPass();
             graphics.Shaders.World3D.SetModulateColor(-1);
diff --git a/Source/Core/Resources/Actions.cfg b/Source/Core/Resources/Actions.cfg
index 454f5eeb5..46ab571f6 100644
--- a/Source/Core/Resources/Actions.cfg
+++ b/Source/Core/Resources/Actions.cfg
@@ -900,6 +900,17 @@ gztogglefx
 	allowscroll = false;
 }
 
+gztoggleeventlines
+{
+        title = "Toggle Event lines";
+	category = "view";
+	description = "When enabled, shows order, in which patrol points and interpolation points are connected.";
+	allowkeys = true;
+	allowmouse = false;
+	allowscroll = false;
+        default = 73;
+}
+
 //////////////////////////////
 //GZDOOMBUILDER MENU ACTIONS//
 //////////////////////////////
@@ -989,7 +1000,7 @@ movethingback
 
 placethingatcursor
 {
-  title = "Move Thing To Cursor Location";
+        title = "Move Thing To Cursor Location";
 	category = "visual";
 	description = "Moves selected Things to cursor location preserving relative offsets in Visual Modes.";
 	allowkeys = true;
diff --git a/Source/Core/Windows/PreferencesForm.Designer.cs b/Source/Core/Windows/PreferencesForm.Designer.cs
index 29b58d056..19ea86d96 100644
--- a/Source/Core/Windows/PreferencesForm.Designer.cs
+++ b/Source/Core/Windows/PreferencesForm.Designer.cs
@@ -155,6 +155,7 @@ namespace CodeImp.DoomBuilder.Windows
             this.label16 = new System.Windows.Forms.Label();
             this.pasteoptions = new CodeImp.DoomBuilder.Controls.PasteOptionsControl();
             this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
+            this.colorInfo = new CodeImp.DoomBuilder.Controls.ColorControl();
             label7 = new System.Windows.Forms.Label();
             label6 = new System.Windows.Forms.Label();
             label5 = new System.Windows.Forms.Label();
@@ -472,6 +473,7 @@ namespace CodeImp.DoomBuilder.Windows
             // 
             this.colorsgroup1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
                         | System.Windows.Forms.AnchorStyles.Left)));
+            this.colorsgroup1.Controls.Add(this.colorInfo);
             this.colorsgroup1.Controls.Add(this.cbStretchModels);
             this.colorsgroup1.Controls.Add(this.colorMD3);
             this.colorsgroup1.Controls.Add(this.label2);
@@ -500,7 +502,7 @@ namespace CodeImp.DoomBuilder.Windows
             // cbStretchModels
             // 
             this.cbStretchModels.AutoSize = true;
-            this.cbStretchModels.Location = new System.Drawing.Point(21, 463);
+            this.cbStretchModels.Location = new System.Drawing.Point(21, 466);
             this.cbStretchModels.Name = "cbStretchModels";
             this.cbStretchModels.Size = new System.Drawing.Size(169, 18);
             this.cbStretchModels.TabIndex = 18;
@@ -523,7 +525,7 @@ namespace CodeImp.DoomBuilder.Windows
             // label2
             // 
             this.label2.AutoSize = true;
-            this.label2.Location = new System.Drawing.Point(18, 333);
+            this.label2.Location = new System.Drawing.Point(18, 358);
             this.label2.Name = "label2";
             this.label2.Size = new System.Drawing.Size(147, 14);
             this.label2.TabIndex = 14;
@@ -545,7 +547,7 @@ namespace CodeImp.DoomBuilder.Windows
             // squarethings
             // 
             this.squarethings.AutoSize = true;
-            this.squarethings.Location = new System.Drawing.Point(21, 438);
+            this.squarethings.Location = new System.Drawing.Point(21, 445);
             this.squarethings.Name = "squarethings";
             this.squarethings.Size = new System.Drawing.Size(93, 18);
             this.squarethings.TabIndex = 8;
@@ -567,7 +569,7 @@ namespace CodeImp.DoomBuilder.Windows
             // doublesidedalphalabel
             // 
             this.doublesidedalphalabel.AutoSize = true;
-            this.doublesidedalphalabel.Location = new System.Drawing.Point(148, 365);
+            this.doublesidedalphalabel.Location = new System.Drawing.Point(148, 390);
             this.doublesidedalphalabel.Name = "doublesidedalphalabel";
             this.doublesidedalphalabel.Size = new System.Drawing.Size(23, 14);
             this.doublesidedalphalabel.TabIndex = 16;
@@ -612,7 +614,7 @@ namespace CodeImp.DoomBuilder.Windows
             // qualitydisplay
             // 
             this.qualitydisplay.AutoSize = true;
-            this.qualitydisplay.Location = new System.Drawing.Point(21, 413);
+            this.qualitydisplay.Location = new System.Drawing.Point(21, 424);
             this.qualitydisplay.Name = "qualitydisplay";
             this.qualitydisplay.Size = new System.Drawing.Size(130, 18);
             this.qualitydisplay.TabIndex = 7;
@@ -658,7 +660,7 @@ namespace CodeImp.DoomBuilder.Windows
             // doublesidedalpha
             // 
             this.doublesidedalpha.LargeChange = 3;
-            this.doublesidedalpha.Location = new System.Drawing.Point(11, 352);
+            this.doublesidedalpha.Location = new System.Drawing.Point(11, 377);
             this.doublesidedalpha.Name = "doublesidedalpha";
             this.doublesidedalpha.Size = new System.Drawing.Size(130, 45);
             this.doublesidedalpha.TabIndex = 2;
@@ -1647,6 +1649,18 @@ namespace CodeImp.DoomBuilder.Windows
             this.pasteoptions.Size = new System.Drawing.Size(666, 427);
             this.pasteoptions.TabIndex = 0;
             // 
+            // colorInfo
+            // 
+            this.colorInfo.BackColor = System.Drawing.Color.Transparent;
+            this.colorInfo.Font = new System.Drawing.Font("Arial", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.colorInfo.Label = "Event lines:";
+            this.colorInfo.Location = new System.Drawing.Point(15, 324);
+            this.colorInfo.MaximumSize = new System.Drawing.Size(10000, 23);
+            this.colorInfo.MinimumSize = new System.Drawing.Size(100, 23);
+            this.colorInfo.Name = "colorInfo";
+            this.colorInfo.Size = new System.Drawing.Size(168, 23);
+            this.colorInfo.TabIndex = 19;
+            // 
             // PreferencesForm
             // 
             this.AcceptButton = this.apply;
@@ -1826,5 +1840,6 @@ namespace CodeImp.DoomBuilder.Windows
         private Dotnetrix.Controls.TrackBar vertexScale;
         private System.Windows.Forms.Label vertexScaleLabel;
         private System.Windows.Forms.Label label22;
+        private CodeImp.DoomBuilder.Controls.ColorControl colorInfo;
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/PreferencesForm.cs b/Source/Core/Windows/PreferencesForm.cs
index cf6966f2c..b7230cd81 100644
--- a/Source/Core/Windows/PreferencesForm.cs
+++ b/Source/Core/Windows/PreferencesForm.cs
@@ -160,6 +160,7 @@ namespace CodeImp.DoomBuilder.Windows
 
             //mxd
             colorMD3.Color = General.Colors.ModelWireframe;
+            colorInfo.Color = General.Colors.InfoLine;
 
 			colorscriptbackground.Color = General.Colors.ScriptBackground;
 			colorlinenumbers.Color = General.Colors.LineNumbers;
@@ -264,6 +265,7 @@ namespace CodeImp.DoomBuilder.Windows
 			General.Colors.Constants = colorconstants.Color;
             //mxd
             General.Colors.ModelWireframe = colorMD3.Color;
+            General.Colors.InfoLine = colorInfo.Color;
 
 			General.Colors.CreateAssistColors();
 			General.Settings.BlackBrowsers = blackbrowsers.Checked;
diff --git a/Source/Core/Windows/ThingEditForm.cs b/Source/Core/Windows/ThingEditForm.cs
index 2de63293c..0af3c239b 100644
--- a/Source/Core/Windows/ThingEditForm.cs
+++ b/Source/Core/Windows/ThingEditForm.cs
@@ -127,10 +127,11 @@ namespace CodeImp.DoomBuilder.Windows
 
             //mxd
             initialPosition = ft.Position;
-			initialFloorHeight = ft.Sector.FloorHeight;
+            if (ft.Sector != null)
+			    initialFloorHeight = ft.Sector.FloorHeight;
             posX.Text = ((int)ft.Position.x).ToString();
             posY.Text = ((int)ft.Position.y).ToString();
-			posZ.Text = ABSOLUTE_HEIGHT ? ((int)ft.Position.z + ft.Sector.FloorHeight).ToString() : ((int)ft.Position.z).ToString();
+            posZ.Text = ABSOLUTE_HEIGHT ? ((int)ft.Position.z + initialFloorHeight).ToString() : ((int)ft.Position.z).ToString();
             posX.ButtonStep = General.Map.Grid.GridSize;
             posY.ButtonStep = General.Map.Grid.GridSize;
 			posZ.ButtonStep = General.Map.Grid.GridSize;
@@ -179,7 +180,7 @@ namespace CodeImp.DoomBuilder.Windows
 				if(t.AngleDoom.ToString() != angle.Text) angle.Text = "";
 				
 				//mxd
-				if(ABSOLUTE_HEIGHT) {
+                if (ABSOLUTE_HEIGHT && t.Sector != null) {
 					if(((int)t.Position.z + t.Sector.FloorHeight).ToString() != posZ.Text) posZ.Text = "";
 				} else {
 					if(((int)t.Position.z).ToString() != posZ.Text) posZ.Text = "";
@@ -417,7 +418,9 @@ namespace CodeImp.DoomBuilder.Windows
                 //mxd
 				//t.Move(t.Position.x, t.Position.y, (float)height.GetResult((int)t.Position.z));
 				float z = (float)posZ.GetResult((int)t.Position.z);
-				t.Move(t.Position.x + delta.x, t.Position.y + delta.y, ABSOLUTE_HEIGHT ? z - t.Sector.FloorHeight : z);
+                if (ABSOLUTE_HEIGHT && t.Sector != null)
+                    z -= t.Sector.FloorHeight;
+				t.Move(t.Position.x + delta.x, t.Position.y + delta.y, z);
 				
 				// Apply all flags
 				foreach(CheckBox c in flags.Checkboxes)
diff --git a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
index 4b2a0d333..bda660f80 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
@@ -33,6 +33,7 @@ using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Actions;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Types;
+using CodeImp.DoomBuilder.GZBuilder.Geometry;
 
 #endregion
 
@@ -161,6 +162,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					BuilderPlug.Me.RenderReverseAssociations(renderer, highlightasso);
 					renderer.RenderThing(highlighted, General.Colors.Highlight, 1.0f);
 				}
+
+                //mxd
+                if(General.Settings.GZShowEventLines)
+                    renderArrows(GZBuilder.Data.LinksCollector.GetThingLinks(General.Map.ThingsFilter.VisibleThings));
+ 
 				renderer.Finish();
 			}
 
@@ -177,6 +183,18 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			renderer.Present();
 		}
+
+        //mxd
+        private void renderArrows(List<Line3D> lines) {
+            foreach (Line3D l in lines) {
+                renderer.RenderLine(l.v1, l.v2, 0.6f, General.Colors.InfoLine, true);
+                float angle = l.GetAngle();
+                //arrowhead
+                float scaler = 20f / renderer.Scale;
+                renderer.RenderLine(l.v2, new Vector2D(l.v2.x - scaler * (float)Math.Sin(angle - 0.52f), l.v2.y + scaler * (float)Math.Cos(angle - 0.52f)), 0.6f, General.Colors.InfoLine, true);
+                renderer.RenderLine(l.v2, new Vector2D(l.v2.x - scaler * (float)Math.Sin(angle + 0.52f), l.v2.y + scaler * (float)Math.Cos(angle + 0.52f)), 0.6f, General.Colors.InfoLine, true);
+            }
+        }
 		
 		// This highlights a new item
 		protected void Highlight(Thing t)
-- 
GitLab