diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index 72797df08ca2ca1a0bef61d8b0dd3781f8099d9c..0983fdadf84ec894171b9d6cdc80a3053d91f8e5 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -3,7 +3,7 @@
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.50727</ProductVersion>
+    <ProductVersion>9.0.30729</ProductVersion>
     <SchemaVersion>2.0</SchemaVersion>
     <ProjectGuid>{818B3D10-F791-4C3F-9AF5-BB2D0079B63C}</ProjectGuid>
     <OutputType>WinExe</OutputType>
diff --git a/Source/Core/Config/ProgramConfiguration.cs b/Source/Core/Config/ProgramConfiguration.cs
index 341665743a637b8af41055450a2f2ed6e3a96300..bff438246c716214044a3da048ca0ef914ddf8d6 100644
--- a/Source/Core/Config/ProgramConfiguration.cs
+++ b/Source/Core/Config/ProgramConfiguration.cs
@@ -48,13 +48,14 @@ namespace CodeImp.DoomBuilder.Config
 		private Configuration cfg;
 		
 		// Cached variables
-		private int undolevels;
+		//private int undolevels;
 		private bool blackbrowsers;
 		private int visualfov;
 		private float visualmousesensx;
 		private float visualmousesensy;
 		private int imagebrightness;
 		private float doublesidedalpha;
+		private byte doublesidedalphabyte;
 		private float backgroundalpha;
 		private bool qualitydisplay;
 		private bool squarethings;
@@ -92,11 +93,12 @@ namespace CodeImp.DoomBuilder.Config
 		#region ================== Properties
 
 		internal Configuration Config { get { return cfg; } }
-		public int UndoLevels { get { return undolevels; } internal set { undolevels = value; } }
+		//public int UndoLevels { get { return undolevels; } internal set { undolevels = value; } }
 		public bool BlackBrowsers { get { return blackbrowsers; } internal set { blackbrowsers = value; } }
 		public int VisualFOV { get { return visualfov; } internal set { visualfov = value; } }
 		public int ImageBrightness { get { return imagebrightness; } internal set { imagebrightness = value; } }
-		public float DoubleSidedAlpha { get { return doublesidedalpha; } internal set { doublesidedalpha = value; } }
+		public float DoubleSidedAlpha { get { return doublesidedalpha; } internal set { doublesidedalpha = value; doublesidedalphabyte = (byte)(doublesidedalpha * 255f); } }
+		public byte DoubleSidedAlphaByte { get { return doublesidedalphabyte; } }
 		public float BackgroundAlpha { get { return backgroundalpha; } internal set { backgroundalpha = value; } }
 		public float VisualMouseSensX { get { return visualmousesensx; } internal set { visualmousesensx = value; } }
 		public float VisualMouseSensY { get { return visualmousesensy; } internal set { visualmousesensy = value; } }
