diff --git a/Source/Core/Controls/ImageBrowserCategoryItem.cs b/Source/Core/Controls/ImageBrowserCategoryItem.cs
index ab8bfa6dcda1a464e5a5ed5a8184c541d148345b..37e105aea2cfdcdbdd084b5f37b7d31b908bd10f 100644
--- a/Source/Core/Controls/ImageBrowserCategoryItem.cs
+++ b/Source/Core/Controls/ImageBrowserCategoryItem.cs
@@ -1,6 +1,7 @@
 #region ================== Namespaces
 
 using System;
+using System.Drawing;
 using CodeImp.DoomBuilder.Data;
 
 #endregion
@@ -12,6 +13,7 @@ namespace CodeImp.DoomBuilder.Controls
 		#region ================== Variables
 
 		private string groupname;
+		private int groupnamewidth;
 
 		#endregion
 
@@ -19,6 +21,7 @@ namespace CodeImp.DoomBuilder.Controls
 
 		public override bool IsPreviewLoaded { get { return true; } }
 		public override string TextureName { get { return groupname; } }
+		public override int TextureNameWidth { get { return groupnamewidth; } }
 
 		#endregion
 
@@ -43,6 +46,9 @@ namespace CodeImp.DoomBuilder.Controls
 				default:
 					throw new NotImplementedException("Unsupported ItemType");
 			}
+
+			// Calculate name width
+			this.groupnamewidth = (int)Math.Ceiling(General.Interface.MeasureString(this.groupname, SystemFonts.MessageBoxFont, 10000, StringFormat.GenericTypographic).Width);
 		}
 
 		#endregion
diff --git a/Source/Core/Controls/ImageBrowserControl.cs b/Source/Core/Controls/ImageBrowserControl.cs
index 870112bbcf21418de6daf7614686dfe6152978c7..4358f6fd4444462512a64ff1a6e1aa0aac2eccbe 100644
--- a/Source/Core/Controls/ImageBrowserControl.cs
+++ b/Source/Core/Controls/ImageBrowserControl.cs
@@ -351,7 +351,7 @@ namespace CodeImp.DoomBuilder.Controls
 				objectname.CharacterCasing = (uselongtexturenames ? CharacterCasing.Normal : CharacterCasing.Upper);
 
 				foreach(var item in items) item.ShowFullName = uselongtexturenames;
-				list.Refresh();
+				list.UpdateRectangles();
 				list.Focus();
 			}
 		}
diff --git a/Source/Core/Controls/ImageBrowserItem.cs b/Source/Core/Controls/ImageBrowserItem.cs
index 7975eb44cdb3a4b8c369477b1c4677e5a0764a07..86a5c146a102f8f66057326bf85a73da36739723 100644
--- a/Source/Core/Controls/ImageBrowserItem.cs
+++ b/Source/Core/Controls/ImageBrowserItem.cs
@@ -29,6 +29,8 @@ namespace CodeImp.DoomBuilder.Controls
 		private bool showfullname;
 		protected ImageBrowserItemType itemtype;
 		private string tooltip;
+		private int namewidth;
+		private int shortnamewidth;
 
 		#endregion
 
@@ -39,6 +41,7 @@ namespace CodeImp.DoomBuilder.Controls
 		public virtual bool IsPreviewLoaded { get { return icon.IsPreviewLoaded; } }
 		public bool ShowFullName { set { showfullname = value; } }
 		public virtual string TextureName { get { return (showfullname ? icon.Name : icon.ShortName); } }
+		public virtual int TextureNameWidth { get { return (showfullname ? namewidth : shortnamewidth); } }
 		public string ToolTip { get { return tooltip; } }
 
 		#endregion
@@ -55,6 +58,10 @@ namespace CodeImp.DoomBuilder.Controls
 			this.showfullname = showfullname; //mxd
 			this.imageloaded = icon.IsPreviewLoaded; //mxd
 			this.tooltip = tooltip; //mxd
+
+			//mxd. Calculate names width
+			this.namewidth = (int)Math.Ceiling(General.Interface.MeasureString(icon.Name, SystemFonts.MessageBoxFont, 10000, StringFormat.GenericTypographic).Width);
+			this.shortnamewidth = (int)Math.Ceiling(General.Interface.MeasureString(icon.ShortName, SystemFonts.MessageBoxFont, 10000, StringFormat.GenericTypographic).Width);
 		}
 
 		#endregion
