diff --git a/Source/Core/Config/ThingTypeInfo.cs b/Source/Core/Config/ThingTypeInfo.cs
index e068768e..926bd6822 100644
--- a/Source/Core/Config/ThingTypeInfo.cs
+++ b/Source/Core/Config/ThingTypeInfo.cs
@@ -18,18 +18,25 @@
 
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using System.Globalization;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 using CodeImp.DoomBuilder.IO;
-using CodeImp.DoomBuilder.Data;
-using CodeImp.DoomBuilder.ZDoom;
 using CodeImp.DoomBuilder.Map;
-using System.Drawing;
+using CodeImp.DoomBuilder.ZDoom;
 
 #endregion
 
 namespace CodeImp.DoomBuilder.Config
 {
+	public struct SpriteFrameInfo //mxd
+	{
+		public string Sprite;
+		public long SpriteLongName;
+		public bool Mirror;
+	}
+	
 	public class ThingTypeInfo : INumberedTitle, IComparable<ThingTypeInfo>
 	{
 		#region ================== Constants
@@ -50,9 +57,9 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly int index;
 		private string title;
 		private string sprite;
+		private SpriteFrameInfo[] spriteframe; //mxd. All rotations for given sprite. Currently contains either 1 or 8 frames
 		private ActorStructure actor;
 		private string classname; //mxd
-		private long spritelongname;
 		private int color;
 		private float alpha; //mxd
 		private byte alphabyte; //mxd
@@ -88,8 +95,8 @@ namespace CodeImp.DoomBuilder.Config
 		public int Index { get { return index; } }
 		public string Title { get { return title; } }
 		public string Sprite { get { return sprite; } }
+		public SpriteFrameInfo[] SpriteFrame { get { return spriteframe; } }
 		public ActorStructure Actor { get { return actor; } }
-		public long SpriteLongName { get { return spritelongname; } }
 		public int Color { get { return color; } }
 		public float Alpha { get { return alpha; } } //mxd
 		public byte AlphaByte { get { return alphabyte; } } //mxd
@@ -147,7 +154,7 @@ namespace CodeImp.DoomBuilder.Config
 			this.spritescale = new SizeF(1.0f, 1.0f);
 			this.fixedsize = false;
 			this.fixedrotation = false; //mxd
-			this.spritelongname = long.MaxValue;
+			this.spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true) } }; //mxd
 			this.args = new ArgumentInfo[Linedef.NUM_ARGS];
 			this.isknown = false;
 			this.absolutez = false;
@@ -199,12 +206,9 @@ namespace CodeImp.DoomBuilder.Config
 			// Safety
 			if(this.radius < 4f || this.fixedsize) this.radius = THING_FIXED_SIZE;
 			if(this.hangs && this.absolutez) this.hangs = false; //mxd
-			
-			// Make long name for sprite lookup
-			if(this.sprite.Length <= 8)
-				this.spritelongname = Lump.MakeLongName(this.sprite);
-			else
-				this.spritelongname = long.MaxValue;
+
+			//mxd. Create sprite frame
+			this.spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true) } };
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -245,12 +249,9 @@ namespace CodeImp.DoomBuilder.Config
 			// Safety
 			if(this.radius < 4f || this.fixedsize) this.radius = THING_FIXED_SIZE;
 			if(this.hangs && this.absolutez) this.hangs = false; //mxd
-			
-			// Make long name for sprite lookup
-			if(this.sprite.Length <= 8)
-				this.spritelongname = Lump.MakeLongName(this.sprite);
-			else
-				this.spritelongname = long.MaxValue;
+
+			//mxd. Create sprite frame
+			this.spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true) } };
 
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -292,6 +293,9 @@ namespace CodeImp.DoomBuilder.Config
 			
 			// Apply settings from actor
 			ModifyByDecorateActor(actor);
+
+			//mxd. Create sprite frame
+			this.spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true) } };
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -313,6 +317,7 @@ namespace CodeImp.DoomBuilder.Config
 
 			// Read properties
 			this.sprite = cat.Sprite;
+			this.spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true), } }; //mxd
 			this.color = cat.Color;
 			this.alpha = cat.Alpha; //mxd
 			this.alphabyte = (byte)(this.alpha * 255); //mxd
@@ -334,6 +339,9 @@ namespace CodeImp.DoomBuilder.Config
 			// Apply settings from actor
 			ModifyByDecorateActor(actor);
 
+			//mxd. Create sprite frame
+			this.spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true) } };
+
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
@@ -354,6 +362,8 @@ namespace CodeImp.DoomBuilder.Config
 
 			// Copy properties
 			this.sprite = other.sprite;
+			this.spriteframe = new SpriteFrameInfo[other.spriteframe.Length]; //mxd
+			other.spriteframe.CopyTo(this.spriteframe, 0); //mxd
 			this.color = other.color;
 			this.alpha = other.alpha; //mxd
 			this.alphabyte = other.alphabyte; //mxd
