diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1e46f5dc37fb5b650165ef49004b5f5177511483..e12b0d34532546595ea627329eba5e973567c671 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
 # DO NOT CHANGE THIS SRB2 STRING! Some variable names depend on this string.
 # Version change is fine.
 project(SRB2
-	VERSION 2.2.4
+	VERSION 2.2.6
 	LANGUAGES C)
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/appveyor.yml b/appveyor.yml
index 5d599a516c4bcf78e67b4dc0b5e2f2505df1df48..820c77e8b0c5a05b7ff6bdaa441e7048a23c86fa 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.4.{branch}-{build}
+version: 2.2.6.{branch}-{build}
 os: MinGW
 
 environment:
@@ -110,8 +110,8 @@ after_build:
 - set BUILDSARCHIVE=%REPO%-%CONFIGURATION%.7z
 - cmd: 7z a %BUILD_ARCHIVE% %BUILD_PATH% -xr!.gitignore
 - appveyor PushArtifact %BUILD_ARCHIVE%
-- cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
-- appveyor PushArtifact %BUILDSARCHIVE%
+#- cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
+#- appveyor PushArtifact %BUILDSARCHIVE%
 ##############################
 # DEPLOYER SCRIPT
 ##############################
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fa7f235a1834b127f19c321ec683171272e7e16c..520fc8929c7338d44890e9f9979c03604efdcfdd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -429,7 +429,6 @@ if(${SRB2_CONFIG_HWRENDER})
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2load.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md3load.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_model.c
-		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_trick.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/u_list.c
 	)
 
diff --git a/src/Makefile b/src/Makefile
index 4bf158d7818b603d905d0caf01ca8c87ea39a33c..9ea1ea239488fea403046535385e911a35c1eab7 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -224,7 +224,7 @@ ifdef NOHW
 else
 	OPTS+=-DHWRENDER
 	OBJS+=$(OBJDIR)/hw_bsp.o $(OBJDIR)/hw_draw.o $(OBJDIR)/hw_light.o \
-		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o $(OBJDIR)/hw_trick.o \
+		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o \
 		 $(OBJDIR)/hw_md2load.o $(OBJDIR)/hw_md3load.o $(OBJDIR)/hw_model.o $(OBJDIR)/u_list.o $(OBJDIR)/hw_batching.o
 endif
 
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 409cc4f22653141214eb1592e631a3a86f5944d8..23e6027981a94bcb652c31d68a92cdfb481c4244 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -1,3 +1,4 @@
+# vim: ft=make
 #
 # Makefile.cfg for SRB2
 #
@@ -7,6 +8,66 @@
 # and other things
 #
 
+# See the following variable don't start with 'GCC'. This is
+# to avoid a false positive with the version detection...
+
+SUPPORTED_GCC_VERSIONS:=\
+	91\
+	81 82 83\
+	71 72\
+	61 62 63 64\
+	51 52 53 54\
+	40 41 42 43 44 45 46 47 48 49
+
+LATEST_GCC_VERSION=9.1
+
+# gcc or g++
+ifdef PREFIX
+	CC=$(PREFIX)-gcc
+	CXX=$(PREFIX)-g++
+	OBJCOPY=$(PREFIX)-objcopy
+	OBJDUMP=$(PREFIX)-objdump
+	STRIP=$(PREFIX)-strip
+	WINDRES=$(PREFIX)-windres
+else
+	OBJCOPY=objcopy
+	OBJDUMP=objdump
+	STRIP=strip
+	WINDRES=windres
+endif
+
+# because Apple screws with us on this
+# need to get bintools from homebrew
+ifdef MACOSX
+	CC=clang
+	CXX=clang
+	OBJCOPY=gobjcopy
+	OBJDUMP=gobjdump
+endif
+
+# Automatically set version flag, but not if one was manually set
+ifeq   (,$(filter GCC%,$(.VARIABLES)))
+ ifneq (,$(findstring GCC,$(shell $(CC) --version))) # if it's GCC
+  version:=$(shell $(CC) -dumpversion)
+
+  # Turn version into words of major, minor
+  v:=$(subst ., ,$(version))
+  # concat. major minor
+  v:=$(word 1,$(v))$(word 2,$(v))
+
+  # If this version is not in the list, default to the latest supported
+  ifeq (,$(filter $(v),$(SUPPORTED_GCC_VERSIONS)))
+   $(info\
+		Your compiler version, GCC $(version), is not supported by the Makefile.\
+		The Makefile will assume GCC $(LATEST_GCC_VERSION).)
+   GCC$(subst .,,$(LATEST_GCC_VERSION))=1
+  else
+   $(info Detected GCC $(version) (GCC$(v)))
+   GCC$(v)=1
+  endif
+ endif
+endif
+
 ifdef GCC91
 GCC83=1
 endif
@@ -358,30 +419,6 @@ ifdef ARCHNAME
 	BIN:=$(BIN)/$(ARCHNAME)
 endif
 
-# gcc or g++
-ifdef PREFIX
-	CC=$(PREFIX)-gcc
-	CXX=$(PREFIX)-g++
-	OBJCOPY=$(PREFIX)-objcopy
-	OBJDUMP=$(PREFIX)-objdump
-	STRIP=$(PREFIX)-strip
-	WINDRES=$(PREFIX)-windres
-else
-	OBJCOPY=objcopy
-	OBJDUMP=objdump
-	STRIP=strip
-	WINDRES=windres
-endif
-
-# because Apple screws with us on this
-# need to get bintools from homebrew
-ifdef MACOSX
-	CC=clang
-	CXX=clang
-	OBJCOPY=gobjcopy
-	OBJDUMP=gobjdump
-endif
-
 OBJDUMP_OPTS?=--wide --source --line-numbers
 LD=$(CC)
 
diff --git a/src/byteptr.h b/src/byteptr.h
index 933c2af34b847ba1c5c10ef48fec205872aad4c6..01a6293b41401f9b663b6b672986a286b85e449a 100644
--- a/src/byteptr.h
+++ b/src/byteptr.h
@@ -150,15 +150,15 @@ FUNCINLINE static ATTRINLINE UINT32 readulong(void *ptr)
 
 #undef DEALIGNED
 
-#define WRITESTRINGN(p,s,n) { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');}
-#define WRITESTRING(p,s)    { size_t tmp_i = 0; for (;              s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');}
-#define WRITEMEM(p,s,n)     { memcpy(p, s, n); p += n; }
+#define WRITESTRINGN(p,s,n) do { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');} while (0)
+#define WRITESTRING(p,s)    do { size_t tmp_i = 0; for (;              s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');} while (0)
+#define WRITEMEM(p,s,n)     do { memcpy(p, s, n); p += n; } while (0)
 
 #define SKIPSTRING(p)       while (READCHAR(p) != '\0')
 
-#define READSTRINGN(p,s,n)  { size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';}
-#define READSTRING(p,s)     { size_t tmp_i = 0; for (;              (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';}
-#define READMEM(p,s,n)      { memcpy(s, p, n); p += n; }
+#define READSTRINGN(p,s,n)  ({ size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
+#define READSTRING(p,s)     ({ size_t tmp_i = 0; for (;              (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
+#define READMEM(p,s,n)      ({ memcpy(s, p, n); p += n; })
 
 #if 0 // old names
 #define WRITEBYTE(p,b)      WRITEUINT8(p,b)
diff --git a/src/command.c b/src/command.c
index fdfc95c6dd17158156e446a88f2aae210cf31fb0..4973812e7c14bf87c951c92ed385b6d1689b04a1 100644
--- a/src/command.c
+++ b/src/command.c
@@ -56,7 +56,13 @@ static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
 static boolean CV_Command(void);
 consvar_t *CV_FindVar(const char *name);
 static const char *CV_StringValue(const char *var_name);
+
 static consvar_t *consvar_vars; // list of registered console variables
+static UINT16     consvar_number_of_netids = 0;
+
+#ifdef OLD22DEMOCOMPAT
+static old_demo_var_t *consvar_old_demo_vars;
+#endif
 
 static char com_token[1024];
 static char *COM_Parse(char *data);
@@ -1121,14 +1127,16 @@ consvar_t *CV_FindVar(const char *name)
 	return NULL;
 }
 
-/** Builds a unique Net Variable identifier number, which is used
-  * in network packets instead of the full name.
+#ifdef OLD22DEMOCOMPAT
+/** Builds a unique Net Variable identifier number, which was used
+  * in network packets and demos instead of the full name.
+  *
+  * This function only still exists to keep compatibility with old demos.
   *
   * \param s Name of the variable.
   * \return A new unique identifier.
-  * \sa CV_FindNetVar
   */
-static inline UINT16 CV_ComputeNetid(const char *s)
+static inline UINT16 CV_ComputeOldDemoID(const char *s)
 {
 	UINT16 ret = 0, i = 0;
 	static UINT16 premiers[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
@@ -1142,16 +1150,47 @@ static inline UINT16 CV_ComputeNetid(const char *s)
 	return ret;
 }
 
+/** Finds a net variable based on its old style hash. If a hash collides, a
+  * warning is printed and this function returns NULL.
+  *
+  * \param chk The variable's old style hash.
+  * \return A pointer to the variable itself if found, or NULL.
+  */
+static old_demo_var_t *CV_FindOldDemoVar(UINT16 chk)
+{
+	old_demo_var_t *demovar;
+
+	for (demovar = consvar_old_demo_vars; demovar; demovar = demovar->next)
+	{
+		if (demovar->checksum == chk)
+		{
+			if (demovar->collides)
+			{
+				CONS_Alert(CONS_WARNING,
+						"Old demo netvar id %hu is a collision\n", chk);
+				return NULL;
+			}
+
+			return demovar;
+		}
+	}
+
+	return NULL;
+}
+#endif/*OLD22DEMOCOMPAT*/
+
 /** Finds a net variable based on its identifier number.
   *
   * \param netid The variable's identifier number.
   * \return A pointer to the variable itself if found, or NULL.
-  * \sa CV_ComputeNetid
   */
 static consvar_t *CV_FindNetVar(UINT16 netid)
 {
 	consvar_t *cvar;
 
+	if (netid >= consvar_number_of_netids)
+		return NULL;
+
 	for (cvar = consvar_vars; cvar; cvar = cvar->next)
 		if (cvar->netid == netid)
 			return cvar;
@@ -1161,6 +1200,32 @@ static consvar_t *CV_FindNetVar(UINT16 netid)
 
 static void Setvalue(consvar_t *var, const char *valstr, boolean stealth);
 
+#ifdef OLD22DEMOCOMPAT
+/* Sets up a netvar for compatibility with old demos. */
+static void CV_RegisterOldDemoVar(consvar_t *variable)
+{
+	old_demo_var_t *demovar;
+	UINT16 old_demo_id;
+
+	old_demo_id = CV_ComputeOldDemoID(variable->name);
+
+	demovar = CV_FindOldDemoVar(old_demo_id);
+
+	if (demovar)
+		demovar->collides = true;
+	else
+	{
+		demovar = ZZ_Calloc(sizeof *demovar);
+
+		demovar->checksum = old_demo_id;
+		demovar->cvar = variable;
+
+		demovar->next = consvar_old_demo_vars;
+		consvar_old_demo_vars = demovar;
+	}
+}
+#endif
+
 /** Registers a variable for later use from the console.
   *
   * \param variable The variable to register.
@@ -1184,11 +1249,15 @@ void CV_RegisterVar(consvar_t *variable)
 	// check net variables
 	if (variable->flags & CV_NETVAR)
 	{
-		const consvar_t *netvar;
-		variable->netid = CV_ComputeNetid(variable->name);
-		netvar = CV_FindNetVar(variable->netid);
-		if (netvar)
-			I_Error("Variables %s and %s have same netid\n", variable->name, netvar->name);
+		variable->netid = consvar_number_of_netids++;
+
+		/* in case of overflow... */
+		if (variable->netid > consvar_number_of_netids)
+			I_Error("Way too many netvars");
+
+#ifdef OLD22DEMOCOMPAT
+		CV_RegisterOldDemoVar(variable);
+#endif
 	}
 
 	// link the variable in
@@ -1448,12 +1517,100 @@ badinput:
 
 static boolean serverloading = false;
 
+static consvar_t *
+ReadNetVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	UINT16  netid;
+	char   *val;
+	boolean stealth;
+
+	consvar_t *cvar;
+
+	netid   = READUINT16 (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	cvar = CV_FindNetVar(netid);
+
+	if (cvar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+
+		DEBFILE(va("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, val));
+	}
+	else
+		CONS_Alert(CONS_WARNING, "Netvar not found with netid %hu\n", netid);
+
+	return cvar;
+}
+
+#ifdef OLD22DEMOCOMPAT
+static consvar_t *
+ReadOldDemoVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	UINT16  id;
+	char   *val;
+	boolean stealth;
+
+	old_demo_var_t *demovar;
+
+	id      = READUINT16 (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	demovar = CV_FindOldDemoVar(id);
+
+	if (demovar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+
+		return demovar->cvar;
+	}
+	else
+	{
+		CONS_Alert(CONS_WARNING, "Netvar not found with old demo id %hu\n", id);
+		return NULL;
+	}
+}
+#endif/*OLD22DEMOCOMPAT*/
+
+static consvar_t *
+ReadDemoVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	char   *name;
+	char   *val;
+	boolean stealth;
+
+	consvar_t *cvar;
+
+	name    = (char *)*p;
+	SKIPSTRING (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	cvar = CV_FindVar(name);
+
+	if (cvar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+	}
+	else
+		CONS_Alert(CONS_WARNING, "Netvar not found with name %s\n", name);
+
+	return cvar;
+}
+
 static void Got_NetVar(UINT8 **p, INT32 playernum)
 {
 	consvar_t *cvar;
-	UINT16 netid;
 	char *svalue;
-	UINT8 stealth = false;
+	boolean stealth;
 
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum) && !serverloading)
 	{
@@ -1463,23 +1620,14 @@ static void Got_NetVar(UINT8 **p, INT32 playernum)
 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
-	netid = READUINT16(*p);
-	cvar = CV_FindNetVar(netid);
-	svalue = (char *)*p;
-	SKIPSTRING(*p);
-	stealth = READUINT8(*p);
 
-	if (!cvar)
-	{
-		CONS_Alert(CONS_WARNING, "Netvar not found with netid %hu\n", netid);
-		return;
-	}
-	DEBFILE(va("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, svalue));
+	cvar = ReadNetVar(p, &svalue, &stealth);
 
-	Setvalue(cvar, svalue, stealth);
+	if (cvar)
+		Setvalue(cvar, svalue, stealth);
 }
 
-void CV_SaveNetVars(UINT8 **p)
+void CV_SaveVars(UINT8 **p, boolean in_demo)
 {
 	consvar_t *cvar;
 	UINT8 *count_p = *p;
@@ -1491,7 +1639,10 @@ void CV_SaveNetVars(UINT8 **p)
 	for (cvar = consvar_vars; cvar; cvar = cvar->next)
 		if ((cvar->flags & CV_NETVAR) && !CV_IsSetToDefault(cvar))
 		{
-			WRITEUINT16(*p, cvar->netid);
+			if (in_demo)
+				WRITESTRING(*p, cvar->name);
+			else
+				WRITEUINT16(*p, cvar->netid);
 			WRITESTRING(*p, cvar->string);
 			WRITEUINT8(*p, false);
 			++count;
@@ -1499,11 +1650,15 @@ void CV_SaveNetVars(UINT8 **p)
 	WRITEUINT16(count_p, count);
 }
 
-void CV_LoadNetVars(UINT8 **p)
+static void CV_LoadVars(UINT8 **p,
+		consvar_t *(*got)(UINT8 **p, char **ret_value, boolean *ret_stealth))
 {
 	consvar_t *cvar;
 	UINT16 count;
 
+	char *val;
+	boolean stealth;
+
 	// prevent "invalid command received"
 	serverloading = true;
 
@@ -1513,11 +1668,33 @@ void CV_LoadNetVars(UINT8 **p)
 
 	count = READUINT16(*p);
 	while (count--)
-		Got_NetVar(p, 0);
+	{
+		cvar = (*got)(p, &val, &stealth);
+
+		if (cvar)
+			Setvalue(cvar, val, stealth);
+	}
 
 	serverloading = false;
 }
 
+void CV_LoadNetVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadNetVar);
+}
+
+#ifdef OLD22DEMOCOMPAT
+void CV_LoadOldDemoVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadOldDemoVar);
+}
+#endif
+
+void CV_LoadDemoVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadDemoVar);
+}
+
 static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth);
 
 void CV_ResetCheatNetVars(void)