diff --git a/Source/Core/Controls/ImageSelectorPanel.cs b/Source/Core/Controls/ImageSelectorPanel.cs
index f954e8d186735629b141a0445e817dc484a2eb4c..ef9bc5e05bb8381ba996eba346ac0d95a2d40d68 100644
--- a/Source/Core/Controls/ImageSelectorPanel.cs
+++ b/Source/Core/Controls/ImageSelectorPanel.cs
@@ -464,7 +464,7 @@ namespace CodeImp.DoomBuilder.Controls
 			if(selection.Count > 0) ScrollToItem(selection[0]);
 		}
 
-		private void UpdateRectangles()
+		internal void UpdateRectangles()
 		{
 			int w = ClientRectangle.Width - scrollbar.Width;
 			const int pad = 2;
@@ -479,7 +479,7 @@ namespace CodeImp.DoomBuilder.Controls
 				Image preview = GetPreview(ti, imagesize);
 				
 				int rw = w - cx;
-				int wid = (imagesize > 0 ? imagesize : preview.Width) + pad + pad;
+				int wid = Math.Max((imagesize > 0 ? imagesize : preview.Width), ti.TextureNameWidth) + pad + pad;
 				int hei = (imagesize > 0 ? imagesize : preview.Height) + pad + pad + font;
 				
 				if(rw < wid)
diff --git a/Source/Core/Data/PreviewManager.cs b/Source/Core/Data/PreviewManager.cs
index 8e58ed00d51e5942cd4be6aa63923fa97ae3f4a6..a281b2d298acaa9757216e1f3818ab37507db1ab 100644
--- a/Source/Core/Data/PreviewManager.cs
+++ b/Source/Core/Data/PreviewManager.cs
@@ -108,6 +108,7 @@ namespace CodeImp.DoomBuilder.Data
 				// Load image if needed
 				if(!img.IsImageLoaded) img.LoadImage();
 				int imagewidth, imageheight;
+				Bitmap image = img.GetBitmap(); //mxd
 				if(!img.LoadFailed)
 				{
 					imagewidth = img.Width;
@@ -115,7 +116,7 @@ namespace CodeImp.DoomBuilder.Data
 				}
 				else
 				{
-					Size size = img.GetBitmap().Size; //mxd
+					Size size = image.Size; //mxd
 					imagewidth = size.Width;
 					imageheight = size.Height;
 				}
@@ -129,31 +130,40 @@ namespace CodeImp.DoomBuilder.Data
 				if(previewwidth < 1) previewwidth = 1;
 				if(previewheight < 1) previewheight = 1;
 
-				// Make new image
-				Bitmap preview = new Bitmap(previewwidth, previewheight, IMAGE_FORMAT);
-				Graphics g = Graphics.FromImage(preview);
-				g.PageUnit = GraphicsUnit.Pixel;
-				//g.CompositingQuality = CompositingQuality.HighQuality; //mxd
-				g.InterpolationMode = InterpolationMode.NearestNeighbor;
-				//g.SmoothingMode = SmoothingMode.HighQuality; //mxd
-				g.PixelOffsetMode = PixelOffsetMode.None;
-				//g.Clear(Color.Transparent); //mxd
-				
-				// Draw image onto atlas
-				Rectangle atlasrect = new Rectangle(0, 0, previewwidth, previewheight);
-				RectangleF imgrect = General.MakeZoomedRect(new Size(imagewidth, imageheight), atlasrect);
-				if(imgrect.Width < 1.0f)
+				//mxd. Expected and actual image sizes and format match?
+				Bitmap preview;
+				if(previewwidth == imagewidth && previewheight == imageheight && image.PixelFormat == IMAGE_FORMAT)
 				{
-					imgrect.X -= 0.5f - imgrect.Width * 0.5f;
-					imgrect.Width = 1.0f;
+					preview = new Bitmap(image);
 				}
-				if(imgrect.Height < 1.0f)
+				else
 				{
-					imgrect.Y -= 0.5f - imgrect.Height * 0.5f;
-					imgrect.Height = 1.0f;
+					// Make new image
+					preview = new Bitmap(previewwidth, previewheight, IMAGE_FORMAT);
+					Graphics g = Graphics.FromImage(preview);
+					g.PageUnit = GraphicsUnit.Pixel;
+					//g.CompositingQuality = CompositingQuality.HighQuality; //mxd
+					g.InterpolationMode = InterpolationMode.NearestNeighbor;
+					//g.SmoothingMode = SmoothingMode.HighQuality; //mxd
+					g.PixelOffsetMode = PixelOffsetMode.None;
+					//g.Clear(Color.Transparent); //mxd
+
+					// Draw image onto atlas
+					Rectangle atlasrect = new Rectangle(0, 0, previewwidth, previewheight);
+					RectangleF imgrect = General.MakeZoomedRect(new Size(imagewidth, imageheight), atlasrect);
+					if(imgrect.Width < 1.0f)
+					{
+						imgrect.X -= 0.5f - imgrect.Width * 0.5f;
+						imgrect.Width = 1.0f;
+					}
+					if(imgrect.Height < 1.0f)
+					{
+						imgrect.Y -= 0.5f - imgrect.Height * 0.5f;
+						imgrect.Height = 1.0f;
+					}
+					g.DrawImage(image, imgrect);
+					g.Dispose();
 				}
-				g.DrawImage(img.GetBitmap(), imgrect);
-				g.Dispose();
 				
 				// Unload image if no longer needed
 				if(!img.IsReferenced) img.UnloadImage();
diff --git a/Source/Core/GZBuilder/Data/LinksCollector.cs b/Source/Core/GZBuilder/Data/LinksCollector.cs
index e511398031c140567831fdc36cd975d8d7ab7e7e..2ac555e5ea4dc3e50dd0ad0af60d25a1a139d377 100644
--- a/Source/Core/GZBuilder/Data/LinksCollector.cs
+++ b/Source/Core/GZBuilder/Data/LinksCollector.cs
@@ -1,4 +1,6 @@
-using System;
+#region ================== Namespaces
+
+using System;
 using System.Collections.Generic;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Map;
@@ -6,10 +8,14 @@ using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Rendering;
 using CodeImp.DoomBuilder.VisualModes;
 
+#endregion
+
 namespace CodeImp.DoomBuilder.GZBuilder.Data 
 {
-	public static class LinksCollector 
+	public static class LinksCollector
 	{
+		#region ================== SpecialThings
+
 		private class SpecialThings 
 		{
 			public readonly Dictionary<int, List<Thing>> PatrolPoints; // PatrolPoint tag, list of PatrolPoints
@@ -34,6 +40,10 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 			}
 		}
 
+		#endregion
+
+		#region ================== PathNode
+
 		private class PathNode
 		{
 			private readonly Thing thing;
@@ -74,7 +84,17 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 			}
 		}
 
-		public static IEnumerable<Line3D> MakeCircleLines(Vector3D pos, PixelColor color, float radius, int numsides)
+		#endregion
+
+		#region ================== Constants
+
+		private const int CIRCLE_SIDES = 24;
+
+		#endregion
+
+		#region ================== Shape creation methods
+
+		private static IEnumerable<Line3D> MakeCircleLines(Vector3D pos, PixelColor color, float radius, int numsides)
 		{
 			List<Line3D> result = new List<Line3D>(numsides);
 			Vector3D start = new Vector3D(pos.x, pos.y + radius, pos.z);
@@ -90,7 +110,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 			return result;
 		}
 
-		public static IEnumerable<Line3D> MakeRectangleLines(Vector3D pos, PixelColor color, float size)
+		private static IEnumerable<Line3D> MakeRectangleLines(Vector3D pos, PixelColor color, float size)
 		{
 			float halfsize = size / 2;
 			Vector3D tl = new Vector3D(pos.x - halfsize, pos.y - halfsize, pos.z);
@@ -105,15 +125,21 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 				new Line3D(bl, br, color, false),
 				new Line3D(bl, tl, color, false),
 			};
-		} 
+		}
+
+		#endregion
 