@@ -443,10 +453,8 @@ namespace CodeImp.DoomBuilder.Config
 			else if(string.IsNullOrEmpty(sprite))//mxd
 				sprite = DataManager.INTERNAL_PREFIX + "unknownthing";
 
-			if(this.sprite.Length < 9)
-				this.spritelongname = Lump.MakeLongName(this.sprite);
-			else
-				this.spritelongname = long.MaxValue;
+			//mxd. Create sprite frame
+			this.spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true) } };
 			
 			// Set sprite scale (mxd. Scale is translated to xscale and yscale in ActorStructure)
 			if(actor.HasPropertyWithValue("xscale"))
@@ -501,6 +509,134 @@ namespace CodeImp.DoomBuilder.Config
 			if(blocking > THING_BLOCKING_NONE) errorcheck = THING_ERROR_INSIDE_STUCK;
 		}
 
+		//mxd. This tries to find all possible sprite rotations
+		internal void SetupSpriteFrame()
+		{
+			// Empty or internal sprites don't have rotations
+			if(string.IsNullOrEmpty(sprite) || sprite.StartsWith(DataManager.INTERNAL_PREFIX)) return;
+
+			// Skip sprites with strange names
+			if(sprite.Length != 6 && sprite.Length != 8)
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unsupported sprite name fromat: \"" + sprite + "\"");
+				return;
+			}
+
+			// Get sprite angle
+			string anglestr = sprite.Substring(5, 1);
+			int sourceangle;
+			if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out sourceangle))
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + sprite + "\"");
+				return;
+			}
+
+			if(sourceangle < 0 || sourceangle > 8)
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + sprite + "\". Sprite angle must be in [0..8] range");
+				return;
+			}
+
+			// No rotations?
+			if(sourceangle == 0) return;
+			
+			// Gather rotations
+			string[] frames = new string[8];
+			bool[] mirror = new bool[8];
+			int processedcount = 0;
+			string sourcename = sprite.Substring(0, 4);
+			IEnumerable<string> spritenames = General.Map.Data.GetSpriteNames(sourcename);
+
+			// Process gathered sprites
+			char sourceframe = sprite[4];
+			foreach(string s in spritenames)
+			{
+				//Check first frame block
+				char targetframe = s[4];
+				if(targetframe == sourceframe)
+				{
+					// Check angle
+					int targetangle;
+					anglestr = s.Substring(5, 1);
+					if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out targetangle))
+					{
+						General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + s + "\"");
+						return;
+					}
+
+					if(targetangle < 0 || targetangle > 8)
+					{
+						General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + s + "\". Sprite angle must be in [0..8] range");
+						return;
+					}
+
+					// Add to collection
+					frames[targetangle - 1] = s;
+					processedcount++;
+				}
+
+				// Check second frame block?
+				if(s.Length == 6) continue;
+
+				targetframe = s[6];
+				if(targetframe == sourceframe)
+				{
+					// Check angle
+					int targetangle;
+					anglestr = s.Substring(7, 1);
+					if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out targetangle))
+					{
+						General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + s + "\"");
+						return;
+					}
+
+					if(targetangle < 0 || targetangle > 8)
+					{
+						General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + s + "\". Sprite angle must be in [0..8] range");
+						return;
+					}
+
+					// Add to collections
+					frames[targetangle - 1] = s;
+					mirror[targetangle - 1] = true;
+					processedcount++;
+				}
+
+				// Gathered all sprites?
+				if(processedcount == 8) break;
+			}
+
+			// Check collected data
+			if(processedcount != 8)
+			{
+				// Check which angles are missing
+				List<string> missingangles = new List<string>();
+				for(int i = 0; i < frames.Length; i++)
+				{
+					if(string.IsNullOrEmpty(frames[i]))
+						missingangles.Add((i + 1).ToString());
+				}
+
+				// Assemble angles to display
+				string ma = string.Join(", ", missingangles.ToArray());
+				if(missingangles.Count > 2)
+				{
+					int pos = ma.LastIndexOf(",", StringComparison.Ordinal);
+					if(pos != -1) ma = ma.Remove(pos, 1).Insert(pos, " and");
+				}
+
+				General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Sprite rotations " + ma + " for sprite " + sourcename + ", frame " + sourceframe + " are missing");
+				return;
+			}
+
+			// Create collection
+			spriteframe = new SpriteFrameInfo[frames.Length];
+			for(int i = 0; i < frames.Length; i++)
+			{
+				spriteframe[i] = new SpriteFrameInfo { Sprite = frames[i], SpriteLongName = Lump.MakeLongName(frames[i]), Mirror = mirror[i] };
+			}
+		}
+
 		// This is used for sorting
 		public int CompareTo(ThingTypeInfo other)
 		{
diff --git a/Source/Core/Controls/DebugConsole.cs b/Source/Core/Controls/DebugConsole.cs
index 2ad24761..6ef52a65 100644
--- a/Source/Core/Controls/DebugConsole.cs
+++ b/Source/Core/Controls/DebugConsole.cs
@@ -41,6 +41,7 @@ namespace CodeImp.DoomBuilder
 
 		private DebugMessageType filters;
 		private static long starttime = -1;
+		private static long storedtime;
 		private static int counter;
 		private static DebugConsole me;
 
@@ -144,11 +145,18 @@ namespace CodeImp.DoomBuilder
 			starttime = SlimDX.Configuration.Timer.ElapsedMilliseconds;
 		}
 