@@ -1574,7 +1751,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
 		// send the value of the variable
 		UINT8 buf[128];
 		UINT8 *p = buf;
-		if (!(server || (IsPlayerAdmin(consoleplayer))))
+		if (!(server || (addedtogame && IsPlayerAdmin(consoleplayer))))
 		{
 			CONS_Printf(M_GetText("Only the server or admin can change: %s %s\n"), var->name, var->string);
 			return;
diff --git a/src/command.h b/src/command.h
index 404052ce4775e301cb819931cfaad9ebe369e1f4..b39153a659802cb4fbb54d489e5bdc883f02dbe5 100644
--- a/src/command.h
+++ b/src/command.h
@@ -144,6 +144,19 @@ typedef struct consvar_s //NULL, NULL, 0, NULL, NULL |, 0, NULL, NULL, 0, 0, NUL
 	struct consvar_s *next;
 } consvar_t;
 
+#ifdef OLD22DEMOCOMPAT
+typedef struct old_demo_var old_demo_var_t;
+
+struct old_demo_var
+{
+	UINT16  checksum;
+	boolean collides;/* this var is a collision of multiple hashes */
+
+	consvar_t      *cvar;
+	old_demo_var_t *next;
+};
+#endif/*OLD22DEMOCOMPAT*/
+
 extern CV_PossibleValue_t CV_OnOff[];
 extern CV_PossibleValue_t CV_YesNo[];
 extern CV_PossibleValue_t CV_Unsigned[];
@@ -184,9 +197,18 @@ void CV_AddValue(consvar_t *var, INT32 increment);
 void CV_SaveVariables(FILE *f);
 
 // load/save gamesate (load and save option and for network join in game)
-void CV_SaveNetVars(UINT8 **p);
+void CV_SaveVars(UINT8 **p, boolean in_demo);
+
+#define CV_SaveNetVars(p) CV_SaveVars(p, false)
 void CV_LoadNetVars(UINT8 **p);
 
+#define CV_SaveDemoVars(p) CV_SaveVars(p, true)
+void CV_LoadDemoVars(UINT8 **p);
+
+#ifdef OLD22DEMOCOMPAT
+void CV_LoadOldDemoVars(UINT8 **p);
+#endif
+
 // reset cheat netvars after cheats is deactivated
 void CV_ResetCheatNetVars(void);
 
diff --git a/src/config.h.in b/src/config.h.in
index 3b25799657cdd10cec5e35f6e06775c6edf48b4a..595bea7b388f01de7375c78db36adb025b0dc9ae 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -30,12 +30,14 @@
  * Last updated 2020 / 02 / 22 - v2.2.2 - patch.pk3
  * Last updated 2020 / 05 / 10 - v2.2.3 - player.dta & patch.pk3
  * Last updated 2020 / 05 / 11 - v2.2.4 - patch.pk3
+ * Last updated 2020 / 07 / 07 - v2.2.5 - player.dta & patch.pk3
+ * Last updated 2020 / 07 / 10 - v2.2.6 - player.dta & patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
 #define ASSET_HASH_ZONES_PK3  "f7e88afb6af7996a834c7d663144bead"
-#define ASSET_HASH_PLAYER_DTA "8a4507ddf9bc0682c09174400f26ad65"
+#define ASSET_HASH_PLAYER_DTA "49dad7b24634c89728cc3e0b689e12bb"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_PK3  "bbbf6af3b20349612ee06e0b55979a76"
+#define ASSET_HASH_PATCH_PK3  "ecf00060f03c76b3e49c6ae3925b627f"
 #endif
 
 #endif
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 78d4555bd3b8af9214de0ab2cf73dae9a34cac0a..c7c5470ae75f3f996361468a6f21a90be219567a 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -5159,7 +5159,7 @@ static void SV_SendTics(void)
 		{
 			// assert supposedtics[n]>=nettics[n]
 			realfirsttic = supposedtics[n];
-			lasttictosend = min(maketic, realfirsttic + CLIENTBACKUPTICS);
+			lasttictosend = min(maketic, nettics[n] + CLIENTBACKUPTICS);
 
 			if (realfirsttic >= lasttictosend)
 			{
@@ -5496,7 +5496,7 @@ void NetUpdate(void)
 		// update node latency values so we can take an average later.
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && playernode[i] != UINT8_MAX)
-				realpingtable[i] += GetLag(playernode[i]) * (1000.00f / TICRATE);
+				realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
 		pingmeasurecount++;
 	}
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 4f5c17c95b905c37b055cd40b06e549efb8bf1b8..592734067ffc629b4525251f6d8f207fa40faa60 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3475,7 +3475,7 @@ static void Command_Version_f(void)
 #ifdef DEVELOP
 	CONS_Printf("Sonic Robo Blast 2 %s-%s (%s %s) ", compbranch, comprevision, compdate, comptime);
 #else
-	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision);
+	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision, compbranch);
 #endif
 
 	// Base library
diff --git a/src/dehacked.c b/src/dehacked.c
index 258cc1f468a5f19841d98b908c29337a5ada4c72..99d4883f6de75ebec0e3275a7d19e0c04a4f248a 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3082,6 +3082,7 @@ static actionpointer_t actionpointers[] =
 	{{A_NapalmScatter},          "A_NAPALMSCATTER"},
 	{{A_SpawnFreshCopy},         "A_SPAWNFRESHCOPY"},
 	{{A_FlickySpawn},            "A_FLICKYSPAWN"},
+	{{A_FlickyCenter},           "A_FLICKYCENTER"},
 	{{A_FlickyAim},              "A_FLICKYAIM"},
 	{{A_FlickyFly},              "A_FLICKYFLY"},
 	{{A_FlickySoar},             "A_FLICKYSOAR"},
diff --git a/src/doomdef.h b/src/doomdef.h
index 1f90a6f7f492776c212b0deecdfed1924bb33a2c..8853b4aae4978239a367bdbab7f94e764594e27f 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -143,9 +143,9 @@ extern char logfilename[1024];
 // we use comprevision and compbranch instead.
 #else
 #define VERSION    202 // Game version
-#define SUBVERSION 4  // more precise version number
-#define VERSIONSTRING "v2.2.4"
-#define VERSIONSTRINGW L"v2.2.4"
+#define SUBVERSION 6  // more precise version number
+#define VERSIONSTRING "v2.2.6"
+#define VERSIONSTRINGW L"v2.2.6"
 // Hey! If you change this, add 1 to the MODVERSION below!
 // Otherwise we can't force updates!
 #endif
@@ -204,7 +204,7 @@ extern char logfilename[1024];
 // Will always resemble the versionstring, 205 = 2.0.5, 210 = 2.1, etc.
 #define CODEBASE 220
 
-// The Modification ID; must be obtained from Rob ( https://mb.srb2.org/private.php?do=newpm&u=546 ).
+// The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/showgroups.php ).
 // DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server.
 // "18" is the default mod ID for version 2.2
 #define MODID 18
@@ -213,7 +213,7 @@ extern char logfilename[1024];
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.2.0 is not version "1".
-#define MODVERSION 44
+#define MODVERSION 47
 
 // To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically.
 // Increment MINOREXECVERSION whenever a config change is needed that does not correspond
@@ -657,4 +657,7 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// Render flats on walls
 #define WALLFLATS
 
+/// Maintain compatibility with older 2.2 demos
+#define OLD22DEMOCOMPAT
+
 #endif // __DOOMDEF__
diff --git a/src/f_finale.c b/src/f_finale.c
index eb6e283ad7ff63ff71f38ad2953107aa466e574e..8d39a75337d84482b20e129ad200f1dfb1edb430 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1674,6 +1674,18 @@ void F_GameEvaluationDrawer(void)
 			V_DrawString(8, 96, V_YELLOWMAP, "Modified games\ncan't unlock\nextras!");
 	}
 #endif
+
+	if (marathonmode)
+	{
+		const char *rtatext, *cuttext;
+		rtatext = (marathonmode & MA_INGAME) ? "In-game timer" : "RTA timer";
+		cuttext = (marathonmode & MA_NOCUTSCENES) ? "" : " w/ cutscenes";
+		if (botskin)
+			endingtext = va("%s & %s, %s%s", skins[players[consoleplayer].skin].realname, skins[botskin-1].realname, rtatext, cuttext);
+		else
+			endingtext = va("%s, %s%s", skins[players[consoleplayer].skin].realname, rtatext, cuttext);
+		V_DrawCenteredString(BASEVIDWIDTH/2, 182, V_SNAPTOBOTTOM|(ultimatemode ? V_REDMAP : V_YELLOWMAP), endingtext);
+	}
 }
 
 void F_GameEvaluationTicker(void)
diff --git a/src/g_demo.c b/src/g_demo.c
index 4dad85a3c93bd05c08c60b117a6529669cc6ebef..6c58f12fb4b7137c1c8be099cde85d3253093aaa 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -1525,7 +1525,7 @@ void G_BeginRecording(void)
 	}
 
 	// Save netvar data
-	CV_SaveNetVars(&demo_p);
+	CV_SaveDemoVars(&demo_p);
 
 	memset(&oldcmd,0,sizeof(oldcmd));
 	memset(&oldghost,0,sizeof(oldghost));
@@ -1756,6 +1756,9 @@ void G_DoPlayDemo(char *defdemoname)
 	UINT32 randseed, followitem;
 	fixed_t camerascale,shieldscale,actionspd,mindash,maxdash,normalspeed,runspeed,jumpfactor,height,spinheight;
 	char msg[1024];
+#ifdef OLD22DEMOCOMPAT
+	boolean use_old_demo_vars = false;
+#endif
 
 	skin[16] = '\0';
 	color[MAXCOLORNAME] = '\0';
@@ -1818,10 +1821,13 @@ void G_DoPlayDemo(char *defdemoname)
 	case DEMOVERSION: // latest always supported
 		cnamelen = MAXCOLORNAME;
 		break;
+#ifdef OLD22DEMOCOMPAT
 	// all that changed between then and now was longer color name
 	case 0x000c:
 		cnamelen = 16;
+		use_old_demo_vars = true;
 		break;
+#endif
 	// too old, cannot support.
 	default:
 		snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname);
@@ -1923,7 +1929,13 @@ void G_DoPlayDemo(char *defdemoname)
 	}
 
 	// net var data
-	CV_LoadNetVars(&demo_p);
+#ifdef OLD22DEMOCOMPAT
+	if (use_old_demo_vars)
+		CV_LoadOldDemoVars(&demo_p);
+	else
+#else
+		CV_LoadDemoVars(&demo_p);
+#endif
 
 	// Sigh ... it's an empty demo.
 	if (*demo_p == DEMOMARKER)
diff --git a/src/g_game.c b/src/g_game.c
index b20157156b8c6b969e09ed028c703ad23a05395c..cce4ac822b65e5ddb1248e52be6663353a9d7ac9 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3729,6 +3729,32 @@ static boolean CanSaveLevel(INT32 mapnum)
 	return (mapheaderinfo[mapnum-1] && (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME));
 }
 