@@ -153,12 +155,13 @@ namespace CodeImp.DoomBuilder.Config
 			{
 				// Read the cache variables
 				blackbrowsers = cfg.ReadSetting("blackbrowsers", false);
-				undolevels = cfg.ReadSetting("undolevels", 20);
+				//undolevels = cfg.ReadSetting("undolevels", 20);
 				visualfov = cfg.ReadSetting("visualfov", 80);
 				visualmousesensx = cfg.ReadSetting("visualmousesensx", 40f);
 				visualmousesensy = cfg.ReadSetting("visualmousesensy", 40f);
 				imagebrightness = cfg.ReadSetting("imagebrightness", 3);
 				doublesidedalpha = cfg.ReadSetting("doublesidedalpha", 0.4f);
+				doublesidedalphabyte = (byte)(doublesidedalpha * 255f);
 				backgroundalpha = cfg.ReadSetting("backgroundalpha", 1.0f);
 				qualitydisplay = cfg.ReadSetting("qualitydisplay", true);
 				squarethings = cfg.ReadSetting("squarethings", false);
@@ -197,7 +200,7 @@ namespace CodeImp.DoomBuilder.Config
 			
 			// Write the cache variables
 			cfg.WriteSetting("blackbrowsers", blackbrowsers);
-			cfg.WriteSetting("undolevels", undolevels);
+			//cfg.WriteSetting("undolevels", undolevels);
 			cfg.WriteSetting("visualfov", visualfov);
 			cfg.WriteSetting("visualmousesensx", visualmousesensx);
 			cfg.WriteSetting("visualmousesensy", visualmousesensy);
@@ -363,7 +366,7 @@ namespace CodeImp.DoomBuilder.Config
 		{
 			t.Type = defaultthingtype;
 			t.Rotate(defaultthingangle);
-			foreach(string f in defaultthingflags) t.Flags[f] = true;
+			foreach(string f in defaultthingflags) t.SetFlag(f, true);
 		}
 		
 		// This attempts to find the default drawing settings
diff --git a/Source/Core/Controls/FieldsEditorControl.cs b/Source/Core/Controls/FieldsEditorControl.cs
index 8794c14a6d21b3cf7730c2f9ba605e1835b6ba9c..9e0d9f0edb16505c2c391f4925fc38d4eb33d539 100644
--- a/Source/Core/Controls/FieldsEditorControl.cs
+++ b/Source/Core/Controls/FieldsEditorControl.cs
@@ -199,6 +199,8 @@ namespace CodeImp.DoomBuilder.Controls
 		// This applies the current fields to a UniFields object
 		public void Apply(UniFields tofields)
 		{
+			tofields.BeforeFieldsChange();
+			
 			// Go for all the fields
 			UniFields tempfields = new UniFields(tofields);
 			foreach(KeyValuePair<string, UniValue> f in tempfields)
diff --git a/Source/Core/Editing/UndoManager.cs b/Source/Core/Editing/UndoManager.cs
index 391453779991d33d530f70868fb4bb7234744496..63780d9a24fef77f7311a845bafd37353527a32b 100644
--- a/Source/Core/Editing/UndoManager.cs
+++ b/Source/Core/Editing/UndoManager.cs
@@ -25,6 +25,7 @@ using System.Threading;
 using System.Windows.Forms;
 using System.IO;
 using System.Reflection;
+using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Plugins;
 using CodeImp.DoomBuilder.Windows;
 using CodeImp.DoomBuilder.IO;
@@ -41,6 +42,39 @@ namespace CodeImp.DoomBuilder.Editing
 	{
 		#region ================== Constants
 
+		// Maximum undo/redo levels
+		private const int MAX_UNDO_LEVELS = 1000;
+		
+		// Default stream capacity
+		private const int STREAM_CAPACITY = 1000;
+		
+		// Stream codes
+		// "Prp" stands for property changes (uses the ReadWrite functions)
+		// "Ref" stands for reference changes
+		private enum StreamCodes : byte
+		{
+			AddVertex,
+			RemVertex,
+			PrpVertex,
+			AddLinedef,
+			RemLinedef,
+			PrpLinedef,
+			RefLinedefStart,
+			RefLinedefEnd,
+			RefLinedefFront,
+			RefLinedefBack,
+			AddSidedef,
+			RemSidedef,
+			PrpSidedef,
+			RefSidedefSector,
+			AddSector,
+			RemSector,
+			PrpSector,
+			AddThing,
+			RemThing,
+			PrpThing,
+		}
+		
 		#endregion
 
 		#region ================== Variables
@@ -57,6 +91,17 @@ namespace CodeImp.DoomBuilder.Editing
 		// Unique tickets
 		private int ticketid;
 		
+		// Writing stream
+		private UndoSnapshot snapshot;
+		private bool isundosnapshot;
+		private MemoryStream stream;
+		private SerializerStream ss;
+		private int commandswritten;
+		private long prevstreamlength;
+		private bool ignorepropchanges;
+		private bool isrecordingcommand;
+		private MapElement propsrecorded;
+		
 		// Background thread
 		private volatile bool dobackgroundwork;
 		private Thread backgroundthread;
@@ -68,10 +113,40 @@ namespace CodeImp.DoomBuilder.Editing
 
 		#region ================== Properties
 
-		public UndoSnapshot NextUndo { get { if(undos.Count > 0) return undos[0]; else return null; } }
-		public UndoSnapshot NextRedo { get { if(redos.Count > 0) return redos[0]; else return null; } }
+		public UndoSnapshot NextUndo
+		{
+			get
+			{
+				if(!isundosnapshot && (snapshot != null))
+					return snapshot;
+				else if(undos.Count > 0)
+					return undos[0];
+				else
+					return null;
+			}
+		}
+		
+		public UndoSnapshot NextRedo
+		{
+			get
+			{
+				if(isundosnapshot && (snapshot != null))
+					return snapshot;
+				else if(redos.Count > 0)
+					return redos[0];
+				else
+					return null;
+			}
+		}
+		
 		public bool IsDisposed { get { return isdisposed; } }
-
+		
+		/// <summary>
+		/// This can be used to ignore insignificant element property changes. Any property changes
+		/// that are made while this is set to True will not be undoable. Use with great care!
+		/// </summary>
+		public bool IgnorePropChanges { get { return ignorepropchanges; } set { ignorepropchanges = value; } }
+		
 		#endregion
 
 		#region ================== Constructor / Disposer
@@ -81,9 +156,9 @@ namespace CodeImp.DoomBuilder.Editing
 		{
 			// Initialize
 			ticketid = 1;
-			undos = new List<UndoSnapshot>(General.Settings.UndoLevels + 1);
-			redos = new List<UndoSnapshot>(General.Settings.UndoLevels + 1);
-
+			undos = new List<UndoSnapshot>(MAX_UNDO_LEVELS + 1);
+			redos = new List<UndoSnapshot>(MAX_UNDO_LEVELS + 1);
+			
 			// Bind any methods
 			General.Actions.BindMethods(this);
 
@@ -154,7 +229,7 @@ namespace CodeImp.DoomBuilder.Editing
 			UndoSnapshot u;
 			
 			// Too many?
-			if(list.Count > General.Settings.UndoLevels)
+			if(list.Count > MAX_UNDO_LEVELS)
 			{
 				// Remove one and dispose map
 				u = list[list.Count - 1];
@@ -175,23 +250,23 @@ namespace CodeImp.DoomBuilder.Editing
 					dobackgroundwork = false;
 
 					int undolevel = 0;
-					UndoSnapshot snapshot;
+					UndoSnapshot us;
 					while(true)
 					{
 						// Get the next snapshot or leave
 						lock(undos)
 						{
 							if(undolevel < undos.Count)
-								snapshot = undos[undolevel];
+								us = undos[undolevel];
 							else
 								break;
 						}
 
 						// Write to file or load from file, if needed
-						if(snapshot.StoreOnDisk && !snapshot.IsOnDisk)
-							snapshot.WriteToFile();
-						else if(!snapshot.StoreOnDisk && snapshot.IsOnDisk)
-							snapshot.RestoreFromFile();
+						if(us.StoreOnDisk && !us.IsOnDisk)
+							us.WriteToFile();
+						else if(!us.StoreOnDisk && us.IsOnDisk)
+							us.RestoreFromFile();
 
 						// Next
 						undolevel++;
@@ -204,16 +279,16 @@ namespace CodeImp.DoomBuilder.Editing
 						lock(redos)
 						{
 							if(redolevel < redos.Count)
-								snapshot = redos[redolevel];
+								us = redos[redolevel];
 							else
 								break;
 						}
 
 						// Write to file or load from file, if needed
-						if(snapshot.StoreOnDisk && !snapshot.IsOnDisk)
-							snapshot.WriteToFile();
-						else if(!snapshot.StoreOnDisk && snapshot.IsOnDisk)
-							snapshot.RestoreFromFile();
+						if(us.StoreOnDisk && !us.IsOnDisk)
+							us.WriteToFile();
+						else if(!us.StoreOnDisk && us.IsOnDisk)
+							us.RestoreFromFile();
 
 						// Next
 						redolevel++;
@@ -225,6 +300,122 @@ namespace CodeImp.DoomBuilder.Editing
 			}
 		}
 		
+		// This starts a new recording
+		private void StartRecording(string description)
+		{
+			stream = new MemoryStream(STREAM_CAPACITY);
+			ss = new SerializerStream(stream);
+			ss.Begin();
+			commandswritten = 0;
+			propsrecorded = null;
+			snapshot = new UndoSnapshot(description, stream, ticketid);
+		}
+
+		// This finishes recording
+		private void FinishRecording()
+		{
+			// End current recording
+			if(stream != null)
+			{
+				propsrecorded = null;
+				ss.wInt(commandswritten);
+				ss.End();
+			}
+		}
+		
+		// This begins writing to the record stream
+		private bool BeginRecordData(StreamCodes code)
+		{
+			if(ss == null) return false;
+			isrecordingcommand = true;
+			prevstreamlength = stream.Length;
+			ss.wByte((byte)code);
+			return true;
+		}
+
+		// This ends writing to the record stream
+		private void EndRecordData()
+		{
+			// We write the difference in bytes to the stream so that
+			// the stream can be read from the end backwards
+			int delta = (int)(stream.Length - prevstreamlength);
+			ss.wInt(delta);
+			commandswritten++;
+			isrecordingcommand = false;
+		}
+
+		// This outputs record info, if desired
+		private void LogRecordInfo(string info)
+		{
+			#if DEBUG
+				//General.WriteLogLine(info);
+			#endif
+		}
+
+		// This plays back a stream in reverse
+		private void PlaybackStream(MemoryStream pstream)
+		{
+			General.Map.Map.AutoRemove = false;
+			
+			General.Map.Map.ClearAllMarks(false);
+			
+			pstream.Seek(0, SeekOrigin.Begin);
+			DeserializerStream ds = new DeserializerStream(pstream);
+			ds.Begin();
+			
+			if(pstream.Length > 4)
+			{
+				// Start at the end
+				pstream.Seek(ds.EndPosition - 4, SeekOrigin.Begin);
+				int numcmds; ds.rInt(out numcmds);
+				pstream.Seek(-8, SeekOrigin.Current);
+				while(numcmds > 0)
+				{
+					// Go back up the stream to the beginning of the prev command
+					int len; ds.rInt(out len);
+					pstream.Seek(-(len + 4), SeekOrigin.Current);
+
+					// Play back the command
+					long beginpos = pstream.Position;
+					byte cmd; ds.rByte(out cmd);
+					switch((StreamCodes)cmd)
+					{
+						case StreamCodes.AddVertex: PlayAddVertex(ds); break;
+						case StreamCodes.RemVertex: PlayRemVertex(ds); break;
+						case StreamCodes.PrpVertex: PlayPrpVertex(ds); break;
+						case StreamCodes.AddLinedef: PlayAddLinedef(ds); break;
+						case StreamCodes.RemLinedef: PlayRemLinedef(ds); break;
+						case StreamCodes.PrpLinedef: PlayPrpLinedef(ds); break;
+						case StreamCodes.RefLinedefStart: PlayRefLinedefStart(ds); break;
+						case StreamCodes.RefLinedefEnd: PlayRefLinedefEnd(ds); break;
+						case StreamCodes.RefLinedefFront: PlayRefLinedefFront(ds); break;
+						case StreamCodes.RefLinedefBack: PlayRefLinedefBack(ds); break;
+						case StreamCodes.AddSidedef: PlayAddSidedef(ds); break;
+						case StreamCodes.RemSidedef: PlayRemSidedef(ds); break;
+						case StreamCodes.PrpSidedef: PlayPrpSidedef(ds); break;
+						case StreamCodes.RefSidedefSector: PlayRefSidedefSector(ds); break;
+						case StreamCodes.AddSector: PlayAddSector(ds); break;
+						case StreamCodes.RemSector: PlayRemSector(ds); break;
+						case StreamCodes.PrpSector: PlayPrpSector(ds); break;
+						case StreamCodes.AddThing: PlayAddThing(ds); break;
+						case StreamCodes.RemThing: PlayRemThing(ds); break;
+						case StreamCodes.PrpThing: PlayPrpThing(ds); break;
+					}
+					
+					// Sanity check
+					if((beginpos + len) != pstream.Position)
+						throw new Exception("The last command did not read the same amount of data that was written for this command!");
+					
+					// Go back for next command
+					pstream.Seek(-(len + 4), SeekOrigin.Current);
+
+					numcmds--;
+				}
+			}
+			
+			General.Map.Map.AutoRemove = true;
+		}
+		
 		#endregion
 		
 		#region ================== Public Methods
@@ -272,25 +463,30 @@ namespace CodeImp.DoomBuilder.Editing
 			   (groupid == 0) || (lastgroupid == 0) || (groupid != lastgroupid) ||
 			   (grouptag != lastgrouptag))
 			{
+				FinishRecording();
+				
 				// Next ticket id
 				if(++ticketid == int.MaxValue) ticketid = 1;
-
+				
 				General.WriteLogLine("Creating undo snapshot \"" + description + "\", Source " + groupsourcename + ", Group " + groupid + ", Tag " + grouptag + ", Ticket ID " + ticketid + "...");
-
-				// Make a snapshot
-				u = new UndoSnapshot(description, General.Map.Map.Serialize(), ticketid);
-
-				lock(undos)
+				
+				if((snapshot != null) && !isundosnapshot)
 				{
-					// The current top of the stack can now be written to disk
-					// because it is no longer the next immediate undo level
-					if(undos.Count > 0) undos[0].StoreOnDisk = true;
-					
-					// Put it on the stack
-					undos.Insert(0, u);
-					LimitUndoRedoLevel(undos);
+					lock(undos)
+					{
+						// The current top of the stack can now be written to disk
+						// because it is no longer the next immediate undo level
+						if(undos.Count > 0) undos[0].StoreOnDisk = true;
+						
+						// Put it on the stack
+						undos.Insert(0, snapshot);
+						LimitUndoRedoLevel(undos);
+					}
 				}
-				
+
+				StartRecording(description);
+				isundosnapshot = false;
+
 				// Clear all redos
 				ClearRedos();
 
@@ -325,16 +521,18 @@ namespace CodeImp.DoomBuilder.Editing
 				if(ticket == undos[0].TicketID)
 				{
 					General.WriteLogLine("Withdrawing undo snapshot \"" + undos[0].Description + "\", Ticket ID " + ticket + "...");
-
-					lock(undos)
+					
+					if(snapshot != null)
 					{
-						// Remove the last made undo
-						undos[0].Dispose();
-						undos.RemoveAt(0);
-						
-						// Make the current top of the stack load into memory
-						// because it just became the next immediate undo level
-						if(undos.Count > 0) undos[0].StoreOnDisk = false;
+						// Just trash this recording
+						// You must call CreateUndo first before making any more changes
+						FinishRecording();
+						isundosnapshot = false;
+						snapshot = null;
+					}
+					else
+					{
+						throw new Exception("No undo is recording that can be withdrawn");
 					}
 					
 					// Update
@@ -348,12 +546,12 @@ namespace CodeImp.DoomBuilder.Editing
 		[BeginAction("undo")]
 		public void PerformUndo()
 		{
-			UndoSnapshot u, r;
+			UndoSnapshot u = null;
 			Cursor oldcursor = Cursor.Current;
 			Cursor.Current = Cursors.WaitCursor;
 			
 			// Anything to undo?
-			if(undos.Count > 0)
+			if((undos.Count > 0) || ((snapshot != null) && !isundosnapshot))
 			{
 				// Let the plugins know
 				if(General.Plugins.OnUndoBegin())
@@ -365,54 +563,80 @@ namespace CodeImp.DoomBuilder.Editing
 						// This returns false when mode was not volatile
 						if(!General.CancelVolatileMode())
 						{
-							lock(undos)
+							FinishRecording();
+							
+							if(isundosnapshot)
 							{
-								// Get undo snapshot
-								u = undos[0];
-								undos.RemoveAt(0);
-
-								// Make the current top of the stack load into memory
-								// because it just became the next immediate undo level
-								if(undos.Count > 0) undos[0].StoreOnDisk = false;
+								if(snapshot != null)
+								{
+									// This snapshot was made by a previous call to this
+									// function and should go on the redo list
+									lock(redos)
+									{
+										// The current top of the stack can now be written to disk
+										// because it is no longer the next immediate redo level
+										if(redos.Count > 0) redos[0].StoreOnDisk = true;
+										
+										// Put it on the stack
+										redos.Insert(0, snapshot);
+										LimitUndoRedoLevel(redos);
+									}
+								}
 							}
-
+							else
+							{
+								// The snapshot can be undone immediately and it will
+								// be recorded for the redo list
+								if(snapshot != null)
+									u = snapshot;
+							}
+							
+							// No immediate snapshot to undo? Then get the next one from the stack
+							if(u == null)
+							{
+								lock(undos)
+								{
+									// Get undo snapshot
+									u = undos[0];
+									undos.RemoveAt(0);
+									
+									// Make the current top of the stack load into memory
+									// because it just became the next immediate undo level
+									if(undos.Count > 0) undos[0].StoreOnDisk = false;
+								}
+							}
+							
 							General.WriteLogLine("Performing undo \"" + u.Description + "\", Ticket ID " + u.TicketID + "...");
 							General.Interface.DisplayStatus(StatusType.Action, u.Description + " undone.");
-
+							
 							// Make a snapshot for redo
-							r = new UndoSnapshot(u, General.Map.Map.Serialize());
-
-							lock(redos)
-							{
-								// The current top of the stack can now be written to disk
-								// because it is no longer the next immediate undo level
-								if(redos.Count > 0) redos[0].StoreOnDisk = true;
-								
-								// Put it on the stack
-								redos.Insert(0, r);
-								LimitUndoRedoLevel(redos);
-							}
+							StartRecording(u.Description);
+							isundosnapshot = true;
 							
 							// Reset grouping
 							lastgroupplugin = null;
 
-							// Change map set
-							MemoryStream data = u.GetMapData();
-							General.Map.ChangeMapSet(new MapSet(data));
+							// Play back the stream in reverse
+							MemoryStream data = u.GetStream();
+							PlaybackStream(data);
 							data.Dispose();
 							
 							// Remove selection
-							General.Map.Map.ClearAllMarks(false);
 							General.Map.Map.ClearAllSelected();
-
+							
+							// Update map
+							General.Map.Map.Update();
+							foreach(Thing t in General.Map.Map.Things) if(t.Marked) t.UpdateConfiguration();
+							General.Map.ThingsFilter.Update();
+							General.Map.Data.UpdateUsedTextures();
+							General.MainWindow.RedrawDisplay();
+							
 							// Done
 							General.Editing.Mode.OnUndoEnd();
 							General.Plugins.OnUndoEnd();
 
-							// Update
+							// Update interface
 							dobackgroundwork = true;
-							General.Map.Data.UpdateUsedTextures();
-							General.MainWindow.RedrawDisplay();
 							General.MainWindow.UpdateInterface();
 						}
 					}
@@ -426,12 +650,12 @@ namespace CodeImp.DoomBuilder.Editing
 		[BeginAction("redo")]
 		public void PerformRedo()
 		{
-			UndoSnapshot u, r;
+			UndoSnapshot r = null;
 			Cursor oldcursor = Cursor.Current;
 			Cursor.Current = Cursors.WaitCursor;
 			
 			// Anything to redo?
-			if(redos.Count > 0)
+			if((redos.Count > 0) || ((snapshot != null) && isundosnapshot))
 			{
 				// Let the plugins know
 				if(General.Plugins.OnRedoBegin())
@@ -442,54 +666,79 @@ namespace CodeImp.DoomBuilder.Editing
 						// Cancel volatile mode, if any
 						General.CancelVolatileMode();
 
-						lock(redos)
+						FinishRecording();
+						
+						if(isundosnapshot)
 						{
-							// Get redo snapshot
-							r = redos[0];
-							redos.RemoveAt(0);
-							
-							// Make the current top of the stack load into memory
-							// because it just became the next immediate undo level
-							if(redos.Count > 0) redos[0].StoreOnDisk = false;
+							// This snapshot was started by PerformUndo, which means
+							// it can directly be used to redo to previous undo
+							if(snapshot != null)
+								r = snapshot;
+						}
+						else
+						{
+							if(snapshot != null)
+							{
+								// This snapshot was made by a previous call to this
+								// function and should go on the undo list
+								lock(undos)
+								{
+									// The current top of the stack can now be written to disk
+									// because it is no longer the next immediate undo level
+									if(undos.Count > 0) undos[0].StoreOnDisk = true;
+
+									// Put it on the stack
+									undos.Insert(0, snapshot);
+									LimitUndoRedoLevel(undos);
+								}
+							}
+						}
+						
+						// No immediate snapshot to redo? Then get the next one from the stack
+						if(r == null)
+						{
+							lock(redos)
+							{
+								// Get redo snapshot
+								r = redos[0];
+								redos.RemoveAt(0);
+								
+								// Make the current top of the stack load into memory
+								// because it just became the next immediate undo level
+								if(redos.Count > 0) redos[0].StoreOnDisk = false;
+							}
 						}
 
 						General.WriteLogLine("Performing redo \"" + r.Description + "\", Ticket ID " + r.TicketID + "...");
 						General.Interface.DisplayStatus(StatusType.Action, r.Description + " redone.");
 
-						// Make a snapshot for undo
-						u = new UndoSnapshot(r, General.Map.Map.Serialize());
-
-						lock(undos)
-						{
-							// The current top of the stack can now be written to disk
-							// because it is no longer the next immediate undo level
-							if(undos.Count > 0) undos[0].StoreOnDisk = true;
-
-							// Put it on the stack
-							undos.Insert(0, u);
-							LimitUndoRedoLevel(undos);
-						}
+						StartRecording(r.Description);
+						isundosnapshot = false;
 						
 						// Reset grouping
 						lastgroupplugin = null;
 
-						// Change map set
-						MemoryStream data = r.GetMapData();
-						General.Map.ChangeMapSet(new MapSet(data));
+						// Play back the stream in reverse
+						MemoryStream data = r.GetStream();
+						PlaybackStream(data);
 						data.Dispose();
 						
 						// Remove selection
-						General.Map.Map.ClearAllMarks(false);
 						General.Map.Map.ClearAllSelected();
 
+						// Update map
+						General.Map.Map.Update();
+						foreach(Thing t in General.Map.Map.Things) if(t.Marked) t.UpdateConfiguration();
+						General.Map.ThingsFilter.Update();
+						General.Map.Data.UpdateUsedTextures();
+						General.MainWindow.RedrawDisplay();
+						
 						// Done
 						General.Editing.Mode.OnRedoEnd();
 						General.Plugins.OnRedoEnd();
 
-						// Update
+						// Update interface
 						dobackgroundwork = true;
-						General.Map.Data.UpdateUsedTextures();
-						General.MainWindow.RedrawDisplay();
 						General.MainWindow.UpdateInterface();
 					}
 				}
@@ -499,5 +748,428 @@ namespace CodeImp.DoomBuilder.Editing
 		}
 
 		#endregion
+		
+		#region ================== Record and Playback
+
+		internal void RecAddVertex(Vertex v)
+		{
+			if(!BeginRecordData(StreamCodes.AddVertex)) return;
+			ss.wInt(v.Index);
+			EndRecordData();
+
+			LogRecordInfo("REC: Adding vertex " + v.Index + " at " + v.Position);
+			propsrecorded = null;
+		}
+
+		internal void PlayAddVertex(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			LogRecordInfo("PLY: Removing vertex " + index);
+			Vertex v = General.Map.Map.GetVertexByIndex(index);
+			v.Dispose();
+		}
+
+		internal void RecRemVertex(Vertex v)
+		{
+			if(!BeginRecordData(StreamCodes.RemVertex)) return;
+			ss.wInt(v.Index);
+			ss.wVector2D(v.Position);
+			v.ReadWrite(ss);
+			EndRecordData();
+
+			LogRecordInfo("REC: Removing vertex " + v.Index + " (at " + v.Position + ")");
+			propsrecorded = null;
+		}
+
+		internal void PlayRemVertex(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Vector2D pos; ds.rVector2D(out pos);
+			LogRecordInfo("PLY: Adding vertex " + index + " at " + pos);
+			Vertex v = General.Map.Map.CreateVertex(index, pos);
+			v.ReadWrite(ds);
+			v.Marked = true;
+		}
+		
+		internal void RecPrpVertex(Vertex v)
+		{
+			if(!ignorepropchanges && !isrecordingcommand && !object.ReferenceEquals(v, propsrecorded))
+			{
+				if(!BeginRecordData(StreamCodes.PrpVertex)) return;
+				ss.wInt(v.Index);
+				v.ReadWrite(ss);
+				EndRecordData();
+				propsrecorded = v;
+			}
+		}
+
+		internal void PlayPrpVertex(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Vertex v = General.Map.Map.GetVertexByIndex(index);
+			v.ReadWrite(ds);
+			v.Marked = true;
+		}
+
+		internal void RecAddLinedef(Linedef l)
+		{
+			if(!BeginRecordData(StreamCodes.AddLinedef)) return;
+			ss.wInt(l.Index);
+			EndRecordData();
+
+			LogRecordInfo("REC: Adding linedef " + l.Index + " from " + ((l.Start != null) ? l.Start.Index : -1) + " to " + ((l.End != null) ? l.End.Index : -1));
+			propsrecorded = null;
+		}
+
+		internal void PlayAddLinedef(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			LogRecordInfo("PLY: Removing linedef " + index);
+			Linedef l = General.Map.Map.GetLinedefByIndex(index);
+			l.Dispose();
+		}
+
+		internal void RecRemLinedef(Linedef l)
+		{
+			if(!BeginRecordData(StreamCodes.RemLinedef)) return;
+			ss.wInt(l.Index);
+			ss.wInt(l.Start.Index);
+			ss.wInt(l.End.Index);
+			l.ReadWrite(ss);
+			EndRecordData();
+
+			LogRecordInfo("REC: Removing linedef " + l.Index + " (from " + ((l.Start != null) ? l.Start.Index : -1) + " to " + ((l.End != null) ? l.End.Index : -1) + ")");
+			propsrecorded = null;
+		}
+
+		internal void PlayRemLinedef(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			int sindex; ds.rInt(out sindex);
+			int eindex; ds.rInt(out eindex);
+			LogRecordInfo("PLY: Adding linedef " + index + " from " + sindex + " to " + eindex);
+			Vertex vs = General.Map.Map.GetVertexByIndex(sindex);
+			Vertex ve = General.Map.Map.GetVertexByIndex(eindex);
+			Linedef l = General.Map.Map.CreateLinedef(index, vs, ve);
+			l.ReadWrite(ds);
+			l.Marked = true;
+		}
+
+		internal void RecPrpLinedef(Linedef l)
+		{
+			if(!ignorepropchanges && !isrecordingcommand && !object.ReferenceEquals(l, propsrecorded))
+			{
+				if(!BeginRecordData(StreamCodes.PrpLinedef)) return;
+				ss.wInt(l.Index);
+				l.ReadWrite(ss);
+				EndRecordData();
+				propsrecorded = l;
+			}
+		}
+
+		internal void PlayPrpLinedef(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Linedef l = General.Map.Map.GetLinedefByIndex(index);
+			l.ReadWrite(ds);
+			l.Marked = true;
+		}
+
+		internal void RecRefLinedefStart(Linedef l)
+		{
+			if(!BeginRecordData(StreamCodes.RefLinedefStart)) return;
+			ss.wInt(l.Index);
+			if(l.Start != null) ss.wInt(l.Start.Index); else ss.wInt(-1);
+			EndRecordData();
+
+			LogRecordInfo("REC: Setting linedef " + l.Index + " start vertex " + ((l.Start != null) ? l.Start.Index : -1));
+			propsrecorded = null;
+		}
+
+		internal void PlayRefLinedefStart(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Linedef l = General.Map.Map.GetLinedefByIndex(index);
+			int vindex; ds.rInt(out vindex);
+			LogRecordInfo("PLY: Setting linedef " + index + " start vertex " + vindex);
+			Vertex v = (vindex >= 0) ? General.Map.Map.GetVertexByIndex(vindex) : null;
+			l.SetStartVertex(v);
+			l.Marked = true;
+		}
+
+		internal void RecRefLinedefEnd(Linedef l)
+		{
+			if(!BeginRecordData(StreamCodes.RefLinedefEnd)) return;
+			ss.wInt(l.Index);
+			if(l.End != null) ss.wInt(l.End.Index); else ss.wInt(-1);
+			EndRecordData();
+
+			LogRecordInfo("REC: Setting linedef " + l.Index + " end vertex " + ((l.End != null) ? l.End.Index : -1));
+			propsrecorded = null;
+		}
+
+		internal void PlayRefLinedefEnd(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Linedef l = General.Map.Map.GetLinedefByIndex(index);
+			int vindex; ds.rInt(out vindex);
+			LogRecordInfo("PLY: Setting linedef " + index + " end vertex " + vindex);
+			Vertex v = (vindex >= 0) ? General.Map.Map.GetVertexByIndex(vindex) : null;
+			l.SetEndVertex(v);
+			l.Marked = true;
+		}
+
+		internal void RecRefLinedefFront(Linedef l)
+		{
+			if(!BeginRecordData(StreamCodes.RefLinedefFront)) return;
+			ss.wInt(l.Index);
+			if(l.Front != null) ss.wInt(l.Front.Index); else ss.wInt(-1);
+			EndRecordData();
+
+			LogRecordInfo("REC: Setting linedef " + l.Index + " front sidedef " + ((l.Front != null) ? l.Front.Index : -1));
+			propsrecorded = null;
+		}
+
+		internal void PlayRefLinedefFront(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Linedef l = General.Map.Map.GetLinedefByIndex(index);
+			int sindex; ds.rInt(out sindex);
+			LogRecordInfo("PLY: Setting linedef " + index + " front sidedef " + sindex);
+			Sidedef sd = (sindex >= 0) ? General.Map.Map.GetSidedefByIndex(sindex) : null;
+			l.AttachFront(sd);
+			l.Marked = true;
+			sd.Marked = true;
+		}
+
+		internal void RecRefLinedefBack(Linedef l)
+		{
+			if(!BeginRecordData(StreamCodes.RefLinedefBack)) return;
+			ss.wInt(l.Index);
+			if(l.Back != null) ss.wInt(l.Back.Index); else ss.wInt(-1);
+			EndRecordData();
+
+			LogRecordInfo("REC: Setting linedef " + l.Index + " back sidedef " + ((l.Back != null) ? l.Back.Index : -1));
+			propsrecorded = null;
+		}
+
+		internal void PlayRefLinedefBack(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Linedef l = General.Map.Map.GetLinedefByIndex(index);
+			int sindex; ds.rInt(out sindex);
+			LogRecordInfo("PLY: Setting linedef " + index + " back sidedef " + sindex);
+			Sidedef sd = (sindex >= 0) ? General.Map.Map.GetSidedefByIndex(sindex) : null;
+			l.AttachBack(sd);
+			l.Marked = true;
+			sd.Marked = true;
+		}
+
+		internal void RecAddSidedef(Sidedef s)
+		{
+			if(!BeginRecordData(StreamCodes.AddSidedef)) return;
+			ss.wInt(s.Index);
+			EndRecordData();
+
+			LogRecordInfo("REC: Adding sidedef " + s.Index + " to linedef " + s.Line.Index + (s.IsFront ? " front" : " back") + " and sector " + s.Sector.Index);
+			propsrecorded = null;
+		}
+
+		internal void PlayAddSidedef(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			LogRecordInfo("PLY: Removing sidedef " + index);
+			Sidedef s = General.Map.Map.GetSidedefByIndex(index);
+			s.Dispose();
+		}
+
+		internal void RecRemSidedef(Sidedef s)
+		{
+			if(!BeginRecordData(StreamCodes.RemSidedef)) return;
+			ss.wInt(s.Index);
+			ss.wInt(s.Line.Index);
+			ss.wBool(s.IsFront);
+			ss.wInt(s.Sector.Index);
+			s.ReadWrite(ss);
+			EndRecordData();
+
+			LogRecordInfo("REC: Removing sidedef " + s.Index + " (from linedef " + s.Line.Index + (s.IsFront ? " front" : " back") + " and sector " + s.Sector.Index + ")");
+			propsrecorded = null;
+		}
+
+		internal void PlayRemSidedef(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			int dindex; ds.rInt(out dindex);
+			bool front; ds.rBool(out front);
+			int sindex; ds.rInt(out sindex);
+			LogRecordInfo("PLY: Adding sidedef " + index + " to linedef " + dindex + (front ? " front" : " back") + " and sector " + sindex);
+			Linedef l = General.Map.Map.GetLinedefByIndex(dindex);
+			Sector s = General.Map.Map.GetSectorByIndex(sindex);
+			Sidedef sd = General.Map.Map.CreateSidedef(index, l, front, s);
+			sd.ReadWrite(ds);
+			sd.Marked = true;
+		}
+
+		internal void RecPrpSidedef(Sidedef s)
+		{
+			if(!ignorepropchanges && !isrecordingcommand && !object.ReferenceEquals(s, propsrecorded))
+			{
+				if(!BeginRecordData(StreamCodes.PrpSidedef)) return;
+				ss.wInt(s.Index);
+				s.ReadWrite(ss);
+				EndRecordData();
+				propsrecorded = s;
+			}
+		}
+
+		internal void PlayPrpSidedef(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Sidedef s = General.Map.Map.GetSidedefByIndex(index);
+			s.ReadWrite(ds);
+			s.Marked = true;
+		}
+
+		internal void RecRefSidedefSector(Sidedef s)
+		{
+			if(!BeginRecordData(StreamCodes.RefSidedefSector)) return;
+			ss.wInt(s.Index);
+			if(s.Sector != null) ss.wInt(s.Sector.Index); else ss.wInt(-1);
+			EndRecordData();
+
+			LogRecordInfo("REC: Setting sidedef " + s.Index + " sector " + ((s.Sector != null) ? s.Sector.Index : -1));
+			propsrecorded = null;
+		}
+
+		internal void PlayRefSidedefSector(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Sidedef sd = General.Map.Map.GetSidedefByIndex(index);
+			int sindex; ds.rInt(out sindex);
+			LogRecordInfo("PLY: Setting sidedef " + index + " sector " + sindex);
+			Sector sc = (sindex >= 0) ? General.Map.Map.GetSectorByIndex(sindex) : null;
+			sd.SetSector(sc);
+			sd.Marked = true;
+			sc.Marked = true;
+		}
+
+		internal void RecAddSector(Sector s)
+		{
+			if(!BeginRecordData(StreamCodes.AddSector)) return;
+			ss.wInt(s.Index);
+			EndRecordData();
+
+			LogRecordInfo("REC: Adding sector " + s.Index);
+			propsrecorded = null;
+		}
+
+		internal void PlayAddSector(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			LogRecordInfo("PLY: Removing sector " + index);
+			Sector s = General.Map.Map.GetSectorByIndex(index);
+			s.Dispose();
+		}
+
+		internal void RecRemSector(Sector s)
+		{
+			if(!BeginRecordData(StreamCodes.RemSector)) return;
+			ss.wInt(s.Index);
+			s.ReadWrite(ss);
+			EndRecordData();
+
+			LogRecordInfo("REC: Removing sector " + s.Index);
+			propsrecorded = null;
+		}
+
+		internal void PlayRemSector(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			LogRecordInfo("PLY: Adding sector " + index);
+			Sector s = General.Map.Map.CreateSector(index);
+			s.ReadWrite(ds);
+			s.Marked = true;
+		}
+
+		internal void RecPrpSector(Sector s)
+		{
+			if(!ignorepropchanges && !isrecordingcommand && !object.ReferenceEquals(s, propsrecorded))
+			{
+				if(!BeginRecordData(StreamCodes.PrpSector)) return;
+				ss.wInt(s.Index);
+				s.ReadWrite(ss);
+				EndRecordData();
+				propsrecorded = s;
+			}
+		}
+
+		internal void PlayPrpSector(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Sector s = General.Map.Map.GetSectorByIndex(index);
+			s.ReadWrite(ds);
+			s.Marked = true;
+		}
+
+		internal void RecAddThing(Thing t)
+		{
+			if(!BeginRecordData(StreamCodes.AddThing)) return;
+			ss.wInt(t.Index);
+			EndRecordData();
+
+			LogRecordInfo("REC: Adding thing " + t.Index);
+			propsrecorded = null;
+		}
+
+		internal void PlayAddThing(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			LogRecordInfo("PLY: Removing thing " + index);
+			Thing t = General.Map.Map.GetThingByIndex(index);
+			t.Dispose();
+		}
+
+		internal void RecRemThing(Thing t)
+		{
+			if(!BeginRecordData(StreamCodes.RemThing)) return;
+			ss.wInt(t.Index);
+			t.ReadWrite(ss);
+			EndRecordData();
+
+			LogRecordInfo("REC: Removing thing " + t.Index);
+			propsrecorded = null;
+		}
+
+		internal void PlayRemThing(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			LogRecordInfo("PLY: Adding thing " + index);
+			Thing t = General.Map.Map.CreateThing(index);
+			t.ReadWrite(ds);
+			t.Marked = true;
+		}
+
+		internal void RecPrpThing(Thing t)
+		{
+			if(!ignorepropchanges && !isrecordingcommand && !object.ReferenceEquals(t, propsrecorded))
+			{
+				if(!BeginRecordData(StreamCodes.PrpThing)) return;
+				ss.wInt(t.Index);
+				t.ReadWrite(ss);
+				EndRecordData();
+				propsrecorded = t;
+			}
+		}
+
+		internal void PlayPrpThing(DeserializerStream ds)
+		{
+			int index; ds.rInt(out index);
+			Thing t = General.Map.Map.GetThingByIndex(index);
+			t.ReadWrite(ds);
+			t.Marked = true;
+		}
+
+		#endregion
 	}
 }
diff --git a/Source/Core/Editing/UndoSnapshot.cs b/Source/Core/Editing/UndoSnapshot.cs
index 641afe8a1444a6f07401a188c868aafbdc0a6a24..add013941f0c483f64be1ef3ecc21465e19a8f19 100644
--- a/Source/Core/Editing/UndoSnapshot.cs
+++ b/Source/Core/Editing/UndoSnapshot.cs
@@ -40,7 +40,7 @@ namespace CodeImp.DoomBuilder.Editing
 	{
 		#region ================== Variables
 
-		private MemoryStream mapdata;
+		private MemoryStream recstream;
 		private string filename;
 		private string description;
 		private int ticketid;			// For safe withdrawing
@@ -62,22 +62,22 @@ namespace CodeImp.DoomBuilder.Editing
 		#region ================== Constructor / Disposer
 
 		// Constructor
-		internal UndoSnapshot(string description, MemoryStream mapdata, int ticketid)
+		internal UndoSnapshot(string description, MemoryStream recstream, int ticketid)
 		{
-			if(mapdata == null) General.Fail("Argument cannot be null!");
+			if(recstream == null) General.Fail("Argument cannot be null!");
 			this.ticketid = ticketid;
 			this.description = description;
-			this.mapdata = mapdata;
+			this.recstream = recstream;
 			this.filename = null;
 		}
 
 		// Constructor
-		internal UndoSnapshot(UndoSnapshot info, MemoryStream mapdata)
+		internal UndoSnapshot(UndoSnapshot info, MemoryStream recstream)
 		{
-			if(mapdata == null) General.Fail("Argument cannot be null!");
+			if(recstream == null) General.Fail("Argument cannot be null!");
 			this.ticketid = info.ticketid;
 			this.description = info.description;
-			this.mapdata = mapdata;
+			this.recstream = recstream;
 			this.filename = null;
 		}
 
@@ -87,8 +87,8 @@ namespace CodeImp.DoomBuilder.Editing
 			lock(this)
 			{
 				isdisposed = true;
-				if(mapdata != null) mapdata.Dispose();
-				mapdata = null;
+				if(recstream != null) recstream.Dispose();
+				recstream = null;
 				if(isondisk) File.Delete(filename);
 				isondisk = false;
 			}
@@ -99,15 +99,15 @@ namespace CodeImp.DoomBuilder.Editing
 		#region ================== Methods
 
 		// This returns the map data
-		internal MemoryStream GetMapData()
+		internal MemoryStream GetStream()
 		{
 			lock(this)
 			{
 				// Restore into memory if needed
 				if(isondisk) RestoreFromFile();
 				
-				// Return a copy of the buffer
-				return new MemoryStream(mapdata.ToArray());
+				// Return the buffer
+				return recstream;
 			}
 		}
 		
@@ -121,9 +121,9 @@ namespace CodeImp.DoomBuilder.Editing
 				isondisk = true;
 				
 				// Compress data
-				mapdata.Seek(0, SeekOrigin.Begin);
-				MemoryStream outstream = new MemoryStream((int)mapdata.Length);
-				BZip2.Compress(mapdata, outstream, 300000);
+				recstream.Seek(0, SeekOrigin.Begin);
+				MemoryStream outstream = new MemoryStream((int)recstream.Length);
+				BZip2.Compress(recstream, outstream, 300000);
 
 				// Make temporary file
 				filename = General.MakeTempFilename(General.Map.TempPath, "snapshot");
@@ -132,8 +132,8 @@ namespace CodeImp.DoomBuilder.Editing
 				File.WriteAllBytes(filename, outstream.ToArray());
 
 				// Remove data from memory
-				mapdata.Dispose();
-				mapdata = null;
+				recstream.Dispose();
+				recstream = null;
 				outstream.Dispose();
 			}
 		}
@@ -154,7 +154,7 @@ namespace CodeImp.DoomBuilder.Editing
 				MemoryStream outstream = new MemoryStream((int)instream.Length * 4);
 				instream.Seek(0, SeekOrigin.Begin);
 				BZip2.Decompress(instream, outstream);
-				mapdata = new MemoryStream(outstream.ToArray());
+				recstream = new MemoryStream(outstream.ToArray());
 				
 				// Clean up
 				instream.Dispose();
diff --git a/Source/Core/General/MapManager.cs b/Source/Core/General/MapManager.cs
index 06ab56e41a16513c1f172ddbfe6c24429077f0fc..f30fdfc17c099f445864fc1c4d4986852e18a971 100644
--- a/Source/Core/General/MapManager.cs
+++ b/Source/Core/General/MapManager.cs
@@ -348,13 +348,17 @@ namespace CodeImp.DoomBuilder
 			General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
 			io = MapSetIO.Create(config.FormatInterface, tempwad, this);
 			General.WriteLogLine("Reading map data structures from file...");
-			try { map = io.Read(map, TEMP_MAP_HEADER); }
-			catch(Exception e)
-			{
-				General.ErrorLogger.Add(ErrorType.Error, "Unable to read the map data structures with the specified configuration. " + e.GetType().Name + ": " + e.Message);
-				General.ShowErrorMessage("Unable to read the map data structures with the specified configuration.", MessageBoxButtons.OK);
-				return false;
-			}
+			#if DEBUG
+				map = io.Read(map, TEMP_MAP_HEADER);
+			#else
+				try { map = io.Read(map, TEMP_MAP_HEADER); }
+				catch(Exception e)
+				{
+					General.ErrorLogger.Add(ErrorType.Error, "Unable to read the map data structures with the specified configuration. " + e.GetType().Name + ": " + e.Message);
+					General.ShowErrorMessage("Unable to read the map data structures with the specified configuration.", MessageBoxButtons.OK);
+					return false;
+				}
+			#endif
 			map.EndAddRemove();
 			
 			// Load data manager
@@ -446,6 +450,8 @@ namespace CodeImp.DoomBuilder
 				{
 					if(t.Type == config.Start3DModeThingType)
 					{
+						// We're not using SetFlag here, this doesn't have to be undone.
+						// Please note that this is totally exceptional!
 						List<string> flagkeys = new List<string>(t.Flags.Keys);
 						foreach(string k in flagkeys) t.Flags[k] = false;
 					}
diff --git a/Source/Core/Geometry/Tools.cs b/Source/Core/Geometry/Tools.cs
index 0cac495772e51d558bfaa4b2a816230fe749ba14..985c7c8bcf3261ae504e5073ec99eff4ea12ca1a 100644
--- a/Source/Core/Geometry/Tools.cs
+++ b/Source/Core/Geometry/Tools.cs
@@ -566,14 +566,14 @@ namespace CodeImp.DoomBuilder.Geometry
 				{
 					// Create sidedef is needed and ensure it points to the new sector
 					if(ls.Line.Front == null) General.Map.Map.CreateSidedef(ls.Line, true, newsector);
-					if(ls.Line.Front.Sector != newsector) ls.Line.Front.ChangeSector(newsector);
+					if(ls.Line.Front.Sector != newsector) ls.Line.Front.SetSector(newsector);
 					ApplyDefaultsToSidedef(ls.Line.Front, sourceside);
 				}
 				else
 				{
 					// Create sidedef is needed and ensure it points to the new sector
 					if(ls.Line.Back == null) General.Map.Map.CreateSidedef(ls.Line, false, newsector);
-					if(ls.Line.Back.Sector != newsector) ls.Line.Back.ChangeSector(newsector);
+					if(ls.Line.Back.Sector != newsector) ls.Line.Back.SetSector(newsector);
 					ApplyDefaultsToSidedef(ls.Line.Back, sourceside);
 				}
 
@@ -622,7 +622,7 @@ namespace CodeImp.DoomBuilder.Geometry
 					else
 					{
 						// Link to the new sector
-						ls.Line.Front.ChangeSector(original.Sector);
+						ls.Line.Front.SetSector(original.Sector);
 					}
 				}
 				else
@@ -641,7 +641,7 @@ namespace CodeImp.DoomBuilder.Geometry
 					else
 					{
 						// Link to the new sector
-						ls.Line.Back.ChangeSector(original.Sector);
+						ls.Line.Back.SetSector(original.Sector);
 					}
 				}
 			}