+		public static void PauseTimer()
+		{
+			if(starttime == -1) throw new InvalidOperationException("DebugConsole.StartTimer() must be called before DebugConsole.PauseTimer()!");
+			
+			storedtime += SlimDX.Configuration.Timer.ElapsedMilliseconds - starttime;
+		}
+
 		public static void StopTimer(string message) 
 		{
 			if(starttime == -1) throw new InvalidOperationException("DebugConsole.StartTimer() must be called before DebugConsole.StopTimer()!");
 
-			long duration = SlimDX.Configuration.Timer.ElapsedMilliseconds - starttime;
+			long duration = SlimDX.Configuration.Timer.ElapsedMilliseconds - starttime + storedtime;
 			
 			if(message.Contains("%"))
 				message = message.Replace("%", duration.ToString(CultureInfo.InvariantCulture));
@@ -158,6 +166,7 @@ namespace CodeImp.DoomBuilder
 			WriteLine(DebugMessageType.SPECIAL, message);
 
 			starttime = -1;
+			storedtime = 0;
 		}
 
 		public static void IncrementCounter() { IncrementCounter(1); }
diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs
index e82ffcea..7bbaacc8 100644
--- a/Source/Core/Data/DataManager.cs
+++ b/Source/Core/Data/DataManager.cs
@@ -65,6 +65,9 @@ namespace CodeImp.DoomBuilder.Data
 		public const string INTERNAL_PREFIX = "internal:";
 		public const int CLASIC_IMAGE_NAME_LENGTH = 8; //mxd
 		private const int MAX_SKYTEXTURE_SIZE = 2048; //mxd
+
+		private long UNKNOWN_THING; //mxd
+		private long MISSING_THING; //mxd
 		
 		#endregion
 
@@ -121,7 +124,7 @@ namespace CodeImp.DoomBuilder.Data
 		private ImageData hourglass3d;
 		private ImageData crosshair;
 		private ImageData crosshairbusy;
-		private Dictionary<string, ImageData> internalsprites;
+		private Dictionary<string, long> internalspriteslookup; //mxd
 		private ImageData whitetexture;
 		private ImageData blacktexture; //mxd
 		private ImageData thingtexture; //mxd
@@ -323,7 +326,6 @@ namespace CodeImp.DoomBuilder.Data
 			texturesets = new List<MatchingTextureSet>();
 			usedtextures = new Dictionary<long, bool>(); //mxd
 			usedflats = new Dictionary<long, bool>(); //mxd
-			internalsprites = new Dictionary<string, ImageData>(StringComparer.Ordinal);
 			thingcategories = General.Map.Config.GetThingCategories();
 			thingtypes = General.Map.Config.GetThingTypes();
 
@@ -586,7 +588,6 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(KeyValuePair<long, ImageData> i in textures) i.Value.Dispose();
 			foreach(KeyValuePair<long, ImageData> i in flats) i.Value.Dispose();
 			foreach(KeyValuePair<long, ImageData> i in sprites) i.Value.Dispose();
-			foreach(KeyValuePair<string, ImageData> i in internalsprites) i.Value.Dispose(); //mxd
 			palette = null;
 
 			//mxd. Dispose models
@@ -615,7 +616,6 @@ namespace CodeImp.DoomBuilder.Data
 			texturenames = null;
 			flatnames = null;
 			imageque = null;
-			internalsprites = null;
 			mapinfo = null; //mxd
 		}
 		
@@ -1499,53 +1499,60 @@ namespace CodeImp.DoomBuilder.Data
 				// Valid sprite name?
 				if(ti.Sprite.Length == 0 || ti.Sprite.Length > CLASIC_IMAGE_NAME_LENGTH) continue; //mxd
 					
-				ImageData image = null;
+				//mxd. Find all sprite angles
+				ti.SetupSpriteFrame();
 