+static void G_HandleSaveLevel(void)
+{
+	// do this before running the intermission or custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c
+	if (nextmap >= 1100-1)
+	{
+		if (!gamecomplete)
+			gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission
+		if (cursaveslot > 0)
+		{
+			if (marathonmode)
+			{
+				// don't keep a backup around when the run is done!
+				if (FIL_FileExists(liveeventbackup))
+					remove(liveeventbackup);
+				cursaveslot = 0;
+			}
+			else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking))
+				G_SaveGame((UINT32)cursaveslot, spstage_start);
+		}
+	}
+	// and doing THIS here means you don't lose your progress if you close the game mid-intermission
+	else if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
+		&& (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(lastmap+1))
+		G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages
+}
+
 //
 // G_DoCompleted
 //
@@ -3875,6 +3901,7 @@ static void G_DoCompleted(void)
 	if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed) || (intertype == int_none))
 	{
 		G_UpdateVisited();
+		G_HandleSaveLevel();
 		G_AfterIntermission();
 	}
 	else
@@ -3882,30 +3909,8 @@ static void G_DoCompleted(void)
 		G_SetGamestate(GS_INTERMISSION);
 		Y_StartIntermission();
 		G_UpdateVisited();
+		G_HandleSaveLevel();
 	}
-
-	// do this before running the intermission or custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c
-	if (nextmap >= 1100-1)
-	{
-		if (!gamecomplete)
-			gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission
-		if (cursaveslot > 0)
-		{
-			if (marathonmode)
-			{
-				// don't keep a backup around when the run is done!
-				if (FIL_FileExists(liveeventbackup))
-					remove(liveeventbackup);
-				cursaveslot = 0;
-			}
-			else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking))
-				G_SaveGame((UINT32)cursaveslot, spstage_start);
-		}
-	}
-	// and doing THIS here means you don't lose your progress if you close the game mid-intermission
-	else if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
-		&& (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(lastmap+1))
-		G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages
 }
 
 // See also F_EndCutscene, the only other place which handles intra-map/ending transitions