-		public static List<Line3D> GetThingLinks(IEnumerable<Thing> things) { return GetThingLinks(things, null); }
-		public static List<Line3D> GetThingLinks(IEnumerable<Thing> things, VisualBlockMap blockmap) 
+		#region ================== GetHelperShapes
+
+		public static List<Line3D> GetHelperShapes(ICollection<Thing> things) { return GetHelperShapes(things, null); }
+		public static List<Line3D> GetHelperShapes(ICollection<Thing> things, VisualBlockMap blockmap)
 		{
-			return GetThingLinks(GetSpecialThings(things, blockmap), blockmap);
+			var lines = GetHelperShapes(GetSpecialThings(things, blockmap), blockmap);
+			lines.AddRange(GetThingArgumentShapes(things, blockmap, CIRCLE_SIDES));
+			return lines;
 		}
 
-		private static SpecialThings GetSpecialThings(IEnumerable<Thing> things, VisualBlockMap blockmap) 
+		private static SpecialThings GetSpecialThings(ICollection<Thing> things, VisualBlockMap blockmap) 
 		{
 			SpecialThings result = new SpecialThings();
 
@@ -189,7 +215,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 			return result;
 		}
 
-		private static List<Line3D> GetThingLinks(SpecialThings result, VisualBlockMap blockmap) 
+		private static List<Line3D> GetHelperShapes(SpecialThings result, VisualBlockMap blockmap) 
 		{
 			var lines = new List<Line3D>();
 			var actormovertargets = new Dictionary<int, List<Thing>>();
@@ -403,9 +429,19 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 				}
 			}
 