-				// Sprite not in our collection yet?
-				if(!sprites.ContainsKey(ti.SpriteLongName)) 
+				//mxd. Load them all
+				foreach(SpriteFrameInfo info in ti.SpriteFrame)
 				{
-					//mxd. Go for all opened containers
-					bool spritefound = false;
-					if(!string.IsNullOrEmpty(ti.Sprite))
+					ImageData image = null;
+					
+					// Sprite not in our collection yet?
+					if(!sprites.ContainsKey(info.SpriteLongName))
 					{
-						for(int i = containers.Count - 1; i >= 0; i--)
+						//mxd. Go for all opened containers
+						bool spritefound = false;
+						if(!string.IsNullOrEmpty(info.Sprite))
 						{
-							// This contain provides this sprite?
-							if(containers[i].GetSpriteExists(ti.Sprite))
+							for(int i = containers.Count - 1; i >= 0; i--)
 							{
-								spritefound = true;
-								break;
+								// This contain provides this sprite?
+								if(containers[i].GetSpriteExists(info.Sprite))
+								{
+									spritefound = true;
+									break;
+								}
 							}
 						}
-					}
 
-					if(spritefound)
-					{
-						// Make new sprite image
-						image = new SpriteImage(ti.Sprite);
+						if(spritefound)
+						{
+							// Make new sprite image
+							image = new SpriteImage(info.Sprite);
 
-						// Add to collection
-						sprites.Add(ti.SpriteLongName, image);
+							// Add to collection
+							sprites.Add(info.SpriteLongName, image);
+						}
+						else
+						{
+							General.ErrorLogger.Add(ErrorType.Error, "Missing sprite lump \"" + info.Sprite + "\". Forgot to include required resources?");
+						}
 					}
 					else
 					{
-						General.ErrorLogger.Add(ErrorType.Error, "Missing sprite lump \"" + ti.Sprite + "\". Forgot to include required resources?");
+						image = sprites[info.SpriteLongName];
 					}
-				} 
-				else 
-				{
-					image = sprites[ti.SpriteLongName];
-				}
 
-				// Add to preview manager
-				if(image != null) previews.AddImage(image);
+					// Add to preview manager
+					if(image != null) previews.AddImage(image);
+				}
 			}
 			
 			// Output info
 			return sprites.Count;
 		}
 		
-		// This returns a specific patch stream
+		// This returns a specific sprite stream
 		internal Stream GetSpriteData(string pname, ref string spritelocation)
 		{
 			if(!string.IsNullOrEmpty(pname))
@@ -1553,13 +1560,13 @@ namespace CodeImp.DoomBuilder.Data
 				// Go for all opened containers
 				for(int i = containers.Count - 1; i >= 0; i--)
 				{
-					// This contain provides this patch?
+					// This contain provides this sprite?
 					Stream spritedata = containers[i].GetSpriteData(pname, ref spritelocation);
 					if(spritedata != null) return spritedata;
 				}
 			}
 			
-			// No such patch found
+			// No such sprite found
 			return null;
 		}
 
@@ -1574,12 +1581,12 @@ namespace CodeImp.DoomBuilder.Data
 				// Go for all opened containers
 				for(int i = containers.Count - 1; i >= 0; i--)
 				{
-					// This contain provides this patch?
+					// This contain provides this sprite?
 					if(containers[i].GetSpriteExists(pname)) return true;
 				}
 			}
 			
-			// No such patch found
+			// No such sprite found
 			return false;
 		}
 		
@@ -1587,39 +1594,54 @@ namespace CodeImp.DoomBuilder.Data
 		private void LoadInternalSprites()
 		{
 			// Add sprite icon files from directory
+			string name;
 			string[] files = Directory.GetFiles(General.SpritesPath, "*.png", SearchOption.TopDirectoryOnly);
+			internalspriteslookup = new Dictionary<string, long>(files.Length + 2); //mxd
 			foreach(string spritefile in files)
 			{
 				ImageData img = new FileImage(Path.GetFileNameWithoutExtension(spritefile).ToLowerInvariant(), spritefile);
 				img.LoadImage();
 				img.AllowUnload = false;
-				internalsprites.Add(img.Name, img);
+				name = INTERNAL_PREFIX + img.Name;
+				long hash = Lump.MakeLongName(name, true); //mxd
+				sprites[hash] = img; //mxd
+				internalspriteslookup[name] = hash; //mxd
 			}
 			
-			// Add some internal resources
-			if(!internalsprites.ContainsKey("nothing"))
+			// Add some internal resources.
+			// mxd. Doesn't seem to be used anywhere
+			/*name = INTERNAL_PREFIX + "nothing";
+			if(!internalspriteslookup.ContainsKey(name))
 			{
 				ImageData img = new ResourceImage("CodeImp.DoomBuilder.Resources.Nothing.png");
 				img.LoadImage();
 				img.AllowUnload = false;
-				internalsprites.Add("nothing", img);
-			}
-			
-			if(!internalsprites.ContainsKey("unknownthing"))
+				long hash = Lump.MakeLongName(name, true); //mxd
+				sprites[hash] = img; //mxd
+				internalspriteslookup[name] = hash; //mxd
+			}*/
+
+			name = INTERNAL_PREFIX + "unknownthing";
+			UNKNOWN_THING = Lump.MakeLongName(name, true);
+			if(!internalspriteslookup.ContainsKey(name))
 			{
 				ImageData img = new ResourceImage("CodeImp.DoomBuilder.Resources.UnknownThing.png");
 				img.LoadImage();
 				img.AllowUnload = false;
-				internalsprites.Add("unknownthing", img);
+				sprites[UNKNOWN_THING] = img; //mxd
+				internalspriteslookup[name] = UNKNOWN_THING; //mxd
 			}
 
 			//mxd
-			if(!internalsprites.ContainsKey("missingthing")) 
+			name = INTERNAL_PREFIX + "missingthing";
+			MISSING_THING = Lump.MakeLongName(name, true);
+			if(!internalspriteslookup.ContainsKey(name)) 
 			{
 				ImageData img = new ResourceImage("CodeImp.DoomBuilder.Resources.MissingThing.png");
 				img.LoadImage();
 				img.AllowUnload = false;
-				internalsprites.Add("missingthing", img);
+				sprites[MISSING_THING] = img; //mxd
+				internalspriteslookup[name] = MISSING_THING; //mxd
 			}
 		}
 		