@@ -4498,7 +4503,10 @@ void G_SaveGame(UINT32 slot, INT16 mapnum)
 		P_SaveGame(mapnum);
 		if (marathonmode)
 		{
-			WRITEUINT32(save_p, marathontime);
+			UINT32 writetime = marathontime;
+			if (!(marathonmode & MA_INGAME))
+				writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map
+			WRITEUINT32(save_p, writetime);
 			WRITEUINT8(save_p, (marathonmode & ~MA_INIT));
 		}
 
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 79610cbd0c1e75af84654e770d090690c7b21eee..ed3b6afee8010f3a8a7ce6e6f4bdb4d2eadcafce 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -117,7 +117,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 			{
 				case 2 : // uhhhhhhhh..........
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+							 texel = ASTBlendPaletteIndexes(*(dest+1), texel, originPatch->style, originPatch->alpha);
 						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
@@ -126,7 +126,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
@@ -136,14 +136,14 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
 				default:
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+							 *dest = ASTBlendPaletteIndexes(*dest, texel, originPatch->style, originPatch->alpha);
 						 else
 							 *dest = texel;
 						 break;
@@ -229,7 +229,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 			{
 				case 2 : // uhhhhhhhh..........
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+							 texel = ASTBlendPaletteIndexes(*(dest+1), texel, originPatch->style, originPatch->alpha);
 						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
@@ -238,7 +238,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
@@ -248,14 +248,14 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
 				default:
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+							 *dest = ASTBlendPaletteIndexes(*dest, texel, originPatch->style, originPatch->alpha);
 						 else
 							 *dest = texel;
 						 break;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 57f3c54d90df34847d0770b4e2d3a8e0c523db3f..6ede8448bf88cc6ee2b9a7083546e2b2e059e904 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -63,11 +63,12 @@ typedef struct gl_vissprite_s
 {
 	float x1, x2;
 	float tz, ty;
+	float tracertz; // for MF2_LINKDRAW sprites, this contains tracer's tz for use in sorting
 	//lumpnum_t patchlumpnum;
 	GLPatch_t *gpatch;
 	boolean flip;
 	UINT8 translucency;       //alpha level 0-255
-	mobj_t *mobj;
+	mobj_t *mobj; // NOTE: This is a precipmobj_t if precip is true !!! Watch out.
 	boolean precip; // Tails 08-25-2002
 	boolean vflip;
    //Hurdler: 25/04/2000: now support colormap in hardware mode
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index db9dfdda791cd4c9cecba61c15bd6dc456405fe8..85d8d454e9a25df9a4db06e56d1e2419306396c6 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -2990,34 +2990,11 @@ static void HWR_Subsector(size_t num)
 	//gl_cursectorlight.blue  = light;
 	//gl_cursectorlight.alpha = light;
 
-// ----- for special tricks with HW renderer -----
-	if (gl_frontsector->pseudoSector)
-	{
-		cullFloorHeight = locFloorHeight = gl_frontsector->virtualFloorheight;
-		cullCeilingHeight = locCeilingHeight = gl_frontsector->virtualCeilingheight;
-	}
-	else if (gl_frontsector->virtualFloor)
-	{
-		///@TODO Is this whole virtualFloor mess even useful? I don't think it even triggers ever.
-		cullFloorHeight = locFloorHeight = gl_frontsector->virtualFloorheight;
-		if (gl_frontsector->virtualCeiling)
-			cullCeilingHeight = locCeilingHeight = gl_frontsector->virtualCeilingheight;
-		else
-			cullCeilingHeight = locCeilingHeight = gl_frontsector->ceilingheight;
-	}
-	else if (gl_frontsector->virtualCeiling)
-	{
-		cullCeilingHeight = locCeilingHeight = gl_frontsector->virtualCeilingheight;
-		cullFloorHeight   = locFloorHeight   = gl_frontsector->floorheight;
-	}
-	else
-	{
-		cullFloorHeight   = P_GetSectorFloorZAt  (gl_frontsector, viewx, viewy);
-		cullCeilingHeight = P_GetSectorCeilingZAt(gl_frontsector, viewx, viewy);
-		locFloorHeight    = P_GetSectorFloorZAt  (gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
-		locCeilingHeight  = P_GetSectorCeilingZAt(gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
-	}
 // ----- end special tricks -----
+	cullFloorHeight   = P_GetSectorFloorZAt  (gl_frontsector, viewx, viewy);
+	cullCeilingHeight = P_GetSectorCeilingZAt(gl_frontsector, viewx, viewy);
+	locFloorHeight    = P_GetSectorFloorZAt  (gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
+	locCeilingHeight  = P_GetSectorCeilingZAt(gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
 
 	if (gl_frontsector->ffloors)
 	{
@@ -3501,6 +3478,54 @@ static gl_vissprite_t *HWR_NewVisSprite(void)
 	return HWR_GetVisSprite(gl_visspritecount++);
 }
 
+// A hack solution for transparent surfaces appearing on top of linkdraw sprites.
+// Keep a list of linkdraw sprites and draw their shapes to the z-buffer after all other
+// sprite drawing is done. (effectively the z-buffer drawing of linkdraw sprites is delayed)
+// NOTE: This will no longer be necessary once full translucent sorting is implemented, where
+// translucent sprites and surfaces are sorted together.
+
+typedef struct
+{
+	FOutVector verts[4];
+	gr_vissprite_t *spr;
+} zbuffersprite_t;
+
+// this list is used to store data about linkdraw sprites
+zbuffersprite_t linkdrawlist[MAXVISSPRITES];
+UINT32 linkdrawcount = 0;
+
+// add the necessary data to the list for delayed z-buffer drawing
+static void HWR_LinkDrawHackAdd(FOutVector *verts, gr_vissprite_t *spr)
+{
+	if (linkdrawcount < MAXVISSPRITES)
+	{
+		memcpy(linkdrawlist[linkdrawcount].verts, verts, sizeof(FOutVector) * 4);
+		linkdrawlist[linkdrawcount].spr = spr;
+		linkdrawcount++;
+	}
+}
+
+// process and clear the list of sprites for delayed z-buffer drawing
+static void HWR_LinkDrawHackFinish(void)
+{
+	UINT32 i;
+	FSurfaceInfo surf;
+	surf.PolyColor.rgba = 0xFFFFFFFF;
+	surf.TintColor.rgba = 0xFFFFFFFF;
+	surf.FadeColor.rgba = 0xFFFFFFFF;
+	surf.LightInfo.light_level = 0;
+	surf.LightInfo.fade_start = 0;
+	surf.LightInfo.fade_end = 31;
+	for (i = 0; i < linkdrawcount; i++)
+	{
+		// draw sprite shape, only to z-buffer
+		HWR_GetPatch(linkdrawlist[i].spr->gpatch);
+		HWR_ProcessPolygon(&surf, linkdrawlist[i].verts, 4, PF_Translucent|PF_Occlude|PF_Invisible|PF_Clip, 0, false);
+	}
+	// reset list
+	linkdrawcount = 0;
+}
+
 //
 // HWR_DoCulling
 // Hardware version of R_DoCulling
@@ -3687,6 +3712,8 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	extracolormap_t *colormap;
 	FUINT lightlevel;
 	FBITFIELD blend = 0;
+	FBITFIELD occlusion;
+	boolean use_linkdraw_hack = false;
 	UINT8 alpha;
 
 	INT32 i;
@@ -3780,10 +3807,18 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	// co-ordinates
 	memcpy(wallVerts, baseWallVerts, sizeof(baseWallVerts));
 
+	// if sprite has linkdraw, then dont write to z-buffer (by not using PF_Occlude)
+	// this will result in sprites drawn afterwards to be drawn on top like intended when using linkdraw.
+	if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+		occlusion = 0;
+	else
+		occlusion = PF_Occlude;
+
 	if (!cv_translucency.value) // translucency disabled
 	{
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = PF_Translucent|PF_Occlude;
+		blend = PF_Translucent|occlusion;
+		if (!occlusion) use_linkdraw_hack = true;
 	}
 	else if (spr->mobj->flags2 & MF2_SHADOW)
 	{
@@ -3799,7 +3834,8 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		// Hurdler: PF_Environement would be cool, but we need to fix
 		//          the issue with the fog before
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = PF_Translucent|PF_Occlude;
+		blend = PF_Translucent|occlusion;
+		if (!occlusion) use_linkdraw_hack = true;
 	}
 
 	alpha = Surf.PolyColor.s.alpha;
@@ -3904,6 +3940,9 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, 3, false); // sprite shader
 
+		if (use_linkdraw_hack)
+			HWR_LinkDrawHackAdd(wallVerts, spr);
+
 		top = bot;
 		endtop = endbot;
 	}
@@ -3929,6 +3968,9 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	Surf.PolyColor.s.alpha = alpha;
 
 	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, 3, false); // sprite shader
+
+	if (use_linkdraw_hack)
+		HWR_LinkDrawHackAdd(wallVerts, spr);
 }
 
 // -----------------+
@@ -4051,10 +4093,21 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 
 	{
 		FBITFIELD blend = 0;
+		FBITFIELD occlusion;
+		boolean use_linkdraw_hack = false;
+
+		// if sprite has linkdraw, then dont write to z-buffer (by not using PF_Occlude)
+		// this will result in sprites drawn afterwards to be drawn on top like intended when using linkdraw.
+		if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+			occlusion = 0;
+		else
+			occlusion = PF_Occlude;
+
 		if (!cv_translucency.value) // translucency disabled
 		{
 			Surf.PolyColor.s.alpha = 0xFF;
-			blend = PF_Translucent|PF_Occlude;
+			blend = PF_Translucent|occlusion;
+			if (!occlusion) use_linkdraw_hack = true;
 		}
 		else if (spr->mobj->flags2 & MF2_SHADOW)
 		{
@@ -4070,10 +4123,14 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 			// Hurdler: PF_Environement would be cool, but we need to fix
 			//          the issue with the fog before
 			Surf.PolyColor.s.alpha = 0xFF;
-			blend = PF_Translucent|PF_Occlude;
+			blend = PF_Translucent|occlusion;
+			if (!occlusion) use_linkdraw_hack = true;
 		}
 
 		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, 3, false); // sprite shader
+
+		if (use_linkdraw_hack)
+			HWR_LinkDrawHackAdd(wallVerts, spr);
 	}
 }
 
@@ -4189,6 +4246,7 @@ static int CompareVisSprites(const void *p1, const void *p2)
 	gl_vissprite_t* spr2 = *(gl_vissprite_t*const*)p2;
 	int idiff;
 	float fdiff;
+	float tz1, tz2;
 
 	// Make transparent sprites last. Comment from the previous sort implementation:
 	// Sryder:	Oh boy, while it's nice having ALL the sprites sorted properly, it fails when we bring MD2's into the
@@ -4196,12 +4254,53 @@ static int CompareVisSprites(const void *p1, const void *p2)
 	//			everything else, but still ordered of course, the depth buffer can handle the opaque ones plenty fine.
 	//			We just need to move all translucent ones to the end in order
 	// TODO:	Fully sort all sprites and MD2s with walls and floors, this part will be unnecessary after that
-	int transparency1 = (spr1->mobj->flags2 & MF2_SHADOW) || (spr1->mobj->frame & FF_TRANSMASK);
-	int transparency2 = (spr2->mobj->flags2 & MF2_SHADOW) || (spr2->mobj->frame & FF_TRANSMASK);
+	int transparency1;
+	int transparency2;
+
+	// check for precip first, because then sprX->mobj is actually a precipmobj_t and does not have flags2 or tracer
+	int linkdraw1 = !spr1->precip && (spr1->mobj->flags2 & MF2_LINKDRAW) && spr1->mobj->tracer;
+	int linkdraw2 = !spr2->precip && (spr2->mobj->flags2 & MF2_LINKDRAW) && spr2->mobj->tracer;
+
+	// ^ is the XOR operation
+	// if comparing a linkdraw and non-linkdraw sprite or 2 linkdraw sprites with different tracers, then use
+	// the tracer's properties instead of the main sprite's.
+	if ((linkdraw1 && linkdraw2 && spr1->mobj->tracer != spr2->mobj->tracer) || (linkdraw1 ^ linkdraw2))
+	{
+		if (linkdraw1)
+		{
+			tz1 = spr1->tracertz;
+			transparency1 = (spr1->mobj->tracer->flags2 & MF2_SHADOW) || (spr1->mobj->tracer->frame & FF_TRANSMASK);
+		}
+		else
+		{
+			tz1 = spr1->tz;
+			transparency1 = (!spr1->precip && (spr1->mobj->flags2 & MF2_SHADOW)) || (spr1->mobj->frame & FF_TRANSMASK);
+		}
+		if (linkdraw2)
+		{
+			tz2 = spr2->tracertz;
+			transparency2 = (spr2->mobj->tracer->flags2 & MF2_SHADOW) || (spr2->mobj->tracer->frame & FF_TRANSMASK);
+		}
+		else
+		{
+			tz2 = spr2->tz;
+			transparency2 = (!spr2->precip && (spr2->mobj->flags2 & MF2_SHADOW)) || (spr2->mobj->frame & FF_TRANSMASK);
+		}
+	}
+	else
+	{
+		tz1 = spr1->tz;
+		transparency1 = (!spr1->precip && (spr1->mobj->flags2 & MF2_SHADOW)) || (spr1->mobj->frame & FF_TRANSMASK);
+		tz2 = spr2->tz;
+		transparency2 = (!spr2->precip && (spr2->mobj->flags2 & MF2_SHADOW)) || (spr2->mobj->frame & FF_TRANSMASK);
+	}
+
+	// first compare transparency flags, then compare tz, then compare dispoffset
+
 	idiff = transparency1 - transparency2;
 	if (idiff != 0) return idiff;
 
-	fdiff = spr2->tz - spr1->tz; // this order seems correct when checking with apitrace. Back to front.
+	fdiff = tz2 - tz1; // this order seems correct when checking with apitrace. Back to front.
 	if (fabsf(fdiff) < 1.0E-36f)
 		return spr1->dispoffset - spr2->dispoffset; // smallest dispoffset first if sprites are at (almost) same location.
 	else if (fdiff > 0)
@@ -4525,6 +4624,7 @@ static void HWR_CreateDrawNodes(void)
 static void HWR_DrawSprites(void)
 {
 	UINT32 i;
+	boolean skipshadow = false; // skip shadow if it was drawn already for a linkdraw sprite encountered earlier in the list
 	HWD.pfnSetSpecialState(HWD_SET_MODEL_LIGHTING, cv_glmodellighting.value);
 	for (i = 0; i < gl_visspritecount; i++)
 	{
@@ -4535,11 +4635,32 @@ static void HWR_DrawSprites(void)
 		else
 #endif
 		{
-			if (spr->mobj && spr->mobj->shadowscale && cv_shadow.value)
+			if (spr->mobj && spr->mobj->shadowscale && cv_shadow.value && !skipshadow)
 			{
 				HWR_DrawDropShadow(spr->mobj, spr->mobj->shadowscale);
 			}
 
+			if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+			{
+				// If this linkdraw sprite is behind a sprite that has a shadow,
+				// then that shadow has to be drawn first, otherwise the shadow ends up on top of
+				// the linkdraw sprite because the linkdraw sprite does not modify the z-buffer.
+				// The !skipshadow check is there in case there are multiple linkdraw sprites connected
+				// to the same tracer, so the tracer's shadow only gets drawn once.
+				if (cv_shadow.value && !skipshadow && spr->dispoffset < 0 && spr->mobj->tracer->shadowscale)
+				{
+					HWR_DrawDropShadow(spr->mobj->tracer, spr->mobj->tracer->shadowscale);
+					skipshadow = true;
+					// The next sprite in this loop should be either another linkdraw sprite or the tracer.
+					// When the tracer is inevitably encountered, skipshadow will cause it's shadow
+					// to get skipped and skipshadow will get set to false by the 'else' clause below.
+				}
+			}
+			else
+			{
+				skipshadow = false;
+			}
+
 			if (spr->mobj && spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
 			{
 				if (!cv_glmodels.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
@@ -4563,6 +4684,16 @@ static void HWR_DrawSprites(void)
 		}
 	}
 	HWD.pfnSetSpecialState(HWD_SET_MODEL_LIGHTING, 0);
+
+	// At the end of sprite drawing, draw shapes of linkdraw sprites to z-buffer, so they
+	// don't get drawn over by transparent surfaces.
+	HWR_LinkDrawHackFinish();
+	// Work around a r_opengl.c bug with PF_Invisible by making this SetBlend call
+	// where PF_Invisible is off and PF_Masked is on.
+	// (Other states probably don't matter. Here I left them same as in LinkDrawHackFinish)
+	// Without this workaround the rest of the draw calls in this frame (including UI, screen texture)
+	// can get drawn using an incorrect glBlendFunc, resulting in a occasional black screen.
+	HWD.pfnSetBlend(PF_Translucent|PF_Occlude|PF_Clip|PF_Masked);
 }
 
 // --------------------------------------------------------------------------
@@ -4624,6 +4755,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	gl_vissprite_t *vis;
 	float tr_x, tr_y;
 	float tz;
+	float tracertz = 0.0f;
 	float x1, x2;
 	float rightsin, rightcos;
 	float this_scale;
@@ -4638,6 +4770,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
 	boolean mirrored = thing->mirrored;
 	boolean hflip = (!(thing->frame & FF_HORIZONTALFLIP) != !mirrored);
+	INT32 dispoffset;
 
 	angle_t ang;
 	INT32 heightsec, phs;
@@ -4655,6 +4788,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (!thing)
 		return;
 
+	dispoffset = thing->info->dispoffset;
+
 	this_scale = FIXED_TO_FLOAT(thing->scale);
 
 	// transform the origin point
@@ -4867,9 +5002,28 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if ((thing->flags2 & MF2_LINKDRAW) && thing->tracer)
 	{
-		// bodge support - not nearly as comprehensive as r_things.c, but better than nothing
 		if (! R_ThingVisible(thing->tracer))
 			return;
+
+		// calculate tz for tracer, same way it is calculated for this sprite
+		// transform the origin point
+		tr_x = FIXED_TO_FLOAT(thing->tracer->x) - gr_viewx;
+		tr_y = FIXED_TO_FLOAT(thing->tracer->y) - gr_viewy;
+
+		// rotation around vertical axis
+		tracertz = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
+
+		// Software does not render the linkdraw sprite if the tracer is behind the view plane,
+		// so do the same check here.
+		// NOTE: This check has the same flaw as the view plane check at the beginning of HWR_ProjectSprite:
+		// the view aiming angle is not taken into account, leading to sprites disappearing too early when they
+		// can still be seen when looking down/up at steep angles.
+		if (tracertz < ZCLIP_PLANE)
+			return;
+
+		// if the sprite is behind the tracer, invert dispoffset, putting the sprite behind the tracer
+		if (tz > tracertz)
+			dispoffset *= -1;
 	}
 
 	// store information in a vissprite
@@ -4877,7 +5031,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->x1 = x1;
 	vis->x2 = x2;
 	vis->tz = tz; // Keep tz for the simple sprite sorting that happens
-	vis->dispoffset = thing->info->dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
+	vis->tracertz = tracertz;
+	vis->dispoffset = dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
 	//vis->patchlumpnum = sprframe->lumppat[rot];
 #ifdef ROTSPRITE
 	if (rotsprite)
@@ -5688,7 +5843,7 @@ static CV_PossibleValue_t grshearing_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Thi
 static void CV_glfiltermode_OnChange(void);
 static void CV_glanisotropic_OnChange(void);
 
-static CV_PossibleValue_t grfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSAMPLED, "Nearest"},
+static CV_PossibleValue_t glfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSAMPLED, "Nearest"},
 	{HWD_SET_TEXTUREFILTER_BILINEAR, "Bilinear"}, {HWD_SET_TEXTUREFILTER_TRILINEAR, "Trilinear"},
 	{HWD_SET_TEXTUREFILTER_MIXED1, "Linear_Nearest"},
 	{HWD_SET_TEXTUREFILTER_MIXED2, "Nearest_Linear"},
@@ -5716,12 +5871,11 @@ consvar_t cv_glskydome = {"gr_skydome", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL,
 consvar_t cv_glfakecontrast = {"gr_fakecontrast", "Smooth", CV_SAVE, grfakecontrast_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_glslopecontrast = {"gr_slopecontrast", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_glfiltermode = {"gr_filtermode", "Nearest", CV_SAVE|CV_CALL, grfiltermode_cons_t,
+consvar_t cv_glfiltermode = {"gr_filtermode", "Nearest", CV_SAVE|CV_CALL, glfiltermode_cons_t,
                              CV_glfiltermode_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_glanisotropicmode = {"gr_anisotropicmode", "1", CV_CALL, granisotropicmode_cons_t,
                              CV_glanisotropic_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_glcorrecttricks = {"gr_correcttricks", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_glsolvetjoin = {"gr_solvetjoin", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_glbatching = {"gr_batching", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -5761,7 +5915,6 @@ void HWR_AddCommands(void)
 	CV_RegisterVar(&cv_glshaders);
 
 	CV_RegisterVar(&cv_glfiltermode);
-	CV_RegisterVar(&cv_glcorrecttricks);
 	CV_RegisterVar(&cv_glsolvetjoin);
 
 	CV_RegisterVar(&cv_renderstats);
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index f404d8c5e80d6738afdd6f5f1dd7aa7a5be36c15..ddb3696b6f6810339b9f0170ddaaa09a552c0fc5 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -52,7 +52,6 @@ boolean HWR_Screenshot(const char *pathname);
 
 void HWR_AddCommands(void);
 void HWR_AddSessionCommands(void);
-void HWR_CorrectSWTricks(void);
 void transform(float *cx, float *cy, float *cz);
 FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf);
 INT32 HWR_GetTextureUsed(void);
@@ -87,7 +86,6 @@ extern consvar_t cv_glmodelinterpolation;
 extern consvar_t cv_glmodellighting;
 extern consvar_t cv_glfiltermode;
 extern consvar_t cv_glanisotropicmode;
-extern consvar_t cv_glcorrecttricks;
 extern consvar_t cv_fovchange;
 extern consvar_t cv_glsolvetjoin;
 extern consvar_t cv_glshearing;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index c311d64623205e9f859adde604d7caec2003eedb..b23b3600c16db46173c79c72f5d967ce455f681b 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1177,6 +1177,34 @@ static UINT8 HWR_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t
 	return spr2;
 }
 
+static void adjustTextureCoords(model_t *model, GLPatch_t *gpatch)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		int j;
+		mesh_t *mesh = &model->meshes[i];
+		int numVertices;
+		float *uvPtr = mesh->uvs;
+
+		// i dont know if this is actually possible, just logical conclusion of structure in CreateModelVBOs
+		if (!mesh->frames && !mesh->tinyframes) return;
+
+		if (mesh->frames) // again CreateModelVBO and CreateModelVBOTiny iterate like this so I'm gonna do that too
+			numVertices = mesh->numTriangles * 3;
+		else
+			numVertices = mesh->numVertices;
+
+		// fix uvs (texture coordinates) to take into account that the actual texture
+		// has empty space added until the next power of two
+		for (j = 0; j < numVertices; j++)
+		{
+			*uvPtr++ *= gpatch->max_s;
+			*uvPtr++ *= gpatch->max_t;
+		}
+	}
+}
+
 //
 // HWR_DrawModel
 //
@@ -1278,6 +1306,19 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 			sprinfo = &spriteinfo[spr->mobj->sprite];
 		}
 
+		// texture loading before model init, so it knows if sprite graphics are used, which
+		// means that texture coordinates have to be adjusted
+		gpatch = md2->grpatch;
+		if (!gpatch || ((!gpatch->mipmap->grInfo.format || !gpatch->mipmap->downloaded) && !md2->notexturefile))
+			md2_loadTexture(md2);
+		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
+
+		if ((gpatch && gpatch->mipmap->grInfo.format) // don't load the blend texture if the base texture isn't available
+			&& (!md2->blendgrpatch
+			|| ((!((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded)
+			&& !md2->noblendfile)))
+			md2_loadBlendTexture(md2);
+
 		if (md2->error)
 			return false; // we already failed loading this before :(
 		if (!md2->model)
@@ -1289,6 +1330,10 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 			if (md2->model)
 			{
 				md2_printModelInfo(md2->model);
+				// if model uses sprite patch as texture, then
+				// adjust texture coordinates to take power of two textures into account
+				if (!gpatch || !gpatch->mipmap->grInfo.format)
+					adjustTextureCoords(md2->model, spr->gpatch);
 				HWD.pfnCreateModelVBOs(md2->model);
 			}
 			else
@@ -1306,16 +1351,6 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		//HWD.pfnSetBlend(blend); // This seems to actually break translucency?
 		finalscale = md2->scale;
 		//Hurdler: arf, I don't like that implementation at all... too much crappy
-		gpatch = md2->grpatch;
-		if (!gpatch || ((!gpatch->mipmap->format || !gpatch->mipmap->downloaded) && !md2->notexturefile))
-			md2_loadTexture(md2);
-		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
-
-		if ((gpatch && gpatch->mipmap->format) // don't load the blend texture if the base texture isn't available
-			&& (!md2->blendgrpatch
-			|| ((!((GLPatch_t *)md2->blendgrpatch)->mipmap->format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded)
-			&& !md2->noblendfile)))
-			md2_loadBlendTexture(md2);
 
 		if (gpatch && gpatch->mipmap->format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
 		{
diff --git a/src/hardware/hw_trick.c b/src/hardware/hw_trick.c
deleted file mode 100644
index 312be66d46f58c18a6d3da16073757c62f488d8e..0000000000000000000000000000000000000000
--- a/src/hardware/hw_trick.c
+++ /dev/null
@@ -1,914 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1998-2001 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief special trick routines to make some SW tricks look OK with
-///	HW rendering. This includes:
-///	- deepwatereffect (e.g. tnt/map02)
-///	- invisible staircase (e.g. eternal/map02)
-///	- floating ceilings (e.g. eternal/map03)
-///
-///	It is not guaranteed that it looks identical to the SW mode,
-///	but it looks in most of the cases far better than having
-///	holes in the architecture, HOM, etc.
-///
-///	It fixes as well missing textures, which are replaced by either
-///	a default texture or the midtexture.
-///
-///	words of notice:
-///	pseudosectors, as mentioned in this file, are sectors where both
-///	sidedefs point to the same sector. This expression is also used
-///	for sectors which are enclosed by another sector but have no
-///	correct sidedefs at all
-///
-///	if a vertex is inside a poly is determined by the angles between
-///	this vertex and all angles on the linedefs (imagine walking along
-///	a circle always facing a certain point inside/outside the circle;
-///	if inside, angle have taken all values [0..pi), otherwise the
-///	range was < pi/2
-
-#include <math.h>
-#include "../doomdef.h"
-#include "../doomstat.h"
-
-#ifdef HWRENDER
-#include "hw_glob.h"
-#include "hw_dll.h"
-#include "../r_local.h"
-#include "../i_system.h"
-
-//
-// add a line to a sectors list of lines
-//
-static void addLineToChain(sector_t *sector, line_t *line)
-{
-	linechain_t *thisElem = NULL, *nextElem;
-
-	if (!sector)
-		return;
-
-	nextElem = sector->sectorLines;
-
-	while (nextElem) // walk through chain
-	{
-		thisElem = nextElem;
-		nextElem = thisElem->next;
-	}
-
-	// add a new element into the chain
-	if (thisElem)
-	{
-		thisElem->next = malloc(sizeof (linechain_t));
-		if (thisElem->next)
-		{
-			thisElem->next->line = line;
-			thisElem->next->next = NULL;
-		}
-		else
-		{
-			I_Error("Out of memory in addLineToChain(.)\n");
-		}
-	}
-	else // first element in chain
-	{
-		sector->sectorLines =  malloc(sizeof (linechain_t));
-		if (sector->sectorLines)
-		{
-			sector->sectorLines->line = line;
-			sector->sectorLines->next = NULL;
-		}
-		else
-		{
-			I_Error("Out of memory in addLineToChain(.)\n");
-		}
-	}
-}
-
-//
-// We dont want a memory hole, do we?;-)
-//
-static void releaseLineChains(void)
-{
-	linechain_t *thisElem, *nextElem;
-	sector_t *sector;
-	size_t i;
-
-	for (i = 0; i < numsectors; i++)
-	{
-		sector = &sectors[i];
-		nextElem = sector->sectorLines;
-
-		while (nextElem)
-		{
-			thisElem = nextElem;
-			nextElem = thisElem->next;
-			free(thisElem);
-		}
-
-		sector->sectorLines = NULL;
-	}
-}
-
-//
-// check if a pseudo sector is valid by checking all its linedefs
-//
-static boolean isPSectorValid(sector_t *sector)
-{
-	linechain_t *thisElem, *nextElem;
-
-	if (!sector->pseudoSector) // check only pseudosectors, others dont care
-	{
-#ifdef PARANOIA
-		CONS_Debug(DBG_RENDER, "Alert! non-pseudosector fed to isPSectorClosed()\n");
-#endif
-		return false;
-	}
-
-	nextElem = sector->sectorLines;
-
-	while (nextElem)
-	{
-		thisElem = nextElem;
-		nextElem = thisElem->next;
-		if (thisElem->line->frontsector != thisElem->line->backsector)
-			return false;
-	}
-	return true;
-}
-
-//
-// angles are always phiMax-phiMin [0...2\pi)
-//
-FUNCMATH static double phiDiff(double phiMin, double phiMax)
-{
-	double result;
-
-	result = phiMax-phiMin;
-
-	if (result < 0.0l)
-		result += 2.0l*M_PIl;
-
-	return result;
-}
-
-//
-// sort phi's so that enclosed angle < \pi
-//
-static void sortPhi(double phi1, double phi2, double *phiMin, double *phiMax)
-{
-	if (phiDiff(phi1, phi2) < M_PIl)
-	{
-		*phiMin = phi1;
-		*phiMax = phi2;
-	}
-	else
-	{
-		*phiMin = phi2;
-		*phiMax = phi1;
-	}
-}
-
-//
-// return if angle(phi1, phi2) is bigger than \pi
-// if so, the vertex lies inside the poly
-//
-FUNCMATH static boolean biggerThanPi(double phi1, double phi2)
-{
-	if (phiDiff(phi1, phi2) > M_PIl)
-		return true;
-
-	return false;
-}
-
-#define DELTAPHI (M_PIl/100.0l) // some small phi << \pi
-
-//
-// calculate bounds for minimum angle
-//
-static void phiBounds(double phi1, double phi2, double *phiMin, double *phiMax)
-{
-	double phi1Tmp, phi2Tmp;
-	double psi1, psi2, psi3, psi4, psi5, psi6, psi7; // for optimization
-
-	sortPhi(phi1, phi2, &phi1Tmp, &phi2Tmp);
-	phi1 = phi1Tmp;
-	phi2 = phi2Tmp;
-
-	// check start condition
-	if (*phiMin > M_PIl || *phiMax > M_PIl)
-	{
-		*phiMin = phi1;
-		*phiMax = phi2;
-		return;
-	}
-
-	// 6 cases:
-	// new angles inbetween phiMin, phiMax -> forget it
-	// new angles enclose phiMin -> set phiMin
-	// new angles enclose phiMax -> set phiMax
-	// new angles completely outside phiMin, phiMax -> leave largest area free
-	// new angles close the range completely!
-	// new angles enlarges range on both sides
-
-	psi1 = phiDiff(*phiMin, phi1);
-	psi2 = phiDiff(*phiMin, phi2);
-	psi3 = phiDiff(*phiMax, phi1);
-	psi4 = phiDiff(*phiMax, phi2);
-	psi5 = phiDiff(*phiMin, *phiMax);
-	psi6 = (double)(2.0l*M_PIl - psi5); // phiDiff(*phiMax, *phiMin);
-	psi7 = (double)(2.0l*M_PIl - psi2); // phiDiff(phi2, *phiMin);
-
-	// case 1 & 5!
-	if ((psi1 <= psi5) && (psi2 <= psi5))
-	{
-		if (psi1 <= psi2) // case 1
-		{
-			return;
-		}
-		else // case 5
-		{
-			// create some artificial interval here not to get into numerical trouble
-			// in fact we know now the sector is completely enclosed -> base for computational optimization
-			*phiMax = 0.0l;
-			*phiMin = DELTAPHI;
-			return;
-		}
-	}
-
-	// case 2
-	if ((psi1 >= psi5) && (psi2 <= psi5))
-	{
-		*phiMin = phi1;
-		return;
-	}
-
-	// case 3
-	if ((psi3 >= psi6) && (psi4 <= psi6))
-	{
-		*phiMax = phi2;
-		return;
-	}
-
-	// case 4 & 6
-#ifdef PARANOIA
-	if ((psi3 <= psi6) && (psi4 <= psi6)) // FIXME: isn't this case implicitly true anyway??
-#endif
-	{
-		if (psi3 <= psi4) //case 4
-		{
-			if (psi3 >= psi7)
-			{
-				*phiMin = phi1;
-				return;
-			}
-			else
-			{
-				*phiMax = phi2;
-				return;
-			}
-		}
-		else // case 6
-		{
-			*phiMin = phi1;
-			*phiMax = phi2;
-			return;
-		}
-	}
-
-#ifdef PARANOIA
-	CONS_Debug(DBG_RENDER, "phiMin = %f, phiMax = %f, phi1 = %f, phi2 = %f\n", *phiMin, *phiMax, phi1, phi2);
-	I_Error("phiBounds() out of range!\n");
-#endif
-}
-
-//
-// Check if a vertex lies inside a sector
-// This works for "well-behaved" convex polygons
-// If we need it mathematically correct, we need to sort the
-// linedefs first so we have them in a row, then walk along the linedefs,
-// but this is a bit overdone
-//
-static inline boolean isVertexInside(vertex_t *vertex, sector_t *sector)
-{
-	double xa, ya, xe, ye;
-	linechain_t *chain;
-	double phiMin, phiMax;
-	double phi1, phi2;
-
-	chain = sector->sectorLines;
-	phiMin = phiMax = 10.0l*M_PIl; // some value > \pi
-
-	while (chain)
-	{
-		// start and end vertex
-		xa = (double)chain->line->v1->x - (double)vertex->x;
-		ya = (double)chain->line->v1->y - (double)vertex->y;
-		xe = (double)chain->line->v2->x - (double)vertex->x;
-		ye = (double)chain->line->v2->y - (double)vertex->y;
-
-		// angle phi of connection between the vertices and the x-axis
-		phi1 = atan2(ya, xa);
-		phi2 = atan2(ye, xe);
-
-		// if we have just started, we can have to create start bounds for phi
-
-		phiBounds(phi1, phi2, &phiMin, &phiMax);
-		chain = chain->next;
-	}
-
-	return biggerThanPi(phiMin, phiMax);
-}
-
-
-#define MAXSTACK 256 // Not more than 256 polys in each other?
-//
-// generate a list of sectors which enclose the given sector
-//
-static void generateStacklist(sector_t *thisSector)
-{
-	size_t i, stackCnt = 0;
-	sector_t *locStacklist[MAXSTACK];
-	sector_t *checkSector;
-
-	for (i = 0; i < numsectors; i++)
-	{
-		checkSector = &sectors[i];
-
-		if (checkSector == thisSector) // dont check self
-			continue;
-
-		// buggy sector?
-		if (!thisSector->sectorLines)
-			continue;
-
-		// check if an arbitrary vertex of thisSector lies inside the checkSector
-		if (isVertexInside(thisSector->sectorLines->line->v1, checkSector))
-		{
-			// if so, the thisSector lies inside the checkSector
-			locStacklist[stackCnt] = checkSector;
-			stackCnt++;
-
-			if (MAXSTACK-1 == stackCnt) // beware of the SIGSEGV! and consider terminating NULL!
-				break;
-		}
-	}
-
-	thisSector->stackList = malloc(sizeof (sector_t *) * (stackCnt+1));
-	if (NULL == thisSector->stackList)
-	{
-		I_Error("Out of memory error in generateStacklist()");
-	}
-
-	locStacklist[stackCnt] = NULL; // terminating NULL
-
-	memcpy(thisSector->stackList, locStacklist, sizeof (sector_t *) * (stackCnt+1));
-}
-
-//
-// Bubble sort the stacklist with rising lineoutlengths
-//
-static void sortStacklist(sector_t *sector)
-{
-	sector_t **list;
-	sector_t *sec1, *sec2;
-	boolean finished;
-	size_t i;
-
-	list = sector->stackList;
-	finished = false;
-
-	if (!*list)
-		return; // nothing to sort
-
-	while (!finished)
-	{
-		i = 0;
-		finished = true;
-
-		while (*(list+i+1))
-		{
-			sec1 = *(list+i);
-			sec2 = *(list+i+1);
-
-			if (sec1->lineoutLength > sec2->lineoutLength)
-			{
-				*(list+i) = sec2;
-				*(list+i+1) = sec1;
-				finished = false;
-			}
-			i++;
-		}
-	}
-}
-
-//
-// length of a line in euclidian sense
-//
-static double lineLength(line_t *line)
-{
-	double dx, dy, length;
-
-	dx = (double) line->v1->x - (double) line->v2->x;
-	dy = (double) line->v1->y - (double) line->v2->y;
-
-	length = hypot(dx, dy);
-
-	return length;
-}
-
-
-//
-// length of the sector lineout
-//
-static double calcLineoutLength(sector_t *sector)
-{
-	linechain_t *chain;
-	double length = 0.0L;
-	chain = sector->sectorLines;
-
-	while (chain) // sum up lengths of all lines
-	{
-		length += lineLength(chain->line);
-		chain = chain->next;
-	}
-	return length;
-}
-
-//
-// Calculate length of the sectors lineout
-//
-static void calcLineouts(sector_t *sector)
-{
-	size_t secCount = 0;
-	sector_t *encSector = *(sector->stackList);
-
-	while (encSector)
-	{
-		if (encSector->lineoutLength < 0.0L) // if length has not yet been calculated
-		{
-			encSector->lineoutLength = calcLineoutLength(encSector);
-		}
-
-		secCount++;
-		encSector = *((sector->stackList) + secCount);
-	}
-}
-
-//
-// Free Stacklists of all sectors
-//
-static void freeStacklists(void)
-{
-	size_t i;
-
-	for (i = 0; i < numsectors; i++)
-	{
-		if (sectors[i].stackList)
-		{
-			free(sectors[i].stackList);
-			sectors[i].stackList = NULL;
-		}
-	}
-}
-
-//
-// if more than half of the toptextures are missing
-//
-static boolean areToptexturesMissing(sector_t *thisSector)
-{
-	linechain_t *thisElem, *nextElem = thisSector->sectorLines;
-	sector_t *frontSector, *backSector;
-	size_t nomiss = 0;
-	side_t *sidel, *sider;
-
-	while (nextElem) // walk through chain
-	{
-		thisElem = nextElem;
-		nextElem = thisElem->next;
-
-		frontSector = thisElem->line->frontsector;
-		backSector  = thisElem->line->backsector;
-
-		if (frontSector == backSector) // skip damn renderer tricks here
-			continue;
-
-		if (!frontSector || !backSector)
-			continue;
-
-		sider = &sides[thisElem->line->sidenum[0]];
-		sidel = &sides[thisElem->line->sidenum[1]];
-
-		if (backSector->ceilingheight < frontSector->ceilingheight)
-		{
-			if (sider->toptexture != 0)
-			{
-				nomiss++;
-				break; // we can stop here if decision criterium is ==0
-			}
-		}
-		else if (backSector->ceilingheight > frontSector->ceilingheight)
-		{
-			if (sidel->toptexture != 0)
-			{
-				nomiss++;
-				break; // we can stop here if decision criterium is ==0
-			}
-		}
-	}
-
-	return nomiss == 0;
-}
-
-//
-// are more textures missing than present?
-//
-static boolean areBottomtexturesMissing(sector_t *thisSector)
-{
-	linechain_t *thisElem, *nextElem = thisSector->sectorLines;
-	sector_t *frontSector, *backSector;
-	size_t nomiss = 0;
-	side_t *sidel, *sider;
-
-	while (nextElem) // walk through chain
-	{
-		thisElem = nextElem;
-		nextElem = thisElem->next;
-
-		frontSector = thisElem->line->frontsector;
-		backSector  = thisElem->line->backsector;
-
-		if (frontSector == backSector) // skip damn renderer tricks here
-			continue;
-
-		if (!frontSector || !backSector)
-			continue;
-
-		sider = &sides[thisElem->line->sidenum[0]];
-		sidel = &sides[thisElem->line->sidenum[1]];
-
-		if (backSector->floorheight > frontSector->floorheight)
-		{
-			if (sider->bottomtexture != 0)
-			{
-				nomiss++;
-				break; // we can stop here if decision criterium is ==0
-			}
-		}
-
-		else if (backSector->floorheight < frontSector->floorheight)
-		{
-			if (sidel->bottomtexture != 0)
-			{
-				nomiss++;
-				break; // we can stop here if decision criterium is ==0
-			}
-		}
-	}
-
-	//    return missing >= nomiss;
-	return nomiss == 0;
-}
-
-//
-// check if no adjacent sector has same ceiling height
-//
-static boolean isCeilingFloating(sector_t *thisSector)
-{
-	sector_t *adjSector, *refSector = NULL, *frontSector, *backSector;
-	linechain_t *thisElem, *nextElem;
-
-	if (!thisSector)
-		return false;
-
-	nextElem = thisSector->sectorLines;
-
-	while (nextElem) // walk through chain
-	{
-		thisElem = nextElem;
-		nextElem = thisElem->next;
-
-		frontSector = thisElem->line->frontsector;
-		backSector  = thisElem->line->backsector;
-
-		if (frontSector == thisSector)
-			adjSector = backSector;
-		else
-			adjSector = frontSector;
-
-		if (!adjSector) // assume floating sectors have surrounding sectors
-			return false;
-
-		if (adjSector->c_slope) // Don't bother with slopes
-			return false;
-
-		if (!refSector)
-		{
-			refSector = adjSector;
-			continue;
-		}
-
-		// if adjacent sector has same height or more than one adjacent sector exists -> stop
-		if (thisSector->ceilingheight == adjSector->ceilingheight || refSector != adjSector)
-			return false;
-	}
-
-	// now check for walltextures
-	if (!areToptexturesMissing(thisSector))
-		return false;
-
-	return true;
-}
-
-//
-// check if no adjacent sector has same ceiling height
-// FIXME: throw that together with isCeilingFloating??
-//
-static boolean isFloorFloating(sector_t *thisSector)
-{
-	sector_t *adjSector, *refSector = NULL, *frontSector, *backSector;
-	linechain_t *thisElem, *nextElem;
-
-	if (!thisSector)
-		return false;
-
-	nextElem = thisSector->sectorLines;
-
-	while (nextElem) // walk through chain
-	{
-		thisElem = nextElem;
-		nextElem = thisElem->next;
-
-		frontSector = thisElem->line->frontsector;
-		backSector  = thisElem->line->backsector;
-
-		if (frontSector == thisSector)
-			adjSector = backSector;
-		else
-			adjSector = frontSector;
-
-		if (!adjSector) // assume floating sectors have surrounding sectors
-			return false;
-
-		if (adjSector->f_slope) // Don't bother with slopes
-			return false;
-
-		if (!refSector)
-		{
-			refSector = adjSector;
-			continue;
-		}
-
-		// if adjacent sector has same height or more than one adjacent sector exists -> stop
-		if (thisSector->floorheight == adjSector->floorheight || refSector != adjSector)
-			return false;
-	}
-
-	// now check for walltextures
-	if (!areBottomtexturesMissing(thisSector))
-		return false;
-
-	return true;
-}
-
-//
-// estimate ceilingheight according to height of adjacent sector
-//
-static fixed_t estimateCeilHeight(sector_t *thisSector)
-{
-	sector_t *adjSector;
-
-	if (!thisSector || !thisSector->sectorLines || !thisSector->sectorLines->line)
-		return 0;
-
-	adjSector = thisSector->sectorLines->line->frontsector;
-	if (adjSector == thisSector)
-		adjSector = thisSector->sectorLines->line->backsector;
-
-	if (!adjSector)
-		return 0;
-
-	return adjSector->ceilingheight;
-}
-
-//
-// estimate ceilingheight according to height of adjacent sector
-//
-static fixed_t estimateFloorHeight(sector_t *thisSector)
-{
-	sector_t *adjSector;
-
-	if (!thisSector || !thisSector->sectorLines || !thisSector->sectorLines->line)
-		return 0;
-
-	adjSector = thisSector->sectorLines->line->frontsector;
-	if (adjSector == thisSector)
-		adjSector = thisSector->sectorLines->line->backsector;
-
-	if (!adjSector)
-		return 0;
-
-	return adjSector->floorheight;
-}
-
-#define CORRECT_FLOAT_EXPERIMENTAL
-
-// --------------------------------------------------------------------------
-// Some levels have missing sidedefs, which produces HOM, so lets try to compensate for that
-// and some levels have deep water trick, invisible staircases etc.
-// --------------------------------------------------------------------------
-// FIXME: put some nice default texture in legacy.dat and use it
-void HWR_CorrectSWTricks(void)
-{
-	size_t i;
-	size_t k;
-	line_t *ld;
-	side_t *sidel = NULL, *sider;
-	sector_t *secl, *secr;
-	sector_t **sectorList;
-	sector_t *outSector;
-
-	if ((0 == cv_glcorrecttricks.value))
-		return;
-
-	// determine lines for sectors
-	for (i = 0; i < numlines; i++)
-	{
-		ld = &lines[i];
-		secr = ld->frontsector;
-		secl = ld->backsector;
-
-		if (secr == secl)
-		{
-			secr->pseudoSector = true; // special renderer trick?
-			addLineToChain(secr, ld);
-		}
-		else
-		{
-			addLineToChain(secr, ld);
-			addLineToChain(secl, ld);
-		}
-	}
-
-	// preprocessing
-	for (i = 0; i < numsectors; i++)
-	{
-		sector_t *checkSector;
-
-		checkSector = &sectors[i];
-
-		// identify real pseudosectors first
-		if (checkSector->pseudoSector)
-		{
-			if (!isPSectorValid(checkSector)) // drop invalid pseudo sectors
-			{
-				checkSector->pseudoSector = false;
-			}
-		}
-
-		// determine enclosing sectors for pseudosectors ... used later
-		if (checkSector->pseudoSector)
-		{
-			generateStacklist(checkSector);
-			calcLineouts(checkSector);
-			sortStacklist(checkSector);
-		}
-	}
-
-	// set virtual floor heights for pseudo sectors
-	// required for deep water effect e.g.
-	for (i = 0; i < numsectors; i++)
-	{
-		if (sectors[i].pseudoSector)
-		{
-			sectorList = sectors[i].stackList;
-			k = 0;
-			while (*(sectorList+k))
-			{
-				outSector = *(sectorList+k);
-				if (!outSector->pseudoSector)
-				{
-					sectors[i].virtualFloorheight = outSector->floorheight;
-					sectors[i].virtualCeilingheight = outSector->ceilingheight;
-					break;
-				}
-				k++;
-			}
-			if (*(sectorList+k) == NULL) // sorry, did not work :(
-			{
-				sectors[i].virtualFloorheight = sectors[i].floorheight;
-				sectors[i].virtualCeilingheight = sectors[i].ceilingheight;
-			}
-		}
-	}
-#ifdef CORRECT_FLOAT_EXPERIMENTAL
-	// correct ceiling/floor heights of totally floating sectors
-	for (i = 0; i < numsectors; i++)
-	{
-		sector_t *floatSector;
-
-		floatSector = &sectors[i];
-
-		// correct height of floating sectors
-		if (isCeilingFloating(floatSector))
-		{
-			floatSector->virtualCeilingheight = estimateCeilHeight(floatSector);
-			floatSector->virtualCeiling = true;
-		}
-		if (isFloorFloating(floatSector))
-		{
-			floatSector->virtualFloorheight = estimateFloorHeight(floatSector);
-			floatSector->virtualFloor = true;
-		}
-	}
-#endif
-
-	// now for the missing textures
-	for (i = 0; i < numlines; i++)
-	{
-		ld = &lines[i];
-		sider = &sides[ld->sidenum[0]];
-		if (ld->sidenum[1] != 0xffff)
-			sidel = &sides[ld->sidenum[1]];
-
-		secr = ld->frontsector;
-		secl = ld->backsector;
-
-		if (secr == secl) // special renderer trick
-			continue; // we cant correct missing textures here
-
-		if (secl) // only if there is a backsector
-		{
-			if (secr->pseudoSector || secl->pseudoSector)
-				continue;
-			if (!secr->virtualFloor && !secl->virtualFloor)
-			{
-				if (secl->floorheight > secr->floorheight)
-				{
-					// now check if r-sidedef is correct
-					if (sider->bottomtexture == 0)
-					{
-						if (sider->midtexture == 0)
-							sider->bottomtexture = 0; // Tails // More redwall sky shenanigans
-						else
-							sider->bottomtexture = sider->midtexture;
-					}
-				}
-				else if (secl->floorheight < secr->floorheight)
-				{
-					// now check if l-sidedef is correct
-					if (sidel->bottomtexture == 0)
-					{
-						if (sidel->midtexture == 0)
-							sidel->bottomtexture = 0; // Tails // More redwall sky shenanigans
-						else
-							sidel->bottomtexture = sidel->midtexture;
-					}
-				}
-			}
-
-			if (!secr->virtualCeiling && !secl->virtualCeiling)
-			{
-					if (secl->ceilingheight < secr->ceilingheight)
-				{
-					// now check if r-sidedef is correct
-					if (sider->toptexture == 0)
-					{
-						if (sider->midtexture == 0)
-							sider->toptexture = 0; // Tails // When this was REDWALL it was causing issues in the sky sometimes
-						else
-							sider->toptexture = sider->midtexture;
-					}
-				}
-				else if (secl->ceilingheight > secr->ceilingheight)
-				{
-					// now check if l-sidedef is correct
-					if (sidel->toptexture == 0)
-					{
-						if (sidel->midtexture == 0)
-							sidel->toptexture = 0; // Tails // When this was REDWALL it was causing issues in the sky sometimes
-						else
-							sidel->toptexture = sidel->midtexture;
-					}
-				}
-			}
-		} // if (NULL != secl)
-	} // for (i = 0; i < numlines; i++)
-
-	// release all linechains
-	releaseLineChains();
-	freeStacklists();
-}
-
-#endif // HWRENDER
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 8b48a043881ae180329164e30f8964aed7594497..08d688e1dd9036524f48c10da59b13c412977529 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2939,6 +2939,8 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 	if (shearing)
 	{
 		float fdy = stransform->viewaiming * 2;
+		if (stransform->flip)
+			fdy *= -1.0f;
 		pglTranslatef(0.0f, -fdy/BASEVIDHEIGHT, 0.0f);
 	}
 
diff --git a/src/info.c b/src/info.c
index 36806eca337acaa31ca779b7215960bac7b723c4..778f1f7418c97e9cd3af6c05787c79c8e15aa4e5 100644
--- a/src/info.c
+++ b/src/info.c
@@ -21803,11 +21803,11 @@ skincolor_t skincolors[MAXSKINCOLORS] = {
 	{"Super Orange 4", {0x00, 0xd0, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x44, 0x45, 0x46}, SKINCOLOR_SAPPHIRE, 4,  V_ORANGEMAP, false}, // SKINCOLOR_SUPERORANGE4
 	{"Super Orange 5", {0xd0, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x44, 0x45, 0x46, 0x47}, SKINCOLOR_SAPPHIRE, 3,  V_ORANGEMAP, false}, // SKINCOLOR_SUPERORANGE5
 
-	{"Super Gold 1", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x51, 0x52, 0x53, 0x48}, SKINCOLOR_CORNFLOWER, 15, 0,           false}, // SKINCOLOR_SUPERGOLD1
-	{"Super Gold 2", {0x00, 0x50, 0x51, 0x52, 0x53, 0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41}, SKINCOLOR_CORNFLOWER, 9,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD2
-	{"Super Gold 3", {0x51, 0x52, 0x53, 0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43}, SKINCOLOR_CORNFLOWER, 8,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD3
-	{"Super Gold 4", {0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}, SKINCOLOR_CORNFLOWER, 8,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD4
-	{"Super Gold 5", {0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47}, SKINCOLOR_CORNFLOWER, 8,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD5
+	{"Super Gold 1", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x51, 0x52, 0x53, 0x48, 0x48, 0x48}, SKINCOLOR_CORNFLOWER, 15, 0,           false}, // SKINCOLOR_SUPERGOLD1
+	{"Super Gold 2", {0x00, 0x50, 0x51, 0x52, 0x53, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x41, 0x41}, SKINCOLOR_CORNFLOWER, 9,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD2
+	{"Super Gold 3", {0x51, 0x52, 0x53, 0x53, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x43, 0x43}, SKINCOLOR_CORNFLOWER, 8,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD3
+	{"Super Gold 4", {0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x46, 0x46}, SKINCOLOR_CORNFLOWER, 8,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD4
+	{"Super Gold 5", {0x48, 0x48, 0x49, 0x49, 0x49, 0x40, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x47}, SKINCOLOR_CORNFLOWER, 8,  V_YELLOWMAP, false}, // SKINCOLOR_SUPERGOLD5
 
 	{"Super Peridot 1", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x58, 0x58, 0xbc, 0xbc, 0xbc}, SKINCOLOR_COBALT, 15, 0,            false}, // SKINCOLOR_SUPERPERIDOT1
 	{"Super Peridot 2", {0x00, 0x58, 0x58, 0x58, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe}, SKINCOLOR_COBALT, 4,  V_PERIDOTMAP, false}, // SKINCOLOR_SUPERPERIDOT2
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 775d9ec0213a8f076e1e573ce0712f516ab3fe68..2e05008cab249418881b4d14dbd54897a0dab700 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -902,6 +902,17 @@ static int lib_pMaceRotate(lua_State *L)
 	return 0;
 }
 
+static int lib_pRailThinker(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!mobj)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_RailThinker(mobj));
+	return 1;
+}
+
 // P_USER
 ////////////
 
@@ -3259,6 +3270,7 @@ static luaL_Reg lib[] = {
 	{"P_CheckSolidLava",lib_pCheckSolidLava},
 	{"P_CanRunOnWater",lib_pCanRunOnWater},
 	{"P_MaceRotate",lib_pMaceRotate},
+	{"P_RailThinker",lib_pRailThinker},
 
 	// p_user
 	{"P_GetPlayerHeight",lib_pGetPlayerHeight},
diff --git a/src/m_menu.c b/src/m_menu.c
index 338d9b1be556a56c80854ec665114f42ba231c37..ddadec79971f08b0021d8d8ebc8e991510a27acf 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -487,7 +487,7 @@ CV_PossibleValue_t loadless_cons_t[] = {{0, "Realtime"}, {1, "In-game"}, {0, NUL
 
 consvar_t cv_dummymarathon = {"dummymarathon", "Standard", CV_HIDEN, marathon_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_dummycutscenes = {"dummycutscenes", "Off", CV_HIDEN, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_dummyloadless = {"dummyloadless", "Realtime", CV_HIDEN, loadless_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_dummyloadless = {"dummyloadless", "In-game", CV_HIDEN, loadless_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // ==========================================================================
 // ORGANIZATION START.
@@ -1942,7 +1942,7 @@ static menu_t SP_NightsGhostDef =
 static menu_t SP_MarathonDef =
 {
 	MTREE2(MN_SP_MAIN, MN_SP_MARATHON),
-	"M_ATTACK", // temporary
+	"M_RATHON",
 	sizeof(SP_MarathonMenu)/sizeof(menuitem_t),
 	&MainDef,  // Doesn't matter.
 	SP_MarathonMenu,
@@ -10563,8 +10563,7 @@ static void M_StartMarathon(INT32 choice)
 	(void)choice;
 	marathontime = 0;
 	marathonmode = MA_RUNNING|MA_INIT;
-	if (cv_dummymarathon.value == 1)
-		cursaveslot = MARATHONSLOT;
+	cursaveslot = (cv_dummymarathon.value == 1) ? MARATHONSLOT : 0;
 	if (!cv_dummycutscenes.value)
 		marathonmode |= MA_NOCUTSCENES;
 	if (cv_dummyloadless.value)
@@ -10709,7 +10708,7 @@ void M_DrawMarathon(void)
 			recatkdrawtimer -= (10*TICRATE);
 	}
 
-	//M_DrawMenuTitle();
+	M_DrawMenuTitle();
 
 	// draw menu (everything else goes on top of it)
 	// Sadly we can't just use generic mode menus because we need some extra hacks
diff --git a/src/m_misc.c b/src/m_misc.c
index c527d22960ae18a2ad6ccaa90793c68292c438f5..216fde056c001690899debd7ad1ff2b2f982604b 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -162,7 +162,7 @@ consvar_t cv_zlib_memorya = {"apng_memory_level", "(Max Memory) 9", CV_SAVE, zli
 consvar_t cv_zlib_levela = {"apng_compress_level", "4", CV_SAVE, zlib_level_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_zlib_strategya = {"apng_strategy", "RLE", CV_SAVE, zlib_strategy_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_zlib_window_bitsa = {"apng_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_apng_delay = {"apng_speed", "1/2x", CV_SAVE, apng_delay_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_apng_delay = {"apng_speed", "1x", CV_SAVE, apng_delay_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 boolean takescreenshot = false; // Take a screenshot this tic
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 9571b0b4a5a8244998ba0bc873ad444380880dfb..fd30f8e38a54924284776da4b7a11cba46612e50 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -5214,7 +5214,7 @@ void A_SignPlayer(mobj_t *actor)
 
 	actor->tracer->color = signcolor;
 	if (signcolor && signcolor < numskincolors)
-		signframe += (15 - skincolors[facecolor].invshade);
+		signframe += (15 - skincolors[skincolors[signcolor].invcolor].invshade);
 	actor->tracer->frame = signframe;
 }
 
diff --git a/src/p_inter.c b/src/p_inter.c
index 9caed927d31a6ac5036d9dab2eb7fc6a14ae74d2..bd044f32a1672f81eacb28c1b9a284643d796591 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -467,10 +467,22 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
 			{
-				if (elementalpierce == 2)
-					P_DoBubbleBounce(player);
-				else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
-					toucher->momz = -toucher->momz;
+				if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+				{
+					fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+					
+					if (elementalpierce == 2) // Reset bubblewrap, part 1
+						P_DoBubbleBounce(player);
+					toucher->momz = setmomz;
+					if (elementalpierce == 2) // Reset bubblewrap, part 2
+					{
+						boolean underwater = toucher->eflags & MFE_UNDERWATER;
+							
+						if (underwater)
+							toucher->momz /= 2;
+						toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
+					}
+				}
 			}
 			if (player->pflags & PF_BOUNCING)
 				P_DoAbilityBounce(player, false);
diff --git a/src/p_map.c b/src/p_map.c
index b7ad14808bde78f5944526f73e1eea28bbd27825..f7db52f6a0353a73d8b149de2f4bb8f6774816a2 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1678,13 +1678,23 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				&& (flipval*(*momz) < 0) // monitor is on the floor and you're going down, or on the ceiling and you're going up
 				&& (elementalpierce != 1)) // you're not piercing through the monitor...
 				{
-					if (elementalpierce == 2)
-						P_DoBubbleBounce(player);
-					else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+					if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 					{
-						*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
+						fixed_t setmomz = -*momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+					
+						if (elementalpierce == 2) // Reset bubblewrap, part 1
+							P_DoBubbleBounce(player);
+						*momz = setmomz; // Therefore, you should be thrust in the opposite direction, vertically.
 						if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
 							P_TwinSpinRejuvenate(player, player->thokitem);
+						if (elementalpierce == 2) // Reset bubblewrap, part 2
+						{
+							boolean underwater = tmthing->eflags & MFE_UNDERWATER;
+							
+							if (underwater)
+								*momz /= 2;
+							*momz -= (*momz/(underwater ? 8 : 4)); // Cap the height!
+						}
 					}
 				}
 				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 62bcad500c90e8ac48c875e8cdbbb1e16da083ef..c26308a59d653353ecb324e24c2a0f26070815de 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -442,7 +442,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 
 			mobj->sprite2 = spr2;
 			mobj->frame = frame|(st->frame&~FF_FRAMEMASK);
-			if (mobj->color >= FIRSTSUPERCOLOR && mobj->color < numskincolors) // Super colours? Super bright!
+			if (player->powers[pw_super] || (player->powers[pw_carry] == CR_NIGHTSMODE && (player->charflags & (SF_SUPER|SF_NONIGHTSSUPER)) == SF_SUPER)) // Super colours? Super bright!
 				mobj->frame |= FF_FULLBRIGHT;
 		}
 		// Regular sprites
diff --git a/src/p_setup.c b/src/p_setup.c
index 942deecf8015122e2a5bb41c720401f3101989f6..2fd7ba5b095207db4cd1838bbaa1e17f693f85ef 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -995,17 +995,6 @@ static void P_InitializeSector(sector_t *ss)
 
 	ss->extra_colormap = NULL;
 
-#ifdef HWRENDER // ----- for special tricks with HW renderer -----
-	ss->pseudoSector = false;
-	ss->virtualFloor = false;
-	ss->virtualFloorheight = 0;
-	ss->virtualCeiling = false;
-	ss->virtualCeilingheight = 0;
-	ss->sectorLines = NULL;
-	ss->stackList = NULL;
-	ss->lineoutLength = -1.0l;
-#endif // ----- end special tricks -----
-
 	ss->gravity = NULL;
 	ss->verticalflip = false;
 	ss->flags = SF_FLIPSPECIAL_FLOOR;
@@ -3767,7 +3756,7 @@ boolean P_LoadLevel(boolean fromnetsave)
 		if (!lastmaploaded) // Start a new game?
 		{
 			// I'd love to do this in the menu code instead of here, but everything's a mess and I can't guarantee saving proper player struct info before the first act's started. You could probably refactor it, but it'd be a lot of effort. Easier to just work off known good code. ~toast 22/06/2020
-			if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
+			if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || marathonmode)
 			&& (!modifiedgame || savemoddata) && cursaveslot > 0)
 				G_SaveGame((UINT32)cursaveslot, gamemap);
 			// If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted.
@@ -3825,8 +3814,6 @@ void HWR_SetupLevel(void)
 	HWR_ResetLights();
 #endif
 
-	// Correct missing sidedefs & deep water trick
-	HWR_CorrectSWTricks();
 	HWR_CreatePlanePolygons((INT32)numnodes - 1);
 }
 #endif
diff --git a/src/p_user.c b/src/p_user.c
index df037504e2a18c83b877f9521701ba25a101496e..c62606970fc6c7ddec0b6062e73c274fa4c53d23 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4863,7 +4863,7 @@ void P_DoBubbleBounce(player_t *player)
 	player->pflags |= PF_THOKKED;
 	player->pflags &= ~PF_STARTJUMP;
 	player->secondjump = UINT8_MAX;
-	player->mo->momz = FixedMul(player->mo->momz, 5*FRACUNIT/4);
+	player->mo->momz = FixedMul(player->mo->momz, 11*FRACUNIT/8);
 }
 
 //
@@ -5112,15 +5112,19 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 									boolean elem = ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL);
 									player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 									if (elem)
+									{
+										player->mo->momx = player->mo->momy = 0;
 										S_StartSound(player->mo, sfx_s3k43);
+									}
 									else
 									{
+										player->mo->momx -= (player->mo->momx/3);
+										player->mo->momy -= (player->mo->momy/3);
 										player->pflags &= ~PF_NOJUMPDAMAGE;
 										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 										S_StartSound(player->mo, sfx_s3k44);
 									}
 									player->secondjump = 0;
-									player->mo->momx = player->mo->momy = 0;
 									P_SetObjectMomZ(player->mo, -24*FRACUNIT, false);
 									break;
 								}
diff --git a/src/r_data.c b/src/r_data.c
index b7221499650a9048c892dafbabff76264fd7b2f9..d10919d81891fe04a4dff8d458afb0ba1da0ad0d 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -227,6 +227,8 @@ static inline void R_DrawFlippedColumnInCache(column_t *patch, UINT8 *cache, tex
 	}
 }
 
+// Blends two pixels together, using the equation
+// that matches the specified alpha style.
 UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
 {
 	RGBA_t output;
@@ -245,7 +247,13 @@ UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alph
 			// if the background pixel is empty,
 			// match software and don't blend anything
 			if (!background.s.alpha)
-				output.s.alpha = 0;
+			{
+				// ...unless the foreground pixel ISN'T actually translucent.
+				if (alpha == 0xFF)
+					output.rgba = foreground.rgba;
+				else
+					output.rgba = 0;
+			}
 			else
 			{
 				UINT8 beta = (0xFF - alpha);
@@ -302,18 +310,46 @@ UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alph
 	return 0;
 }
 
-UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha)
+INT32 ASTTextureBlendingThreshold[2] = {255/11, (10*255/11)};
+
+// Blends a pixel for a texture patch.
+UINT32 ASTBlendTexturePixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
+{
+	// Alpha style set to translucent?
+	if (style == AST_TRANSLUCENT)
+	{
+		// Is the alpha small enough for translucency?
+		if (alpha <= ASTTextureBlendingThreshold[1])
+		{
+			// Is the patch way too translucent? Don't blend then.
+			if (alpha < ASTTextureBlendingThreshold[0])
+				return background.rgba;
+
+			return ASTBlendPixel(background, foreground, style, alpha);
+		}
+		else // just copy the pixel
+			return foreground.rgba;
+	}
+	else
+		return ASTBlendPixel(background, foreground, style, alpha);
+}
+
+// Blends two palette indexes for a texture patch, then
+// finds the nearest palette index from the blended output.
+UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT8 alpha)
 {
 	// Alpha style set to translucent?
 	if (style == AST_TRANSLUCENT)
 	{
 		// Is the alpha small enough for translucency?
-		if (alpha <= (10*255/11))
+		if (alpha <= ASTTextureBlendingThreshold[1])
 		{
 			UINT8 *mytransmap;
+
 			// Is the patch way too translucent? Don't blend then.
-			if (alpha < 255/11)
+			if (alpha < ASTTextureBlendingThreshold[0])
 				return background;
+
 			// The equation's not exact but it works as intended. I'll call it a day for now.
 			mytransmap = transtables + ((8*(alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT);
 			if (background != 0xFF)
@@ -378,7 +414,7 @@ static inline void R_DrawBlendColumnInCache(column_t *patch, UINT8 *cache, texpa
 		{
 			for (; dest < cache + position + count; source++, dest++)
 				if (*source != 0xFF)
-					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
+					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
 		}
 
 		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
@@ -422,7 +458,7 @@ static inline void R_DrawBlendFlippedColumnInCache(column_t *patch, UINT8 *cache
 		{
 			for (; dest < cache + position + count; --source, dest++)
 				if (*source != 0xFF)
-					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
+					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
 		}
 
 		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
diff --git a/src/r_data.h b/src/r_data.h
index 78ce35a4139567da1fa395634c3d1d32da91e907..8b8d08c52e35a959a981c58388ac07e655f35b9d 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -26,7 +26,10 @@
 enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
 
 UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
-UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha);
+UINT32 ASTBlendTexturePixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
+UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT8 alpha);
+
+extern INT32 ASTTextureBlendingThreshold[2];
 
 UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
 
diff --git a/src/r_defs.h b/src/r_defs.h
index a36568192ebbd320a58b88a58c079e15ba76d833..fd868ee97daed196294a650a1ec4836acc9da403 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -221,20 +221,6 @@ typedef struct r_lightlist_s
 	INT32 lightnum;
 } r_lightlist_t;
 
-// ----- for special tricks with HW renderer -----
-
-//
-// For creating a chain with the lines around a sector
-//
-typedef struct linechain_s
-{
-	struct line_s *line;
-	struct linechain_s *next;
-} linechain_t;
-// ----- end special tricks -----
-
-
-
 // Slopes
 typedef enum {
 	SL_NOPHYSICS = 1, /// This plane will have no physics applied besides the positioning.
@@ -348,17 +334,6 @@ typedef struct sector_s
 	// per-sector colormaps!
 	extracolormap_t *extra_colormap;
 
-#ifdef HWRENDER // ----- for special tricks with HW renderer -----
-	boolean pseudoSector;
-	boolean virtualFloor;
-	fixed_t virtualFloorheight;
-	boolean virtualCeiling;
-	fixed_t virtualCeilingheight;
-	linechain_t *sectorLines;
-	struct sector_s **stackList;
-	double lineoutLength;
-#endif // ----- end special tricks -----
-
 	// This points to the master's floorheight, so it can be changed in realtime!
 	fixed_t *gravity; // per-sector gravity
 	boolean verticalflip; // If gravity < 0, then allow flipped physics
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index beaed6a3bd53172903216b3f33c89fc492a661bc..5592de86b39510debd1ec1582048e56e159ebb7a 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -381,7 +381,6 @@
     <ClCompile Include="..\hardware\hw_md2load.c" />
     <ClCompile Include="..\hardware\hw_md3load.c" />
     <ClCompile Include="..\hardware\hw_model.c" />
-    <ClCompile Include="..\hardware\hw_trick.c" />
     <ClCompile Include="..\hardware\r_opengl\r_opengl.c" />
     <ClCompile Include="..\hardware\u_list.c" />
     <ClCompile Include="..\hu_stuff.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 80af94b657d61acc5ea948d14dd115cb11bb4da4..db1aa123fbbbcee61984f5d05db423be5974e24b 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -669,9 +669,6 @@
     <ClCompile Include="..\hardware\hw_model.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
-    <ClCompile Include="..\hardware\hw_trick.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
     <ClCompile Include="..\hardware\u_list.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
diff --git a/src/sdl/Srb2SDL-vc9.vcproj b/src/sdl/Srb2SDL-vc9.vcproj
index d407b288466760deb4e3342be91f1988f5fc110d..cfa49ea502873a6dbe393544a61f77c2aed52010 100644
--- a/src/sdl/Srb2SDL-vc9.vcproj
+++ b/src/sdl/Srb2SDL-vc9.vcproj
@@ -2546,46 +2546,6 @@
 				RelativePath="..\hardware\hw_md2.h"
 				>
 			</File>
-			<File
-				RelativePath="..\hardware\hw_trick.c"
-				>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Debug|x64"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|x64"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-			</File>
 			<File
 				RelativePath="..\hardware\hws_data.h"
 				>
diff --git a/src/sdl/Srb2SDL.dsp b/src/sdl/Srb2SDL.dsp
index ce35e2e23d0eab733e6f1d68cab441b7db4283c9..9f6dd7b330e924a1026a00e48b10415eda233d79 100644
--- a/src/sdl/Srb2SDL.dsp
+++ b/src/sdl/Srb2SDL.dsp
@@ -604,10 +604,6 @@ SOURCE=..\hardware\hw_md2.h
 # End Source File
 # Begin Source File
 
-SOURCE=..\hardware\hw_trick.c
-# End Source File
-# Begin Source File
-
 SOURCE=..\hardware\hws_data.h
 # End Source File
 # End Group
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 27e015ef7ca01bfa82727ad7987e62d46d9d5ce0..b24ae2814985d04b3674aaf5afa3c09628dcd78c 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -108,7 +108,7 @@ int TimeFunction(int requested_frequency);
 #endif
 #endif
 
-#if (defined (__unix__) && !defined (_MSDOS)) || defined (UNIXCOMMON)
+#if (defined (__unix__) && !defined (_MSDOS)) || (defined (UNIXCOMMON) && !defined(__APPLE__))
 #include <errno.h>
 #include <sys/wait.h>
 #define NEWSIGNALHANDLER
diff --git a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
index ee0bf4697786da59064f241576fa47d97111213d..909bb2ced72d7f3cc7a157d87fea0eac37dfd178 100644
--- a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
@@ -1113,13 +1113,6 @@
 			path = ../../hardware/hw_md2.h;
 			refType = 2;
 		};
-		84177743085A106C000C01D8 = {
-			fileEncoding = 30;
-			isa = PBXFileReference;
-			name = hw_trick.c;
-			path = ../../hardware/hw_trick.c;
-			refType = 2;
-		};
 		84177744085A106C000C01D8 = {
 			fileEncoding = 30;
 			isa = PBXFileReference;
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index f4d7fdd86a6a1c0b9e076f40fb9aa9cd31db7dd6..04f8ecc0aaea916da6795d9841caa8451bd3397c 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -19,7 +19,6 @@
 		1E44AE800B67CC2B00BAD059 /* hw_draw.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AE6C0B67CC2B00BAD059 /* hw_draw.c */; };
 		1E44AE820B67CC2B00BAD059 /* hw_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AE6E0B67CC2B00BAD059 /* hw_main.c */; };
 		1E44AE840B67CC2B00BAD059 /* hw_md2.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AE700B67CC2B00BAD059 /* hw_md2.c */; };
-		1E44AE860B67CC2B00BAD059 /* hw_trick.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AE720B67CC2B00BAD059 /* hw_trick.c */; };
 		1E44AEA40B67CC8500BAD059 /* d_clisrv.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AE8D0B67CC8400BAD059 /* d_clisrv.c */; };
 		1E44AEA70B67CC8500BAD059 /* d_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AE900B67CC8400BAD059 /* d_main.c */; };
 		1E44AEA80B67CC8500BAD059 /* d_net.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AE910B67CC8500BAD059 /* d_net.c */; };
@@ -191,7 +190,6 @@
 		1E44AE6F0B67CC2B00BAD059 /* hw_main.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hw_main.h; path = ../../hardware/hw_main.h; sourceTree = SOURCE_ROOT; };
 		1E44AE700B67CC2B00BAD059 /* hw_md2.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = hw_md2.c; path = ../../hardware/hw_md2.c; sourceTree = SOURCE_ROOT; };
 		1E44AE710B67CC2B00BAD059 /* hw_md2.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hw_md2.h; path = ../../hardware/hw_md2.h; sourceTree = SOURCE_ROOT; };
-		1E44AE720B67CC2B00BAD059 /* hw_trick.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = hw_trick.c; path = ../../hardware/hw_trick.c; sourceTree = SOURCE_ROOT; };
 		1E44AE730B67CC2B00BAD059 /* hws_data.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hws_data.h; path = ../../hardware/hws_data.h; sourceTree = SOURCE_ROOT; };
 		1E44AE8A0B67CC6000BAD059 /* asm_defs.inc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.pascal; name = asm_defs.inc; path = ../../asm_defs.inc; sourceTree = SOURCE_ROOT; };
 		1E44AE8D0B67CC8400BAD059 /* d_clisrv.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = d_clisrv.c; path = ../../d_clisrv.c; sourceTree = SOURCE_ROOT; };
@@ -544,7 +542,6 @@
 				1E44AE6F0B67CC2B00BAD059 /* hw_main.h */,
 				1E44AE700B67CC2B00BAD059 /* hw_md2.c */,
 				1E44AE710B67CC2B00BAD059 /* hw_md2.h */,
-				1E44AE720B67CC2B00BAD059 /* hw_trick.c */,
 				1E44AE730B67CC2B00BAD059 /* hws_data.h */,
 			);
 			name = Hw_Hardware;
@@ -1078,7 +1075,6 @@
 				1E44AE800B67CC2B00BAD059 /* hw_draw.c in Sources */,
 				1E44AE820B67CC2B00BAD059 /* hw_main.c in Sources */,
 				1E44AE840B67CC2B00BAD059 /* hw_md2.c in Sources */,
-				1E44AE860B67CC2B00BAD059 /* hw_trick.c in Sources */,
 				1E44AEA40B67CC8500BAD059 /* d_clisrv.c in Sources */,
 				1E44AEA70B67CC8500BAD059 /* d_main.c in Sources */,
 				1E44AEA80B67CC8500BAD059 /* d_net.c in Sources */,
@@ -1217,7 +1213,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.4;
+				CURRENT_PROJECT_VERSION = 2.2.6;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1229,7 +1225,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.4;
+				CURRENT_PROJECT_VERSION = 2.2.6;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 53d98891339d22c41bb4152667e33e671e0e33e0..d5aa5fbac8fb30e25fbcd000f269896812fe86d3 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -240,8 +240,8 @@ void ST_LoadGraphics(void)
 	int i;
 
 	// SRB2 border patch
-	st_borderpatchnum = W_GetNumForName("GFZFLR01");
-	scr_borderpatch = W_CacheLumpNum(st_borderpatchnum, PU_HUDGFX);
+	// st_borderpatchnum = W_GetNumForName("GFZFLR01");
+	// scr_borderpatch = W_CacheLumpNum(st_borderpatchnum, PU_HUDGFX);
 
 	// the original Doom uses 'STF' as base name for all face graphics
 	// Graue 04-08-2004: face/name graphics are now indexed by skins
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index 72636d60db96798cd1c6f4c4270a5c4af53b7e77..6855a4135b000fdae1b56d19fe9a237ec293cbc2 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -234,7 +234,6 @@
     <ClCompile Include="..\hardware\hw_md2load.c" />
     <ClCompile Include="..\hardware\hw_md3load.c" />
     <ClCompile Include="..\hardware\hw_model.c" />
-    <ClCompile Include="..\hardware\hw_trick.c" />
     <ClCompile Include="..\hardware\u_list.c" />
     <ClCompile Include="..\hu_stuff.c" />
     <ClCompile Include="..\info.c" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index a46eb1e2e6a96bd4551b2ef6fb74ab8d74e790b4..4a980c6bd298eb8526626f9552055522093fa644 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -108,9 +108,6 @@
     <ClCompile Include="..\hardware\hw_md2.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
-    <ClCompile Include="..\hardware\hw_trick.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
     <ClCompile Include="..\hardware\hw3sound.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
diff --git a/src/win32/Srb2win-vc9.vcproj b/src/win32/Srb2win-vc9.vcproj
index e216212f8604615ab8ea069f05696e557f533034..c1c6b5bc43e3e99139bef441da8bf3b3c48bdbb2 100644
--- a/src/win32/Srb2win-vc9.vcproj
+++ b/src/win32/Srb2win-vc9.vcproj
@@ -2287,46 +2287,6 @@
 				RelativePath="..\hardware\hw_md2.h"
 				>
 			</File>
-			<File
-				RelativePath="..\hardware\hw_trick.c"
-				>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Debug|x64"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|x64"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						AdditionalIncludeDirectories=""
-						PreprocessorDefinitions=""
-					/>
-				</FileConfiguration>
-			</File>
 			<File
 				RelativePath="..\hardware\hws_data.h"
 				>
diff --git a/src/win32/Srb2win.dsp b/src/win32/Srb2win.dsp
index c52873f7beb0414b0ce6947f1e5ef1c688bdb5eb..661f3eaf90bcb354898fd9a4a3b1252c4fee014e 100644
--- a/src/win32/Srb2win.dsp
+++ b/src/win32/Srb2win.dsp
@@ -563,10 +563,6 @@ SOURCE=..\hardware\hw_md2.h
 # End Source File
 # Begin Source File
 
-SOURCE=..\hardware\hw_trick.c
-# End Source File
-# Begin Source File
-
 SOURCE=..\hardware\hws_data.h
 # End Source File
 # End Group
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index d7e3383b057ad301a86464edda29f8a4bfd63cc8..b90947a9ece7d48c1166bad85861374687923048 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -66,8 +66,8 @@ END
 #include "../doomdef.h" // Needed for version string
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 2,2,0,0
- PRODUCTVERSION 2,2,0,0
+ FILEVERSION 2,2,6,0
+ PRODUCTVERSION 2,2,6,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L