diff --git a/Source/Core/Geometry/Triangulation.cs b/Source/Core/Geometry/Triangulation.cs
index 499e2624752053cbdce3c457795cf2750792343d..44c1dab441ae8c96b60f7e3998fb7b67f1d9350a 100644
--- a/Source/Core/Geometry/Triangulation.cs
+++ b/Source/Core/Geometry/Triangulation.cs
@@ -101,6 +101,9 @@ namespace CodeImp.DoomBuilder.Geometry
 		// Constructor
 		public Triangulation()
 		{
+			islandvertices = Array.AsReadOnly<int>(new int[0]);
+			vertices = Array.AsReadOnly<Vector2D>(new Vector2D[0]);
+			sidedefs = Array.AsReadOnly<Sidedef>(new Sidedef[0]);
 		}
 
 		// This performs the triangulation
diff --git a/Source/Core/IO/DeserializerStream.cs b/Source/Core/IO/DeserializerStream.cs
index 019ba949a21953dce550bdbc292e470a14c0375d..6fd35f13cf66b229587b2ca63659e7c8c0c5914c 100644
--- a/Source/Core/IO/DeserializerStream.cs
+++ b/Source/Core/IO/DeserializerStream.cs
@@ -42,6 +42,7 @@ namespace CodeImp.DoomBuilder.IO
 		private Stream stream;
 		private BinaryReader reader;
 		private string[] stringstable;