@@ -1630,11 +1652,11 @@ namespace CodeImp.DoomBuilder.Data
 			if((name.Length > INTERNAL_PREFIX.Length) && name.ToLowerInvariant().StartsWith(INTERNAL_PREFIX))
 			{
 				// Get the internal sprite
-				string internalname = name.Substring(INTERNAL_PREFIX.Length).ToLowerInvariant();
-				if(internalsprites.ContainsKey(internalname))
-					return internalsprites[internalname];
+				string internalname = name.ToLowerInvariant();
+				if(internalspriteslookup.ContainsKey(internalname)) //mxd
+					return sprites[internalspriteslookup[internalname]];
 
-				return internalsprites["unknownthing"]; //mxd
+				return sprites[UNKNOWN_THING]; //mxd
 			}
 			else
 			{
@@ -1678,7 +1700,7 @@ namespace CodeImp.DoomBuilder.Data
 					}
 					else //mxd
 					{
-						ImageData img = string.IsNullOrEmpty(name) ? internalsprites["unknownthing"] : internalsprites["missingthing"];
+						ImageData img = string.IsNullOrEmpty(name) ? sprites[UNKNOWN_THING] : sprites[MISSING_THING];
 						
 						// Add to collection
 						sprites.Add(longname, img);
@@ -1689,6 +1711,17 @@ namespace CodeImp.DoomBuilder.Data
 				}
 			}
 		}
+
+		//mxd. Returns all sprite names, which start with given string
+		internal IEnumerable<string> GetSpriteNames(string startswith)
+		{
+			HashSet<string> result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+
+			foreach(DataReader reader in containers)
+				result.UnionWith(reader.GetSpriteNames(startswith));
+
+			return result;
+		}
 		
 		#endregion
 
diff --git a/Source/Core/Data/DataReader.cs b/Source/Core/Data/DataReader.cs
index 59b2469c..bf9e74de 100644
--- a/Source/Core/Data/DataReader.cs
+++ b/Source/Core/Data/DataReader.cs
@@ -209,6 +209,9 @@ namespace CodeImp.DoomBuilder.Data
 
 		// When implemented, this checks if the given sprite lump exists
 		public abstract bool GetSpriteExists(string pname);
+
+		//mxd. When implemented, returns all sprites, which name starts with given string
+		public abstract HashSet<string> GetSpriteNames(string startswith);
 		
 		#endregion
 
diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs
index d2e553e5..e70381f6 100644
--- a/Source/Core/Data/PK3StructuredReader.cs
+++ b/Source/Core/Data/PK3StructuredReader.cs
@@ -377,14 +377,14 @@ namespace CodeImp.DoomBuilder.Data
 
 		#region ================== Sprites
 
-		// This loads the textures
+		// This loads the sprites
 		public override IEnumerable<ImageData> LoadSprites(Dictionary<string, TexturesParser> cachedparsers)
 		{
-			Dictionary<long, ImageData> images = new Dictionary<long, ImageData>();
-			List<ImageData> imgset = new List<ImageData>();
-			
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
+
+			Dictionary<long, ImageData> images = new Dictionary<long, ImageData>();
+			List<ImageData> imgset = new List<ImageData>();
 			
 			// Load from wad files
 			// Note the backward order, because the last wad's images have priority
@@ -421,7 +421,32 @@ namespace CodeImp.DoomBuilder.Data
 			
 			return new List<ImageData>(images.Values);
 		}
-		
+
+		//mxd. Returns all sprites, which name starts with given string
+		public override HashSet<string> GetSpriteNames(string startswith)
+		{
+			// Error when suspended
+			if(issuspended) throw new Exception("Data reader is suspended");
+
+			HashSet<string> result = new HashSet<string>();
+
+			// Load from wad files
+			// Note the backward order, because the last wad's images have priority
+			for(int i = wads.Count - 1; i >= 0; i--)
+			{
+				result.UnionWith(wads[i].GetSpriteNames(startswith));
+			}
+
+			// Load from out own files
+			string[] files = GetAllFilesWhichTitleStartsWith(SPRITES_DIR, startswith, true);
+			foreach(string file in files)
+			{
+				result.Add(Path.GetFileNameWithoutExtension(file).ToUpperInvariant());
+			}
+
+			return result;
+		}
+
 		#endregion
 
 		#region ================== Colormaps