-			// Process arg helpers
-			const int numsides = 24;
-			foreach(Thing t in General.Map.ThingsFilter.VisibleThings)
+			return lines;
+		}
+
+		#endregion
+
+		#region ================== GetThingArgumentShapes
+
+		// Create argument value/min/max shapes
+		private static List<Line3D> GetThingArgumentShapes(ICollection<Thing> things, VisualBlockMap blockmap, int numsides)
+		{
+			var lines = new List<Line3D>();
+			
+			foreach(Thing t in things)
 			{
 				if(t.Action != 0) continue;
 				ThingTypeInfo tti = General.Map.Data.GetThingInfoEx(t.Type);
@@ -416,27 +452,178 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 
 				for(int i = 0; i < t.Args.Length; i++)
 				{
-					if(t.Args[i] != 0 && tti.Args[i].RenderStyle != ArgumentInfo.ArgumentRenderStyle.NONE)
+					if(t.Args[i] == 0) continue; // Avoid visual noise
+					var a = tti.Args[i]; //TODO: can this be null?
+					
+					switch(a.RenderStyle)
 					{
-						switch(tti.Args[i].RenderStyle)
-						{
-							case ArgumentInfo.ArgumentRenderStyle.CIRCLE:
-								lines.AddRange(MakeCircleLines(pos, tti.Args[i].RenderColor, t.Args[i], numsides));
-								break;
+						case ArgumentInfo.ArgumentRenderStyle.CIRCLE:
+							lines.AddRange(MakeCircleLines(pos, a.RenderColor, t.Args[i], numsides));
+							if(a.MinRange > 0) lines.AddRange(MakeCircleLines(pos, a.MinRangeColor, a.MinRange, numsides));
+							if(a.MaxRange > 0) lines.AddRange(MakeCircleLines(pos, a.MaxRangeColor, a.MaxRange, numsides));
+							break;
+
+						case ArgumentInfo.ArgumentRenderStyle.RECTANGLE:
+							lines.AddRange(MakeRectangleLines(pos, a.RenderColor, t.Args[i]));
+							if(a.MinRange > 0) lines.AddRange(MakeRectangleLines(pos, a.MinRangeColor, a.MinRange));
+							if(a.MaxRange > 0) lines.AddRange(MakeRectangleLines(pos, a.MaxRangeColor, a.MaxRange));
+							break;
+
+						case ArgumentInfo.ArgumentRenderStyle.NONE:
+							break;
+
+						default: throw new NotImplementedException("Unknown ArgumentRenderStyle");
+					}
+				}
+			}
 
-							case ArgumentInfo.ArgumentRenderStyle.RECTANGLE:
-								lines.AddRange(MakeRectangleLines(pos, tti.Args[i].RenderColor, t.Args[i]));
-								break;
+			return lines;
+		}
 
-							default: throw new NotImplementedException("Unknown ArgumentRenderStyle");
-						}
+		#endregion
+
+		#region ================== GetDynamicLightShapes
+
+		public static List<Line3D> GetDynamicLightShapes(IEnumerable<Thing> things, bool highlight)
+		{
+			List<Line3D> circles = new List<Line3D>();
+			if(General.Map.DOOM) return circles;
+
+			const int linealpha = 128;
+			foreach(Thing t in things)
+			{
+				int lightid = Array.IndexOf(GZGeneral.GZ_LIGHTS, t.Type);
+				if(lightid == -1) continue;
+
+				// TODO: this basically duplicates VisualThing.UpdateLight()...
+				// Determine light radiii
+				int primaryradius;
+				int secondaryradius = 0;
+
+				if(lightid < GZGeneral.GZ_LIGHT_TYPES[2]) //if it's gzdoom light
+				{
+					int n;
+					if(lightid < GZGeneral.GZ_LIGHT_TYPES[0]) n = 0;
+					else if(lightid < GZGeneral.GZ_LIGHT_TYPES[1]) n = 10;
+					else n = 20;
+					DynamicLightType lightType = (DynamicLightType)(t.Type - 9800 - n);
+
+					if(lightType == DynamicLightType.SECTOR)
+					{
+						if(t.Sector == null) t.DetermineSector();
+						int scaler = (t.Sector != null ? t.Sector.Brightness / 4 : 2);
+						primaryradius = t.Args[3] * scaler;
+					}
+					else
+					{
+						primaryradius = t.Args[3] * 2; //works... that.. way in GZDoom
+						if(lightType > 0) secondaryradius = t.Args[4] * 2;
 					}
 				}
+				else //it's one of vavoom lights
+				{
+					primaryradius = t.Args[0] * 8;
+				}
+
+				// Check radii...
+				if(primaryradius < 1 && secondaryradius < 1) continue;
+
+				// Determine light color
+				PixelColor color;
+				if(highlight)
+				{
+					color = General.Colors.Highlight.WithAlpha(linealpha);
+				}
+				else
+				{
+					switch(t.Type)
+					{
+						case 1502: // Vavoom light
+							color = new PixelColor(linealpha, 255, 255, 255);
+							break;
+
+						case 1503: // Vavoom colored light
+							color = new PixelColor(linealpha, (byte)t.Args[1], (byte)t.Args[2], (byte)t.Args[3]);
+							break;
+
+						default:
+							color = new PixelColor(linealpha, (byte)t.Args[0], (byte)t.Args[1], (byte)t.Args[2]);
+							break;
+					}
+				}
+
+				// Add lines if visible
+				if(primaryradius > 0) circles.AddRange(MakeCircleLines(t.Position, color, primaryradius, CIRCLE_SIDES));
+				if(secondaryradius > 0) circles.AddRange(MakeCircleLines(t.Position, color, secondaryradius, CIRCLE_SIDES));
 			}
 
-			return lines;
+			// Done
+			return circles;
+		}
+
+		#endregion
+
+		#region ================== GetAmbientSoundShapes
+
+		public static List<Line3D> GetAmbientSoundShapes(IEnumerable<Thing> things, bool highlight)
+		{
+			List<Line3D> circles = new List<Line3D>();
+			const int linealpha = 128;
+
+			foreach(Thing t in things)
+			{
+				ThingTypeInfo info = General.Map.Data.GetThingInfoEx(t.Type);
+				if(info == null) continue;
+
+				float minradius, maxradius;
+				if(info.AmbientSound != null)
+				{
+					minradius = info.AmbientSound.MinimumRadius;
+					maxradius = info.AmbientSound.MaximumRadius;
+				}
+				else if(!General.Map.DOOM && (info.ClassName == "AmbientSound" || info.ClassName == "AmbientSoundNoGravity"))
+				{
+					//arg0: ambient slot
+					//arg1: (optional) sound volume, in percent. 1 is nearly silent, 100 and above are full volume. If left to zero, full volume is also used.
+					//arg2: (optional) minimum distance, in map units, at which volume attenuation begins. Note that arg3 must also be set. If both are left to zero, normal rolloff is used instead.
+					//arg3: (optional) maximum distance, in map units, at which the sound can be heard. If left to zero or lower than arg2, normal rolloff is used instead.
+					//arg4: (optional) scalar by which to multiply the values of arg2 and arg3. If left to zero, no multiplication takes place.
+
+					if(t.Args[0] == 0 || !General.Map.Data.AmbientSounds.ContainsKey(t.Args[0]))
+						continue;
+
+					// Use custom radii?
+					if(t.Args[2] > 0 && t.Args[3] > 0 && t.Args[3] > t.Args[2])
+					{
+						minradius = t.Args[2] * (t.Args[4] != 0 ? t.Args[4] : 1.0f);
+						maxradius = t.Args[3] * (t.Args[4] != 0 ? t.Args[4] : 1.0f);
+					}
+					else
+					{
+						minradius = General.Map.Data.AmbientSounds[t.Args[0]].MinimumRadius;
+						maxradius = General.Map.Data.AmbientSounds[t.Args[0]].MaximumRadius;
+					}
+				}
+				else
+				{
+					continue;
+				}
+
+				// Determine color
+				PixelColor color = (highlight ? General.Colors.Highlight.WithAlpha(linealpha) : t.Color.WithAlpha(linealpha));
+
+				// Add lines if visible
+				if(minradius > 0) circles.AddRange(MakeCircleLines(t.Position, color, minradius, CIRCLE_SIDES));
+				if(maxradius > 0) circles.AddRange(MakeCircleLines(t.Position, color, maxradius, CIRCLE_SIDES));
+			}
+
+			return circles;
 		}
 