+		private int stringtablepos;
 
 		#endregion
 
@@ -49,6 +50,8 @@ namespace CodeImp.DoomBuilder.IO
 
 		public bool IsWriting { get { return false; } }
 
+		public int EndPosition { get { return stringtablepos; } }
+
 		#endregion
 
 		#region ================== Constructor / Destructor
@@ -69,8 +72,8 @@ namespace CodeImp.DoomBuilder.IO
 		public void Begin()
 		{
 			// First 4 bytes are reserved for the offset of the strings table
-			int offset = reader.ReadInt32();
-			stream.Seek(offset, SeekOrigin.Begin);
+			stringtablepos = reader.ReadInt32();
+			stream.Seek(stringtablepos, SeekOrigin.Begin);
 			
 			// Read the strings
 			List<string> strings = new List<string>();
diff --git a/Source/Core/IO/UniversalStreamReader.cs b/Source/Core/IO/UniversalStreamReader.cs
index da22483f5dc76068a13f01821f603262d7b47bd8..638feffac7526a61a0b53c9ef14ab2decb83d3a9 100644
--- a/Source/Core/IO/UniversalStreamReader.cs
+++ b/Source/Core/IO/UniversalStreamReader.cs
@@ -375,6 +375,8 @@ namespace CodeImp.DoomBuilder.IO
 		// This reads custom fields from a collection and adds them to a map element
 		private void ReadCustomFields(UniversalCollection collection, MapElement element, string elementname)
 		{
+			element.Fields.BeforeFieldsChange();
+			
 			// Go for all the elements in the collection
 			foreach(UniversalEntry e in collection)
 			{
diff --git a/Source/Core/Map/Linedef.cs b/Source/Core/Map/Linedef.cs
index 923e208533a0ed395c8c57a44315826332e46527..67f25ffe16b8c539ea99acba54374c3ca512721b 100644
--- a/Source/Core/Map/Linedef.cs
+++ b/Source/Core/Map/Linedef.cs
@@ -67,6 +67,8 @@ namespace CodeImp.DoomBuilder.Map
 		private float lengthinv;
 		private float angle;
 		private RectangleF rect;
+		private bool blocksoundflag;
+		private bool impassableflag;
 		
 		// Properties
 		private Dictionary<string, bool> flags;
@@ -89,10 +91,10 @@ namespace CodeImp.DoomBuilder.Map
 		public Sidedef Front { get { return front; } }
 		public Sidedef Back { get { return back; } }
 		public Line2D Line { get { return new Line2D(start.Position, end.Position); } }
-		public Dictionary<string, bool> Flags { get { return flags; } }
-		public int Action { get { return action; } set { action = value; } }
-		public int Activate { get { return activate; } set { activate = value; } }
-		public int Tag { get { return tag; } set { tag = value; if((tag < General.Map.FormatInterface.MinTag) || (tag > General.Map.FormatInterface.MaxTag)) throw new ArgumentOutOfRangeException("Tag", "Invalid tag number"); } }
+		internal Dictionary<string, bool> Flags { get { return flags; } }
+		public int Action { get { return action; } set { BeforePropsChange(); action = value; } }
+		public int Activate { get { return activate; } set { BeforePropsChange(); activate = value; } }
+		public int Tag { get { return tag; } set { BeforePropsChange(); tag = value; if((tag < General.Map.FormatInterface.MinTag) || (tag > General.Map.FormatInterface.MaxTag)) throw new ArgumentOutOfRangeException("Tag", "Invalid tag number"); } }
 		public float LengthSq { get { return lengthsq; } }
 		public float Length { get { return length; } }
 		public float LengthInv { get { return lengthinv; } }
@@ -102,7 +104,9 @@ namespace CodeImp.DoomBuilder.Map
 		public int[] Args { get { return args; } }
 		internal int SerializedIndex { get { return serializedindex; } set { serializedindex = value; } }
 		internal bool FrontInterior { get { return frontinterior; } set { frontinterior = value; } }
-
+		internal bool ImpassableFlag { get { return impassableflag; } }
+		internal bool BlockSoundFlag { get { return blocksoundflag; } }
+		
 		#endregion
 
 		#region ================== Constructor / Disposer
@@ -113,41 +117,23 @@ namespace CodeImp.DoomBuilder.Map
 			// Initialize
 			this.map = map;
 			this.listindex = listindex;
-			this.start = start;
-			this.end = end;
 			this.updateneeded = true;
 			this.args = new int[NUM_ARGS];
 			this.flags = new Dictionary<string, bool>();
 			
 			// Attach to vertices
-			startvertexlistitem = start.AttachLinedef(this);
-			endvertexlistitem = end.AttachLinedef(this);
-			
-			// We have no destructor
-			GC.SuppressFinalize(this);
-		}
-
-		// Constructor
-		internal Linedef(MapSet map, int listindex, Vertex start, Vertex end, IReadWriteStream stream)
-		{
-			// Initialize
-			this.map = map;
-			this.listindex = listindex;
 			this.start = start;
+			this.startvertexlistitem = start.AttachLinedefP(this);
 			this.end = end;
-			this.updateneeded = true;
-			this.args = new int[NUM_ARGS];
-
-			// Attach to vertices
-			startvertexlistitem = start.AttachLinedef(this);
-			endvertexlistitem = end.AttachLinedef(this);
-
-			ReadWrite(stream);
+			this.endvertexlistitem = end.AttachLinedefP(this);
+			
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecAddLinedef(this);
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
-
+		
 		// Disposer
 		public override void Dispose()
 		{
@@ -157,18 +143,23 @@ namespace CodeImp.DoomBuilder.Map
 				// Already set isdisposed so that changes can be prohibited
 				isdisposed = true;
 
+				// Dispose sidedefs
+				if((front != null) && map.AutoRemove) front.Dispose(); else AttachFrontP(null);
+				if((back != null) && map.AutoRemove) back.Dispose(); else AttachBackP(null);
+				
+				if(map == General.Map.Map)
+					General.Map.UndoRedo.RecRemLinedef(this);
+
 				// Remove from main list
 				map.RemoveLinedef(listindex);
-
+				
 				// Detach from vertices
-				start.DetachLinedef(startvertexlistitem);
-				end.DetachLinedef(endvertexlistitem);
+				if(startvertexlistitem != null) start.DetachLinedefP(startvertexlistitem);
 				startvertexlistitem = null;
+				start = null;
+				if(endvertexlistitem != null) end.DetachLinedefP(endvertexlistitem);
 				endvertexlistitem = null;
-				
-				// Dispose sidedefs
-				if((front != null) && map.AutoRemove) front.Dispose();
-				if((back != null) && map.AutoRemove) back.Dispose();
+				end = null;
 				
 				// Clean up
 				start = null;
@@ -185,10 +176,19 @@ namespace CodeImp.DoomBuilder.Map
 		#endregion
 
 		#region ================== Management
-
-		// Serialize / deserialize
+		
+		// Call this before changing properties
+		protected override void BeforePropsChange()
+		{
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecPrpLinedef(this);
+		}
+		
+		// Serialize / deserialize (passive: doesn't record)
 		internal void ReadWrite(IReadWriteStream s)
 		{
+			if(!s.IsWriting) BeforePropsChange();
+			
 			base.ReadWrite(s);
 			
 			if(s.IsWriting)
@@ -223,32 +223,46 @@ namespace CodeImp.DoomBuilder.Map
 		// This sets new start vertex
 		public void SetStartVertex(Vertex v)
 		{
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecRefLinedefStart(this);
+			
 			// Change start
-			if(startvertexlistitem != null) start.DetachLinedef(startvertexlistitem);
+			if(startvertexlistitem != null) start.DetachLinedefP(startvertexlistitem);
 			startvertexlistitem = null;
 			start = v;
-			if(start != null) startvertexlistitem = start.AttachLinedef(this);
+			if(start != null) startvertexlistitem = start.AttachLinedefP(this);
 			this.updateneeded = true;
 		}
 
 		// This sets new end vertex
 		public void SetEndVertex(Vertex v)
 		{
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecRefLinedefEnd(this);
+
 			// Change end
-			if(endvertexlistitem != null) end.DetachLinedef(endvertexlistitem);
+			if(endvertexlistitem != null) end.DetachLinedefP(endvertexlistitem);
 			endvertexlistitem = null;
 			end = v;
-			if(end != null) endvertexlistitem = end.AttachLinedef(this);
+			if(end != null) endvertexlistitem = end.AttachLinedefP(this);
 			this.updateneeded = true;
 		}
 
 		// This detaches a vertex
-		internal void DetachVertex(Vertex v)
+		internal void DetachVertexP(Vertex v)
 		{
 			if(v == start)
-				SetStartVertex(null);
+			{
+				if(startvertexlistitem != null) start.DetachLinedefP(startvertexlistitem);
+				startvertexlistitem = null;
+				start = null;
+			}
 			else if(v == end)
-				SetEndVertex(null);
+			{
+				if(endvertexlistitem != null) end.DetachLinedefP(endvertexlistitem);
+				endvertexlistitem = null;
+				end = null;
+			}
 			else 
 				throw new Exception("Specified Vertex is not attached to this Linedef.");
 		}
@@ -256,6 +270,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This copies all properties to another line
 		new public void CopyPropertiesTo(Linedef l)
 		{
+			l.BeforePropsChange();
+			
 			// Copy properties
 			l.action = action;
 			l.args = (int[])args.Clone();
@@ -263,42 +279,57 @@ namespace CodeImp.DoomBuilder.Map
 			l.tag = tag;
 			l.updateneeded = true;
 			l.activate = activate;
+			l.impassableflag = impassableflag;
+			l.blocksoundflag = blocksoundflag;
 			base.CopyPropertiesTo(l);
 		}
 		
 		// This attaches a sidedef on the front
-		public void AttachFront(Sidedef s)
+		internal void AttachFront(Sidedef s)
 		{
-			// No sidedef here yet?
-			if(front == null)
-			{
-				// Attach and recalculate
-				front = s;
-				updateneeded = true;
-			}
-			else throw new Exception("Linedef already has a front Sidedef.");
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecRefLinedefFront(this);
+			
+			// Attach and recalculate
+			AttachFrontP(s);
+		}
+		
+		// Passive version, does not record the change
+		internal void AttachFrontP(Sidedef s)
+		{
+			// Attach and recalculate
+			front = s;
+			if(front != null) front.SetLinedefP(this);
+			updateneeded = true;
 		}
 
 		// This attaches a sidedef on the back
-		public void AttachBack(Sidedef s)
+		internal void AttachBack(Sidedef s)
 		{
-			// No sidedef here yet?
-			if(back == null)
-			{
-				// Attach and recalculate
-				back = s;
-				updateneeded = true;
-			}
-			else throw new Exception("Linedef already has a back Sidedef.");
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecRefLinedefBack(this);
+
+			// Attach and recalculate
+			AttachBackP(s);
+		}
+		
+		// Passive version, does not record the change
+		internal void AttachBackP(Sidedef s)
+		{
+			// Attach and recalculate
+			back = s;
+			if(back != null) back.SetLinedefP(this);
+			updateneeded = true;
 		}
 
 		// This detaches a sidedef from the front
-		public void DetachSidedef(Sidedef s)
+		internal void DetachSidedefP(Sidedef s)
 		{
 			// Sidedef is on the front?
 			if(front == s)
 			{
 				// Remove sidedef reference
+				if(front != null) front.SetLinedefP(null);
 				front = null;
 				updateneeded = true;
 			}
@@ -306,10 +337,11 @@ namespace CodeImp.DoomBuilder.Map
 			else if(back == s)
 			{
 				// Remove sidedef reference
+				if(back != null) back.SetLinedefP(null);
 				back = null;
 				updateneeded = true;
 			}
-			else throw new Exception("Specified Sidedef is not attached to this Linedef.");
+			//else throw new Exception("Specified Sidedef is not attached to this Linedef.");
 		}
 		
 		// This updates the line when changes have been made
@@ -333,6 +365,10 @@ namespace CodeImp.DoomBuilder.Map
 				float b = Math.Max(start.Position.y, end.Position.y);
 				rect = new RectangleF(l, t, r - l, b - t);
 				
+				// Cached flags
+				blocksoundflag = IsFlagSet(General.Map.Config.SoundLinedefFlag);
+				impassableflag = IsFlagSet(General.Map.Config.ImpassableFlag);
+				
 				// Updated
 				updateneeded = false;
 			}
@@ -478,20 +514,45 @@ namespace CodeImp.DoomBuilder.Map
 			else
 				return false;
 		}
+
+		// This sets a flag
+		public void SetFlag(string flagname, bool value)
+		{
+			if(!flags.ContainsKey(flagname) || (IsFlagSet(flagname) != value))
+			{
+				BeforePropsChange();
+				
+				flags[flagname] = value;
+
+				// Cached flags
+				if(flagname == General.Map.Config.SoundLinedefFlag) blocksoundflag = value;
+				if(flagname == General.Map.Config.ImpassableFlag) impassableflag = value;
+			}
+		}
+
+		// This returns a copy of the flags dictionary
+		public Dictionary<string, bool> GetFlags()
+		{
+			return new Dictionary<string, bool>(flags);
+		}
+		
+		// This clears all flags
+		public void ClearFlags()
+		{
+			flags.Clear();
+			blocksoundflag = false;
+			impassableflag = false;
+		}
 		
 		// This flips the linedef's vertex attachments
 		public void FlipVertices()
 		{
 			// Flip vertices
-			Vertex v = start;
-			start = end;
-			end = v;
-
-			// Flip tickets accordingly
-			LinkedListNode<Linedef> vn = startvertexlistitem;
-			startvertexlistitem = endvertexlistitem;
-			endvertexlistitem = vn;
-
+			Vertex oldstart = start;
+			Vertex oldend = end;
+			SetStartVertex(oldend);
+			SetEndVertex(oldstart);
+			
 			// For drawing, the interior now lies on the other side
 			frontinterior = !frontinterior;
 
@@ -504,9 +565,10 @@ namespace CodeImp.DoomBuilder.Map
 		public void FlipSidedefs()
 		{
 			// Flip sidedefs
-			Sidedef sd = front;
-			front = back;
-			back = sd;
+			Sidedef oldfront = front;
+			Sidedef oldback = back;
+			AttachFront(oldback);
+			AttachBack(oldfront);
 			
 			General.Map.IsChanged = true;
 		}
@@ -544,14 +606,14 @@ namespace CodeImp.DoomBuilder.Map
 			if((front != null) && (back != null))
 			{
 				// Apply or remove flags for doublesided line
-				flags[General.Map.Config.SingleSidedFlag] = false;
-				flags[General.Map.Config.DoubleSidedFlag] = true;
+				SetFlag(General.Map.Config.SingleSidedFlag, false);
+				SetFlag(General.Map.Config.DoubleSidedFlag, true);
 			}
 			else
 			{
 				// Apply or remove flags for singlesided line
-				flags[General.Map.Config.SingleSidedFlag] = true;
-				flags[General.Map.Config.DoubleSidedFlag] = false;
+				SetFlag(General.Map.Config.SingleSidedFlag, true);
+				SetFlag(General.Map.Config.DoubleSidedFlag, false);
 			}
 			
 			General.Map.IsChanged = true;
@@ -952,6 +1014,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This updates all properties
 		public void Update(Dictionary<string, bool> flags, int activate, int tag, int action, int[] args)
 		{
+			BeforePropsChange();
+			
 			// Apply changes
 			this.flags = new Dictionary<string, bool>(flags);
 			this.tag = tag;
diff --git a/Source/Core/Map/MapElement.cs b/Source/Core/Map/MapElement.cs
index b8b5365b431ff1f3abbd79a9c797de65a0554c17..810d2142fde4b0c4e6990182d72f436b842e7910 100644
--- a/Source/Core/Map/MapElement.cs
+++ b/Source/Core/Map/MapElement.cs
@@ -68,13 +68,14 @@ namespace CodeImp.DoomBuilder.Map
 		internal MapElement()
 		{
 			// Initialize
-			fields = new UniFields();
+			fields = new UniFields(this);
 		}
 
 		// Disposer
 		public virtual void Dispose()
 		{
 			// Clean up
+			fields.Owner = null;
 			fields = null;
 			
 			// Done
@@ -101,7 +102,7 @@ namespace CodeImp.DoomBuilder.Map
 			}
 			else
 			{
-				fields = new UniFields(c);
+				fields = new UniFields(this, c);
 				for(int i = 0; i < c; i++)
 				{
 					string t; s.rString(out t);
@@ -114,7 +115,16 @@ namespace CodeImp.DoomBuilder.Map
 		// This copies properties to any other element
 		public void CopyPropertiesTo(MapElement element)
 		{
-			element.fields = new UniFields(this.fields);
+			element.fields = new UniFields(this, this.fields);
+		}
+		
+		// This must implement the call to the undo system to record the change of properties
+		protected abstract void BeforePropsChange();
+		
+		// This is called before the custom fields change
+		internal void BeforeFieldsChange()
+		{
+			BeforePropsChange();
 		}
 		
 		#endregion
diff --git a/Source/Core/Map/MapSet.cs b/Source/Core/Map/MapSet.cs
index f4aee588e925e787ba9b3b6b110df462391ed1a8..fe21e07cd0b558794217a0691c2349f8d70157df 100644
--- a/Source/Core/Map/MapSet.cs
+++ b/Source/Core/Map/MapSet.cs
@@ -204,7 +204,6 @@ namespace CodeImp.DoomBuilder.Map
 			{
 				// Already set isdisposed so that changes can be prohibited
 				isdisposed = true;
-				autoremove = false;
 				BeginAddRemove();
 				
 				// Dispose all things
@@ -435,6 +434,7 @@ namespace CodeImp.DoomBuilder.Map
 						{
 							virtualsector = newset.CreateSector();
 							l.Front.Sector.CopyPropertiesTo(virtualsector);
+							virtualsector.Fields.BeforeFieldsChange();
 							virtualsector.Fields[VIRTUAL_SECTOR_FIELD] = new UniValue(virtualsectorvalue);
 						}
 						
@@ -463,6 +463,7 @@ namespace CodeImp.DoomBuilder.Map
 						{
 							virtualsector = newset.CreateSector();
 							l.Back.Sector.CopyPropertiesTo(virtualsector);
+							virtualsector.Fields.BeforeFieldsChange();
 							virtualsector.Fields[VIRTUAL_SECTOR_FIELD] = new UniValue(virtualsectorvalue);
 						}
 
@@ -509,24 +510,6 @@ namespace CodeImp.DoomBuilder.Map
 			return v;
 		}
 
-		// This creates a new vertex from a stream
-		private Vertex CreateVertex(IReadWriteStream stream)
-		{
-			// Make the vertex
-			Vertex v = new Vertex(this, numvertices, stream);
-			AddItem(v, ref vertices, numvertices, ref numvertices);
-			return v;
-		}
-
-		// This creates a new vertex from a stream
-		private Vertex CreateVertex(int index, IReadWriteStream stream)
-		{
-			// Make the vertex
-			Vertex v = new Vertex(this, index, stream);
-			AddItem(v, ref vertices, index, ref numvertices);
-			return v;
-		}
-
 		/// <summary>This creates a new linedef and returns it.</summary>
 		public Linedef CreateLinedef(Vertex start, Vertex end)
 		{
@@ -545,24 +528,6 @@ namespace CodeImp.DoomBuilder.Map
 			return l;
 		}
 
-		// This creates a new linedef from a stream
-		private Linedef CreateLinedef(Vertex start, Vertex end, IReadWriteStream stream)
-		{
-			// Make the linedef
-			Linedef l = new Linedef(this, numlinedefs, start, end, stream);
-			AddItem(l, ref linedefs, numlinedefs, ref numlinedefs);
-			return l;
-		}
-
-		// This creates a new linedef from a stream
-		private Linedef CreateLinedef(int index, Vertex start, Vertex end, IReadWriteStream stream)
-		{
-			// Make the linedef
-			Linedef l = new Linedef(this, index, start, end, stream);
-			AddItem(l, ref linedefs, index, ref numlinedefs);
-			return l;
-		}
-
 		/// <summary>This creates a new sidedef and returns it.</summary>
 		public Sidedef CreateSidedef(Linedef l, bool front, Sector s)
 		{
@@ -581,24 +546,6 @@ namespace CodeImp.DoomBuilder.Map
 			return sd;
 		}
 
-		// This creates a new sidedef from a stream
-		private Sidedef CreateSidedef(Linedef l, bool front, Sector s, IReadWriteStream stream)
-		{
-			// Make the sidedef
-			Sidedef sd = new Sidedef(this, numsidedefs, l, front, s, stream);
-			AddItem(sd, ref sidedefs, numsidedefs, ref numsidedefs);
-			return sd;
-		}
-
-		// This creates a new sidedef from a stream
-		private Sidedef CreateSidedef(int index, Linedef l, bool front, Sector s, IReadWriteStream stream)
-		{
-			// Make the sidedef
-			Sidedef sd = new Sidedef(this, index, l, front, s, stream);
-			AddItem(sd, ref sidedefs, index, ref numsidedefs);
-			return sd;
-		}
-
 		/// <summary>This creates a new sector and returns it.</summary>
 		public Sector CreateSector()
 		{
@@ -637,15 +584,6 @@ namespace CodeImp.DoomBuilder.Map
 			return s;
 		}
 
-		// This creates a new sector from a stream
-		private Sector CreateSector(IReadWriteStream stream)
-		{
-			// Make the sector
-			Sector s = new Sector(this, numsectors, stream);
-			AddItem(s, ref sectors, numsectors, ref numsectors);
-			return s;
-		}
-
 		/// <summary>This creates a new thing and returns it.</summary>
 		public Thing CreateThing()
 		{
@@ -919,7 +857,8 @@ namespace CodeImp.DoomBuilder.Map
 			// Go for all vertices
 			for(int i = 0; i < c; i++)
 			{
-				array[i] = CreateVertex(stream);
+				array[i] = CreateVertex(new Vector2D());
+				array[i].ReadWrite(stream);
 			}
 
 			return array;
@@ -941,7 +880,8 @@ namespace CodeImp.DoomBuilder.Map
 
 				stream.rInt(out end);
 
-				array[i] = CreateLinedef(verticesarray[start], verticesarray[end], stream);
+				array[i] = CreateLinedef(verticesarray[start], verticesarray[end]);
+				array[i].ReadWrite(stream);
 			}
 
 			return array;
@@ -964,7 +904,8 @@ namespace CodeImp.DoomBuilder.Map
 
 				stream.rBool(out front);
 
-				CreateSidedef(linedefsarray[lineindex], front, sectorsarray[sectorindex], stream);
+				Sidedef sd = CreateSidedef(linedefsarray[lineindex], front, sectorsarray[sectorindex]);
+				sd.ReadWrite(stream);
 			}
 		}
 
@@ -978,7 +919,8 @@ namespace CodeImp.DoomBuilder.Map
 			// Go for all sectors
 			for(int i = 0; i < c; i++)
 			{
-				array[i] = CreateSector(stream);
+				array[i] = CreateSector();
+				array[i].ReadWrite(stream);
 			}
 
 			return array;
@@ -2677,14 +2619,14 @@ namespace CodeImp.DoomBuilder.Map
 				{
 					// Replace with stored sidedef
 					bool isfront = snsd.IsFront;
-					snsd.Line.DetachSidedef(snsd);
+					snsd.Line.DetachSidedefP(snsd);
 					if(isfront)
 						snsd.Line.AttachFront(stored);
 					else
 						snsd.Line.AttachBack(stored);
 					
 					// Remove the sidedef
-					snsd.ChangeSector(null);
+					snsd.SetSector(null);
 					RemoveSidedef(sn);
 				}
 				else
diff --git a/Source/Core/Map/Sector.cs b/Source/Core/Map/Sector.cs
index b1425a1372b1ba010f0b03496fb56b07834de7d2..cf58f329f174d6edd923ffbf3f2a6527626c64af 100644
--- a/Source/Core/Map/Sector.cs
+++ b/Source/Core/Map/Sector.cs
@@ -86,15 +86,15 @@ namespace CodeImp.DoomBuilder.Map
 		/// An unique index that does not change when other sectors are removed.
 		/// </summary>
 		public int FixedIndex { get { return fixedindex; } }
-		public int FloorHeight { get { return floorheight; } set { floorheight = value; } }
-		public int CeilHeight { get { return ceilheight; } set { ceilheight = value; } }
+		public int FloorHeight { get { return floorheight; } set { BeforePropsChange(); floorheight = value; } }
+		public int CeilHeight { get { return ceilheight; } set { BeforePropsChange(); ceilheight = value; } }
 		public string FloorTexture { get { return floortexname; } }
 		public string CeilTexture { get { return ceiltexname; } }
 		public long LongFloorTexture { get { return longfloortexname; } }
 		public long LongCeilTexture { get { return longceiltexname; } }
-		public int Effect { get { return effect; } set { effect = value; } }
-		public int Tag { get { return tag; } set { tag = value; if((tag < General.Map.FormatInterface.MinTag) || (tag > General.Map.FormatInterface.MaxTag)) throw new ArgumentOutOfRangeException("Tag", "Invalid tag number"); } }
-		public int Brightness { get { return brightness; } set { brightness = value; updateneeded = true; } }
+		public int Effect { get { return effect; } set { BeforePropsChange(); effect = value; } }
+		public int Tag { get { return tag; } set { BeforePropsChange(); tag = value; if((tag < General.Map.FormatInterface.MinTag) || (tag > General.Map.FormatInterface.MaxTag)) throw new ArgumentOutOfRangeException("Tag", "Invalid tag number"); } }
+		public int Brightness { get { return brightness; } set { BeforePropsChange(); brightness = value; updateneeded = true; } }
 		public bool UpdateNeeded { get { return updateneeded; } set { updateneeded |= value; triangulationneeded |= value; } }
 		public RectangleF BBox { get { return bbox; } }
 		internal Sector Clone { get { return clone; } set { clone = value; } }
@@ -122,21 +122,8 @@ namespace CodeImp.DoomBuilder.Map
 			this.triangulationneeded = true;
 			this.surfaceentry = new SurfaceEntry(-1, -1, -1);
 
-			// We have no destructor
-			GC.SuppressFinalize(this);
-		}
-
-		// Constructor
-		internal Sector(MapSet map, int listindex, IReadWriteStream stream)
-		{
-			// Initialize
-			this.map = map;
-			this.listindex = listindex;
-			this.sidedefs = new LinkedList<Sidedef>();
-			this.triangulationneeded = true;
-			this.surfaceentry = new SurfaceEntry(-1, -1, -1);
-
-			ReadWrite(stream);
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecAddSector(this);
 
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -150,10 +137,13 @@ namespace CodeImp.DoomBuilder.Map
 			{
 				// Already set isdisposed so that changes can be prohibited
 				isdisposed = true;
+				
+				if(map == General.Map.Map)
+					General.Map.UndoRedo.RecRemSector(this);
 
 				// Remove from main list
 				map.RemoveSector(listindex);
-
+				
 				// Register the index as free
 				map.AddSectorIndexHole(fixedindex);
 				
@@ -161,14 +151,16 @@ namespace CodeImp.DoomBuilder.Map
 				// because a sidedef cannot exist without reference to its sector.
 				if(map.AutoRemove)
 					foreach(Sidedef sd in sidedefs) sd.Dispose();
-
+				else
+					foreach(Sidedef sd in sidedefs) sd.SetSectorP(null);
+				
 				// Free surface entry
 				General.Map.CRenderer2D.Surfaces.FreeSurfaces(surfaceentry);
-				
+
 				// Clean up
 				sidedefs = null;
 				map = null;
-
+				
 				// Dispose base
 				base.Dispose();
 			}
@@ -178,49 +170,30 @@ namespace CodeImp.DoomBuilder.Map
 
 		#region ================== Management
 
-		// Serialize / deserialize
+		// Call this before changing properties
+		protected override void BeforePropsChange()
+		{
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecPrpSector(this);
+		}
+
+		// Serialize / deserialize (passive: this doesn't record)
 		internal void ReadWrite(IReadWriteStream s)
 		{
-			base.ReadWrite(s);
+			if(!s.IsWriting) BeforePropsChange();
 			
+			base.ReadWrite(s);
+
 			s.rwInt(ref fixedindex);
 			s.rwInt(ref floorheight);
 			s.rwInt(ref ceilheight);
 			s.rwString(ref floortexname);
 			s.rwString(ref ceiltexname);
-			//s.rwLong(ref longfloortexname);
-			//s.rwLong(ref longceiltexname);
+			s.rwLong(ref longfloortexname);
+			s.rwLong(ref longceiltexname);
 			s.rwInt(ref effect);
 			s.rwInt(ref tag);
 			s.rwInt(ref brightness);
-
-			// Use a new triangulator when reading from stream
-			if(!s.IsWriting && (triangles == null)) triangles = new Triangulation();
-			triangles.ReadWrite(s);
-			
-			if(s.IsWriting)
-			{
-				s.wInt(labels.Count);
-				for(int i = 0; i < labels.Count; i++)
-				{
-					s.wVector2D(labels[i].position);
-					s.wFloat(labels[i].radius);
-				}
-			}
-			else
-			{
-				longfloortexname = Lump.MakeLongName(floortexname);
-				longceiltexname = Lump.MakeLongName(ceiltexname);
-				
-				int c; s.rInt(out c);
-				LabelPositionInfo[] labelsarray = new LabelPositionInfo[c];
-				for(int i = 0; i < c; i++)
-				{
-					s.rVector2D(out labelsarray[i].position);
-					s.rFloat(out labelsarray[i].radius);
-				}
-				labels = Array.AsReadOnly<LabelPositionInfo>(labelsarray);
-			}
 		}
 		
 		// After deserialization
@@ -237,6 +210,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This copies all properties to another sector
 		public void CopyPropertiesTo(Sector s)
 		{
+			s.BeforePropsChange();
+			
 			// Copy properties
 			s.ceilheight = ceilheight;
 			s.ceiltexname = ceiltexname;
@@ -252,7 +227,7 @@ namespace CodeImp.DoomBuilder.Map
 		}
 
 		// This attaches a sidedef and returns the listitem
-		public LinkedListNode<Sidedef> AttachSidedef(Sidedef sd)
+		internal LinkedListNode<Sidedef> AttachSidedefP(Sidedef sd)
 		{
 			updateneeded = true;
 			triangulationneeded = true;
@@ -260,7 +235,7 @@ namespace CodeImp.DoomBuilder.Map
 		}
 
 		// This detaches a sidedef
-		public void DetachSidedef(LinkedListNode<Sidedef> l)
+		internal void DetachSidedefP(LinkedListNode<Sidedef> l)
 		{
 			// Not disposing?
 			if(!isdisposed)
@@ -357,6 +332,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This updates the floor surface
 		public void UpdateFloorSurface()
 		{
+			if(flatvertices == null) return;
+			
 			// Create floor vertices
 			FlatVertex[] floorvertices = new FlatVertex[flatvertices.Length];
 			flatvertices.CopyTo(floorvertices, 0);
@@ -372,6 +349,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This updates the ceiling surface
 		public void UpdateCeilingSurface()
 		{
+			if(flatvertices == null) return;
+
 			// Create ceiling vertices
 			FlatVertex[] ceilvertices = new FlatVertex[flatvertices.Length];
 			flatvertices.CopyTo(ceilvertices, 0);
@@ -484,7 +463,7 @@ namespace CodeImp.DoomBuilder.Map
 				// Change secter reference on my sidedefs
 				// This automatically disposes this sector
 				while(sidedefs != null)
-					sidedefs.First.Value.ChangeSector(other);
+					sidedefs.First.Value.SetSector(other);
 			}
 			else
 			{
@@ -509,6 +488,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This updates all properties
 		public void Update(int hfloor, int hceil, string tfloor, string tceil, int effect, int tag, int brightness)
 		{
+			BeforePropsChange();
+			
 			// Apply changes
 			this.floorheight = hfloor;
 			this.ceilheight = hceil;
@@ -523,6 +504,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This sets texture
 		public void SetFloorTexture(string name)
 		{
+			BeforePropsChange();
+			
 			floortexname = name;
 			longfloortexname = Lump.MakeLongName(name);
 			updateneeded = true;
@@ -532,6 +515,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This sets texture
 		public void SetCeilTexture(string name)
 		{
+			BeforePropsChange();
+			
 			ceiltexname = name;
 			longceiltexname = Lump.MakeLongName(name);
 			updateneeded = true;
diff --git a/Source/Core/Map/Sidedef.cs b/Source/Core/Map/Sidedef.cs
index 941429485f64211d4d41478e5da6a9b90ea5ae56..d970bdab5a9b1004e145283512e98fdca52d45de 100644
--- a/Source/Core/Map/Sidedef.cs
+++ b/Source/Core/Map/Sidedef.cs
@@ -67,13 +67,13 @@ namespace CodeImp.DoomBuilder.Map
 		#region ================== Properties
 
 		public MapSet Map { get { return map; } }
-		public bool IsFront { get { return (this == linedef.Front); } }
+		public bool IsFront { get { return (linedef != null) ? (this == linedef.Front) : false; } }
 		public Linedef Line { get { return linedef; } }
 		public Sidedef Other { get { if(this == linedef.Front) return linedef.Back; else return linedef.Front; } }
 		public Sector Sector { get { return sector; } }
 		public float Angle { get { if(IsFront) return linedef.Angle; else return Angle2D.Normalized(linedef.Angle + Angle2D.PI); } }
-		public int OffsetX { get { return offsetx; } set { offsetx = value; } }
-		public int OffsetY { get { return offsety; } set { offsety = value; } }
+		public int OffsetX { get { return offsetx; } set { BeforePropsChange(); offsetx = value; } }
+		public int OffsetY { get { return offsety; } set { BeforePropsChange(); offsety = value; } }
 		public string HighTexture { get { return texnamehigh; } }
 		public string MiddleTexture { get { return texnamemid; } }
 		public string LowTexture { get { return texnamelow; } }
@@ -92,8 +92,6 @@ namespace CodeImp.DoomBuilder.Map
 			// Initialize
 			this.map = map;
 			this.listindex = listindex;
-			this.linedef = l;
-			this.sector = s;
 			this.texnamehigh = "-";
 			this.texnamemid = "-";
 			this.texnamelow = "-";
@@ -101,33 +99,22 @@ namespace CodeImp.DoomBuilder.Map
 			this.longtexnamemid = MapSet.EmptyLongName;
 			this.longtexnamelow = MapSet.EmptyLongName;
 			
-			// Attach to the linedef
-			if(front) l.AttachFront(this); else l.AttachBack(this);
-			
-			// Attach to sector
-			sectorlistitem = s.AttachSidedef(this);
-
-			// We have no destructor
-			GC.SuppressFinalize(this);
-		}
-
-		// Constructor
-		internal Sidedef(MapSet map, int listindex, Linedef l, bool front, Sector s, IReadWriteStream stream)
-		{
-			// Initialize
-			this.map = map;
-			this.listindex = listindex;
+			// Attach linedef
 			this.linedef = l;
-			this.sector = s;
-
-			// Attach to the linedef
-			if(front) l.AttachFront(this); else l.AttachBack(this);
-
-			// Attach to sector
-			sectorlistitem = s.AttachSidedef(this);
-
-			ReadWrite(stream);
-
+			if(l != null)
+			{
+				if(front)
+					l.AttachFrontP(this);
+				else
+					l.AttachBackP(this);
+			}
+			
+			// Attach sector
+			SetSectorP(s);
+			
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecAddSidedef(this);
+			
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
@@ -141,15 +128,18 @@ namespace CodeImp.DoomBuilder.Map
 				// Already set isdisposed so that changes can be prohibited
 				isdisposed = true;
 
+				if(map == General.Map.Map)
+					General.Map.UndoRedo.RecRemSidedef(this);
+
 				// Remove from main list
 				map.RemoveSidedef(listindex);
 
 				// Detach from linedef
-				linedef.DetachSidedef(this);
+				if(linedef != null) linedef.DetachSidedefP(this);
 				
 				// Detach from sector
-				sector.DetachSidedef(sectorlistitem);
-				
+				SetSectorP(null);
+
 				// Clean up
 				sectorlistitem = null;
 				linedef = null;
@@ -164,31 +154,36 @@ namespace CodeImp.DoomBuilder.Map
 		#endregion
 
 		#region ================== Management
-
-		// Serialize / deserialize
+		
+		// Call this before changing properties
+		protected override void BeforePropsChange()
+		{
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecPrpSidedef(this);
+		}
+		
+		// Serialize / deserialize (passive: this doesn't record)
 		internal void ReadWrite(IReadWriteStream s)
 		{
-			base.ReadWrite(s);
+			if(!s.IsWriting) BeforePropsChange();
 			
+			base.ReadWrite(s);
+
 			s.rwInt(ref offsetx);
 			s.rwInt(ref offsety);
 			s.rwString(ref texnamehigh);
 			s.rwString(ref texnamemid);
 			s.rwString(ref texnamelow);
-			//s.rwLong(ref longtexnamehigh);
-			//s.rwLong(ref longtexnamemid);
-			//s.rwLong(ref longtexnamelow);
-			if(!s.IsWriting)
-			{
-				longtexnamehigh = Lump.MakeLongName(texnamehigh);
-				longtexnamemid = Lump.MakeLongName(texnamemid);
-				longtexnamelow = Lump.MakeLongName(texnamelow);
-			}
+			s.rwLong(ref longtexnamehigh);
+			s.rwLong(ref longtexnamemid);
+			s.rwLong(ref longtexnamelow);
 		}
 		
 		// This copies all properties to another sidedef
 		public void CopyPropertiesTo(Sidedef s)
 		{
+			s.BeforePropsChange();
+			
 			// Copy properties
 			s.offsetx = offsetx;
 			s.offsety = offsety;
@@ -201,71 +196,49 @@ namespace CodeImp.DoomBuilder.Map
 			base.CopyPropertiesTo(s);
 		}
 
-		// This creates a checksum from the sidedef properties
-		// Used for faster sidedefs compression
-		public uint GetChecksum()
-		{
-			CRC crc = new CRC();
-			crc.Add(sector.FixedIndex);
-			crc.Add(offsetx);
-			crc.Add(offsety);
-			crc.Add(longtexnamehigh);
-			crc.Add(longtexnamelow);
-			crc.Add(longtexnamemid);
-			return (uint)(crc.Value & 0x00000000FFFFFFFF);
-		}
-		
-		// This copies textures to another sidedef
-		// And possibly also the offsets
-		public void AddTexturesTo(Sidedef s)
+		// This changes sector
+		public void SetSector(Sector newsector)
 		{
-			int copyoffsets = 0;
-
-			// s cannot be null
-			if(s == null) return;
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecRefSidedefSector(this);
 			
-			// Upper texture set?
-			if((texnamehigh.Length > 0) && (texnamehigh[0] != '-'))
-			{
-				// Copy upper texture
-				s.texnamehigh = texnamehigh;
-				s.longtexnamehigh = longtexnamehigh;
-
-				// Counts as a half coice for copying offsets
-				copyoffsets += 1;
-			}
+			// Change sector
+			SetSectorP(newsector);
+		}
 
-			// Middle texture set?
-			if((texnamemid.Length > 0) && (texnamemid[0] != '-'))
-			{
-				// Copy middle texture
-				s.texnamemid = texnamemid;
-				s.longtexnamemid = longtexnamemid;
+		internal void SetSectorP(Sector newsector)
+		{
+			// Detach from sector
+			if(sector != null) sector.DetachSidedefP(sectorlistitem);
 
-				// Counts for copying offsets
-				copyoffsets += 2;
-			}
+			// Change sector
+			sector = newsector;
 
-			// Lower texture set?
-			if((texnamelow.Length > 0) && (texnamelow[0] != '-'))
-			{
-				// Copy middle texture
-				s.texnamelow = texnamelow;
-				s.longtexnamelow = longtexnamelow;
+			// Attach to sector
+			if(sector != null)
+				sectorlistitem = sector.AttachSidedefP(this);
 
-				// Counts as a half coice for copying offsets
-				copyoffsets += 1;
-			}
+			General.Map.IsChanged = true;
+		}
 
-			// Copy offsets also?
-			if(copyoffsets >= 2)
+		// This sets the linedef
+		public void SetLinedef(Linedef ld, bool front)
+		{
+			if(linedef != null) linedef.DetachSidedefP(this);
+			
+			if(ld != null)
 			{
-				// Copy offsets
-				s.offsetx = offsetx;
-				s.offsety = offsety;
+				if(front)
+					ld.AttachFront(this);
+				else
+					ld.AttachBack(this);
 			}
+		}
 
-			General.Map.IsChanged = true;
+		// This sets the linedef (passive: this doesn't tell the linedef and doesn't record)
+		internal void SetLinedefP(Linedef ld)
+		{
+			linedef = ld;
 		}
 		
 		#endregion
@@ -281,6 +254,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This removes textures that are not required
 		public void RemoveUnneededTextures(bool removemiddle, bool force)
 		{
+			BeforePropsChange();
+			
 			// The middle texture can be removed regardless of any sector tag or linedef action
 			if(!MiddleRequired() && removemiddle)
 			{
@@ -347,6 +322,75 @@ namespace CodeImp.DoomBuilder.Map
 				return false;
 			}
 		}
+
+		// This creates a checksum from the sidedef properties
+		// Used for faster sidedefs compression
+		public uint GetChecksum()
+		{
+			CRC crc = new CRC();
+			crc.Add(sector.FixedIndex);
+			crc.Add(offsetx);
+			crc.Add(offsety);
+			crc.Add(longtexnamehigh);
+			crc.Add(longtexnamelow);
+			crc.Add(longtexnamemid);
+			return (uint)(crc.Value & 0x00000000FFFFFFFF);
+		}
+
+		// This copies textures to another sidedef
+		// And possibly also the offsets
+		public void AddTexturesTo(Sidedef s)
+		{
+			int copyoffsets = 0;
+
+			// s cannot be null
+			if(s == null) return;
+
+			s.BeforePropsChange();
+
+			// Upper texture set?
+			if((texnamehigh.Length > 0) && (texnamehigh[0] != '-'))
+			{
+				// Copy upper texture
+				s.texnamehigh = texnamehigh;
+				s.longtexnamehigh = longtexnamehigh;
+
+				// Counts as a half coice for copying offsets
+				copyoffsets += 1;
+			}
+
+			// Middle texture set?
+			if((texnamemid.Length > 0) && (texnamemid[0] != '-'))
+			{
+				// Copy middle texture
+				s.texnamemid = texnamemid;
+				s.longtexnamemid = longtexnamemid;
+
+				// Counts for copying offsets
+				copyoffsets += 2;
+			}
+
+			// Lower texture set?
+			if((texnamelow.Length > 0) && (texnamelow[0] != '-'))
+			{
+				// Copy middle texture
+				s.texnamelow = texnamelow;
+				s.longtexnamelow = longtexnamelow;
+
+				// Counts as a half coice for copying offsets
+				copyoffsets += 1;
+			}
+
+			// Copy offsets also?
+			if(copyoffsets >= 2)
+			{
+				// Copy offsets
+				s.offsetx = offsetx;
+				s.offsety = offsety;
+			}
+
+			General.Map.IsChanged = true;
+		}
 		
 		#endregion
 
@@ -355,6 +399,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This updates all properties
 		public void Update(int offsetx, int offsety, string thigh, string tmid, string tlow)
 		{
+			BeforePropsChange();
+			
 			// Apply changes
 			this.offsetx = offsetx;
 			this.offsety = offsety;
@@ -366,6 +412,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This sets texture
 		public void SetTextureHigh(string name)
 		{
+			BeforePropsChange();
+			
 			texnamehigh = name;
 			longtexnamehigh = Lump.MakeLongName(name);
 			General.Map.IsChanged = true;
@@ -374,6 +422,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This sets texture
 		public void SetTextureMid(string name)
 		{
+			BeforePropsChange();
+			
 			texnamemid = name;
 			longtexnamemid = Lump.MakeLongName(name);
 			General.Map.IsChanged = true;
@@ -382,26 +432,12 @@ namespace CodeImp.DoomBuilder.Map
 		// This sets texture
 		public void SetTextureLow(string name)
 		{
+			BeforePropsChange();
+			
 			texnamelow = name;
 			longtexnamelow = Lump.MakeLongName(name);
 			General.Map.IsChanged = true;
 		}
-
-		// This changes sector
-		public void ChangeSector(Sector newsector)
-		{
-			// Detach from sector
-			sector.DetachSidedef(sectorlistitem);
-
-			// Change sector
-			sector = newsector;
-			
-			// Attach to sector
-			if(sector != null)
-				sectorlistitem = sector.AttachSidedef(this);
-
-			General.Map.IsChanged = true;
-		}
 		
 		#endregion
 	}
diff --git a/Source/Core/Map/Thing.cs b/Source/Core/Map/Thing.cs
index 4fdfaeba9affc311ff3673f06df7670450bcde3b..c7b06a55d5901d66deb4450244b2d2db5663f01b 100644
--- a/Source/Core/Map/Thing.cs
+++ b/Source/Core/Map/Thing.cs
@@ -71,18 +71,18 @@ namespace CodeImp.DoomBuilder.Map
 		#region ================== Properties
 
 		public MapSet Map { get { return map; } }
-		public int Type { get { return type; } set { type = value; } }
+		public int Type { get { return type; } set { BeforePropsChange(); type = value; } }
 		public Vector3D Position { get { return pos; } }
 		public float Angle { get { return angle; } }
 		public int AngleDeg { get { return (int)Angle2D.RadToDeg(angle); } }
-		public Dictionary<string, bool> Flags { get { return flags; } }
-		public int Action { get { return action; } set { action = value; } }
+		internal Dictionary<string, bool> Flags { get { return flags; } }
+		public int Action { get { return action; } set { BeforePropsChange(); action = value; } }
 		public int[] Args { get { return args; } }
 		public float Size { get { return size; } }
 		public float IconOffset { get { return iconoffset; } }
 		public PixelColor Color { get { return color; } }
 		public bool FixedSize { get { return fixedsize; } }
-		public int Tag { get { return tag; } set { tag = value; if((tag < General.Map.FormatInterface.MinTag) || (tag > General.Map.FormatInterface.MaxTag)) throw new ArgumentOutOfRangeException("Tag", "Invalid tag number"); } }
+		public int Tag { get { return tag; } set { BeforePropsChange(); tag = value; if((tag < General.Map.FormatInterface.MinTag) || (tag > General.Map.FormatInterface.MaxTag)) throw new ArgumentOutOfRangeException("Tag", "Invalid tag number"); } }
 		public Sector Sector { get { return sector; } }
 
 		#endregion
@@ -98,6 +98,9 @@ namespace CodeImp.DoomBuilder.Map
 			this.flags = new Dictionary<string, bool>();
 			this.args = new int[NUM_ARGS];
 			
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecAddThing(this);
+			
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
@@ -111,6 +114,9 @@ namespace CodeImp.DoomBuilder.Map
 				// Already set isdisposed so that changes can be prohibited
 				isdisposed = true;
 
+				if(map == General.Map.Map)
+					General.Map.UndoRedo.RecRemThing(this);
+
 				// Remove from main list
 				map.RemoveThing(listindex);
 
@@ -129,10 +135,19 @@ namespace CodeImp.DoomBuilder.Map
 		#endregion
 
 		#region ================== Management
-
+		
+		// Call this before changing properties
+		protected override void BeforePropsChange()
+		{
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecPrpThing(this);
+		}
+		
 		// Serialize / deserialize
 		internal void ReadWrite(IReadWriteStream s)
 		{
+			if(!s.IsWriting) BeforePropsChange();
+			
 			base.ReadWrite(s);
 
 			if(s.IsWriting)
@@ -169,6 +184,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This copies all properties to another thing
 		public void CopyPropertiesTo(Thing t)
 		{
+			t.BeforePropsChange();
+			
 			// Copy properties
 			t.type = type;
 			t.angle = angle;
@@ -325,6 +342,8 @@ namespace CodeImp.DoomBuilder.Map
 		// NOTE: This does not update sector! (call DetermineSector)
 		public void Move(Vector3D newpos)
 		{
+			BeforePropsChange();
+			
 			// Change position
 			this.pos = newpos;
 			General.Map.IsChanged = true;
@@ -334,6 +353,8 @@ namespace CodeImp.DoomBuilder.Map
 		// NOTE: This does not update sector! (call DetermineSector)
 		public void Move(Vector2D newpos)
 		{
+			BeforePropsChange();
+			
 			// Change position
 			this.pos = new Vector3D(newpos.x, newpos.y, pos.z);
 			General.Map.IsChanged = true;
@@ -343,6 +364,8 @@ namespace CodeImp.DoomBuilder.Map
 		// NOTE: This does not update sector! (call DetermineSector)
 		public void Move(float x, float y, float zoffset)
 		{
+			BeforePropsChange();
+			
 			// Change position
 			this.pos = new Vector3D(x, y, zoffset);
 			General.Map.IsChanged = true;
@@ -351,6 +374,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This rotates the thing
 		public void Rotate(float newangle)
 		{
+			BeforePropsChange();
+			
 			// Change angle
 			this.angle = newangle;
 			General.Map.IsChanged = true;
@@ -403,7 +428,41 @@ namespace CodeImp.DoomBuilder.Map
 		#endregion
 
 		#region ================== Methods
+		
+		// This checks and returns a flag without creating it
+		public bool IsFlagSet(string flagname)
+		{
+			if(flags.ContainsKey(flagname))
+				return flags[flagname];
+			else
+				return false;
+		}
+		
+		// This sets a flag
+		public void SetFlag(string flagname, bool value)
+		{
+			if(!flags.ContainsKey(flagname) || (IsFlagSet(flagname) != value))
+			{
+				BeforePropsChange();
 
+				flags[flagname] = value;
+			}
+		}
+		
+		// This returns a copy of the flags dictionary
+		public Dictionary<string, bool> GetFlags()
+		{
+			return new Dictionary<string,bool>(flags);
+		}
+
+		// This clears all flags
+		public void ClearFlags()
+		{
+			BeforePropsChange();
+			
+			flags.Clear();
+		}
+		
 		// This snaps the vertex to the grid
 		public void SnapToGrid()
 		{
diff --git a/Source/Core/Map/UniFields.cs b/Source/Core/Map/UniFields.cs
index 6976ccc44ddb49e2d43f02699d4a3c95967c4918..c20f28f6ee1431813206a72e2536cab22406ac48 100644
--- a/Source/Core/Map/UniFields.cs
+++ b/Source/Core/Map/UniFields.cs
@@ -15,6 +15,10 @@ namespace CodeImp.DoomBuilder.Map
 	/// </summary>
 	public class UniFields : SortedList<string, UniValue>
 	{
+		// Owner of this list
+		protected MapElement owner;
+		public MapElement Owner { get { return owner; } internal set { owner = value; } }
+		
 		// New constructor
 		///<summary></summary>
 		public UniFields() : base(2)
@@ -32,5 +36,33 @@ namespace CodeImp.DoomBuilder.Map
 		public UniFields(UniFields copyfrom) : base(copyfrom)
 		{
 		}
+		
+		// New constructor
+		///<summary></summary>
+		public UniFields(MapElement owner) : base(2)
+		{
+			this.owner = owner;
+		}
+
+		// New constructor
+		///<summary></summary>
+		public UniFields(MapElement owner, int capacity) : base(capacity)
+		{
+			this.owner = owner;
+		}
+
+		// Copy constructor
+		///<summary></summary>
+		public UniFields(MapElement owner, UniFields copyfrom) : base(copyfrom)
+		{
+			this.owner = owner;
+		}
+		
+		/// <summary>Call this before making changes to the fields, or they may not be updated correctly with undo/redo!</summary>
+		public void BeforeFieldsChange()
+		{
+			if(owner != null)
+				owner.BeforeFieldsChange();
+		}
 	}
 }
diff --git a/Source/Core/Map/Vertex.cs b/Source/Core/Map/Vertex.cs
index e5d876c5df4e510c96f9f7fdf78ac5ebf89a82c6..0e22428742bf1c9b2c979074ad386e28ecd39dc9 100644
--- a/Source/Core/Map/Vertex.cs
+++ b/Source/Core/Map/Vertex.cs
@@ -81,20 +81,9 @@ namespace CodeImp.DoomBuilder.Map
 			this.listindex = listindex;
 			this.pos = pos;
 			
-			// We have no destructor
-			GC.SuppressFinalize(this);
-		}
-
-		// Constructor
-		internal Vertex(MapSet map, int listindex, IReadWriteStream stream)
-		{
-			// Initialize
-			this.map = map;
-			this.linedefs = new LinkedList<Linedef>();
-			this.listindex = listindex;
-
-			ReadWrite(stream);
-
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecAddVertex(this);
+			
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
@@ -107,6 +96,9 @@ namespace CodeImp.DoomBuilder.Map
 			{
 				// Already set isdisposed so that changes can be prohibited
 				isdisposed = true;
+
+				if(map == General.Map.Map)
+					General.Map.UndoRedo.RecRemVertex(this);
 				
 				// Remove from main list
 				map.RemoveVertex(listindex);
@@ -120,7 +112,7 @@ namespace CodeImp.DoomBuilder.Map
 				else
 				{
 					// Detach from linedefs
-					foreach(Linedef ld in linedefs) ld.DetachVertex(this);
+					foreach(Linedef ld in linedefs) ld.DetachVertexP(this);
 				}
 				
 				// Clean up
@@ -136,11 +128,21 @@ namespace CodeImp.DoomBuilder.Map
 
 		#region ================== Management
 
+		// Call this before changing properties
+		protected override void BeforePropsChange()
+		{
+			if(map == General.Map.Map)
+				General.Map.UndoRedo.RecPrpVertex(this);
+		}
+
 		// This attaches a linedef and returns the listitem
-		public LinkedListNode<Linedef> AttachLinedef(Linedef l) { return linedefs.AddLast(l); }
+		internal LinkedListNode<Linedef> AttachLinedefP(Linedef l)
+		{
+			return linedefs.AddLast(l);
+		}
 
 		// This detaches a linedef
-		public void DetachLinedef(LinkedListNode<Linedef> l)
+		internal void DetachLinedefP(LinkedListNode<Linedef> l)
 		{
 			// Not disposing?
 			if(!isdisposed)
@@ -160,9 +162,17 @@ namespace CodeImp.DoomBuilder.Map
 		// Serialize / deserialize
 		internal void ReadWrite(IReadWriteStream s)
 		{
+			if(!s.IsWriting) BeforePropsChange();
+			
 			base.ReadWrite(s);
 			
 			s.rwVector2D(ref pos);
+			
+			if(s.IsWriting)
+			{
+				// Let all lines know they need an update
+				foreach(Linedef l in linedefs) l.NeedUpdate();
+			}
 		}
 
 		// Selected
@@ -187,6 +197,8 @@ namespace CodeImp.DoomBuilder.Map
 		// This copies all properties to another thing
 		public void CopyPropertiesTo(Vertex v)
 		{
+			v.BeforePropsChange();
+			
 			// Copy properties
 			v.pos = pos;
 			base.CopyPropertiesTo(v);
@@ -213,6 +225,8 @@ namespace CodeImp.DoomBuilder.Map
 			// Do we actually move?
 			if(newpos != pos)
 			{
+				BeforePropsChange();
+				
 				// Change position
 				pos = newpos;
 
diff --git a/Source/Core/Rendering/Renderer2D.cs b/Source/Core/Rendering/Renderer2D.cs
index 8e2b641a38797eb79650de217b95cf8ac3e4078a..0d0de346915455f17a8b1a27c5a0d480047ec523 100644
--- a/Source/Core/Rendering/Renderer2D.cs
+++ b/Source/Core/Rendering/Renderer2D.cs
@@ -575,22 +575,20 @@ namespace CodeImp.DoomBuilder.Rendering
 		// This returns the color for a linedef
 		public PixelColor DetermineLinedefColor(Linedef l)
 		{
-			// Impassable lines
-			if(l.IsFlagSet(General.Map.Config.ImpassableFlag))
+			if(l.Selected)
+				return General.Colors.Selection;
+			else if(l.ImpassableFlag)
 			{
-				// Determine color
-				if(l.Selected) return General.Colors.Selection;
-				else if(l.Action != 0) return General.Colors.Actions;
+				// Impassable lines
+				if(l.Action != 0) return General.Colors.Actions;
 				else return General.Colors.Linedefs;
 			}
 			else
 			{
-				// Determine color
-				byte a = (byte)(General.Settings.DoubleSidedAlpha * 255.0f);
-				if(l.Selected) return General.Colors.Selection;
-				else if(l.Action != 0) return General.Colors.Actions.WithAlpha(a);
-				else if(l.IsFlagSet(General.Map.Config.SoundLinedefFlag)) return General.Colors.Sounds.WithAlpha(a);
-				else return General.Colors.Linedefs.WithAlpha(a);
+				// Passable lines
+				if(l.Action != 0) return General.Colors.Actions.WithAlpha(General.Settings.DoubleSidedAlphaByte);
+				else if(l.BlockSoundFlag) return General.Colors.Sounds.WithAlpha(General.Settings.DoubleSidedAlphaByte);
+				else return General.Colors.Linedefs.WithAlpha(General.Settings.DoubleSidedAlphaByte);
 			}
 		}
 
diff --git a/Source/Core/VisualModes/VisualMode.cs b/Source/Core/VisualModes/VisualMode.cs
index 064afd52663afc8f5e02240da4ab42e338cd359c..431b487099c5992ae2d7328a756cd8eb53628c17 100644
--- a/Source/Core/VisualModes/VisualMode.cs
+++ b/Source/Core/VisualModes/VisualMode.cs
@@ -191,8 +191,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		public override void OnUndoEnd()
 		{
 			base.OnUndoEnd();
-			ResourcesReloaded();
-			General.Map.Data.UpdateUsedTextures();
+			ResourcesReloadedPartial();
 			renderer.SetCrosshairBusy(false);
 		}
 
@@ -206,8 +205,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		public override void OnRedoEnd()
 		{
 			base.OnRedoEnd();
-			ResourcesReloaded();
-			General.Map.Data.UpdateUsedTextures();
+			ResourcesReloadedPartial();
 			renderer.SetCrosshairBusy(false);
 		}
 
@@ -616,8 +614,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		#region ================== Processing
 		
 		/// <summary>
-		/// This disposes all resources and rebuilds the ones needed. 
-		/// This usually happens when geometry is changed by undo, redo, cut or paste actions.
+		/// This disposes all resources. Needed geometry will be rebuild automatically.
 		/// </summary>
 		protected virtual void ResourcesReloaded()
 		{
@@ -647,7 +644,92 @@ namespace CodeImp.DoomBuilder.VisualModes
 			// Visibility culling (this re-creates the needed resources)
 			DoCulling();
 		}
-		
+
+		/// <summary>
+		/// This disposes orphaned resources and resources on changed geometry.
+		/// This usually happens when geometry is changed by undo, redo, cut or paste actions
+		/// and uses the marks to check what needs to be reloaded.
+		/// </summary>
+		protected virtual void ResourcesReloadedPartial()
+		{
+			Dictionary<Sector, VisualSector> newsectors = new Dictionary<Sector,VisualSector>(allsectors.Count);
+			int counter = 0;
+			
+			// Neighbour sectors must be updated as well
+			foreach(Sector s in General.Map.Map.Sectors)
+			{
+				if(s.Marked)
+				{
+					foreach(Sidedef sd in s.Sidedefs)
+						if(sd.Other != null) sd.Other.Marked = true;
+				}
+			}
+			
+			// Go for all sidedefs to mark sectors that need updating
+			foreach(Sidedef sd in General.Map.Map.Sidedefs)
+				if(sd.Marked) sd.Sector.Marked = true;
+			
+			// Go for all vertices to mark linedefs that need updating
+			foreach(Vertex v in General.Map.Map.Vertices)
+			{
+				if(v.Marked)
+				{
+					foreach(Linedef ld in v.Linedefs)
+						ld.Marked = true;
+				}
+			}
+			
+			// Go for all linedefs to mark sectors that need updating
+			foreach(Linedef ld in General.Map.Map.Linedefs)
+			{
+				if(ld.Marked)
+				{
+					if(ld.Front != null) ld.Front.Sector.Marked = true;
+					if(ld.Back != null) ld.Back.Sector.Marked = true;
+				}
+			}
+			
+			// Dispose if source was disposed or marked
+			foreach(KeyValuePair<Sector, VisualSector> vs in allsectors)
+			{
+				if(vs.Key.IsDisposed || vs.Key.Marked)
+				{
+					vs.Value.Dispose();
+					counter++;
+				}
+				else
+					newsectors.Add(vs.Key, vs.Value);
+			}
+			
+			General.WriteLogLine("VisualSectors disposed: " + counter);
+
+			// Things depend on the sector they are in and because we can't
+			// easily determine which ones changed, we dispose all things
+			foreach(KeyValuePair<Thing, VisualThing> vt in allthings)
+				vt.Value.Dispose();
+			
+			// Apply new lists
+			allsectors = newsectors;
+			allthings = new Dictionary<Thing, VisualThing>(allthings.Count);
+			
+			// Clear visibility collections
+			visiblesectors.Clear();
+			visibleblocks.Clear();
+			visiblegeometry.Clear();
+			visiblethings.Clear();
+			
+			// Make new blockmap
+			if(blockmap != null)
+			{
+				blockmap.Dispose();
+				blockmap = new VisualBlockMap();
+				FillBlockMap();
+			}
+			
+			// Visibility culling (this re-creates the needed resources)
+			DoCulling();
+		}
+
 		/// <summary>
 		/// Implement this to create an instance of your VisualSector implementation.
 		/// </summary>
diff --git a/Source/Core/Windows/LinedefEditForm.cs b/Source/Core/Windows/LinedefEditForm.cs
index 91e5ec30a1c55592518a8f5886c04d6ccfe931c7..ff13dfbbd396c76a1f02079aaa43d233071866ad 100644
--- a/Source/Core/Windows/LinedefEditForm.cs
+++ b/Source/Core/Windows/LinedefEditForm.cs
@@ -362,8 +362,8 @@ namespace CodeImp.DoomBuilder.Windows
 				// Apply all flags
 				foreach(CheckBox c in flags.Checkboxes)
 				{
-					if(c.CheckState == CheckState.Checked) l.Flags[c.Tag.ToString()] = true;
-					else if(c.CheckState == CheckState.Unchecked) l.Flags[c.Tag.ToString()] = false;
+					if(c.CheckState == CheckState.Checked) l.SetFlag(c.Tag.ToString(), true);
+					else if(c.CheckState == CheckState.Unchecked) l.SetFlag(c.Tag.ToString(), false);
 				}
 				
 				// Apply chosen activation flag
@@ -374,8 +374,8 @@ namespace CodeImp.DoomBuilder.Windows
 				foreach(CheckBox c in udmfactivates.Checkboxes)
 				{
 					LinedefActivateInfo ai = (c.Tag as LinedefActivateInfo);
-					if(c.CheckState == CheckState.Checked) l.Flags[ai.Key] = true;
-					else if(c.CheckState == CheckState.Unchecked) l.Flags[ai.Key] = false;
+					if(c.CheckState == CheckState.Checked) l.SetFlag(ai.Key, true);
+					else if(c.CheckState == CheckState.Unchecked) l.SetFlag(ai.Key, false);
 				}
 				
 				// Action/tags
@@ -415,7 +415,7 @@ namespace CodeImp.DoomBuilder.Windows
 					if(l.Front == null) General.Map.Map.CreateSidedef(l, true, s);
 
 					// Change sector?
-					if(l.Front.Sector != s) l.Front.ChangeSector(s);
+					if(l.Front.Sector != s) l.Front.SetSector(s);
 
 					// Apply settings
 					l.Front.OffsetX = General.Clamp(frontoffsetx.GetResult(l.Front.OffsetX), General.Map.FormatInterface.MinTextureOffset, General.Map.FormatInterface.MaxTextureOffset);
@@ -442,7 +442,7 @@ namespace CodeImp.DoomBuilder.Windows
 					if(l.Back == null) General.Map.Map.CreateSidedef(l, false, s);
 
 					// Change sector?
-					if(l.Back.Sector != s) l.Back.ChangeSector(s);
+					if(l.Back.Sector != s) l.Back.SetSector(s);
 
 					// Apply settings
 					l.Back.OffsetX = General.Clamp(backoffsetx.GetResult(l.Back.OffsetX), General.Map.FormatInterface.MinTextureOffset, General.Map.FormatInterface.MaxTextureOffset);
diff --git a/Source/Core/Windows/ThingEditForm.cs b/Source/Core/Windows/ThingEditForm.cs
index 36bf1b88c7281dee48227ff73b1436ea530b8ee8..984300b02f63522ce36df11a3963c3535d6a756e 100644
--- a/Source/Core/Windows/ThingEditForm.cs
+++ b/Source/Core/Windows/ThingEditForm.cs
@@ -291,8 +291,8 @@ namespace CodeImp.DoomBuilder.Windows
 				// Apply all flags
 				foreach(CheckBox c in flags.Checkboxes)
 				{
-					if(c.CheckState == CheckState.Checked) t.Flags[c.Tag.ToString()] = true;
-					else if(c.CheckState == CheckState.Unchecked) t.Flags[c.Tag.ToString()] = false;
+					if(c.CheckState == CheckState.Checked) t.SetFlag(c.Tag.ToString(), true);
+					else if(c.CheckState == CheckState.Unchecked) t.SetFlag(c.Tag.ToString(), false);
 				}
 
 				// Action/tags
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs
index 597ff183d2f7b663d501a83dc1f63e7e4bdc453e..89ab53487bc1800e33ad1ff43e19e87ce347f5af 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs
@@ -115,6 +115,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.dragstartmappos = dragstartmappos;
 			
 			Cursor.Current = Cursors.AppStarting;
+			
+			// We don't want to record this for undoing while we move the geometry around.
+			// This will be set back to normal when we're done.
+			General.Map.UndoRedo.IgnorePropChanges = true;
 
 			// Make list of selected vertices
 			selectedverts = General.Map.Map.GetMarkedVertices(true);
@@ -272,6 +276,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			// Move geometry back to original position
 			MoveGeometryRelative(new Vector2D(0f, 0f), false, false);
+			
+			// Resume normal undo/redo recording
+			General.Map.UndoRedo.IgnorePropChanges = false;
 
 			// If only a single vertex was selected, deselect it now
 			if(selectedverts.Count == 1) General.Map.Map.ClearSelectedVertices();
@@ -308,6 +315,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Move geometry back to original position
 				MoveGeometryRelative(new Vector2D(0f, 0f), false, false);
 
+				// Resume normal undo/redo recording
+				General.Map.UndoRedo.IgnorePropChanges = false;
+
 				// Make undo for the dragging
 				General.Map.UndoRedo.CreateUndo("Drag geometry");
 
@@ -364,10 +374,17 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				General.Map.Map.Update(true, false);
 
 				// Redraw
-				General.Interface.RedrawDisplay();
+				UpdateRedraw();
+				renderer.Present();
+				//General.Interface.RedrawDisplay();
 			}
 		}
 
+		// This redraws only the required things
+		protected virtual void UpdateRedraw()
+		{
+		}
+
 		// When edit button is released
 		protected override void OnEditEnd()
 		{
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs
index 81f808ffbae9ac5a9f47c34768f77281d144293b..232cfc2f24c3d22d71a8e01feeeb72e985edec97 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs
@@ -136,6 +136,24 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			renderer.RedrawSurface();
 
+			UpdateRedraw();
+
+			if(viewchanged)
+			{
+				// Start rendering things
+				if(renderer.StartThings(true))
+				{
+					renderer.RenderThingSet(General.Map.Map.Things, 1.0f);
+					renderer.Finish();
+				}
+			}
+
+			renderer.Present();
+		}
+
+		// This redraws only the required things
+		protected override void UpdateRedraw()
+		{
 			// Start rendering structure
 			if(renderer.StartPlotter(true))
 			{
@@ -148,21 +166,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// This is important to know, because this item is used
 				// for snapping to the grid and snapping to nearest items
 				renderer.PlotVertex(dragitem, ColorCollection.HIGHLIGHT);
-				
+
 				// Done
 				renderer.Finish();
 			}
 
-			if(viewchanged)
-			{
-				// Start rendering things
-				if(renderer.StartThings(true))
-				{
-					renderer.RenderThingSet(General.Map.Map.Things, 1.0f);
-					renderer.Finish();
-				}
-			}
-
 			// Redraw overlay
 			if(renderer.StartOverlay(true))
 			{
@@ -172,8 +180,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				}
 				renderer.Finish();
 			}
-
-			renderer.Present();
 		}
 		
 		#endregion
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs
index 3a04fd179fbeff1fe3163b0859237130ce869fcf..980f2f6c1011e61b4bc7a77c0d473edd13a9ef8c 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs
@@ -141,6 +141,24 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			renderer.RedrawSurface();
 
+			UpdateRedraw();
+			
+			// Redraw things when view changed
+			if(viewchanged)
+			{
+				if(renderer.StartThings(true))
+				{
+					renderer.RenderThingSet(General.Map.Map.Things, 1.0f);
+					renderer.Finish();
+				}
+			}
+
+			renderer.Present();
+		}
+		
+		// This redraws only the required things
+		protected override void UpdateRedraw()
+		{
 			// Start rendering
 			if(renderer.StartPlotter(true))
 			{
@@ -159,16 +177,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				renderer.Finish();
 			}
 
-			// Redraw things when view changed
-			if(viewchanged)
-			{
-				if(renderer.StartThings(true))
-				{
-					renderer.RenderThingSet(General.Map.Map.Things, 1.0f);
-					renderer.Finish();
-				}
-			}
-
 			// Redraw overlay
 			if(renderer.StartOverlay(true))
 			{
@@ -178,8 +186,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				}
 				renderer.Finish();
 			}
-
-			renderer.Present();
 		}
 		
 		#endregion
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs
index 49e4a187558193de9ddc3e4b4fa1520daca74e28..3ae54dd306a6b9cbd58ccf3830cabc4a2eb16fa3 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs
@@ -232,6 +232,15 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				}
 			}
 			
+			// Render things
+			UpdateRedraw();
+
+			renderer.Present();
+		}
+
+		// This redraws only changed things
+		private void UpdateRedraw()
+		{
 			// Render things
 			if(renderer.StartThings(true))
 			{
@@ -248,8 +257,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Done
 				renderer.Finish();
 			}
-
-			renderer.Present();
 		}
 		
 		// Cancelled
@@ -346,7 +353,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				General.Map.Map.Update();
 
 				// Redraw
-				General.Interface.RedrawDisplay();
+				UpdateRedraw();
+				renderer.Present();
+				//General.Interface.RedrawDisplay();
 			}
 		}
 
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs
index 78234183ab153aea61b138d7b3459bb38ba91bea..94f637a4f08e3ba3435809b498c4a29f8a095fb6 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs
@@ -123,6 +123,24 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			renderer.RedrawSurface();
 
+			UpdateRedraw();
+			
+			// Redraw things when view changed
+			if(viewchanged)
+			{
+				if(renderer.StartThings(true))
+				{
+					renderer.RenderThingSet(General.Map.Map.Things, 1.0f);
+					renderer.Finish();
+				}
+			}
+
+			renderer.Present();
+		}
+		
+		// This redraws only the required things
+		protected override void UpdateRedraw()
+		{
 			// Start rendering
 			if(renderer.StartPlotter(true))
 			{
@@ -140,16 +158,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				renderer.Finish();
 			}
 
-			// Redraw things when view changed
-			if(viewchanged)
-			{
-				if(renderer.StartThings(true))
-				{
-					renderer.RenderThingSet(General.Map.Map.Things, 1.0f);
-					renderer.Finish();
-				}
-			}
-
 			// Redraw overlay
 			if(renderer.StartOverlay(true))
 			{
@@ -159,8 +167,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				}
 				renderer.Finish();
 			}
-
-			renderer.Present();
 		}
 		
 		#endregion
diff --git a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs
index d8dad863ebae7dcf6b6b4b736f491178745b29de..4d07cfff9952568b72422872b4da29cfc31b1d7d 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs
@@ -613,7 +613,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Add toolbar buttons
 			General.Interface.AddButton(BuilderPlug.Me.MenusForm.FlipSelectionH);
 			General.Interface.AddButton(BuilderPlug.Me.MenusForm.FlipSelectionV);
-
+			
+			// We don't want to record this for undoing while we move the geometry around.
+			// This will be set back to normal when we're done.
+			General.Map.UndoRedo.IgnorePropChanges = true;
+			
 			// Convert geometry selection
 			General.Map.Map.ClearAllMarks(false);
 			General.Map.Map.MarkSelectedVertices(true, true);
@@ -751,6 +755,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Paste operation?
 			if(pasting)
 			{
+				// Resume normal undo/redo recording
+				General.Map.UndoRedo.IgnorePropChanges = false;
+				
 				// Remove the geometry
 				int index = 0;
 				foreach(Vertex v in selectedvertices)
@@ -773,6 +780,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					t.Rotate(thingangle[index]);
 					t.Move(thingpos[index++]);
 				}
+				
+				// Resume normal undo/redo recording
+				General.Map.UndoRedo.IgnorePropChanges = false;
 			}
 			
 			General.Map.Map.Update(true, true);
@@ -810,7 +820,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					// Make undo
 					General.Map.UndoRedo.CreateUndo("Edit selection");
 				}
-				
+
+				// Resume normal undo/redo recording
+				General.Map.UndoRedo.IgnorePropChanges = false;
+
 				// Mark selected geometry
 				General.Map.Map.ClearAllMarks(false);
 				General.Map.Map.MarkAllSelectedGeometry(true, true, true, true, false);
@@ -863,7 +876,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 									if(joinsidedef != null)
 									{
 										// Join!
-										s.ChangeSector(joinsidedef.Sector);
+										s.SetSector(joinsidedef.Sector);
 										s.Marked = false;
 										joined = true;
 
diff --git a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
index 5bef100e56477a3290793997bd609f1a5d240059..8450b6d3a63bd41b4f93d61b76bbee151d41147f 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
@@ -912,8 +912,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 								sd.SetTextureLow("-");
 
 								// Set upper/lower unpegged flags
-								sd.Line.Flags[General.Map.Config.UpperUnpeggedFlag] = false;
-								sd.Line.Flags[General.Map.Config.LowerUnpeggedFlag] = true;
+								sd.Line.SetFlag(General.Map.Config.UpperUnpeggedFlag, false);
+								sd.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, true);
 							}
 							else
 							{
@@ -923,8 +923,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 								if(doortex.Length > 0) sd.Other.SetTextureHigh(doortex);
 
 								// Set upper/lower unpegged flags
-								sd.Line.Flags[General.Map.Config.UpperUnpeggedFlag] = false;
-								sd.Line.Flags[General.Map.Config.LowerUnpeggedFlag] = false;
+								sd.Line.SetFlag(General.Map.Config.UpperUnpeggedFlag, false);
+								sd.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, false);
 								
 								// Get door linedef type from config
 								sd.Line.Action = General.Map.Config.MakeDoorAction;
diff --git a/Source/Plugins/BuilderModes/General/CopyStructures.cs b/Source/Plugins/BuilderModes/General/CopyStructures.cs
index 868b1706d9765dead819f555c6204d3eb21e0192..781fa7dd8ecd37f875a4b8551a54ed4aace658d2 100644
--- a/Source/Plugins/BuilderModes/General/CopyStructures.cs
+++ b/Source/Plugins/BuilderModes/General/CopyStructures.cs
@@ -113,9 +113,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			type = t.Type;
 			angle = t.Angle;
-			flags = new Dictionary<string, bool>(t.Flags);
+			flags = t.GetFlags();
 			tag = t.Tag;
-			action = t.Action;
+			action = t.Action;       
 			args = (int[])(t.Args.Clone());
 		}
 		
@@ -123,9 +123,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			t.Type = type;
 			t.Rotate(angle);
-			t.Flags.Clear();
+			t.ClearFlags();
 			foreach(KeyValuePair<string, bool> f in flags)
-				t.Flags.Add(f.Key, f.Value);
+				t.SetFlag(f.Key, f.Value);
 			t.Tag = tag;
 			t.Action = action;
 			for(int i = 0; i < t.Args.Length; i++)
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
index 982383db958a10c6ad871ee5033c5ea440254f1b..c21b9c64526a1ecdb009eb487ca597df1755e977 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
@@ -220,8 +220,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// Toggle upper-unpegged
 		public virtual void OnToggleUpperUnpegged()
 		{
-			if(this.Sidedef.Line.Flags.ContainsKey(General.Map.Config.UpperUnpeggedFlag) &&
-			   this.Sidedef.Line.Flags[General.Map.Config.UpperUnpeggedFlag])
+			if(this.Sidedef.Line.IsFlagSet(General.Map.Config.UpperUnpeggedFlag))
 			{
 				// Remove flag
 				mode.ApplyUpperUnpegged(false);
@@ -236,8 +235,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// Toggle lower-unpegged
 		public virtual void OnToggleLowerUnpegged()
 		{
-			if(this.Sidedef.Line.Flags.ContainsKey(General.Map.Config.LowerUnpeggedFlag) &&
-			   this.Sidedef.Line.Flags[General.Map.Config.LowerUnpeggedFlag])
+			if(this.Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
 			{
 				// Remove flag
 				mode.ApplyLowerUnpegged(false);
@@ -258,14 +256,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Remove flag
 				mode.CreateUndo("Remove upper-unpegged setting");
 				mode.SetActionResult("Removed upper-unpegged setting.");
-				this.Sidedef.Line.Flags[General.Map.Config.UpperUnpeggedFlag] = false;
+				this.Sidedef.Line.SetFlag(General.Map.Config.UpperUnpeggedFlag, false);
 			}
 			else
 			{
 				// Add flag
 				mode.CreateUndo("Set upper-unpegged setting");
 				mode.SetActionResult("Set upper-unpegged setting.");
-				this.Sidedef.Line.Flags[General.Map.Config.UpperUnpeggedFlag] = true;
+				this.Sidedef.Line.SetFlag(General.Map.Config.UpperUnpeggedFlag, true);
 			}
 
 			// Update sidedef geometry
@@ -290,14 +288,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Remove flag
 				mode.CreateUndo("Remove lower-unpegged setting");
 				mode.SetActionResult("Removed lower-unpegged setting.");
-				this.Sidedef.Line.Flags[General.Map.Config.LowerUnpeggedFlag] = false;
+				this.Sidedef.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, false);
 			}
 			else
 			{
 				// Add flag
 				mode.CreateUndo("Set lower-unpegged setting");
 				mode.SetActionResult("Set lower-unpegged setting.");
-				this.Sidedef.Line.Flags[General.Map.Config.LowerUnpeggedFlag] = true;
+				this.Sidedef.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, true);
 			}
 
 			// Update sidedef geometry
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
index 65df0a2039abebb04cdfe338018d98947a223613..a988fa807cdfbe1e44c73b57cc8df28e02955979 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
@@ -512,6 +512,13 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			PickTarget();
 		}
 		
+		// After resources were partially reloaded
+		protected override void ResourcesReloadedPartial()
+		{
+			base.ResourcesReloadedPartial();
+			PickTarget();
+		}
+		
 		// Mouse moves
 		public override void OnMouseMove(MouseEventArgs e)
 		{