@@ -429,12 +454,12 @@ namespace CodeImp.DoomBuilder.Data
 		// This loads the textures
 		public override ICollection<ImageData> LoadColormaps()
 		{
-			Dictionary<long, ImageData> images = new Dictionary<long, ImageData>();
-			ICollection<ImageData> collection;
-
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
+			Dictionary<long, ImageData> images = new Dictionary<long, ImageData>();
+			ICollection<ImageData> collection;
+
 			// Load from wad files
 			// Note the backward order, because the last wad's images have priority
 			for(int i = wads.Count - 1; i >= 0; i--)
@@ -448,8 +473,7 @@ namespace CodeImp.DoomBuilder.Data
 			AddImagesToList(images, collection);
 
 			// Add images to the container-specific texture set
-			foreach(ImageData img in images.Values)
-				textureset.AddFlat(img);
+			foreach(ImageData img in images.Values) textureset.AddFlat(img);
 
 			return new List<ImageData>(images.Values);
 		}
diff --git a/Source/Core/Data/WADReader.cs b/Source/Core/Data/WADReader.cs
index a79b52ee..06437efd 100644
--- a/Source/Core/Data/WADReader.cs
+++ b/Source/Core/Data/WADReader.cs
@@ -879,7 +879,29 @@ namespace CodeImp.DoomBuilder.Data
 
 			return false;
 		}
-		
+
+		//mxd. Returns all sprites, which name starts with given string
+		public override HashSet<string> GetSpriteNames(string startswith)
+		{
+			// Error when suspended
+			if(issuspended) throw new Exception("Data reader is suspended");
+
+			HashSet<string> result = new HashSet<string>();
+			if(startswith.Length > 8) return result;
+
+			startswith = startswith.ToUpperInvariant();
+			foreach(LumpRange range in spriteranges)
+			{
+				for(int i = range.start; i < range.end + 1; i++)
+				{
+					if(file.Lumps[i].Name.StartsWith(startswith))
+						result.Add(file.Lumps[i].Name);
+				}
+			}
+
+			return result;
+		}
+
 		#endregion
 
 		#region ================== Voxels (mxd)
diff --git a/Source/Core/Rendering/Renderer2D.cs b/Source/Core/Rendering/Renderer2D.cs
index ef753b97..c3c770a6 100644
--- a/Source/Core/Rendering/Renderer2D.cs
+++ b/Source/Core/Rendering/Renderer2D.cs
@@ -1083,25 +1083,28 @@ namespace CodeImp.DoomBuilder.Rendering
 		}
 
 		//mxd
-		private static void CreateThingSpriteVerts(Vector2D screenpos, float width, float height, ref FlatVertex[] verts, int offset, int color) 
+		private static void CreateThingSpriteVerts(Vector2D screenpos, float width, float height, ref FlatVertex[] verts, int offset, int color, bool mirror)
 		{
+			float ul = (mirror ? 1f : 0f);
+			float ur = (mirror ? 0f : 1f);
+			
 			// Setup fixed rect for circle
 			verts[offset].x = screenpos.x - width;
 			verts[offset].y = screenpos.y - height;
 			verts[offset].c = color;
-			verts[offset].u = 0;
+			verts[offset].u = ul;
 			verts[offset].v = 0;
 			offset++;
 			verts[offset].x = screenpos.x + width;
 			verts[offset].y = screenpos.y - height;
 			verts[offset].c = color;
-			verts[offset].u = 1;
+			verts[offset].u = ur;
 			verts[offset].v = 0;
 			offset++;
 			verts[offset].x = screenpos.x - width;
 			verts[offset].y = screenpos.y + height;
 			verts[offset].c = color;
-			verts[offset].u = 0;
+			verts[offset].u = ul;
 			verts[offset].v = 1;
 			offset++;
 			verts[offset] = verts[offset - 2];
@@ -1111,7 +1114,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			verts[offset].x = screenpos.x + width;
 			verts[offset].y = screenpos.y + height;
 			verts[offset].c = color;
-			verts[offset].u = 1;
+			verts[offset].u = ur;
 			verts[offset].v = 1;
 		}
 		
@@ -1232,112 +1235,135 @@ namespace CodeImp.DoomBuilder.Rendering
 					// Find sprite texture
 					if(info.Sprite.Length == 0) continue;
 