+		#endregion
+
+		#region ================== Utility
+
 		// Taken from Xabis' "curved interpolation points paths" patch.
 		private static float SplineLerp(float u, float p1, float p2, float p3, float p4)
 		{
@@ -459,5 +646,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 			if(thing.Sector != null) height += thing.Sector.FloorHeight;
 			return height;
 		}
+
+		#endregion
 	}
 }
diff --git a/Source/Core/Windows/IMainForm.cs b/Source/Core/Windows/IMainForm.cs
index 4c7d59d57059da40b001af6ac37e4946e1cc605e..f8b6eede187c29a937272ad25bdc530463b6bb5a 100644
--- a/Source/Core/Windows/IMainForm.cs
+++ b/Source/Core/Windows/IMainForm.cs
@@ -78,6 +78,7 @@ namespace CodeImp.DoomBuilder.Windows
 		void SetCursor(Cursor cursor);
 		void MessageBeep(MessageBeepType type);
 		SizeF MeasureString(string text, Font font); //mxd
+		SizeF MeasureString(string text, Font font, int width, StringFormat format); //mxd
 
 		/// <summary>
 		/// This moves the focus to the editing display.
diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs
index d6c68b9dd9b68205dd6bd2b04210aab7dcfd88bb..5ceb177da355295d5c6e39daadab01ca152a876f 100644
--- a/Source/Core/Windows/MainForm.cs
+++ b/Source/Core/Windows/MainForm.cs
@@ -4393,6 +4393,11 @@ namespace CodeImp.DoomBuilder.Windows
 			return graphics.MeasureString(text, font);
 		}
 
