diff --git a/Build/Configurations/Eternity_Doom2Doom.cfg b/Build/Configurations/Eternity_Doom2Doom.cfg
index ac838ceb811a9f98873b590a2168d85c79349d33..446e04d3a6e2d7124950fa039d290ff323911f24 100755
--- a/Build/Configurations/Eternity_Doom2Doom.cfg
+++ b/Build/Configurations/Eternity_Doom2Doom.cfg
@@ -26,6 +26,11 @@ include("Includes\\Doom_common.cfg", "common");
 // Settings common to Doom map format
 include("Includes\\Eternity_common.cfg", "mapformat_doom");
 
+compatibility
+{
+	include("Includes\\Eternity_common.cfg", "common.compatibility");
+}
+
 // Settings common to Doom games
 include("Includes\\Game_Doom.cfg");
 
diff --git a/Build/Configurations/Includes/Eternity_common.cfg b/Build/Configurations/Includes/Eternity_common.cfg
index 8b4a509442f7108121877b4cfea9f1cde72c46f0..0e031f4a05ecdb6c4f3fb885fa4f358a19110049 100755
--- a/Build/Configurations/Includes/Eternity_common.cfg
+++ b/Build/Configurations/Includes/Eternity_common.cfg
@@ -94,6 +94,11 @@ common
 	//mxd. Built-in Damage types
 	// ioanch: From base/things.edf
 	damagetypes = "Fist Pistol Shotgun Chaingun Plasma BFG BFG_Splash Chainsaw SShotgun BetaBFG BFGBurst Slime Lava Crush Telefrag Falling Suicide Barrel Splash Quake Rocket R_Splash BFG11k_Splash Grenade Hit PlayerMisc Fire";
+	
+	compatibility
+	{
+		fixnegativepatchoffsets = true;
+	}
 }
 
 mapformat_doom
diff --git a/Build/Configurations/Includes/ZDoom_common.cfg b/Build/Configurations/Includes/ZDoom_common.cfg
index 78df9e55e864967448f064f69bee7d8dd9ea5359..f92144a76ada1996893bc545b9e9d133ecfc6ba4 100755
--- a/Build/Configurations/Includes/ZDoom_common.cfg
+++ b/Build/Configurations/Includes/ZDoom_common.cfg
@@ -94,6 +94,11 @@ common
 	
 	//mxd. These logical sound names won't trigger a warning when they are not bound to actual sounds in SNDINFO
 	internalsoundnames = "*death *xdeath *wimpydeath *crazydeath *burndeath *gibbed *splat *pain100 *pain75 *pain50 *pain25 *grunt *land *falling *jump *fist *fistgrunt *usefail *evillaugh *weaponlaugh *puzzfail *poison *dive *surface *gasp *taunt *regenerate *drainhealth misc/i_pkup";
+	
+	compatibility
+	{
+		fixnegativepatchoffsets = true;
+	}
 }
 
 // ***********************************************************