-					ImageData sprite = General.Map.Data.GetSpriteImage(info.Sprite);
-					if(sprite == null) continue; 
-					if(!sprite.IsImageLoaded) 
+					// Sort by sprite angle...
+					Dictionary<int, List<Thing>> thingsbyangle = new Dictionary<int, List<Thing>>(group.Value.Count);
+					if(info.SpriteFrame.Length == 8)
 					{
-						sprite.SetUsedInMap(true);
-						continue;
-					}
-					if(sprite.Texture == null) sprite.CreateTexture();
-
-					graphics.Shaders.Things2D.Texture1 = sprite.Texture;
-					graphics.Shaders.Things2D.ApplySettings();
-
-					// Determine next lock size
-					locksize = (group.Value.Count > THING_BUFFER_SIZE) ? THING_BUFFER_SIZE : group.Value.Count;
-					verts = new FlatVertex[THING_BUFFER_SIZE * 6];
-
-					// Go for all things
-					buffercount = 0;
-					totalcount = 0;
-
-					foreach(Thing t in group.Value) 
-					{
-						if(t.IsModel && ((General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected) || (General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER && alpha == 1.0f))) continue;
-
-						bool forcespriterendering;
-						float spritewidth, spriteheight, spritescale;
-
-						// Determine sizes
-						if(t.FixedSize && scale > 1.0f)
+						foreach(Thing t in group.Value)
 						{
-							spritescale = 1.0f;
-							forcespriterendering = true; // Always render sprite when thing size is affected by FixedSize setting
-						}
-						else if(General.Settings.FixedThingsScale && t.Size * scale > FIXED_THING_SIZE)
-						{
-							spritescale =  FIXED_THING_SIZE / t.Size;
-							forcespriterendering = true; // Always render sprite when thing size is affected by FixedThingsScale setting
-						}
-						else
-						{
-							spritescale = scale;
-							forcespriterendering = false;
-						}
+							// Choose which sprite angle to show
+							int spriteangle = General.ClampAngle(-t.AngleDoom + 270) / 45;  // Convert to [0..7] range
 
-						// Calculate scaled sprite size
-						if(sprite.Width > sprite.Height)
-						{
-							spritewidth = (t.Size - THING_SPRITE_SHRINK) * spritescale;
-							spriteheight = spritewidth * ((float)sprite.Height / sprite.Width);
-						}
-						else if(sprite.Width < sprite.Height)
-						{
-							spriteheight = (t.Size - THING_SPRITE_SHRINK) * spritescale;
-							spritewidth = spriteheight * ((float)sprite.Width / sprite.Height);
-						}
-						else
-						{
-							spritewidth = (t.Size - THING_SPRITE_SHRINK) * spritescale;
-							spriteheight = spritewidth;
-						}
-
-						float spritesize = Math.Max(spritewidth, spriteheight); 
-						
-						if(!forcespriterendering && spritesize < MINIMUM_SPRITE_RADIUS)
-						{
-							// Hackish way to tell arrow rendering code to draw bigger arrow...
-							Vector3D v = thingsByPosition[t];
-							v.z = -1; 
-							thingsByPosition[t] = v;
-							
-							// Don't render tiny little sprites
-							continue; 
-						}
-						
-						CreateThingSpriteVerts(thingsByPosition[t], spritewidth, spriteheight, ref verts, buffercount * 6, (t.Selected ? selectionColor : 0xFFFFFF));
-						buffercount++;
-						totalcount++;
-
-						// Buffer filled?
-						if(buffercount == locksize) 
-						{
-							// Write to buffer
-							stream = thingsvertices.Lock(0, locksize * 6 * FlatVertex.Stride, LockFlags.Discard);
-							stream.WriteRange(verts, 0, buffercount * 6);
-							thingsvertices.Unlock();
-							stream.Dispose();
-
-							// Draw!
-							graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, buffercount * 2);
-
-							buffercount = 0;
-
-							// Determine next lock size
-							locksize = ((group.Value.Count - totalcount) > THING_BUFFER_SIZE) ? THING_BUFFER_SIZE : (group.Value.Count - totalcount);
+							// Add to collection
+							if(!thingsbyangle.ContainsKey(spriteangle)) thingsbyangle.Add(spriteangle, new List<Thing>());
+							thingsbyangle[spriteangle].Add(t);
 						}
 					}
+					else
+					{
+						thingsbyangle[0] = group.Value;
+					}
 