+		public SizeF MeasureString(string text, Font font, int width, StringFormat format)
+		{
+			return graphics.MeasureString(text, font, width, format);
+		}
+
 		#endregion
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
index 1ea0f7cad8c637aa0fd018a65ecc85c62972f408..933a44bd8bb2fe32c37224ba9a44edbfe8a769ef 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
@@ -259,7 +259,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				{
 					eventlines.AddRange(dynamiclightshapes);
 					if(highlighted != null && !highlighted.IsDisposed)
-						eventlines.AddRange(GetDynamicLightShapes(new List<Thing> { highlighted } ));
+						eventlines.AddRange(LinksCollector.GetDynamicLightShapes(new List<Thing> { highlighted }, true));
 				}
 
 				//mxd. Ambient sound radii
@@ -267,7 +267,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				{
 					eventlines.AddRange(ambientsoundshapes);
 					if(highlighted != null && !highlighted.IsDisposed)
-						eventlines.AddRange(GetAmbientSoundShapes(new List<Thing> { highlighted }));
+						eventlines.AddRange(LinksCollector.GetAmbientSoundShapes(new List<Thing> { highlighted }, true));
 				}
 
 				//mxd
@@ -1073,190 +1073,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		//mxd
 		private void UpdateHelperObjects()
 		{
-			// Update event lines
-			persistenteventlines = LinksCollector.GetThingLinks(General.Map.ThingsFilter.VisibleThings);
+			// Update event lines and argument shapes
+			persistenteventlines = LinksCollector.GetHelperShapes(General.Map.ThingsFilter.VisibleThings);
 
 			// Update light radii
-			dynamiclightshapes = GetDynamicLightShapes(General.Map.Map.Things);
+			dynamiclightshapes = LinksCollector.GetDynamicLightShapes(General.Map.ThingsFilter.VisibleThings, false);
 
 			// Update ambient sound radii
-			ambientsoundshapes = GetAmbientSoundShapes(General.Map.Map.Things);
-
-			// Update argument range shapes
-			persistenteventlines.AddRange(GetArgumentRangeShapes(General.Map.Map.Things));
-		}
-
-		//mxd
-		private List<Line3D> GetDynamicLightShapes(IEnumerable<Thing> things)
-		{
-			List<Line3D> circles = new List<Line3D>();
-			if(General.Map.DOOM) return circles;
-
-			const int linealpha = 128;
-			const int numsides = 24;
-			foreach(Thing t in things)
-			{
-				int lightid = Array.IndexOf(GZBuilder.GZGeneral.GZ_LIGHTS, t.Type);
-				if(lightid == -1) continue;
-
-				// TODO: this basically duplicates VisualThing.UpdateLight()...
-				// Determine light radiii
-				int primaryradius;
-				int secondaryradius = 0;
-
-				if(lightid < GZBuilder.GZGeneral.GZ_LIGHT_TYPES[2]) //if it's gzdoom light
-				{
-					int n;
-					if(lightid < GZBuilder.GZGeneral.GZ_LIGHT_TYPES[0]) n = 0;
-					else if(lightid < GZBuilder.GZGeneral.GZ_LIGHT_TYPES[1]) n = 10;
-					else n = 20;
-					DynamicLightType lightType = (DynamicLightType)(t.Type - 9800 - n);
-
-					if(lightType == DynamicLightType.SECTOR)
-					{
-						if(t.Sector == null) t.DetermineSector();
-						int scaler = (t.Sector != null ? t.Sector.Brightness / 4 : 2);
-						primaryradius = t.Args[3] * scaler;
-					}
-					else
-					{
-						primaryradius = t.Args[3] * 2; //works... that.. way in GZDoom
-						if(lightType > 0) secondaryradius = t.Args[4] * 2;
-					}
-				}
-				else //it's one of vavoom lights
-				{
-					primaryradius = t.Args[0] * 8;
-				}
-
-				// Check radii...
-				if(primaryradius < 1 && secondaryradius < 1) continue;
-
-				// Determine light color
-				PixelColor color;
-				if(t == highlighted)
-				{
-					color = General.Colors.Highlight.WithAlpha(linealpha);
-				}
-				else
-				{
-					switch(t.Type)
-					{
-						case 1502: // Vavoom light
-							color = new PixelColor(linealpha, 255, 255, 255);
-							break;
-
-						case 1503: // Vavoom colored light
-							color = new PixelColor(linealpha, (byte)t.Args[1], (byte)t.Args[2], (byte)t.Args[3]);
-							break;
-
-						default:
-							color = new PixelColor(linealpha, (byte)t.Args[0], (byte)t.Args[1], (byte)t.Args[2]);
-							break;
-					}
-				}
-
-				// Add lines if visible
-				if(primaryradius > 0) circles.AddRange(LinksCollector.MakeCircleLines(t.Position, color, primaryradius, numsides));
-				if(secondaryradius > 0) circles.AddRange(LinksCollector.MakeCircleLines(t.Position, color, secondaryradius, numsides));
-			}
-
-			// Done
-			return circles;
-		}
-
-		//mxd
-		private List<Line3D> GetAmbientSoundShapes(IEnumerable<Thing> things)
-		{
-			List<Line3D> circles = new List<Line3D>();
-			const int linealpha = 128;
-			const int numsides = 24;
-
-			foreach(Thing t in things)
-			{
-				ThingTypeInfo info = General.Map.Data.GetThingInfoEx(t.Type);
-				if(info == null) continue;
-				
-				float minradius, maxradius;
-				if(info.AmbientSound != null)
-				{
-					minradius = info.AmbientSound.MinimumRadius;
-					maxradius = info.AmbientSound.MaximumRadius;
-				}
-				else if(!General.Map.DOOM && (info.ClassName == "AmbientSound" || info.ClassName == "AmbientSoundNoGravity"))
-				{
-					//arg0: ambient slot
-					//arg1: (optional) sound volume, in percent. 1 is nearly silent, 100 and above are full volume. If left to zero, full volume is also used.
-					//arg2: (optional) minimum distance, in map units, at which volume attenuation begins. Note that arg3 must also be set. If both are left to zero, normal rolloff is used instead.
-					//arg3: (optional) maximum distance, in map units, at which the sound can be heard. If left to zero or lower than arg2, normal rolloff is used instead.
-					//arg4: (optional) scalar by which to multiply the values of arg2 and arg3. If left to zero, no multiplication takes place.
-
-					if(t.Args[0] == 0 || !General.Map.Data.AmbientSounds.ContainsKey(t.Args[0]))
-						continue;
-
-					// Use custom radii?
-					if(t.Args[2] > 0 && t.Args[3] > 0 && t.Args[3] > t.Args[2])
-					{
-						minradius = t.Args[2] * (t.Args[4] != 0 ? t.Args[4] : 1.0f);
-						maxradius = t.Args[3] * (t.Args[4] != 0 ? t.Args[4] : 1.0f);
-					}
-					else
-					{
-						minradius = General.Map.Data.AmbientSounds[t.Args[0]].MinimumRadius;
-						maxradius = General.Map.Data.AmbientSounds[t.Args[0]].MaximumRadius;
-					}
-				}
-				else
-				{
-					continue;
-				}
-
-				// Determine color
-				PixelColor color = (t == highlighted ? General.Colors.Highlight.WithAlpha(linealpha) : t.Color.WithAlpha(linealpha));
-
-				// Add lines if visible
-				if(minradius > 0) circles.AddRange(LinksCollector.MakeCircleLines(t.Position, color, minradius, numsides));
-				if(maxradius > 0) circles.AddRange(LinksCollector.MakeCircleLines(t.Position, color, maxradius, numsides));
-			}
-
-			return circles;
-		}
-
-		private List<Line3D> GetArgumentRangeShapes(IEnumerable<Thing> things)
-		{
-			List<Line3D> lines = new List<Line3D>();
-			const int numsides = 24;
-
-			foreach(Thing t in things)
-			{
-				ThingTypeInfo info = General.Map.Data.GetThingInfoEx(t.Type);
-				if(info == null) continue;
-
-				// Process argument based range finders
-				for(int i = 0; i < t.Args.Length; i++)
-				{
-					if(t.Args[i] == 0) continue; // Avoid visual noise
-
-					var a = info.Args[i];
-					switch(a.RenderStyle)
-					{
-						case ArgumentInfo.ArgumentRenderStyle.CIRCLE:
-							if(a.MinRange > 0) lines.AddRange(LinksCollector.MakeCircleLines(t.Position, a.MinRangeColor, a.MinRange, numsides));
-							if(a.MaxRange > 0) lines.AddRange(LinksCollector.MakeCircleLines(t.Position, a.MaxRangeColor, a.MaxRange, numsides));
-							break;
-
-						case ArgumentInfo.ArgumentRenderStyle.RECTANGLE:
-							if(a.MinRange > 0) lines.AddRange(LinksCollector.MakeRectangleLines(t.Position, a.MinRangeColor, a.MinRange));
-							if(a.MaxRange > 0) lines.AddRange(LinksCollector.MakeRectangleLines(t.Position, a.MaxRangeColor, a.MaxRange));
-							break;
-
-						case ArgumentInfo.ArgumentRenderStyle.NONE:break;
-						default: throw new NotImplementedException("Unknown ArgumentRenderStyle");
-					}
-				}
-			}
-
-			return lines;
+			ambientsoundshapes = LinksCollector.GetAmbientSoundShapes(General.Map.ThingsFilter.VisibleThings, false);
 		}
 
 		#endregion
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
index 86532ab52f662fee6fc5185c3524e6ab09f59972..007b3b8be44373e61a8934925c5f39bfbab924fa 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
@@ -518,7 +518,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			}
 
 			//mxd. Update event lines (still better than updating them on every frame redraw)
-			renderer.SetEventLines(LinksCollector.GetThingLinks(General.Map.ThingsFilter.VisibleThings, blockmap));
+			renderer.SetEventLines(LinksCollector.GetHelperShapes(General.Map.ThingsFilter.VisibleThings, blockmap));
 		}
 
 		//mxd
@@ -1062,7 +1062,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			RebuildElementData();
 
 			//mxd. Update event lines
-			renderer.SetEventLines(LinksCollector.GetThingLinks(General.Map.ThingsFilter.VisibleThings, blockmap));
+			renderer.SetEventLines(LinksCollector.GetHelperShapes(General.Map.ThingsFilter.VisibleThings, blockmap));
 		}
 
 		// When returning to another mode
@@ -3159,7 +3159,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Map.ThingsFilter.Update();
 
 			// Update event lines
-			renderer.SetEventLines(LinksCollector.GetThingLinks(General.Map.ThingsFilter.VisibleThings, blockmap));
+			renderer.SetEventLines(LinksCollector.GetHelperShapes(General.Map.ThingsFilter.VisibleThings, blockmap));
 		}
 
 		//mxd. We'll just use currently selected objects