diff --git a/Source/Core/Config/GameConfiguration.cs b/Source/Core/Config/GameConfiguration.cs
index 4f4f7376e3a55bf3dc604b99c7b2e9767da8c9a4..42d9796281eabddaba7ca92979575d04a90de064 100755
--- a/Source/Core/Config/GameConfiguration.cs
+++ b/Source/Core/Config/GameConfiguration.cs
@@ -34,6 +34,16 @@ using CodeImp.DoomBuilder.Data;
 
 namespace CodeImp.DoomBuilder.Config
 {
+	public struct CompatibilityOptions
+	{
+		public readonly bool FixNegativePatchOffsets;
+
+		public CompatibilityOptions(Configuration cfg)
+		{
+			FixNegativePatchOffsets = cfg.ReadSetting("compatibility.fixnegativepatchoffsets", false);
+		}
+	}
+
 	public class GameConfiguration
 	{
 		#region ================== Constants
@@ -185,6 +195,9 @@ namespace CodeImp.DoomBuilder.Config
         // [ZZ] compat
         private readonly bool buggymodeldefpitch;
 
+		// Compatibility options
+		CompatibilityOptions compatibility;
+
         #endregion
 
         #region ================== Properties
@@ -327,6 +340,9 @@ namespace CodeImp.DoomBuilder.Config
 
         // [ZZ] compat
         public bool BuggyModelDefPitch { get { return buggymodeldefpitch; } } // reverses +USEACTORPITCH (as in before GZDoom 2.4, but after +INHERITACTORPITCH)
+
+		// Compatibility options
+		public CompatibilityOptions Compatibility { get { return compatibility; } }
 		
 		#endregion
 
@@ -523,6 +539,9 @@ namespace CodeImp.DoomBuilder.Config
 
 			// Make door flags
 			LoadMakeDoorFlags();
+
+			// Compatibility options
+			compatibility = new CompatibilityOptions(cfg);
 		}
 
 		// Destructor
diff --git a/Source/Core/Data/TextureImage.cs b/Source/Core/Data/TextureImage.cs
index 7ec1527b0b9a7aaac4b70fe1e9b6d60047e56329..236279725c525c56f08f259a018f173393d873dd 100755
--- a/Source/Core/Data/TextureImage.cs
+++ b/Source/Core/Data/TextureImage.cs
@@ -21,7 +21,6 @@ using System.Collections.Generic;
 using System.Drawing;
 using System.Drawing.Imaging;
 using CodeImp.DoomBuilder.Rendering;
-using CodeImp.DoomBuilder.IO;
 using System.IO;
 using System.Linq;
 
@@ -80,6 +79,11 @@ namespace CodeImp.DoomBuilder.Data
 
             Bitmap bitmap = null;
             List<LogMessage> messages = new List<LogMessage>();
+			int[] columnumpatches = new int[width];
+			bool hasnegativeoffsetpatch = false;
+			Dictionary<TexturePatch, Bitmap> patchbmps = new Dictionary<TexturePatch, Bitmap>();
+
+			bool fixnegativeoffsets = General.Map.Config.Compatibility.FixNegativePatchOffsets;
 
 			// Create texture bitmap
 			try
@@ -100,49 +104,82 @@ namespace CodeImp.DoomBuilder.Data
 
 			if(!messages.Any(x => x.Type == ErrorType.Error))
 			{
-				// Go for all patches
-				foreach(TexturePatch p in patches)
+				// Load all patch bitmaps, and see if the patch has a negative vertical offset
+				foreach (TexturePatch p in patches)
 				{
 					// Get the patch data stream
 					string patchlocation = string.Empty; //mxd
 					Stream patchdata = General.Map.Data.GetPatchData(p.LumpName, p.HasLongName, ref patchlocation);
-					if(patchdata != null)
+					if (patchdata != null)
 					{
 						// Get a reader for the data
 						Bitmap patchbmp = ImageDataFormat.TryLoadImage(patchdata, ImageDataFormat.DOOMPICTURE, General.Map.Data.Palette);
-						if(patchbmp == null)
+						if (patchbmp == null)
 						{
 							//mxd. Probably that's a flat?..
-							if(General.Map.Config.MixTexturesFlats) 
+							if (General.Map.Config.MixTexturesFlats)
 							{
 								patchbmp = ImageDataFormat.TryLoadImage(patchdata, ImageDataFormat.DOOMFLAT, General.Map.Data.Palette);
 							}
-							if(patchbmp == null) 
+							if (patchbmp == null)
 							{
-                                // Data is in an unknown format!
-                                messages.Add(new LogMessage(ErrorType.Error, "Patch lump \"" + Path.Combine(patchlocation, p.LumpName) + "\" data format could not be read, while loading texture \"" + this.Name + "\". Does this lump contain valid picture data at all?"));
+								// Data is in an unknown format!
+								messages.Add(new LogMessage(ErrorType.Error, "Patch lump \"" + Path.Combine(patchlocation, p.LumpName) + "\" data format could not be read, while loading texture \"" + this.Name + "\". Does this lump contain valid picture data at all?"));
 								missingpatches++; //mxd
 							}
 						}
 
-						if(patchbmp != null)
-						{
-                            // Draw the patch
-							DrawToPixelData(patchbmp, pixels, width, height, p.X, p.Y);
-                            patchbmp.Dispose();
-						}
+						// Store the bitmap
+						patchbmps[p] = patchbmp;
 
-                        // Done
-                        patchdata.Dispose();
+						// Done
+						patchdata.Dispose();
 					}
 					else
 					{
-                        // Missing a patch lump!
-                        messages.Add(new LogMessage(ErrorType.Error, "Missing patch lump \"" + p.LumpName + "\" while loading texture \"" + this.Name + "\". Did you forget to include required resources?"));
+						// Missing a patch lump!
+						messages.Add(new LogMessage(ErrorType.Error, "Missing patch lump \"" + p.LumpName + "\" while loading texture \"" + this.Name + "\". Did you forget to include required resources?"));
 						missingpatches++; //mxd
 					}
+
+					// Check if there are any patches with negative vertical offsets
+					if (p.Y < 0)
+						hasnegativeoffsetpatch = true;
 				}
 
+				// There's a bug in vanilla Doom where negative patch offsets are ignored (the patch y offset is set to 0). If
+				// the configuration is for an engine that doesn't fix the bug we have to emulate that behavior
+				// See https://doomwiki.org/wiki/Vertical_offsets_are_ignored_in_texture_patches
+				if (!fixnegativeoffsets && hasnegativeoffsetpatch)
+				{
+					// Check which columns have more than one patch
+					foreach(TexturePatch p in patches)
+					{
+						if (patchbmps[p] == null) continue;
+
+						for(int x=0; x < patchbmps[p].Width; x++)
+						{
+							int ox = p.X + x;
+							if (ox >= 0 && ox < columnumpatches.Length)
+								columnumpatches[ox]++;
+						}
+					}
+				}
+
+				// Go for all patches
+				foreach(TexturePatch p in patches)
+				{
+					if(patchbmps.ContainsKey(p) && patchbmps[p] != null)
+					{
+                        // Draw the patch
+						DrawToPixelData(patchbmps[p], pixels, width, height, p.X, p.Y, fixnegativeoffsets, columnumpatches);
+					}
+				}
+
+				// Don't need the bitmaps anymore
+				foreach (Bitmap bmp in patchbmps.Values)
+					bmp?.Dispose();
+
 				// Done
 				bitmap.UnlockBits(bitmapdata);
 			}
@@ -158,26 +195,39 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
         // This draws the picture to the given pixel color data
-        static unsafe void DrawToPixelData(Bitmap bmp, PixelColor* target, int targetwidth, int targetheight, int x, int y)
+        static unsafe void DrawToPixelData(Bitmap bmp, PixelColor* target, int targetwidth, int targetheight, int x, int y, bool fixnegativeoffsets, int[] columnumpatches)
         {
             // Get bitmap
             int width = bmp.Size.Width;
             int height = bmp.Size.Height;
+			int patchy = y;
 
             // Lock bitmap pixels
             BitmapData bmpdata = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
             PixelColor* pixels = (PixelColor*)bmpdata.Scan0.ToPointer();
 
+			// Negative vertical offsets not allowed?
+			if (y < 0 && !fixnegativeoffsets)
+				y = 0;
+
             // Go for all pixels in the original image
             for (int ox = 0; ox < width; ox++)
             {
-                for (int oy = 0; oy < height; oy++)
+				int tx = x + ox;
+				int drawheight = height;
+
+				// If we have to emulate the negative vertical offset bug we also have to recalculate the height of the
+				// patch that is actually drawn, since it'll only draw as many pixels as it'd draw as if the negative
+				// vertical offset was taken into account
+				if (patchy < 0 && !fixnegativeoffsets && tx < width && tx >= 0 && tx < columnumpatches.Length && columnumpatches[tx] > 1)
+					drawheight = height + patchy;
+
+				for (int oy = 0; oy < drawheight; oy++)
                 {
                     // Copy this pixel?
                     if (pixels[oy * width + ox].a > 0.5f)
                     {
                         // Calculate target pixel and copy when within bounds
-                        int tx = x + ox;
                         int ty = y + oy;
                         if ((tx >= 0) && (tx < targetwidth) && (ty >= 0) && (ty < targetheight))
                             target[ty * targetwidth + tx] = pixels[oy * width + ox];
diff --git a/Source/Core/Data/TexturePatch.cs b/Source/Core/Data/TexturePatch.cs
index ffda23e14dc2405238a9549ed17b6da5345eac09..7820cc2895b7fe718c15c685b631dafbbbc7611c 100755
--- a/Source/Core/Data/TexturePatch.cs
+++ b/Source/Core/Data/TexturePatch.cs
@@ -65,7 +65,7 @@ namespace CodeImp.DoomBuilder.Data
 			// Initialize
 			this.LumpName = lumpname;
 			this.X = x;
-			this.Y = y < 0 ? 0 : y;
+			this.Y = y;
 			this.FlipX = false;
 			this.FlipY = false;
 			this.Rotate = 0;