-					// Write to buffer
-					stream = thingsvertices.Lock(0, locksize * 6 * FlatVertex.Stride, LockFlags.Discard);
-					if(buffercount > 0) stream.WriteRange(verts, 0, buffercount * 6);
-					thingsvertices.Unlock();
-					stream.Dispose();
+					foreach(KeyValuePair<int, List<Thing>> framegroup in thingsbyangle)
+					{
+						SpriteFrameInfo sfi = info.SpriteFrame[framegroup.Key];
+						ImageData sprite = General.Map.Data.GetSpriteImage(sfi.Sprite);
+						if(sprite == null) continue;
+						if(!sprite.IsImageLoaded)
+						{
+							sprite.SetUsedInMap(true);
+							continue;
+						}
+						if(sprite.Texture == null) sprite.CreateTexture();
 
-					// Draw what's still remaining
-					if(buffercount > 0) 
-						graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, buffercount * 2);
+						graphics.Shaders.Things2D.Texture1 = sprite.Texture;
+						graphics.Shaders.Things2D.ApplySettings();
+
+						// Determine next lock size
+						locksize = (framegroup.Value.Count > THING_BUFFER_SIZE) ? THING_BUFFER_SIZE : framegroup.Value.Count;
+						verts = new FlatVertex[THING_BUFFER_SIZE * 6];
+
+						// Go for all things
+						buffercount = 0;
+						totalcount = 0;
+
+						foreach(Thing t in framegroup.Value)
+						{
+							if(t.IsModel && ((General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected) || (General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER && alpha == 1.0f)))
+								continue;
+
+							bool forcespriterendering;
+							float spritewidth, spriteheight, spritescale;
+
+							// Determine sizes
+							if(t.FixedSize && scale > 1.0f)
+							{
+								spritescale = 1.0f;
+								forcespriterendering = true; // Always render sprite when thing size is affected by FixedSize setting
+							}
+							else if(General.Settings.FixedThingsScale && t.Size * scale > FIXED_THING_SIZE)
+							{
+								spritescale = FIXED_THING_SIZE / t.Size;
+								forcespriterendering = true; // Always render sprite when thing size is affected by FixedThingsScale setting
+							}
+							else
+							{
+								spritescale = scale;
+								forcespriterendering = false;
+							}
+
+							// Calculate scaled sprite size
+							if(sprite.Width > sprite.Height)
+							{
+								spritewidth = (t.Size - THING_SPRITE_SHRINK) * spritescale;
+								spriteheight = spritewidth * ((float)sprite.Height / sprite.Width);
+							}
+							else if(sprite.Width < sprite.Height)
+							{
+								spriteheight = (t.Size - THING_SPRITE_SHRINK) * spritescale;
+								spritewidth = spriteheight * ((float)sprite.Width / sprite.Height);
+							}
+							else
+							{
+								spritewidth = (t.Size - THING_SPRITE_SHRINK) * spritescale;
+								spriteheight = spritewidth;
+							}
+
+							float spritesize = Math.Max(spritewidth, spriteheight);
+
+							if(!forcespriterendering && spritesize < MINIMUM_SPRITE_RADIUS)
+							{
+								// Hackish way to tell arrow rendering code to draw bigger arrow...
+								Vector3D v = thingsByPosition[t];
+								v.z = -1;
+								thingsByPosition[t] = v;
+
+								// Don't render tiny little sprites
+								continue;
+							}
+
+							CreateThingSpriteVerts(thingsByPosition[t], spritewidth, spriteheight, ref verts, buffercount * 6, (t.Selected ? selectionColor : 0xFFFFFF), sfi.Mirror);
+							buffercount++;
+							totalcount++;
+
+							// Buffer filled?
+							if(buffercount == locksize)
+							{
+								// Write to buffer
+								stream = thingsvertices.Lock(0, locksize * 6 * FlatVertex.Stride, LockFlags.Discard);
+								stream.WriteRange(verts, 0, buffercount * 6);
+								thingsvertices.Unlock();
+								stream.Dispose();
+
+								// Draw!
+								graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, buffercount * 2);
+
+								buffercount = 0;
+
+								// Determine next lock size
+								locksize = ((framegroup.Value.Count - totalcount) > THING_BUFFER_SIZE) ? THING_BUFFER_SIZE : (framegroup.Value.Count - totalcount);
+							}
+						}
+
+						// Write to buffer
+						stream = thingsvertices.Lock(0, locksize * 6 * FlatVertex.Stride, LockFlags.Discard);
+						if(buffercount > 0) stream.WriteRange(verts, 0, buffercount * 6);
+						thingsvertices.Unlock();
+						stream.Dispose();
+
+						// Draw what's still remaining
+						if(buffercount > 0) graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, buffercount * 2);
+					}
 				}
 
 				// Done
diff --git a/Source/Core/ZDoom/TexturesParser.cs b/Source/Core/ZDoom/TexturesParser.cs
index d01c295f..a8ce9a88 100644
--- a/Source/Core/ZDoom/TexturesParser.cs
+++ b/Source/Core/ZDoom/TexturesParser.cs
@@ -144,10 +144,10 @@ namespace CodeImp.DoomBuilder.ZDoom
 							TextureStructure tx = new TextureStructure(this, "sprite", virtualpath);
 							if(this.HasError) return false;
 
-							// if a limit for the sprite name length is set make sure that it's not exceeded
-							if(tx.Name.Length > DataManager.CLASIC_IMAGE_NAME_LENGTH)
+							//mxd. Sprite name length must be either 6 or 8 chars
+							if(tx.Name.Length != 6 && tx.Name.Length != 8)
 							{
-								ReportError("Sprite name \"" + tx.Name + "\" too long. Sprite names must have a length of " + DataManager.CLASIC_IMAGE_NAME_LENGTH + " characters or less");
+								ReportError("Sprite name \"" + tx.Name + "\" is incorrect. Sprite names must have a length of 6 or 8 characters");
 								return false;
 							}