diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ef23044104a5eb16ae9628933baee44086d0b52a..4e284ce65464fc244f364459f8f9b254f2aefee3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -356,7 +356,7 @@ Debian stable:arm64:
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 ERRORMODE=1 NONX86=1C || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 NONX86=1 ARM64=1
+      - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 ERRORMODE=1 NONX86=1 ARM64=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 NONX86=1 ARM64=1
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 10784169ce93220640839b19c148036ad2a5c3ad..1cd14800f7a9e592066485fdba2341622a10b1b6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -101,6 +101,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
 	lua_blockmaplib.c
 	lua_hudlib.c
 	lua_hudlib_drawlist.c
+	lua_colorlib.c
 	lua_inputlib.c
 )
 
diff --git a/src/Sourcefile b/src/Sourcefile
index cca697e80ad46b4566655f67f5ddfbc51fa02ff5..ad5eacfeb9e96f75316ce0d4c66ae8d1e31b4fe1 100644
--- a/src/Sourcefile
+++ b/src/Sourcefile
@@ -96,3 +96,4 @@ lua_blockmaplib.c
 lua_hudlib.c
 lua_hudlib_drawlist.c
 lua_inputlib.c
+lua_colorlib.c
diff --git a/src/b_bot.c b/src/b_bot.c
index 57f7623042d318df981af823ac59d1865c44fb75..033288a867a171b91e2903c3a89549f62d934b8a 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -239,7 +239,8 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	// SPINNING
 	if (!(player->pflags & (PF_SPINNING|PF_STARTDASH)) && mem->thinkstate == AI_SPINFOLLOW)
 		mem->thinkstate = AI_FOLLOW;
-	else if (mem->thinkstate == AI_FOLLOW || mem->thinkstate == AI_SPINFOLLOW)
+	else if ((mem->thinkstate == AI_FOLLOW || mem->thinkstate == AI_SPINFOLLOW)
+		&& bot->charability2 == CA2_SPINDASH)
 	{
 		if (!_2d)
 		{
@@ -329,7 +330,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	if (mem->thinkstate == AI_FOLLOW || mem->thinkstate == AI_CATCHUP || (mem->thinkstate == AI_SPINFOLLOW && player->pflags & PF_JUMPED))
 	{
 		// Flying catch-up
-		if (bot->pflags & PF_THOKKED)
+		if (bot->charability == CA_FLY && bot->pflags & PF_THOKKED)
 		{
 			cmd->forwardmove = min(MAXPLMOVE, (dist/scale)>>3);
 			if (zdist < -64*scale)
diff --git a/src/command.c b/src/command.c
index e0deff8e1cc995109c7a704d16c92bd578914190..d41a551538fa222c85b0919829987f36c9810846 100644
--- a/src/command.c
+++ b/src/command.c
@@ -51,9 +51,11 @@ static void COM_CEchoDuration_f(void);
 static void COM_Exec_f(void);
 static void COM_Wait_f(void);
 static void COM_Help_f(void);
+static void COM_Find_f(void);
 static void COM_Toggle_f(void);
 static void COM_Add_f(void);
 
+
 static void CV_EnforceExecVersion(void);
 static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
 static boolean CV_Command(void);
@@ -344,6 +346,7 @@ void COM_Init(void)
 	COM_AddCommand("exec", COM_Exec_f, 0);
 	COM_AddCommand("wait", COM_Wait_f, 0);
 	COM_AddCommand("help", COM_Help_f, COM_LUA);
+	COM_AddCommand("find", COM_Find_f, COM_LUA);
 	COM_AddCommand("toggle", COM_Toggle_f, COM_LUA);
 	COM_AddCommand("add", COM_Add_f, COM_LUA);
 	RegisterNetXCmd(XD_NETVAR, Got_NetVar);
@@ -879,7 +882,7 @@ static void COM_Help_f(void)
 			boolean floatmode = false;
 			const char *cvalue = NULL;
 			CONS_Printf("\x82""Variable %s:\n", cvar->name);
-			CONS_Printf(M_GetText("  flags :"));
+			CONS_Printf(M_GetText("  flags: "));
 			if (cvar->flags & CV_SAVE)
 				CONS_Printf("AUTOSAVE ");
 			if (cvar->flags & CV_FLOAT)
@@ -976,31 +979,8 @@ static void COM_Help_f(void)
 				return;
 			}
 
-			CONS_Printf("No exact match, searching...\n");
-
-			// variables
-			CONS_Printf("\x82""Variables:\n");
-			for (cvar = consvar_vars; cvar; cvar = cvar->next)
-			{
-				if ((cvar->flags & CV_NOSHOWHELP) || (!strstr(cvar->name, help)))
-					continue;
-				CONS_Printf("%s ", cvar->name);
-				i++;
-			}
-
-			// commands
-			CONS_Printf("\x82""\nCommands:\n");
-			for (cmd = com_commands; cmd; cmd = cmd->next)
-			{
-				if (!strstr(cmd->name, help))
-					continue;
-				CONS_Printf("%s ",cmd->name);
-				i++;
-			}
-
-			CONS_Printf("\x82""\nCheck wiki.srb2.org for more or type help <command or variable>\n");
-
-			CONS_Debug(DBG_GAMELOGIC, "\x87Total : %d\n", i);
+			CONS_Printf("No variable or command named %s", help);
+			CONS_Printf("\x82""\nCheck wiki.srb2.org for more or try typing help without arguments\n");
 		}
 		return;
 	}
@@ -1030,6 +1010,76 @@ static void COM_Help_f(void)
 	}
 }
 
+static void COM_Find_f(void)
+{
+	static char prefix[80];
+	xcommand_t *cmd;
+	consvar_t *cvar;
+	cmdalias_t *alias;
+	const char *match;
+	const char *help;
+	size_t helplen;
+	boolean matchesany;
+
+	if (COM_Argc() != 2)
+	{
+		CONS_Printf(M_GetText("find <text>: Search for variables, commands and aliases containing <text>\n"));
+		return;
+	}
+
+	help = COM_Argv(1);
+	helplen = strlen(help);
+	CONS_Printf("\x82""Variables:\n");
+	matchesany = false;
+	for (cvar = consvar_vars; cvar; cvar = cvar->next)
+	{
+		if (cvar->flags & CV_NOSHOWHELP)
+			continue;
+		match = strstr(cvar->name, help);
+		if (match != NULL)
+		{
+			memcpy(prefix, cvar->name, match - cvar->name);
+			prefix[match - cvar->name] = '\0';
+			CONS_Printf("  %s\x83%s\x80%s\n", prefix, help, &match[helplen]);
+			matchesany = true;
+		}
+	}
+	if (!matchesany)
+		CONS_Printf("  (none)\n");
+
+	CONS_Printf("\x82""Commands:\n");
+	matchesany = false;
+	for (cmd = com_commands; cmd; cmd = cmd->next)
+	{
+		match = strstr(cmd->name, help);
+		if (match != NULL)
+		{
+			memcpy(prefix, cmd->name, match - cmd->name);
+			prefix[match - cmd->name] = '\0';
+			CONS_Printf("  %s\x83%s\x80%s\n", prefix, help, &match[helplen]);
+			matchesany = true;
+		}
+	}
+	if (!matchesany)
+		CONS_Printf("  (none)\n");
+
+	CONS_Printf("\x82""Aliases:\n");
+	matchesany = false;
+	for (alias = com_alias; alias; alias = alias->next)
+	{
+		match = strstr(alias->name, help);
+		if (match != NULL)
+		{
+			memcpy(prefix, alias->name, match - alias->name);
+			prefix[match - alias->name] = '\0';
+			CONS_Printf("  %s\x83%s\x80%s\n", prefix, help, &match[helplen]);
+			matchesany = true;
+		}
+	}
+	if (!matchesany)
+		CONS_Printf("  (none)\n");
+}
+
 /** Toggles a console variable. Useful for on/off values.
   *
   * This works on on/off, yes/no values only
diff --git a/src/console.c b/src/console.c
index dbd7c938a0c3e922e50d39d800a00c10bc598bc3..01b90ebc39474a547e5666e8e714fa826b032570 100644
--- a/src/console.c
+++ b/src/console.c
@@ -921,7 +921,8 @@ boolean CON_Responder(event_t *ev)
 	static UINT8 consdown = false; // console is treated differently due to rare usage
 
 	// sequential completions a la 4dos
-	static char completion[80];
+	static char completioncmd[80 + sizeof("find ")] = "find ";
+	static char *completion = &completioncmd[sizeof("find ")-1];
 
 	static INT32 skips;
 
@@ -1057,36 +1058,14 @@ boolean CON_Responder(event_t *ev)
 		// show all cvars/commands that match what we have inputted
 		if (key == KEY_TAB)
 		{
-			size_t i, len;
-
 			if (!completion[0])
 			{
 				if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
 					return true;
 				strcpy(completion, inputlines[inputline]);
 			}
-			len = strlen(completion);
-
-			//first check commands
-			CONS_Printf("\nCommands:\n");
-			for (i = 0, cmd = COM_CompleteCommand(completion, i); cmd; cmd = COM_CompleteCommand(completion, ++i))
-				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
-			if (i == 0) CONS_Printf("  (none)\n");
-
-			//now we move on to CVARs
-			CONS_Printf("Variables:\n");
-			for (i = 0, cmd = CV_CompleteVar(completion, i); cmd; cmd = CV_CompleteVar(completion, ++i))
-				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
-			if (i == 0) CONS_Printf("  (none)\n");
-
-			//and finally aliases
-			CONS_Printf("Aliases:\n");
-			for (i = 0, cmd = COM_CompleteAlias(completion, i); cmd; cmd = COM_CompleteAlias(completion, ++i))
-				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
-			if (i == 0) CONS_Printf("  (none)\n");
-
+			COM_BufInsertText(completioncmd);
 			completion[0] = 0;
-
 			return true;
 		}
 		// ---
diff --git a/src/d_player.h b/src/d_player.h
index ca700d4e1f593c26423d3f0947a9dae1348fe903..1409e28b3d5cb86537c8d576249881665b9b8e8e 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -158,6 +158,10 @@ typedef enum
 	PF_FORCESTRAFE = 1<<28, // Turning inputs are translated into strafing inputs
 	PF_CANCARRY    = 1<<29, // Can carry another player?
 	PF_FINISHED    = 1<<30, // The player finished the level. NOT the same as exiting
+	
+	// True if shield button down last tic
+	// This may be the final flag, but 2.3 could free up the others
+	PF_SHIELDDOWN    = 1<<31,
 
 	// up to 1<<31 is free
 } pflags_t;
diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h
index 2481ed738b23769a546fef840500d10ba7b024f5..43eb0f00b8df58da69c2dc7123d274d69070c104 100644
--- a/src/d_ticcmd.h
+++ b/src/d_ticcmd.h
@@ -26,20 +26,23 @@
 // Button/action code definitions.
 typedef enum
 {
-	// First 4 bits are weapon change info, DO NOT USE!
-	BT_WEAPONMASK = 0x0F, //our first four bits.
+	// First 3 bits are weapon change info, DO NOT USE!
+	BT_WEAPONMASK = 0x07,  //our first three bits.
+	
+	BT_SHIELD     = 1<<3,  // shield or super action
 
-	BT_WEAPONNEXT = 1<<4,
-	BT_WEAPONPREV = 1<<5,
-
-	BT_ATTACK     = 1<<6, // shoot rings
-	BT_SPIN       = 1<<7,
-	BT_CAMLEFT    = 1<<8, // turn camera left
-	BT_CAMRIGHT   = 1<<9, // turn camera right
-	BT_TOSSFLAG   = 1<<10,
-	BT_JUMP       = 1<<11,
-	BT_FIRENORMAL = 1<<12, // Fire a normal ring no matter what
+	BT_WEAPONNEXT = 1<<4,  // select next weapon
+	BT_WEAPONPREV = 1<<5,  // select previous weapon
 
+	BT_ATTACK     = 1<<6,  // shoot rings
+	BT_SPIN       = 1<<7,  // spin action
+	BT_CAMLEFT    = 1<<8,  // turn camera left
+	BT_CAMRIGHT   = 1<<9,  // turn camera right
+	BT_TOSSFLAG   = 1<<10, // toss flag or emeralds
+	BT_JUMP       = 1<<11, // jump action
+	BT_FIRENORMAL = 1<<12, // fire a normal ring no matter what
+	
+	// custom lua buttons
 	BT_CUSTOM1    = 1<<13,
 	BT_CUSTOM2    = 1<<14,
 	BT_CUSTOM3    = 1<<15,
diff --git a/src/deh_lua.c b/src/deh_lua.c
index bbcb3879d899c661daa8df6007c5dceb961ec7a4..3eec5315b1ce24d8aa5da6ee57238a1a77ee04c9 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -601,7 +601,7 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word)
 	{
 		CacheAndPushConstant(L, word, (lua_Integer)BT_SPIN);
 		return 1;
-	}
+	} 
 
 	for (i = 0; INT_CONST[i].n; i++)
 		if (fastcmp(word,INT_CONST[i].n)) {
diff --git a/src/deh_tables.c b/src/deh_tables.c
index 617dd3ae0226e98cdc27c2718e55071789fee572..fabcb3642e71dd02d67085609b36ca0901456b4c 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -5577,7 +5577,8 @@ struct int_const_s const INT_CONST[] = {
 	{"ROTAXIS_Z",ROTAXIS_Z},
 
 	// Buttons (ticcmd_t)
-	{"BT_WEAPONMASK",BT_WEAPONMASK}, //our first four bits.
+	{"BT_WEAPONMASK",BT_WEAPONMASK}, //our first three bits.
+	{"BT_SHIELD",BT_SHIELD},
 	{"BT_WEAPONNEXT",BT_WEAPONNEXT},
 	{"BT_WEAPONPREV",BT_WEAPONPREV},
 	{"BT_ATTACK",BT_ATTACK}, // shoot rings
@@ -5757,9 +5758,7 @@ struct int_const_s const INT_CONST[] = {
 	{"GC_WEPSLOT5",GC_WEPSLOT5},
 	{"GC_WEPSLOT6",GC_WEPSLOT6},
 	{"GC_WEPSLOT7",GC_WEPSLOT7},
-	{"GC_WEPSLOT8",GC_WEPSLOT8},
-	{"GC_WEPSLOT9",GC_WEPSLOT9},
-	{"GC_WEPSLOT10",GC_WEPSLOT10},
+	{"GC_SHIELD",GC_SHIELD},
 	{"GC_FIRE",GC_FIRE},
 	{"GC_FIRENORMAL",GC_FIRENORMAL},
 	{"GC_TOSSFLAG",GC_TOSSFLAG},
diff --git a/src/dummy/i_sound.c b/src/dummy/i_sound.c
index ba0fc64239267c0a5964e4b619eb367194a60912..436187805da2968c514f640404ffc4bc34f57c87 100644
--- a/src/dummy/i_sound.c
+++ b/src/dummy/i_sound.c
@@ -164,7 +164,7 @@ void I_SetMusicVolume(UINT8 volume)
 	(void)volume;
 }
 
-boolean I_SetSongTrack(int track)
+boolean I_SetSongTrack(INT32 track)
 {
 	(void)track;
 	return false;
diff --git a/src/filesrch.c b/src/filesrch.c
index 313f286e1f29dad11f8b6e5ec29d9fa39a4613ae..111dfd6e7a8be99e9f9b2764d773ec9d09dadd2a 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -433,9 +433,19 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 		// okay, now we actually want searchpath to incorporate d_name
 		strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name);
 
+#if defined(__linux__) || defined(__FreeBSD__)
+		if (dent->d_type == DT_UNKNOWN)
+			if (lstat(searchpath,&fsstat) == 0 && S_ISDIR(fsstat.st_mode))
+				dent->d_type = DT_DIR;
+
+		// Linux and FreeBSD has a special field for file type on dirent, so use that to speed up lookups.
+		// FIXME: should we also follow symlinks?
+		if (dent->d_type == DT_DIR && depthleft)
+#else
 		if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat
 			; // was the file (re)moved? can't stat it
 		else if (S_ISDIR(fsstat.st_mode) && depthleft)
+#endif
 		{
 			searchpathindex[--depthleft] = strlen(searchpath) + 1;
 			dirhandle[depthleft] = opendir(searchpath);
diff --git a/src/g_game.c b/src/g_game.c
index 438428b90d9f1a69c126f3741c2d8bc925678ed1..1388148e71aa6390261a8f43d45297334525c44a 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1335,7 +1335,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 #if NUM_WEAPONS > 10
 "Add extra inputs to g_input.h/gamecontrols_e"
 #endif
-	//use the four avaliable bits to determine the weapon.
+	//use the three avaliable bits to determine the weapon.
 	cmd->buttons &= ~BT_WEAPONMASK;
 	for (i = 0; i < NUM_WEAPONS; ++i)
 		if (PLAYERINPUTDOWN(ssplayer, GC_WEPSLOT1 + i))
@@ -1353,9 +1353,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	axis = PlayerJoyAxis(ssplayer, JA_FIRENORMAL);
 	if (PLAYERINPUTDOWN(ssplayer, GC_FIRENORMAL) || (usejoystick && axis > 0))
 		cmd->buttons |= BT_FIRENORMAL;
-
+	
+	// Toss flag button
 	if (PLAYERINPUTDOWN(ssplayer, GC_TOSSFLAG))
 		cmd->buttons |= BT_TOSSFLAG;
+	
+	// Shield button
+	if (PLAYERINPUTDOWN(ssplayer, GC_SHIELD))
+		cmd->buttons |= BT_SHIELD;
 
 	// Lua scriptable buttons
 	if (PLAYERINPUTDOWN(ssplayer, GC_CUSTOM1))
@@ -2748,6 +2753,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	p->pflags |= PF_SPINDOWN;
 	p->pflags |= PF_ATTACKDOWN;
 	p->pflags |= PF_JUMPDOWN;
+	p->pflags |= PF_SHIELDDOWN;
 
 	p->playerstate = PST_LIVE;
 	p->panim = PA_IDLE; // standing animation
diff --git a/src/g_input.c b/src/g_input.c
index 8b50563313c77937822d68d2a170b1974fa3837d..3f1be37ba3f588fb2f3b2f6012ae7bf29c397b98 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -576,9 +576,7 @@ static const char *gamecontrolname[NUM_GAMECONTROLS] =
 	"weapon5",
 	"weapon6",
 	"weapon7",
-	"weapon8",
-	"weapon9",
-	"weapon10",
+	"shield",
 	"fire",
 	"firenormal",
 	"tossflag",
@@ -693,6 +691,7 @@ void G_DefineDefaultControls(void)
 	gamecontroldefault[gcs_fps][GC_CENTERVIEW ][0] = KEY_LCTRL;
 	gamecontroldefault[gcs_fps][GC_JUMP       ][0] = KEY_SPACE;
 	gamecontroldefault[gcs_fps][GC_SPIN       ][0] = KEY_LSHIFT;
+	gamecontroldefault[gcs_fps][GC_SHIELD     ][0] = KEY_LALT;
 	gamecontroldefault[gcs_fps][GC_FIRE       ][0] = KEY_RCTRL;
 	gamecontroldefault[gcs_fps][GC_FIRE       ][1] = KEY_MOUSE1+0;
 	gamecontroldefault[gcs_fps][GC_FIRENORMAL ][0] = KEY_RALT;
@@ -713,6 +712,7 @@ void G_DefineDefaultControls(void)
 	gamecontroldefault[gcs_platform][GC_CENTERVIEW ][0] = KEY_END;
 	gamecontroldefault[gcs_platform][GC_JUMP       ][0] = KEY_SPACE;
 	gamecontroldefault[gcs_platform][GC_SPIN       ][0] = KEY_LSHIFT;
+	gamecontroldefault[gcs_platform][GC_SHIELD     ][0] = KEY_LALT;
 	gamecontroldefault[gcs_platform][GC_FIRE       ][0] = 's';
 	gamecontroldefault[gcs_platform][GC_FIRE       ][1] = KEY_MOUSE1+0;
 	gamecontroldefault[gcs_platform][GC_FIRENORMAL ][0] = 'w';
@@ -728,9 +728,6 @@ void G_DefineDefaultControls(void)
 		gamecontroldefault[i][GC_WEPSLOT5     ][0] = '5';
 		gamecontroldefault[i][GC_WEPSLOT6     ][0] = '6';
 		gamecontroldefault[i][GC_WEPSLOT7     ][0] = '7';
-		gamecontroldefault[i][GC_WEPSLOT8     ][0] = '8';
-		gamecontroldefault[i][GC_WEPSLOT9     ][0] = '9';
-		gamecontroldefault[i][GC_WEPSLOT10    ][0] = '0';
 		gamecontroldefault[i][GC_TOSSFLAG     ][0] = '\'';
 		gamecontroldefault[i][GC_CAMTOGGLE    ][0] = 'v';
 		gamecontroldefault[i][GC_CAMRESET     ][0] = 'r';
@@ -749,15 +746,15 @@ void G_DefineDefaultControls(void)
 		gamecontroldefault[i][GC_CUSTOM1      ][1] = KEY_JOY1+1; // B
 		gamecontroldefault[i][GC_CUSTOM2      ][1] = KEY_JOY1+3; // Y
 		gamecontroldefault[i][GC_CUSTOM3      ][1] = KEY_JOY1+8; // Left Stick
-		gamecontroldefault[i][GC_CAMTOGGLE    ][1] = KEY_JOY1+4; // LB
+		gamecontroldefault[i][GC_SHIELD       ][1] = KEY_JOY1+4; // LB
 		gamecontroldefault[i][GC_CENTERVIEW   ][1] = KEY_JOY1+5; // RB
-		gamecontroldefault[i][GC_SCREENSHOT   ][1] = KEY_JOY1+6; // Back
+		gamecontroldefault[i][GC_SCORES       ][1] = KEY_JOY1+6; // Back
 		gamecontroldefault[i][GC_SYSTEMMENU   ][0] = KEY_JOY1+7; // Start
 		gamecontroldefault[i][GC_WEAPONPREV   ][1] = KEY_HAT1+2; // D-Pad Left
 		gamecontroldefault[i][GC_WEAPONNEXT   ][1] = KEY_HAT1+3; // D-Pad Right
 		gamecontroldefault[i][GC_VIEWPOINTNEXT][1] = KEY_JOY1+9; // Right Stick
 		gamecontroldefault[i][GC_TOSSFLAG     ][1] = KEY_HAT1+0; // D-Pad Up
-		gamecontroldefault[i][GC_SCORES       ][1] = KEY_HAT1+1; // D-Pad Down
+		gamecontroldefault[i][GC_CAMTOGGLE    ][1] = KEY_HAT1+1; // D-Pad Down
 
 		// Second player controls only have joypad defaults
 		gamecontrolbisdefault[i][GC_JUMP         ][1] = KEY_2JOY1+0; // A
@@ -765,15 +762,15 @@ void G_DefineDefaultControls(void)
 		gamecontrolbisdefault[i][GC_CUSTOM1      ][1] = KEY_2JOY1+1; // B
 		gamecontrolbisdefault[i][GC_CUSTOM2      ][1] = KEY_2JOY1+3; // Y
 		gamecontrolbisdefault[i][GC_CUSTOM3      ][1] = KEY_2JOY1+8; // Left Stick
-		gamecontrolbisdefault[i][GC_CAMTOGGLE    ][1] = KEY_2JOY1+4; // LB
+		gamecontrolbisdefault[i][GC_SHIELD       ][1] = KEY_2JOY1+4; // LB
 		gamecontrolbisdefault[i][GC_CENTERVIEW   ][1] = KEY_2JOY1+5; // RB
-		gamecontrolbisdefault[i][GC_SCREENSHOT   ][1] = KEY_2JOY1+6; // Back
+		//gamecontrolbisdefault[i][GC_SCORES       ][1] = KEY_2JOY1+6; // Back
 		//gamecontrolbisdefault[i][GC_SYSTEMMENU   ][0] = KEY_2JOY1+7; // Start
 		gamecontrolbisdefault[i][GC_WEAPONPREV   ][1] = KEY_2HAT1+2; // D-Pad Left
 		gamecontrolbisdefault[i][GC_WEAPONNEXT   ][1] = KEY_2HAT1+3; // D-Pad Right
 		gamecontrolbisdefault[i][GC_VIEWPOINTNEXT][1] = KEY_2JOY1+9; // Right Stick
 		gamecontrolbisdefault[i][GC_TOSSFLAG     ][1] = KEY_2HAT1+0; // D-Pad Up
-		//gamecontrolbisdefault[i][GC_SCORES       ][1] = KEY_2HAT1+1; // D-Pad Down
+		gamecontrolbisdefault[i][GC_CAMTOGGLE    ][1] = KEY_2HAT1+1; // D-Pad Down
 	}
 }
 
@@ -1004,6 +1001,7 @@ static void setcontrol(INT32 (*gc)[2])
 
 	// TODO: 2.3: Delete the "use" alias
 	namectrl = (stricmp(COM_Argv(1), "use")) ? COM_Argv(1) : "spin";
+		
 
 	for (numctrl = 0; numctrl < NUM_GAMECONTROLS && stricmp(namectrl, gamecontrolname[numctrl]);
 		numctrl++)
diff --git a/src/g_input.h b/src/g_input.h
index e9c909e6e2c222360086874ddb27e2495b4e0381..48c103076667df50884767685386b98bd4865a02 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -74,9 +74,7 @@ typedef enum
 	GC_WEPSLOT5,
 	GC_WEPSLOT6,
 	GC_WEPSLOT7,
-	GC_WEPSLOT8,
-	GC_WEPSLOT9,
-	GC_WEPSLOT10,
+	GC_SHIELD,
 	GC_FIRE,
 	GC_FIRENORMAL,
 	GC_TOSSFLAG,
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 74c4ed7d2eec91d1c846ca0a43f23d636a1024f8..b02f2824aad4437cdbd5f496534e019893870557 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -276,9 +276,6 @@ struct FSurfaceInfo
 };
 typedef struct FSurfaceInfo FSurfaceInfo;
 
-#define GL_DEFAULTMIX 0x00000000
-#define GL_DEFAULTFOG 0xFF000000
-
 //Hurdler: added for backward compatibility
 enum hwdsetspecialstate
 {
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 6c1b8881ffdca7a59716e8f909fcf337ef340d9b..29b3c9b24b51a8489997f36567d079a3f0d1baa2 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -170,7 +170,6 @@ ps_metric_t ps_hw_batchdrawtime = {0};
 
 boolean gl_init = false;
 boolean gl_maploaded = false;
-boolean gl_sessioncommandsadded = false;
 boolean gl_shadersavailable = true;
 
 // ==========================================================================
@@ -187,8 +186,8 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col
 	RGBA_t poly_color, tint_color, fade_color;
 
 	poly_color.rgba = 0xFFFFFFFF;
-	tint_color.rgba = (colormap != NULL) ? (UINT32)colormap->rgba : GL_DEFAULTMIX;
-	fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : GL_DEFAULTFOG;
+	tint_color.rgba = (colormap != NULL) ? (UINT32)colormap->rgba : 0x00000000;
+	fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : 0xFF000000;
 
 	// Crappy backup coloring if you can't do shaders
 	if (!HWR_UseShader())
@@ -202,7 +201,7 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col
 		blue = (float)poly_color.s.blue;
 
 		// 48 is just an arbritrary value that looked relatively okay.
-		tint_alpha = (float)(sqrt(tint_color.s.alpha) * 48) / 255.0f;
+		tint_alpha = (float)(sqrt((float)tint_color.s.alpha / 10.2) * 48) / 255.0f;
 
 		// 8 is roughly the brightness of the "close" color in Software, and 16 the brightness of the "far" color.
 		// 8 is too bright for dark levels, and 16 is too dark for bright levels.
@@ -245,7 +244,7 @@ UINT8 HWR_FogBlockAlpha(INT32 light, extracolormap_t *colormap) // Let's see if
 	RGBA_t realcolor, surfcolor;
 	INT32 alpha;
 
-	realcolor.rgba = (colormap != NULL) ? colormap->rgba : GL_DEFAULTMIX;
+	realcolor.rgba = (colormap != NULL) ? colormap->rgba : 0x00000000;
 
 	if (cv_glshaders.value && gl_shadersavailable)
 	{
@@ -585,10 +584,26 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 				P_ClosestPointOnLine(viewx, viewy, line->linedef, &v);
 				dist = FIXED_TO_FLOAT(R_PointToDist(v.x, v.y));
 
-				x1 = ((polyvertex_t *)line->pv1)->x;
-				y1 = ((polyvertex_t *)line->pv1)->y;
-				xd = ((polyvertex_t *)line->pv2)->x - x1;
-				yd = ((polyvertex_t *)line->pv2)->y - y1;
+				if (line->pv1)
+				{
+					x1 = ((polyvertex_t *)line->pv1)->x;
+					y1 = ((polyvertex_t *)line->pv1)->y;
+				}
+				else
+				{
+					x1 = FIXED_TO_FLOAT(line->v1->x);
+					y1 = FIXED_TO_FLOAT(line->v1->x);
+				}
+				if (line->pv2)
+				{
+					xd = ((polyvertex_t *)line->pv2)->x - x1;
+					yd = ((polyvertex_t *)line->pv2)->y - y1;
+				}
+				else
+				{
+					xd = FIXED_TO_FLOAT(line->v2->x) - x1;
+					yd = FIXED_TO_FLOAT(line->v2->y) - y1;
+				}
 
 				// Based on the seg length and the distance from the line, split horizon into multiple poly sets to reduce distortion
 				dist = sqrtf((xd*xd) + (yd*yd)) / dist / 16.0f;
@@ -1071,10 +1086,26 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 	gl_sidedef = gl_curline->sidedef;
 	gl_linedef = gl_curline->linedef;
 
-	vs.x = ((polyvertex_t *)gl_curline->pv1)->x;
-	vs.y = ((polyvertex_t *)gl_curline->pv1)->y;
-	ve.x = ((polyvertex_t *)gl_curline->pv2)->x;
-	ve.y = ((polyvertex_t *)gl_curline->pv2)->y;
+	if (gl_curline->pv1)
+	{
+		vs.x = ((polyvertex_t *)gl_curline->pv1)->x;
+		vs.y = ((polyvertex_t *)gl_curline->pv1)->y;
+	}
+	else
+	{
+		vs.x = FIXED_TO_FLOAT(gl_curline->v1->x);
+		vs.y = FIXED_TO_FLOAT(gl_curline->v1->y);
+	}
+	if (gl_curline->pv2)
+	{
+		ve.x = ((polyvertex_t *)gl_curline->pv2)->x;
+		ve.y = ((polyvertex_t *)gl_curline->pv2)->y;
+	}
+	else
+	{
+		ve.x = FIXED_TO_FLOAT(gl_curline->v2->x);
+		ve.y = FIXED_TO_FLOAT(gl_curline->v2->y);
+	}
 
 	v1x = FLOAT_TO_FIXED(vs.x);
 	v1y = FLOAT_TO_FIXED(vs.y);
@@ -1869,10 +1900,26 @@ static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacks
 	if (afrontsector->f_slope || afrontsector->c_slope || abacksector->f_slope || abacksector->c_slope)
 	{
 		fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
-		v1x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->x);
-		v1y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->y);
-		v2x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->x);
-		v2y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->y);
+		if (gl_curline->pv1)
+		{
+			v1x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->x);
+			v1y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->y);
+		}
+		else
+		{
+			v1x = gl_curline->v1->x;
+			v1y = gl_curline->v1->y;
+		}
+		if (gl_curline->pv2)
+		{
+			v2x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->x);
+			v2y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->y);
+		}
+		else
+		{
+			v2x = gl_curline->v2->x;
+			v2y = gl_curline->v2->y;
+		}
 #define SLOPEPARAMS(slope, end1, end2, normalheight) \
 		end1 = P_GetZAt(slope, v1x, v1y, normalheight); \
 		end2 = P_GetZAt(slope, v2x, v2y, normalheight);
@@ -2245,10 +2292,26 @@ static void HWR_AddLine(seg_t * line)
 
 	gl_curline = line;
 
-	v1x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->x);
-	v1y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->y);
-	v2x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->x);
-	v2y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->y);
+	if (gl_curline->pv1)
+	{
+		v1x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->x);
+		v1y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->y);
+	}
+	else
+	{
+		v1x = gl_curline->v1->x;
+		v1y = gl_curline->v1->y;
+	}
+	if (gl_curline->pv2)
+	{
+		v2x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->x);
+		v2y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->y);
+	}
+	else
+	{
+		v2x = gl_curline->v2->x;
+		v2y = gl_curline->v2->y;
+	}
 
 	// OPTIMIZE: quickly reject orthogonal back sides.
 	angle1 = R_PointToAngle64(v1x, v1y);
@@ -5261,7 +5324,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 			rollangle = R_GetRollAngle(spriterotangle);
 		}
 
-		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
+		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, sprinfo, rollangle);
 
 		if (rotsprite != NULL)
 		{
@@ -6571,7 +6634,7 @@ consvar_t cv_glfakecontrast = CVAR_INIT ("gr_fakecontrast", "Smooth", CV_SAVE, g
 consvar_t cv_glslopecontrast = CVAR_INIT ("gr_slopecontrast", "Off", CV_SAVE, CV_OnOff, NULL);
 
 consvar_t cv_glfiltermode = CVAR_INIT ("gr_filtermode", "Nearest", CV_SAVE|CV_CALL, glfiltermode_cons_t, CV_glfiltermode_OnChange);
-consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_CALL, glanisotropicmode_cons_t, CV_glanisotropic_OnChange);
+consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_SAVE|CV_CALL, glanisotropicmode_cons_t, CV_glanisotropic_OnChange);
 
 consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL);
 
@@ -6613,6 +6676,7 @@ void HWR_AddCommands(void)
 	CV_RegisterVar(&cv_glallowshaders);
 
 	CV_RegisterVar(&cv_glfiltermode);
+	CV_RegisterVar(&cv_glanisotropicmode);
 	CV_RegisterVar(&cv_glsolvetjoin);
 
 	CV_RegisterVar(&cv_glbatching);
@@ -6622,14 +6686,6 @@ void HWR_AddCommands(void)
 #endif
 }
 
-void HWR_AddSessionCommands(void)
-{
-	if (gl_sessioncommandsadded)
-		return;
-	CV_RegisterVar(&cv_glanisotropicmode);
-	gl_sessioncommandsadded = true;
-}
-
 // --------------------------------------------------------------------------
 // Setup the hardware renderer
 // --------------------------------------------------------------------------
@@ -6640,7 +6696,6 @@ void HWR_Startup(void)
 		CONS_Printf("HWR_Startup()...\n");
 
 		HWR_InitPolyPool();
-		HWR_AddSessionCommands();
 		HWR_InitMapTextures();
 		HWR_InitModels();
 #ifdef ALAM_LIGHTING
@@ -6663,10 +6718,6 @@ void HWR_Startup(void)
 // --------------------------------------------------------------------------
 void HWR_Switch(void)
 {
-	// Add session commands
-	if (!gl_sessioncommandsadded)
-		HWR_AddSessionCommands();
-
 	// Set special states from CVARs
 	HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_glfiltermode.value);
 	HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_glanisotropicmode.value);
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 9450ca2c56ed56d52916f587136e2e9352f3f4b4..cce4e8f0ac4bb48e04c5443656b8dfe1ffa35b2c 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -54,7 +54,6 @@ UINT8 *HWR_GetScreenshot(void);
 boolean HWR_Screenshot(const char *pathname);
 
 void HWR_AddCommands(void);
-void HWR_AddSessionCommands(void);
 void transform(float *cx, float *cy, float *cz);
 INT32 HWR_GetTextureUsed(void);
 void HWR_DoPostProcessor(player_t *player);
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 0f834213592e041c866ca05a90d2599476c81c76..3e080105cbde44fd66775c1d4065c10c57fb7041 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1140,9 +1140,6 @@ static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 ski
 	Z_ChangeTag(newMipmap->data, PU_HWRMODELTEXTURE_UNLOCKED);
 }
 
-#define NORMALFOG 0x00000000
-#define FADEFOG 0x19000000
-
 static boolean HWR_AllowModel(mobj_t *mobj)
 {
 	// Signpost overlay. Not needed.
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 71cb5ca70375629ee06c7943e0c5be881555a0c1..6c0c90fc5b06136f6ad06918e3bd027b4f57779a 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -697,7 +697,7 @@ static GLRGBAFloat shader_defaultcolor = {1.0f, 1.0f, 1.0f, 1.0f};
 #define GLSL_SOFTWARE_TINT_EQUATION \
 	"if (tint_color.a > 0.0) {\n" \
 		"float color_bright = sqrt((base_color.r * base_color.r) + (base_color.g * base_color.g) + (base_color.b * base_color.b));\n" \
-		"float strength = sqrt(9.0 * tint_color.a);\n" \
+		"float strength = sqrt(tint_color.a);\n" \
 		"final_color.r = clamp((color_bright * (tint_color.r * strength)) + (base_color.r * (1.0 - strength)), 0.0, 1.0);\n" \
 		"final_color.g = clamp((color_bright * (tint_color.g * strength)) + (base_color.g * (1.0 - strength)), 0.0, 1.0);\n" \
 		"final_color.b = clamp((color_bright * (tint_color.b * strength)) + (base_color.b * (1.0 - strength)), 0.0, 1.0);\n" \
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 16f238abeb19575cb5ce0e626be4660e723727ca..9c723353b33ab6057fea4fc5b24de2499e607c50 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -213,6 +213,8 @@ static const struct {
 	{META_HUDINFO,      "hudinfo_t"},
 	{META_PATCH,        "patch_t"},
 	{META_COLORMAP,     "colormap"},
+	{META_EXTRACOLORMAP,"extracolormap_t"},
+	{META_LIGHTTABLE,   "lighttable_t"},
 	{META_CAMERA,       "camera_t"},
 
 	{META_ACTION,       "action"},
@@ -1686,11 +1688,12 @@ static int lib_pHomingAttack(lua_State *L)
 static int lib_pSuperReady(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	boolean transform = (boolean)lua_opttrueboolean(L, 2);
 	//HUDSAFE
 	INLEVEL
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
-	lua_pushboolean(L, P_SuperReady(player));
+	lua_pushboolean(L, P_SuperReady(player, transform));
 	return 1;
 }
 
@@ -2021,6 +2024,30 @@ static int lib_pCeilingzAtPos(lua_State *L)
 	return 1;
 }
 
+static int lib_pGetSectorColormapAt(lua_State *L)
+{
+	boolean has_sector = false;
+	sector_t *sector = NULL;
+	if (!lua_isnoneornil(L, 1))
+	{
+		has_sector = true;
+		sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
+	}
+	fixed_t x = luaL_checkfixed(L, 2);
+	fixed_t y = luaL_checkfixed(L, 3);
+	fixed_t z = luaL_checkfixed(L, 4);
+	INLEVEL
+	if (has_sector && !sector)
+		return LUA_ErrInvalid(L, "sector_t");
+	extracolormap_t *exc;
+	if (sector)
+		exc = P_GetColormapFromSectorAt(sector, x, y, z);
+	else
+		exc = P_GetSectorColormapAt(x, y, z);
+	LUA_PushUserdata(L, exc, META_EXTRACOLORMAP);
+	return 1;
+}
+
 static int lib_pDoSpring(lua_State *L)
 {
 	mobj_t *spring = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@@ -4303,6 +4330,7 @@ static luaL_Reg lib[] = {
 	{"P_RadiusAttack",lib_pRadiusAttack},
 	{"P_FloorzAtPos",lib_pFloorzAtPos},
 	{"P_CeilingzAtPos",lib_pCeilingzAtPos},
+	{"P_GetSectorColormapAt",lib_pGetSectorColormapAt},
 	{"P_DoSpring",lib_pDoSpring},
 	{"P_TouchSpecialThing",lib_pTouchSpecialThing},
 	{"P_TryCameraMove", lib_pTryCameraMove},
diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..1e6e82333f18f28421a651728afd42ffc6d387f5
--- /dev/null
+++ b/src/lua_colorlib.c
@@ -0,0 +1,332 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2021-2022 by "Lactozilla".
+// Copyright (C) 2014-2023 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  lua_colorlib.c
+/// \brief color and colormap libraries for Lua scripting
+
+#include "doomdef.h"
+#include "fastcmp.h"
+#include "r_data.h"
+
+#include "lua_script.h"
+#include "lua_libs.h"
+
+#define IS_HEX_CHAR(x) ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F'))
+#define ARE_HEX_CHARS(str, i) IS_HEX_CHAR(str[i]) && IS_HEX_CHAR(str[i + 1])
+
+static UINT32 hex2int(char x)
+{
+	if (x >= '0' && x <= '9')
+		return x - '0';
+	else if (x >= 'a' && x <= 'f')
+		return x - 'a' + 10;
+	else if (x >= 'A' && x <= 'F')
+		return x - 'A' + 10;
+
+	return 0;
+}
+
+static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc)
+{
+	const char *hex = str;
+
+	if (hex[0] == '#')
+		hex++;
+	else if (!IS_HEX_CHAR(hex[0]))
+		return 0;
+
+	size_t len = strlen(hex);
+
+	if (len == 3)
+	{
+		// Shorthand like #09C
+		for (unsigned i = 0; i < 3; i++)
+		{
+			if (!IS_HEX_CHAR(hex[i]))
+				return 0;
+
+			UINT32 hx = hex2int(hex[i]);
+			*rgba++ = (hx * 16) + hx;
+		}
+
+		return 3;
+	}
+	else if (len == 6 || len == 8)
+	{
+		if (numc != 4)
+			len = 6;
+
+		// A triplet like #0099CC
+		for (unsigned i = 0; i < len; i += 2)
+		{
+			if (!ARE_HEX_CHARS(hex, i))
+				return false;
+
+			*rgba++ = (hex2int(hex[i]) * 16) + hex2int(hex[i + 1]);
+		}
+
+		return len;
+	}
+
+	return 0;
+}
+
+/////////////////////////
+// extracolormap userdata
+/////////////////////////
+
+enum extracolormap_e {
+	extracolormap_red = 0,
+	extracolormap_green,
+	extracolormap_blue,
+	extracolormap_alpha,
+	extracolormap_color,
+	extracolormap_fade_red,
+	extracolormap_fade_green,
+	extracolormap_fade_blue,
+	extracolormap_fade_alpha,
+	extracolormap_fade_color,
+	extracolormap_fade_start,
+	extracolormap_fade_end,
+	extracolormap_colormap
+};
+
+static const char *const extracolormap_opt[] = {
+	"red",
+	"green",
+	"blue",
+	"alpha",
+	"color",
+	"fade_red",
+	"fade_green",
+	"fade_blue",
+	"fade_alpha",
+	"fade_color",
+	"fade_start",
+	"fade_end",
+	"colormap",
+	NULL};
+
+static int extracolormap_get(lua_State *L)
+{
+	extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP));
+	enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt);
+
+	switch (field)
+	{
+	case extracolormap_red:
+		lua_pushinteger(L, R_GetRgbaR(exc->rgba));
+		break;
+	case extracolormap_green:
+		lua_pushinteger(L, R_GetRgbaG(exc->rgba));
+		break;
+	case extracolormap_blue:
+		lua_pushinteger(L, R_GetRgbaB(exc->rgba));
+		break;
+	case extracolormap_alpha:
+		lua_pushinteger(L, R_GetRgbaA(exc->rgba));
+		break;
+	case extracolormap_color:
+		lua_pushinteger(L, R_GetRgbaR(exc->rgba));
+		lua_pushinteger(L, R_GetRgbaG(exc->rgba));
+		lua_pushinteger(L, R_GetRgbaB(exc->rgba));
+		lua_pushinteger(L, R_GetRgbaA(exc->rgba));
+		return 4;
+	case extracolormap_fade_red:
+		lua_pushinteger(L, R_GetRgbaR(exc->fadergba));
+		break;
+	case extracolormap_fade_green:
+		lua_pushinteger(L, R_GetRgbaG(exc->fadergba));
+		break;
+	case extracolormap_fade_blue:
+		lua_pushinteger(L, R_GetRgbaB(exc->fadergba));
+		break;
+	case extracolormap_fade_alpha:
+		lua_pushinteger(L, R_GetRgbaA(exc->fadergba));
+		break;
+	case extracolormap_fade_color:
+		lua_pushinteger(L, R_GetRgbaR(exc->fadergba));
+		lua_pushinteger(L, R_GetRgbaG(exc->fadergba));
+		lua_pushinteger(L, R_GetRgbaB(exc->fadergba));
+		lua_pushinteger(L, R_GetRgbaA(exc->fadergba));
+		return 4;
+	case extracolormap_fade_start:
+		lua_pushinteger(L, exc->fadestart);
+		break;
+	case extracolormap_fade_end:
+		lua_pushinteger(L, exc->fadeend);
+		break;
+	case extracolormap_colormap:
+		LUA_PushUserdata(L, exc->colormap, META_LIGHTTABLE);
+		break;
+	}
+	return 1;
+}
+
+static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba, int arg)
+{
+	if (lua_type(L, arg) == LUA_TSTRING)
+	{
+		const char *str = lua_tostring(L, arg);
+		UINT8 parsed = ParseHTMLColor(str, rgba, 4);
+		if (!parsed)
+			luaL_error(L, "Malformed HTML color '%s'", str);
+	}
+	else
+	{
+		UINT32 colors = lua_tointeger(L, arg);
+		if (colors > 0xFFFFFF)
+		{
+			rgba[0] = (colors >> 24) & 0xFF;
+			rgba[1] = (colors >> 16) & 0xFF;
+			rgba[2] = (colors >> 8) & 0xFF;
+			rgba[3] = colors & 0xFF;
+		}
+		else
+		{
+			rgba[0] = (colors >> 16) & 0xFF;
+			rgba[1] = (colors >> 8) & 0xFF;
+			rgba[2] = colors & 0xFF;
+			rgba[3] = 0xFF;
+		}
+	}
+}
+
+static int extracolormap_set(lua_State *L)
+{
+	extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP));
+	enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt);
+
+	UINT8 r = R_GetRgbaR(exc->rgba);
+	UINT8 g = R_GetRgbaG(exc->rgba);
+	UINT8 b = R_GetRgbaB(exc->rgba);
+	UINT8 a = R_GetRgbaA(exc->rgba);
+
+	UINT8 fr = R_GetRgbaR(exc->fadergba);
+	UINT8 fg = R_GetRgbaG(exc->fadergba);
+	UINT8 fb = R_GetRgbaB(exc->fadergba);
+	UINT8 fa = R_GetRgbaA(exc->fadergba);
+
+	UINT8 rgba[4];
+
+	INT32 old_rgba = exc->rgba, old_fade_rgba = exc->fadergba; // It's not unsigned?
+	UINT8 old_fade_start = exc->fadestart, old_fade_end = exc->fadeend;
+
+#define val luaL_checkinteger(L, 3)
+
+	switch(field)
+	{
+	case extracolormap_red:
+		exc->rgba = R_PutRgbaRGBA(val, g, b, a);
+		break;
+	case extracolormap_green:
+		exc->rgba = R_PutRgbaRGBA(r, val, b, a);
+		break;
+	case extracolormap_blue:
+		exc->rgba = R_PutRgbaRGBA(r, g, val, a);
+		break;
+	case extracolormap_alpha:
+		exc->rgba = R_PutRgbaRGBA(r, g, b, val);
+		break;
+	case extracolormap_color:
+		rgba[0] = r;
+		rgba[1] = g;
+		rgba[2] = b;
+		rgba[3] = a;
+		GetExtraColormapRGBA(L, rgba, 3);
+		exc->rgba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]);
+		break;
+	case extracolormap_fade_red:
+		exc->fadergba = R_PutRgbaRGBA(val, fg, fb, fa);
+		break;
+	case extracolormap_fade_green:
+		exc->fadergba = R_PutRgbaRGBA(fr, val, fb, fa);
+		break;
+	case extracolormap_fade_blue:
+		exc->fadergba = R_PutRgbaRGBA(fr, fg, val, fa);
+		break;
+	case extracolormap_fade_alpha:
+		exc->fadergba = R_PutRgbaRGBA(fr, fg, fb, val);
+		break;
+	case extracolormap_fade_color:
+		rgba[0] = fr;
+		rgba[1] = fg;
+		rgba[2] = fb;
+		rgba[3] = fa;
+		GetExtraColormapRGBA(L, rgba, 3);
+		exc->fadergba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]);
+		break;
+	case extracolormap_fade_start:
+		if (val > 31)
+			return luaL_error(L, "fade start %d out of range (0 - 31)", val);
+		exc->fadestart = val;
+		break;
+	case extracolormap_fade_end:
+		if (val > 31)
+			return luaL_error(L, "fade end %d out of range (0 - 31)", val);
+		exc->fadeend = val;
+		break;
+	case extracolormap_colormap:
+		return luaL_error(L, LUA_QL("extracolormap_t") " field " LUA_QS " should not be set directly.", extracolormap_opt[field]);
+	}
+
+#undef val
+
+	if (exc->rgba != old_rgba
+		|| exc->fadergba != old_fade_rgba
+		|| exc->fadestart != old_fade_start
+		|| exc->fadeend != old_fade_end)
+	R_GenerateLightTable(exc, true);
+
+	return 0;
+}
+
+static int lighttable_get(lua_State *L)
+{
+	void **userdata;
+
+	lighttable_t *table = *((lighttable_t **)luaL_checkudata(L, 1, META_LIGHTTABLE));
+	UINT32 row = luaL_checkinteger(L, 2);
+	if (row < 1 || row > 34)
+		return luaL_error(L, "lighttable row %d out of range (1 - %d)", row, 34);
+
+	userdata = lua_newuserdata(L, sizeof(void *));
+	*userdata = &table[256 * (row - 1)];
+	luaL_getmetatable(L, META_COLORMAP);
+	lua_setmetatable(L, -2);
+
+	return 1;
+}
+
+static int lighttable_len(lua_State *L)
+{
+	lua_pushinteger(L, NUM_PALETTE_ENTRIES);
+	return 1;
+}
+
+int LUA_ColorLib(lua_State *L)
+{
+	luaL_newmetatable(L, META_EXTRACOLORMAP);
+		lua_pushcfunction(L, extracolormap_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, extracolormap_set);
+		lua_setfield(L, -2, "__newindex");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_LIGHTTABLE);
+		lua_pushcfunction(L, lighttable_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, lighttable_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	return 0;
+}
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 0ec636468a5b68f448f138282fc41f3b50db9f14..2e6721a3a25d505126548a149dc63c3c9d889b31 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -517,7 +517,7 @@ static int libd_getSpritePatch(lua_State *L)
 		INT32 rot = R_GetRollAngle(rollangle);
 
 		if (rot) {
-			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), true, &spriteinfo[i], rot);
+			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), &spriteinfo[i], rot);
 			LUA_PushUserdata(L, rotsprite, META_PATCH);
 			lua_pushboolean(L, false);
 			lua_pushboolean(L, true);
@@ -629,7 +629,7 @@ static int libd_getSprite2Patch(lua_State *L)
 		INT32 rot = R_GetRollAngle(rollangle);
 
 		if (rot) {
-			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), true, &skins[i].sprinfo[j], rot);
+			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), &skins[i].sprinfo[j], rot);
 			LUA_PushUserdata(L, rotsprite, META_PATCH);
 			lua_pushboolean(L, false);
 			lua_pushboolean(L, true);
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 2e3c706528f24dd127f77b0e1256463643c59654..26f919ad89fb0e7ecde8aa39903c1838cf0776e5 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -85,6 +85,8 @@ extern boolean ignoregameinputs;
 #define META_HUDINFO "HUDINFO_T*"
 #define META_PATCH "PATCH_T*"
 #define META_COLORMAP "COLORMAP"
+#define META_EXTRACOLORMAP "EXTRACOLORMAP_T*"
+#define META_LIGHTTABLE "LIGHTTABLE_T*"
 #define META_CAMERA "CAMERA_T*"
 
 #define META_ACTION "ACTIONF_T*"
@@ -112,4 +114,5 @@ int LUA_TagLib(lua_State *L);
 int LUA_PolyObjLib(lua_State *L);
 int LUA_BlockmapLib(lua_State *L);
 int LUA_HudLib(lua_State *L);
+int LUA_ColorLib(lua_State *L);
 int LUA_InputLib(lua_State *L);
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index df06e9721788a200516aae7c30be77c08c920e87..603fc0d8f033825fe89d708a978879adfec5d6e0 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -57,6 +57,7 @@ enum sector_e {
 	sector_ffloors,
 	sector_fslope,
 	sector_cslope,
+	sector_colormap,
 	sector_flags,
 	sector_specialflags,
 	sector_damagetype,
@@ -95,6 +96,7 @@ static const char *const sector_opt[] = {
 	"ffloors",
 	"f_slope",
 	"c_slope",
+	"colormap",
 	"flags",
 	"specialflags",
 	"damagetype",
@@ -751,6 +753,9 @@ static int sector_get(lua_State *L)
 	case sector_cslope: // c_slope
 		LUA_PushUserdata(L, sector->c_slope, META_SLOPE);
 		return 1;
+	case sector_colormap: // extra_colormap
+		LUA_PushUserdata(L, sector->extra_colormap, META_EXTRACOLORMAP);
+		return 1;
 	case sector_flags: // flags
 		lua_pushinteger(L, sector->flags);
 		return 1;
@@ -1062,7 +1067,7 @@ static int line_get(lua_State *L)
 		LUA_PushUserdata(L, &sides[line->sidenum[0]], META_SIDE);
 		return 1;
 	case line_backside: // backside
-		if (line->sidenum[1] == 0xffff)
+		if (line->sidenum[1] == NO_SIDEDEF)
 			return 0;
 		LUA_PushUserdata(L, &sides[line->sidenum[1]], META_SIDE);
 		return 1;
@@ -1235,6 +1240,9 @@ static int side_get(lua_State *L)
 	// TODO: 2.3: Delete
 	case side_text:
 		{
+			boolean isfrontside;
+			size_t sidei = side-sides;
+
 			if (udmf)
 			{
 				LUA_Deprecated(L, "(sidedef_t).text", "(sidedef_t).line.stringargs");
@@ -1242,7 +1250,7 @@ static int side_get(lua_State *L)
 				return 1;
 			}
 
-			boolean isfrontside = side->line->sidenum[0] == side-sides;
+			isfrontside = side->line->sidenum[0] == sidei;
 
 			lua_pushstring(L, side->line->stringargs[isfrontside ? 0 : 1]);
 			return 1;
diff --git a/src/lua_script.c b/src/lua_script.c
index 5cacc3f01bcad77d41c33ac101a7b48795695fde..b0b6eeec79654768bb7aa1815800cb1afbb30c74 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -58,6 +58,7 @@ static lua_CFunction liblist[] = {
 	LUA_PolyObjLib, // polyobj_t
 	LUA_BlockmapLib, // blockmap stuff
 	LUA_HudLib, // HUD stuff
+	LUA_ColorLib, // general color functions
 	LUA_InputLib, // inputs
 	NULL
 };
diff --git a/src/m_menu.c b/src/m_menu.c
index 629f53d2460f70b00da78e0d1a31a6ce71892373..45e74c0fc7b2b61b02cd2392ef1c0dd4f3e51ec3 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1068,6 +1068,7 @@ static menuitem_t OP_ChangeControlsMenu[] =
 	{IT_CALL | IT_STRING2, NULL, "Move Right",       M_ChangeControl, GC_STRAFERIGHT },
 	{IT_CALL | IT_STRING2, NULL, "Jump",             M_ChangeControl, GC_JUMP      },
 	{IT_CALL | IT_STRING2, NULL, "Spin",             M_ChangeControl, GC_SPIN     },
+	{IT_CALL | IT_STRING2, NULL, "Shield",           M_ChangeControl, GC_SHIELD    },
 	{IT_HEADER, NULL, "Camera", NULL, 0},
 	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
 	{IT_CALL | IT_STRING2, NULL, "Look Up",        M_ChangeControl, GC_LOOKUP      },
@@ -13167,23 +13168,23 @@ static void M_Setup1PControlsMenu(INT32 choice)
 	currentMenu->lastOn = itemOn;
 
 	// Unhide the nine non-P2 controls and their headers
-	//OP_ChangeControlsMenu[18+0].status = IT_HEADER;
-	//OP_ChangeControlsMenu[18+1].status = IT_SPACE;
+	//OP_ChangeControlsMenu[19+0].status = IT_HEADER;
+	//OP_ChangeControlsMenu[19+1].status = IT_SPACE;
 	// ...
-	OP_ChangeControlsMenu[18+2].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[18+3].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[18+4].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[18+5].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[18+6].status = IT_CALL|IT_STRING2;
-	//OP_ChangeControlsMenu[18+7].status = IT_CALL|IT_STRING2;
-	//OP_ChangeControlsMenu[18+8].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[18+9].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[19+2].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[19+3].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[19+4].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[19+5].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[19+6].status = IT_CALL|IT_STRING2;
+	//OP_ChangeControlsMenu[19+7].status = IT_CALL|IT_STRING2;
+	//OP_ChangeControlsMenu[19+8].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[19+9].status = IT_CALL|IT_STRING2;
 	// ...
-	OP_ChangeControlsMenu[28+0].status = IT_HEADER;
-	OP_ChangeControlsMenu[28+1].status = IT_SPACE;
+	OP_ChangeControlsMenu[29+0].status = IT_HEADER;
+	OP_ChangeControlsMenu[29+1].status = IT_SPACE;
 	// ...
-	OP_ChangeControlsMenu[28+2].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[28+3].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[29+2].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[29+3].status = IT_CALL|IT_STRING2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P1ControlsDef;
 	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level
@@ -13199,23 +13200,23 @@ static void M_Setup2PControlsMenu(INT32 choice)
 	currentMenu->lastOn = itemOn;
 
 	// Hide the nine non-P2 controls and their headers
-	//OP_ChangeControlsMenu[18+0].status = IT_GRAYEDOUT2;
-	//OP_ChangeControlsMenu[18+1].status = IT_GRAYEDOUT2;
+	//OP_ChangeControlsMenu[19+0].status = IT_GRAYEDOUT2;
+	//OP_ChangeControlsMenu[19+1].status = IT_GRAYEDOUT2;
 	// ...
-	OP_ChangeControlsMenu[18+2].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[18+3].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[18+4].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[18+5].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[18+6].status = IT_GRAYEDOUT2;
-	//OP_ChangeControlsMenu[18+7].status = IT_GRAYEDOUT2;
-	//OP_ChangeControlsMenu[18+8].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[18+9].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[19+2].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[19+3].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[19+4].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[19+5].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[19+6].status = IT_GRAYEDOUT2;
+	//OP_ChangeControlsMenu[19+7].status = IT_GRAYEDOUT2;
+	//OP_ChangeControlsMenu[19+8].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[19+9].status = IT_GRAYEDOUT2;
 	// ...
-	OP_ChangeControlsMenu[28+0].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[28+1].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[29+0].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[29+1].status = IT_GRAYEDOUT2;
 	// ...
-	OP_ChangeControlsMenu[28+2].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[28+3].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[29+2].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[29+3].status = IT_GRAYEDOUT2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P2ControlsDef;
 	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level
diff --git a/src/m_misc.c b/src/m_misc.c
index 4013ff85052be65d0eca88542d3425d359a170f7..ec00a9f5eb835188a33043a82038bc5361fe0550 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2677,3 +2677,17 @@ boolean M_IsStringEmpty(const char *s)
 
 	return true;
 }
+
+// Rounds off floating numbers and checks for 0 - 255 bounds
+int M_RoundUp(double number)
+{
+	if (number > 255.0l)
+		return 255;
+	if (number < 0.0l)
+		return 0;
+
+	if ((int)number <= (int)(number - 0.5f))
+		return (int)number + 1;
+
+	return (int)number;
+}
diff --git a/src/m_misc.h b/src/m_misc.h
index 8cad7ba9a43353c0aaca9acf3688939ebf270775..753991e70465c075fa874ce7662d6aa6603e7b6a 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -112,6 +112,9 @@ boolean M_IsStringEmpty(const char *s);
 // counting bits, for weapon ammo code, usually
 FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size);
 
+// Rounds off floating numbers and checks for 0 - 255 bounds
+int M_RoundUp(double number);
+
 #include "w_wad.h"
 extern char configfile[MAX_WADPATH];
 
diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c
index 698234579160dc76d201de5e73ca2e7fe8b2cf66..0b650de49eaa430a1b5563fb83bd971f88644c48 100644
--- a/src/netcode/i_tcp.c
+++ b/src/netcode/i_tcp.c
@@ -154,6 +154,8 @@ typedef union
 	#define ERRSOCKET (-1)
 #endif
 
+#define IPV6_MULTICAST_ADDRESS "ff15::57e1:1a12"
+
 // define socklen_t in DOS/Windows if it is not already defined
 #ifdef USE_WINSOCK1
 	typedef int socklen_t;
@@ -621,6 +623,7 @@ static inline ssize_t SOCK_SendToAddr(SOCKET_TYPE socket, mysockaddr_t *sockaddr
 	socklen_t d6 = (socklen_t)sizeof(struct sockaddr_in6);
 #endif
 	socklen_t d, da = (socklen_t)sizeof(mysockaddr_t);
+	ssize_t status;
 
 	switch (sockaddr->any.sa_family)
 	{
@@ -631,7 +634,12 @@ static inline ssize_t SOCK_SendToAddr(SOCKET_TYPE socket, mysockaddr_t *sockaddr
 		default:       d = da; break;
 	}
 
-	return sendto(socket, (char *)&doomcom->data, doomcom->datalength, 0, &sockaddr->any, d);
+	status = sendto(socket, (char *)&doomcom->data, doomcom->datalength, 0, &sockaddr->any, d);
+	if (status == -1)
+	{
+		CONS_Alert(CONS_WARNING, "Unable to send packet to %s: %s\n", SOCK_AddrToStr(sockaddr), strerror(errno));
+	}
+	return status;
 }
 
 static void SOCK_Send(void)
@@ -770,6 +778,24 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 		return (SOCKET_TYPE)ERRSOCKET;
 	}
 
+#ifdef HAVE_IPV6
+	if (family == AF_INET6)
+	{
+		// we need to set all of this *after* binding to an address!
+		if (memcmp(&straddr.ip6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) //IN6_ARE_ADDR_EQUAL
+		{
+			struct ipv6_mreq maddr;
+
+			inet_pton(AF_INET6, IPV6_MULTICAST_ADDRESS, &maddr.ipv6mr_multiaddr);
+			maddr.ipv6mr_interface = 0;
+			if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&maddr, sizeof(maddr)) != 0)
+			{
+				CONS_Alert(CONS_WARNING, M_GetText("Could not register multicast address\n"));
+			}
+		}
+	}
+#endif
+
 #ifdef FIONBIO
 	// make it non blocking
 	opt = true;
@@ -950,65 +976,28 @@ static boolean UDP_Socket(void)
 	// ip + udp
 	packetheaderlength = 20 + 8; // for stats
 
-	hints.ai_family = AF_INET;
-	gaie = I_getaddrinfo("127.0.0.1", "0", &hints, &ai);
-	if (gaie == 0)
-	{
-		runp = ai;
-		while (runp != NULL && s < MAXNETNODES+1)
-		{
-			memcpy(&clientaddress[s], runp->ai_addr, runp->ai_addrlen);
-			s++;
-			runp = runp->ai_next;
-		}
-		I_freeaddrinfo(ai);
-	}
-	else
-	{
-		clientaddress[s].any.sa_family = AF_INET;
-		clientaddress[s].ip4.sin_port = htons(0);
-		clientaddress[s].ip4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //GetLocalAddress(); // my own ip
-		s++;
-	}
+	clientaddress[s].any.sa_family = AF_INET;
+	clientaddress[s].ip4.sin_port = htons(0);
+	clientaddress[s].ip4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //GetLocalAddress(); // my own ip
+	s++;
 
 	s = 0;
 
 	// setup broadcast adress to BROADCASTADDR entry
-	gaie = I_getaddrinfo("255.255.255.255", "0", &hints, &ai);
-	if (gaie == 0)
-	{
-		runp = ai;
-		while (runp != NULL && s < MAXNETNODES+1)
-		{
-			memcpy(&broadcastaddress[s], runp->ai_addr, runp->ai_addrlen);
-			s++;
-			runp = runp->ai_next;
-		}
-		I_freeaddrinfo(ai);
-	}
-	else
-	{
-		broadcastaddress[s].any.sa_family = AF_INET;
-		broadcastaddress[s].ip4.sin_port = htons(0);
-		broadcastaddress[s].ip4.sin_addr.s_addr = htonl(INADDR_BROADCAST);
-		s++;
-	}
+	broadcastaddress[s].any.sa_family = AF_INET;
+	broadcastaddress[s].ip4.sin_port = htons(atoi(DEFAULTPORT));
+	broadcastaddress[s].ip4.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+	s++;
+
 #ifdef HAVE_IPV6
 	if (b_ipv6)
 	{
-		hints.ai_family = AF_INET6;
-		gaie = I_getaddrinfo("ff02::1", "0", &hints, &ai);
-		if (gaie == 0)
-		{
-			runp = ai;
-			while (runp != NULL && s < MAXNETNODES+1)
-			{
-				memcpy(&broadcastaddress[s], runp->ai_addr, runp->ai_addrlen);
-				s++;
-				runp = runp->ai_next;
-			}
-			I_freeaddrinfo(ai);
-		}
+		broadcastaddress[s].any.sa_family = AF_INET6;
+		broadcastaddress[s].ip6.sin6_port = htons(atoi(DEFAULTPORT));
+		broadcastaddress[s].ip6.sin6_flowinfo = 0;
+		inet_pton(AF_INET6, IPV6_MULTICAST_ADDRESS, &broadcastaddress[s].ip6.sin6_addr);
+		broadcastaddress[s].ip6.sin6_scope_id = 0;
+		s++;
 	}
 #endif
 
diff --git a/src/p_local.h b/src/p_local.h
index c26c098600d3ed75ef0e4d7d15c704fecf47d4a1..ba033dc06fda53ac1dda5db3a1fb3e2192fc4bc8 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -202,7 +202,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet);
 void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
 void P_Earthquake(mobj_t *inflictor, mobj_t *source, fixed_t radius);
 boolean P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
-boolean P_SuperReady(player_t *player);
+boolean P_SuperReady(player_t *player, boolean transform);
 void P_DoJump(player_t *player, boolean soundandstate);
 void P_DoSpinDashDust(player_t *player);
 #define P_AnalogMove(player) (P_ControlStyle(player) == CS_LMAOGALOG)
@@ -445,6 +445,10 @@ boolean PIT_PushableMoved(mobj_t *thing);
 
 boolean P_DoSpring(mobj_t *spring, mobj_t *object);
 
+INT32 P_GetSectorLightAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z);
+extracolormap_t *P_GetColormapFromSectorAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z);
+extracolormap_t *P_GetSectorColormapAt(fixed_t x, fixed_t y, fixed_t z);
+
 //
 // P_SETUP
 //
diff --git a/src/p_map.c b/src/p_map.c
index 56a096ebb5ce7802e04583d09080447ec1faba76..6a152c563de0636c160f25103a82566d85e19b08 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -3905,7 +3905,7 @@ retry:
 	P_PathTraverse(leadx, traily, leadx + mo->momx, traily + mo->momy,
 		PT_ADDLINES, PTR_SlideTraverse);
 
-	if (bestslideline && mo->player && bestslideline->sidenum[1] != 0xffff)
+	if (bestslideline && mo->player && bestslideline->sidenum[1] != NO_SIDEDEF)
 	{
 		sector_t *sec = P_PointOnLineSide(mo->x, mo->y, bestslideline) ? bestslideline->frontsector : bestslideline->backsector;
 		P_CheckLavaWall(mo, sec);
@@ -5071,3 +5071,35 @@ fixed_t P_CeilingzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height)
 
 	return ceilingz;
 }
+
+INT32 P_GetSectorLightAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z)
+{
+	if (!sector->numlights)
+		return -1;
+
+	INT32 light = sector->numlights - 1;
+
+	// R_GetPlaneLight won't work on sloped lights!
+	for (INT32 lightnum = 1; lightnum < sector->numlights; lightnum++) {
+		fixed_t h = P_GetLightZAt(&sector->lightlist[lightnum], x, y);
+		if (h <= z) {
+			light = lightnum - 1;
+			break;
+		}
+	}
+
+	return light;
+}
+
+extracolormap_t *P_GetColormapFromSectorAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z)
+{
+	if (sector->numlights)
+		return *sector->lightlist[P_GetSectorLightAt(sector, x, y, z)].extra_colormap;
+	else
+		return sector->extra_colormap;
+}
+
+extracolormap_t *P_GetSectorColormapAt(fixed_t x, fixed_t y, fixed_t z)
+{
+	return P_GetColormapFromSectorAt(R_PointInSubsector(x, y)->sector, x, y, z);
+}
diff --git a/src/p_maputl.c b/src/p_maputl.c
index e36d5fd724fd152346b34b8ec047277e25735ecb..9c30d3ead094bcbbcf7e3c98e90fc159285a36b0 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -290,7 +290,7 @@ void P_CameraLineOpening(line_t *linedef)
 	sector_t *back;
 	fixed_t frontfloor, frontceiling, backfloor, backceiling;
 
-	if (linedef->sidenum[1] == 0xffff)
+	if (linedef->sidenum[1] == NO_SIDEDEF)
 	{
 		// single sided line
 		openrange = 0;
@@ -426,7 +426,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 {
 	sector_t *front, *back;
 
-	if (linedef->sidenum[1] == 0xffff)
+	if (linedef->sidenum[1] == NO_SIDEDEF)
 	{
 		// single sided line
 		openrange = 0;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 7a5aaf42497c422f9d5d745aee99dafff011ae6f..1b20659202850b284ead150e397dbd75a242ccac 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -11206,6 +11206,10 @@ void P_RemoveMobj(mobj_t *mobj)
 
 	P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
 
+	// clear the reference from the mapthing
+	if (mobj->spawnpoint)
+		mobj->spawnpoint->mobj = NULL;
+
 	R_RemoveMobjInterpolator(mobj);
 
 	// free block
diff --git a/src/p_saveg.c b/src/p_saveg.c
index a5745524b9f8465ab527049938d00f80c235bb35..7974fd12f0a9cd0037c8ad4ce8dd064e8594f155 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1079,7 +1079,7 @@ static void ArchiveSectors(void)
 
 		if (diff)
 		{
-			WRITEUINT16(save_p, i);
+			WRITEUINT32(save_p, i);
 			WRITEUINT8(save_p, diff);
 			if (diff & SD_DIFF2)
 				WRITEUINT8(save_p, diff2);
@@ -1150,18 +1150,19 @@ static void ArchiveSectors(void)
 		}
 	}
 
-	WRITEUINT16(save_p, 0xffff);
+	WRITEUINT32(save_p, 0xffffffff);
 }
 
 static void UnArchiveSectors(void)
 {
-	UINT16 i, j;
+	UINT32 i;
+	UINT16 j;
 	UINT8 diff, diff2, diff3, diff4;
 	for (;;)
 	{
-		i = READUINT16(save_p);
+		i = READUINT32(save_p);
 
-		if (i == 0xffff)
+		if (i == 0xffffffff)
 			break;
 
 		if (i > numsectors)
@@ -1299,7 +1300,7 @@ static void ArchiveLines(void)
 		if (li->executordelay != spawnli->executordelay)
 			diff2 |= LD_EXECUTORDELAY;
 
-		if (li->sidenum[0] != 0xffff)
+		if (li->sidenum[0] != NO_SIDEDEF)
 		{
 			si = &sides[li->sidenum[0]];
 			spawnsi = &spawnsides[li->sidenum[0]];
@@ -1313,7 +1314,7 @@ static void ArchiveLines(void)
 			if (si->midtexture != spawnsi->midtexture)
 				diff |= LD_S1MIDTEX;
 		}
-		if (li->sidenum[1] != 0xffff)
+		if (li->sidenum[1] != NO_SIDEDEF)
 		{
 			si = &sides[li->sidenum[1]];
 			spawnsi = &spawnsides[li->sidenum[1]];
@@ -1332,7 +1333,7 @@ static void ArchiveLines(void)
 
 		if (diff)
 		{
-			WRITEINT16(save_p, i);
+			WRITEUINT32(save_p, i);
 			WRITEUINT8(save_p, diff);
 			if (diff & LD_DIFF2)
 				WRITEUINT8(save_p, diff2);
@@ -1391,21 +1392,21 @@ static void ArchiveLines(void)
 				WRITEINT32(save_p, li->executordelay);
 		}
 	}
-	WRITEUINT16(save_p, 0xffff);
+	WRITEUINT32(save_p, 0xffffffff);
 }
 
 static void UnArchiveLines(void)
 {
-	UINT16 i;
+	UINT32 i;
 	line_t *li;
 	side_t *si;
 	UINT8 diff, diff2; // no diff3
 
 	for (;;)
 	{
-		i = READUINT16(save_p);
+		i = READUINT32(save_p);
 
-		if (i == 0xffff)
+		if (i == 0xffffffff)
 			break;
 		if (i > numlines)
 			I_Error("Invalid line number %u from server", i);
diff --git a/src/p_setup.c b/src/p_setup.c
index 9b6177c95a6063da46e28d6a5110b06b2395bc10..cd74fbbc98327a579512c41cf30cb6ee34958ff3 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1112,40 +1112,40 @@ static void P_InitializeLinedef(line_t *ld)
 	// cph 2006/09/30 - fix sidedef errors right away.
 	// cph 2002/07/20 - these errors are fatal if not fixed, so apply them
 	for (j = 0; j < 2; j++)
-		if (ld->sidenum[j] != 0xffff && ld->sidenum[j] >= (UINT16)numsides)
+		if (ld->sidenum[j] != NO_SIDEDEF && ld->sidenum[j] >= (UINT32)numsides)
 		{
-			ld->sidenum[j] = 0xffff;
+			ld->sidenum[j] = NO_SIDEDEF;
 			CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s has out-of-range sidedef number\n", sizeu1((size_t)(ld - lines)));
 		}
 
 	// killough 11/98: fix common wad errors (missing sidedefs):
-	if (ld->sidenum[0] == 0xffff)
+	if (ld->sidenum[0] == NO_SIDEDEF)
 	{
 		ld->sidenum[0] = 0;  // Substitute dummy sidedef for missing right side
 		// cph - print a warning about the bug
 		CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s missing first sidedef\n", sizeu1((size_t)(ld - lines)));
 	}
 
-	if ((ld->sidenum[1] == 0xffff) && (ld->flags & ML_TWOSIDED))
+	if ((ld->sidenum[1] == NO_SIDEDEF) && (ld->flags & ML_TWOSIDED))
 	{
 		ld->flags &= ~ML_TWOSIDED;  // Clear 2s flag for missing left side
 		// cph - print a warning about the bug
 		CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s has two-sided flag set, but no second sidedef\n", sizeu1((size_t)(ld - lines)));
 	}
 
-	if (ld->sidenum[0] != 0xffff)
+	if (ld->sidenum[0] != NO_SIDEDEF)
 	{
 		sides[ld->sidenum[0]].special = ld->special;
 		sides[ld->sidenum[0]].line = ld;
 	}
-	if (ld->sidenum[1] != 0xffff)
+	if (ld->sidenum[1] != NO_SIDEDEF)
 	{
 		sides[ld->sidenum[1]].special = ld->special;
 		sides[ld->sidenum[1]].line = ld;
 	}
 }
 
-static void P_SetLinedefV1(size_t i, UINT16 vertex_num)
+static void P_SetLinedefV1(size_t i, UINT32 vertex_num)
 {
 	if (vertex_num >= numvertexes)
 	{
@@ -1155,7 +1155,7 @@ static void P_SetLinedefV1(size_t i, UINT16 vertex_num)
 	lines[i].v1 = &vertexes[vertex_num];
 }
 
-static void P_SetLinedefV2(size_t i, UINT16 vertex_num)
+static void P_SetLinedefV2(size_t i, UINT32 vertex_num)
 {
 	if (vertex_num >= numvertexes)
 	{
@@ -1180,17 +1180,22 @@ static void P_LoadLinedefs(UINT8 *data)
 		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		ld->alpha = FRACUNIT;
 		ld->executordelay = 0;
-		P_SetLinedefV1(i, SHORT(mld->v1));
-		P_SetLinedefV2(i, SHORT(mld->v2));
+		P_SetLinedefV1(i, (UINT16)SHORT(mld->v1));
+		P_SetLinedefV2(i, (UINT16)SHORT(mld->v2));
 
-		ld->sidenum[0] = SHORT(mld->sidenum[0]);
-		ld->sidenum[1] = SHORT(mld->sidenum[1]);
+		ld->sidenum[0] = (UINT16)SHORT(mld->sidenum[0]);
+		ld->sidenum[1] = (UINT16)SHORT(mld->sidenum[1]);
+
+		if (ld->sidenum[0] == 0xffff)
+			ld->sidenum[0] = NO_SIDEDEF;
+		if (ld->sidenum[1] == 0xffff)
+			ld->sidenum[1] = NO_SIDEDEF;
 
 		P_InitializeLinedef(ld);
 	}
 }
 
-static void P_SetSidedefSector(size_t i, UINT16 sector_num)
+static void P_SetSidedefSector(size_t i, UINT32 sector_num)
 {
 	// cph 2006/09/30 - catch out-of-range sector numbers; use sector 0 instead
 	if (sector_num >= numsectors)
@@ -1351,7 +1356,7 @@ static void P_LoadSidedefs(UINT8 *data)
 		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0;
 		sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0;
 
-		P_SetSidedefSector(i, SHORT(msd->sector));
+		P_SetSidedefSector(i, (UINT16)SHORT(msd->sector));
 
 		// Special info stored in texture fields!
 		switch (sd->special)
@@ -2490,7 +2495,7 @@ static void P_WriteTextmap(void)
 		fprintf(f, "v1 = %s;\n", sizeu1(wlines[i].v1 - vertexes));
 		fprintf(f, "v2 = %s;\n", sizeu1(wlines[i].v2 - vertexes));
 		fprintf(f, "sidefront = %d;\n", wlines[i].sidenum[0]);
-		if (wlines[i].sidenum[1] != 0xffff)
+		if (wlines[i].sidenum[1] != NO_SIDEDEF)
 			fprintf(f, "sideback = %d;\n", wlines[i].sidenum[1]);
 		firsttag = Tag_FGet(&wlines[i].tags);
 		if (firsttag != 0)
@@ -2955,8 +2960,8 @@ static void P_LoadTextmap(void)
 		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		ld->alpha = FRACUNIT;
 		ld->executordelay = 0;
-		ld->sidenum[0] = 0xffff;
-		ld->sidenum[1] = 0xffff;
+		ld->sidenum[0] = NO_SIDEDEF;
+		ld->sidenum[1] = NO_SIDEDEF;
 
 		TextmapParse(linesPos[i], i, ParseTextmapLinedefParameter);
 
@@ -2964,7 +2969,7 @@ static void P_LoadTextmap(void)
 			I_Error("P_LoadTextmap: linedef %s has no v1 value set!\n", sizeu1(i));
 		if (!ld->v2)
 			I_Error("P_LoadTextmap: linedef %s has no v2 value set!\n", sizeu1(i));
-		if (ld->sidenum[0] == 0xffff)
+		if (ld->sidenum[0] == NO_SIDEDEF)
 			I_Error("P_LoadTextmap: linedef %s has no sidefront value set!\n", sizeu1(i));
 
 		P_InitializeLinedef(ld);
@@ -3018,7 +3023,7 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 	for (; i--; ld++)
 	{
 		ld->frontsector = sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here
-		ld->backsector = ld->sidenum[1] != 0xffff ? sides[ld->sidenum[1]].sector : 0;
+		ld->backsector = ld->sidenum[1] != NO_SIDEDEF ? sides[ld->sidenum[1]].sector : 0;
 
 		if (udmf)
 			continue;
@@ -3257,10 +3262,10 @@ static void P_InitializeSeg(seg_t *seg)
 {
 	if (seg->linedef)
 	{
-		UINT16 side = seg->linedef->sidenum[seg->side];
+		UINT32 side = seg->linedef->sidenum[seg->side];
 
-		if (side == 0xffff)
-			I_Error("P_InitializeSeg: Seg %s refers to side %d of linedef %s, which doesn't exist!\n", sizeu1((size_t)(seg - segs)), seg->side, sizeu1((size_t)(seg->linedef - lines)));
+		if (side == NO_SIDEDEF)
+			I_Error("P_InitializeSeg: Seg %s refers to side %d of linedef %s, which doesn't exist!\n", sizeu1((size_t)(seg - segs)), seg->side, sizeu2((size_t)(seg->linedef - lines)));
 
 		seg->sidedef = &sides[side];
 
@@ -3444,7 +3449,7 @@ static boolean P_LoadExtraVertices(UINT8 **data)
 static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype)
 {
 	size_t i, k;
-	INT16 m;
+	size_t m;
 	seg_t *seg;
 
 	// Subsectors
@@ -3467,23 +3472,33 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		{
 		case NT_XGLN:
 		case NT_XGL3:
-			for (m = 0; m < subsectors[i].numlines; m++, k++)
+			for (m = 0; m < (size_t)subsectors[i].numlines; m++, k++)
 			{
 				UINT32 vertexnum = READUINT32((*data));
-				UINT16 linenum;
-
 				if (vertexnum >= numvertexes)
-					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid vertex %d!\n", sizeu1(k), m, vertexnum);
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid vertex %d!\n", sizeu1(k), sizeu2(m), vertexnum);
 
 				segs[k - 1 + ((m == 0) ? subsectors[i].numlines : 0)].v2 = segs[k].v1 = &vertexes[vertexnum];
 
 				READUINT32((*data)); // partner, can be ignored by software renderer
 
-				linenum = (nodetype == NT_XGL3) ? READUINT32((*data)) : READUINT16((*data));
-				if (linenum != 0xFFFF && linenum >= numlines)
-					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(i), linenum);
-				segs[k].glseg = (linenum == 0xFFFF);
-				segs[k].linedef = (linenum == 0xFFFF) ? NULL : &lines[linenum];
+				if (nodetype == NT_XGL3)
+				{
+					UINT32 linenum = READUINT32((*data));
+					if (linenum != 0xFFFFFFFF && linenum >= numlines)
+						I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(i), linenum);
+					segs[k].glseg = linenum == 0xFFFFFFFF;
+					segs[k].linedef = linenum == 0xFFFFFFFF ? NULL : &lines[linenum];
+				}
+				else
+				{
+					UINT16 linenum = READUINT16((*data));
+					if (linenum != 0xFFFF && linenum >= numlines)
+						I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(i), linenum);
+					segs[k].glseg = linenum == 0xFFFF;
+					segs[k].linedef = linenum == 0xFFFF ? NULL : &lines[linenum];
+				}
+
 				segs[k].side = READUINT8((*data));
 			}
 			while (segs[subsectors[i].firstline].glseg)
@@ -3495,18 +3510,18 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 			break;
 
 		case NT_XNOD:
-			for (m = 0; m < subsectors[i].numlines; m++, k++)
+			for (m = 0; m < (size_t)subsectors[i].numlines; m++, k++)
 			{
 				UINT32 v1num = READUINT32((*data));
 				UINT32 v2num = READUINT32((*data));
 				UINT16 linenum = READUINT16((*data));
 
 				if (v1num >= numvertexes)
-					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v1 %d!\n", sizeu1(k), m, v1num);
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v1 %d!\n", sizeu1(k), sizeu2(m), v1num);
 				if (v2num >= numvertexes)
-					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v2 %d!\n", sizeu1(k), m, v2num);
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid v2 %d!\n", sizeu1(k), sizeu2(m), v2num);
 				if (linenum >= numlines)
-					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %s has invalid linedef %d!\n", sizeu1(k), sizeu2(m), linenum);
 
 				segs[k].v1 = &vertexes[v1num];
 				segs[k].v2 = &vertexes[v2num];
@@ -3991,14 +4006,14 @@ static void P_LinkMapData(void)
 		if (!seg->sidedef)
 			CorruptMapError(va("P_LinkMapData: seg->sidedef is NULL "
 				"(subsector %s, firstline is %d)", sizeu1(i), ss->firstline));
-		if (seg->sidedef - sides < 0 || seg->sidedef - sides > (UINT16)numsides)
+		if (seg->sidedef - sides < 0 || sidei > numsides)
 			CorruptMapError(va("P_LinkMapData: seg->sidedef refers to sidedef %s of %s "
 				"(subsector %s, firstline is %d)", sizeu1(sidei), sizeu2(numsides),
 				sizeu3(i), ss->firstline));
 		if (!seg->sidedef->sector)
 			CorruptMapError(va("P_LinkMapData: seg->sidedef->sector is NULL "
 				"(subsector %s, firstline is %d, sidedef is %s)", sizeu1(i), ss->firstline,
-				sizeu1(sidei)));
+				sizeu2(sidei)));
 		ss->sector = seg->sidedef->sector;
 	}
 
@@ -4916,7 +4931,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 
 			break;
 		case 259: //Custom FOF
-			if (lines[i].sidenum[1] == 0xffff)
+			if (lines[i].sidenum[1] == NO_SIDEDEF)
 				I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
 
 			lines[i].args[0] = tag;
@@ -5270,8 +5285,8 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[1] = sides[lines[i].sidenum[0]].midtexture;
 			lines[i].args[2] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
 			lines[i].args[3] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
-			lines[i].args[4] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0;
-			lines[i].args[5] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : -1;
+			lines[i].args[4] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0;
+			lines[i].args[5] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : -1;
 			lines[i].args[6] = sides[lines[i].sidenum[0]].bottomtexture;
 			break;
 		case 414: //Play sound effect
@@ -5375,7 +5390,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 				lines[i].args[1] = max(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, 0);
 				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
 				// to be consistent with other light and fade specials
-				lines[i].args[2] = ((lines[i].sidenum[1] != 0xFFFF && !(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS)) ?
+				lines[i].args[2] = ((lines[i].sidenum[1] != NO_SIDEDEF && !(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS)) ?
 					max(min(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS, 255), 0)
 					: max(min(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS, 255), 0));
 			}
@@ -5480,7 +5495,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			break;
 		case 442: //Change object type state
 			lines[i].args[0] = tag;
-			lines[i].args[1] = (lines[i].sidenum[1] == 0xffff) ? 1 : 0;
+			lines[i].args[1] = (lines[i].sidenum[1] == NO_SIDEDEF) ? 1 : 0;
 			break;
 		case 443: //Call Lua function
 			if (lines[i].stringargs[0] == NULL)
@@ -5548,7 +5563,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 		case 452: //Set FOF translucency
 			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
 			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
-			lines[i].args[2] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS);
+			lines[i].args[2] = lines[i].sidenum[1] != NO_SIDEDEF ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS);
 			if (lines[i].flags & ML_MIDPEG)
 				lines[i].args[3] |= TMST_RELATIVE;
 			if (lines[i].flags & ML_NOCLIMB)
@@ -5557,8 +5572,8 @@ static void P_ConvertBinaryLinedefTypes(void)
 		case 453: //Fade FOF
 			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
 			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
-			lines[i].args[2] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (lines[i].dx >> FRACBITS);
-			lines[i].args[3] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].rowoffset >> FRACBITS) : (abs(lines[i].dy) >> FRACBITS);
+			lines[i].args[2] = lines[i].sidenum[1] != NO_SIDEDEF ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (lines[i].dx >> FRACBITS);
+			lines[i].args[3] = lines[i].sidenum[1] != NO_SIDEDEF ? (sides[lines[i].sidenum[1]].rowoffset >> FRACBITS) : (abs(lines[i].dy) >> FRACBITS);
 			if (lines[i].flags & ML_MIDPEG)
 				lines[i].args[4] |= TMFT_RELATIVE;
 			if (lines[i].flags & ML_WRAPMIDTEX)
@@ -5585,7 +5600,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			break;
 		case 455: //Fade colormap
 		{
-			INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != 0xFFFF) ?
+			INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != NO_SIDEDEF) ?
 				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
 				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS));
 
@@ -5615,7 +5630,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[0] = tag;
 			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
 			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
-			lines[i].args[3] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0;
+			lines[i].args[3] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0;
 			lines[i].args[4] = !!(lines[i].flags & ML_NOSKEW);
 			break;
 		case 459: //Control text prompt
@@ -5635,7 +5650,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 				lines[i].args[2] |= TMP_ALLPLAYERS;
 			if (lines[i].flags & ML_MIDSOLID)
 				lines[i].args[2] |= TMP_FREEZETHINKERS;*/
-			lines[i].args[3] = (lines[i].sidenum[1] != 0xFFFF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag;
+			lines[i].args[3] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag;
 			break;
 		case 460: //Award rings
 			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
@@ -5648,7 +5663,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[3] = (lines[i].flags & ML_SKEWTD) ? AngleFixed(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y)) >> FRACBITS : 0;
 			if (lines[i].flags & ML_NOCLIMB)
 			{
-				if (lines[i].sidenum[1] != 0xffff) // Make sure the linedef has a back side
+				if (lines[i].sidenum[1] != NO_SIDEDEF) // Make sure the linedef has a back side
 				{
 					lines[i].args[4] = 1;
 					lines[i].args[5] = sides[lines[i].sidenum[1]].textureoffset >> FRACBITS;
@@ -5682,7 +5697,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[0] = tag;
 			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
 			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
-			if (lines[i].sidenum[1] != 0xffff)
+			if (lines[i].sidenum[1] != NO_SIDEDEF)
 				lines[i].args[3] = sides[lines[i].sidenum[1]].textureoffset >> FRACBITS;
 			break;
 		case 482: //Polyobject - move
@@ -5754,7 +5769,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			if (!(lines[i].flags & ML_DONTPEGBOTTOM))
 				lines[i].args[1] /= 100;
 			// allow Back Y Offset to be consistent with other fade specials
-			lines[i].args[2] = (lines[i].sidenum[1] != 0xffff && !sides[lines[i].sidenum[0]].rowoffset) ?
+			lines[i].args[2] = (lines[i].sidenum[1] != NO_SIDEDEF && !sides[lines[i].sidenum[0]].rowoffset) ?
 				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
 				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS);
 			if (lines[i].flags & ML_MIDPEG)
@@ -5781,7 +5796,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[0] = tag;
 			if (lines[i].flags & ML_MIDPEG)
 			{
-				if (lines[i].sidenum[1] == 0xffff)
+				if (lines[i].sidenum[1] == NO_SIDEDEF)
 				{
 					CONS_Debug(DBG_GAMELOGIC, "Line special %d (line #%s) missing back side!\n", lines[i].special, sizeu1(i));
 					lines[i].special = 0;
@@ -5811,7 +5826,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[0] = lines[i].special >= 507;
 			if (lines[i].special % 2 == 0)
 			{
-				if (lines[i].sidenum[1] == 0xffff)
+				if (lines[i].sidenum[1] == NO_SIDEDEF)
 				{
 					CONS_Debug(DBG_GAMELOGIC, "Line special %d (line #%s) missing back side!\n", lines[i].special, sizeu1(i));
 					lines[i].special = 0;
@@ -5967,7 +5982,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 			{
 				UINT8 side = lines[i].special >= 714;
 
-				if (side == 1 && lines[i].sidenum[1] == 0xffff)
+				if (side == 1 && lines[i].sidenum[1] == NO_SIDEDEF)
 					CONS_Debug(DBG_GAMELOGIC, "P_ConvertBinaryMap: Line special %d (line #%s) missing 2nd side!\n", lines[i].special, sizeu1(i));
 				else
 				{
diff --git a/src/p_spec.c b/src/p_spec.c
index aa4ee37cf76f22eedcaed4367635ce654dca51bc..7dc16378ee9191499075c724984e76c8cc9dd9e5 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -557,7 +557,7 @@ static inline sector_t *getSector(INT32 currentSector, INT32 line, INT32 side)
   */
 static inline boolean twoSided(INT32 sector, INT32 line)
 {
-	return (sectors[sector].lines[line])->sidenum[1] != 0xffff;
+	return (sectors[sector].lines[line])->sidenum[1] != NO_SIDEDEF;
 }
 #endif
 
@@ -1796,7 +1796,7 @@ void P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
 
 				if (trigid < 0 || trigid > 31) // limited by 32 bit variable
 				{
-					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
+					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %u): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
 					return;
 				}
 				else if (!(unlocktriggers & (1 << trigid)))
@@ -1809,7 +1809,7 @@ void P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
 
 				if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count
 				{
-					CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
+					CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %u): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
 					return;
 				}
 				else if (!(serverGamedata->unlocked[unlockid-1]))
@@ -2607,7 +2607,15 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
 					CONS_Debug(DBG_SETUP, "Line type 415 Executor: script lump %s not found/not valid.\n", line->stringargs[0]);
 				else
-					COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
+				{
+					void *lump = W_CacheLumpNum(lumpnum, PU_CACHE);
+					size_t len = W_LumpLength(lumpnum);
+					char *text = Z_Malloc(len + 1, PU_CACHE, NULL);
+					memcpy(text, lump, len);
+					text[len] = '\0';
+					COM_BufInsertText(text);
+					Z_Free(text);
+				}
 			}
 			break;
 
@@ -2897,7 +2905,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				size_t linenum;
 				side_t *setfront = &sides[line->sidenum[0]];
-				side_t *setback = (line->args[3] && line->sidenum[1] != 0xffff) ? &sides[line->sidenum[1]] : setfront;
+				side_t *setback = (line->args[3] && line->sidenum[1] != NO_SIDEDEF) ? &sides[line->sidenum[1]] : setfront;
 				side_t *this;
 				boolean always = !(line->args[2]); // If args[2] is set: Only change mid texture if mid texture already exists on tagged lines, etc.
 
@@ -2919,7 +2927,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					}
 
 					// Back side
-					if (line->args[1] != TMSD_FRONT && lines[linenum].sidenum[1] != 0xffff)
+					if (line->args[1] != TMSD_FRONT && lines[linenum].sidenum[1] != NO_SIDEDEF)
 					{
 						this = &sides[lines[linenum].sidenum[1]];
 						if (always || this->toptexture) this->toptexture = setback->toptexture;
@@ -2940,7 +2948,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				INT32 trigid = line->args[0];
 
 				if (trigid < 0 || trigid > 31) // limited by 32 bit variable
-					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", line->sidenum[0], trigid);
+					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %u): bad trigger ID %d\n", line->sidenum[0], trigid);
 				else
 				{
 					unlocktriggers |= 1 << trigid;
@@ -3153,7 +3161,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				if (line->args[2] & TMCF_RELATIVE)
 				{
-					extracolormap_t *target = (!udmf && (line->flags & ML_TFERLINE) && line->sidenum[1] != 0xFFFF) ?
+					extracolormap_t *target = (!udmf && (line->flags & ML_TFERLINE) && line->sidenum[1] != NO_SIDEDEF) ?
 						sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap; // use back colormap instead of target sector
 
 						extracolormap_t *exc = R_AddColormaps(
@@ -3482,7 +3490,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 
 				if (!udmf && (line->flags & ML_TFERLINE)) // use back colormap instead of target sector
-					sectors[secnum].extra_colormap = (line->sidenum[1] != 0xFFFF) ?
+					sectors[secnum].extra_colormap = (line->sidenum[1] != NO_SIDEDEF) ?
 					sides[line->sidenum[1]].colormap_data : NULL;
 
 				exc = sectors[secnum].extra_colormap;
@@ -7600,7 +7608,7 @@ static void P_SpawnScrollers(void)
 					{
 						if (l->args[1] != TMSD_BACK)
 							Add_Scroller(sc_side, l->args[2] << (FRACBITS - SCROLL_SHIFT), l->args[3] << (FRACBITS - SCROLL_SHIFT), control, lines[s].sidenum[0], accel, 0);
-						if (l->args[1] != TMSD_FRONT && lines[s].sidenum[1] != 0xffff)
+						if (l->args[1] != TMSD_FRONT && lines[s].sidenum[1] != NO_SIDEDEF)
 							Add_Scroller(sc_side, l->args[2] << (FRACBITS - SCROLL_SHIFT), l->args[3] << (FRACBITS - SCROLL_SHIFT), control, lines[s].sidenum[1], accel, 0);
 					}
 				break;
@@ -7611,7 +7619,7 @@ static void P_SpawnScrollers(void)
 					Add_Scroller(sc_side, -l->args[1] << FRACBITS, l->args[2] << FRACBITS, -1, l->sidenum[0], accel, 0);
 				if (l->args[0] != TMSD_FRONT)
 				{
-					if (l->sidenum[1] != 0xffff)
+					if (l->sidenum[1] != NO_SIDEDEF)
 						Add_Scroller(sc_side, -l->args[1] << FRACBITS, l->args[2] << FRACBITS, -1, l->sidenum[1], accel, 0);
 					else
 						CONS_Debug(DBG_GAMELOGIC, "Line special 500 (line #%s) missing back side!\n", sizeu1(i));
diff --git a/src/p_user.c b/src/p_user.c
index 454e74e4429e4ddd832ae69ab8cc094631d6f8b1..4ff9cad4423f870ca44a3a0f05c9ba627b11b68a 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -666,7 +666,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->powers[pw_carry] = CR_NIGHTSFALL;
 
 	player->powers[pw_underwater] = 0;
-	player->pflags &= ~(PF_SPINDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
+	player->pflags &= ~(PF_SPINDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_SHIELDDOWN|PF_STARTDASH|PF_GLIDING|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
 	player->secondjump = 0;
 	player->homing = 0;
 	player->climbing = 0;
@@ -794,7 +794,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		}
 	}
 
-	player->pflags &= ~(PF_SPINDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING|PF_DRILLING);
+	player->pflags &= ~(PF_SPINDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_SHIELDDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING|PF_DRILLING);
 	player->homing = 0;
 	player->mo->fuse = 0;
 	player->speed = 0;
@@ -1342,8 +1342,6 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC) && P_IsLocalPlayer(player))
 		P_PlayJingle(player, JT_SUPER);
 
-	S_StartSound(NULL, sfx_supert); //let all players hear it -mattw_cfi
-
 	player->mo->momx = player->mo->momy = player->mo->momz = player->cmomx = player->cmomy = player->rmomx = player->rmomy = 0;
 
 	// Transformation animation
@@ -1360,8 +1358,11 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 		player->powers[pw_sneakers] = 0;
 	}
 
-	if (!G_CoopGametype())
+	if (G_CoopGametype())
+		S_StartSound(player->mo, sfx_supert); //only hear it near yourself in co-op
+	else
 	{
+		S_StartSound(NULL, sfx_supert); //let all players hear it -mattw_cfi
 		HU_SetCEchoFlags(0);
 		HU_SetCEchoDuration(5);
 		HU_DoCEcho(va("%s\\is now super.\\\\\\\\", player_names[player-players]));
@@ -1370,6 +1371,56 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 	P_PlayerFlagBurst(player, false);
 }
 
+//
+// P_DoSuperDetransformation
+//
+// Detransform into regular Sonic!
+static void P_DoSuperDetransformation(player_t *player)
+{
+	player->powers[pw_emeralds] = 0; // lost the power stones
+	P_SpawnGhostMobj(player->mo);
+
+	player->powers[pw_super] = 0;
+
+	// Restore color
+	if ((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER)
+	{
+		player->mo->color = SKINCOLOR_WHITE;
+		G_GhostAddColor(GHC_FIREFLOWER);
+	}
+	else
+	{
+		player->mo->color = P_GetPlayerColor(player);
+		G_GhostAddColor(GHC_NORMAL);
+	}
+
+	if (!G_CoopGametype())
+		player->powers[pw_flashing] = flashingtics-1;
+
+	if (player->mo->sprite2 & FF_SPR2SUPER)
+		P_SetPlayerMobjState(player->mo, player->mo->state-states);
+
+	// Inform the netgame that the champion has fallen in the heat of battle.
+	if (!G_CoopGametype())
+	{
+		S_StartSound(NULL, sfx_s3k66); //let all players hear it.
+		HU_SetCEchoFlags(0);
+		HU_SetCEchoDuration(5);
+		HU_DoCEcho(va("%s\\is no longer super.\\\\\\\\", player_names[player-players]));
+	}
+
+	// Resume normal music if you're the console player
+	if (P_IsLocalPlayer(player))
+	{
+		music_stack_noposition = true; // HACK: Do not reposition next music
+		music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
+	}
+	P_RestoreMusic(player);
+
+	// If you had a shield, restore its visual significance.
+	P_SpawnShieldOrb(player);
+}
+
 // Adds to the player's score
 void P_AddPlayerScore(player_t *player, UINT32 amount)
 {
@@ -4087,6 +4138,16 @@ static void P_DoFiring(player_t *player, ticcmd_t *cmd)
 
 	I_Assert(player != NULL);
 	I_Assert(!P_MobjWasRemoved(player->mo));
+	
+	// Toss a flag
+	if (cmd->buttons & BT_TOSSFLAG && G_GametypeHasTeams()
+		&& !(player->powers[pw_super]) && !(player->tossdelay))
+	{
+		if (!(player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+			P_PlayerEmeraldBurst(player, true); // Toss emeralds
+		else
+			P_PlayerFlagBurst(player, true);
+	}
 
 	if (!(cmd->buttons & (BT_ATTACK|BT_FIRENORMAL)))
 	{
@@ -4096,9 +4157,10 @@ static void P_DoFiring(player_t *player, ticcmd_t *cmd)
 		return;
 	}
 
-	if (player->pflags & PF_ATTACKDOWN || player->climbing || (G_TagGametype() && !(player->pflags & PF_TAGIT)))
+	if (player->pflags & PF_ATTACKDOWN || player->climbing)
 		return;
 
+	// Fire a fireball if we have the Fire Flower powerup!
 	if (((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER) && !(player->weapondelay))
 	{
 		player->pflags |= PF_ATTACKDOWN;
@@ -4110,7 +4172,8 @@ static void P_DoFiring(player_t *player, ticcmd_t *cmd)
 		return;
 	}
 
-	if (!G_RingSlingerGametype() || player->weapondelay)
+	// No ringslinging outside of ringslinger!
+	if (!G_RingSlingerGametype() || player->weapondelay || (G_TagGametype() && !(player->pflags & PF_TAGIT)))
 		return;
 
 	player->pflags |= PF_ATTACKDOWN;
@@ -4288,34 +4351,7 @@ static void P_DoSuperStuff(player_t *player)
 		// If you're super and not Sonic, de-superize!
 		if (!(ALL7EMERALDS(emeralds) && player->charflags & SF_SUPER))
 		{
-			player->powers[pw_super] = 0;
-			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
-			if (P_IsLocalPlayer(player))
-			{
-				music_stack_noposition = true; // HACK: Do not reposition next music
-				music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
-			}
-			P_RestoreMusic(player);
-			P_SpawnShieldOrb(player);
-
-			// Restore color
-			if ((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER)
-			{
-				player->mo->color = SKINCOLOR_WHITE;
-				G_GhostAddColor(GHC_FIREFLOWER);
-			}
-			else
-			{
-				player->mo->color = P_GetPlayerColor(player);
-				G_GhostAddColor(GHC_NORMAL);
-			}
-
-			if (!G_CoopGametype())
-			{
-				HU_SetCEchoFlags(0);
-				HU_SetCEchoDuration(5);
-				HU_DoCEcho(va("%s\\is no longer super.\\\\\\\\", player_names[player-players]));
-			}
+			P_DoSuperDetransformation(player);
 			return;
 		}
 
@@ -4342,69 +4378,37 @@ static void P_DoSuperStuff(player_t *player)
 
 		// Ran out of rings while super!
 		if (player->rings <= 0 || player->exiting)
-		{
-			player->powers[pw_emeralds] = 0; // lost the power stones
-			P_SpawnGhostMobj(player->mo);
-
-			player->powers[pw_super] = 0;
-
-			// Restore color
-			if ((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER)
-			{
-				player->mo->color = SKINCOLOR_WHITE;
-				G_GhostAddColor(GHC_FIREFLOWER);
-			}
-			else
-			{
-				player->mo->color = P_GetPlayerColor(player);
-				G_GhostAddColor(GHC_NORMAL);
-			}
-
-			if (!G_CoopGametype())
-				player->powers[pw_flashing] = flashingtics-1;
-
-			if (player->mo->sprite2 & FF_SPR2SUPER)
-				P_SetPlayerMobjState(player->mo, player->mo->state-states);
-
-			// Inform the netgame that the champion has fallen in the heat of battle.
-			if (!G_CoopGametype())
-			{
-				S_StartSound(NULL, sfx_s3k66); //let all players hear it.
-				HU_SetCEchoFlags(0);
-				HU_SetCEchoDuration(5);
-				HU_DoCEcho(va("%s\\is no longer super.\\\\\\\\", player_names[player-players]));
-			}
-
-			// Resume normal music if you're the console player
-			if (P_IsLocalPlayer(player))
-			{
-				music_stack_noposition = true; // HACK: Do not reposition next music
-				music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
-			}
-			P_RestoreMusic(player);
-
-			// If you had a shield, restore its visual significance.
-			P_SpawnShieldOrb(player);
-		}
+			P_DoSuperDetransformation(player);
 	}
 }
 
 //
 // P_SuperReady
 //
-// Returns true if player is ready to turn super, duh
+// Returns true if player is ready to transform or detransform
 //
-boolean P_SuperReady(player_t *player)
+boolean P_SuperReady(player_t *player, boolean transform)
 {
-	if (!player->powers[pw_super]
-	&& !player->powers[pw_invulnerability]
+	if (!transform &&
+	(player->powers[pw_super] < TICRATE*3/2
+	|| !G_CoopGametype())) // No turning back in competitive!
+		return false;
+	else if (transform
+	&& (player->powers[pw_super]
+	|| !ALL7EMERALDS(emeralds)
+	|| !(player->rings >= 50)))
+		return false;
+	
+	if (player->mo
 	&& !player->powers[pw_tailsfly]
+	&& !player->powers[pw_carry]
 	&& (player->charflags & SF_SUPER)
-	&& (player->pflags & PF_JUMPED)
-	&& !(player->powers[pw_shield] & SH_NOSTACK)
-	&& !(maptol & TOL_NIGHTS)
-	&& ALL7EMERALDS(emeralds)
-	&& (player->rings >= 50))
+	&& !P_PlayerInPain(player)
+	&& !player->climbing
+	&& !(player->pflags & (PF_FULLSTASIS|PF_THOKKED|PF_STARTDASH|PF_GLIDING|PF_SLIDING|PF_SHIELDABILITY))
+	&& ((player->pflags & PF_JUMPED) || (P_IsObjectOnGround(player->mo) && (player->panim == PA_IDLE || player->panim == PA_EDGE
+	|| player->panim == PA_WALK || player->panim == PA_RUN || (player->charflags & SF_DASHMODE && player->panim == PA_DASH))))
+	&& !(maptol & TOL_NIGHTS))
 		return true;
 
 	return false;
@@ -5095,7 +5099,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 {
 	mobj_t *lockonshield = NULL;
 
-	if ((player->powers[pw_shield] & SH_NOSTACK) && !player->powers[pw_super] && !(player->pflags & PF_SPINDOWN)
+	if ((player->powers[pw_shield] & SH_NOSTACK) && !player->powers[pw_super] && !(player->pflags & PF_SHIELDDOWN)
 		&& ((!(player->pflags & PF_THOKKED) || (((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP || (player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT) && player->secondjump == UINT8_MAX) ))) // thokked is optional if you're bubblewrapped / 3dblasted
 	{
 		if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT && !(player->charflags & SF_NOSHIELDABILITY))
@@ -5122,7 +5126,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 				}
 			}
 		}
-		if ((!(player->charflags & SF_NOSHIELDABILITY)) && (cmd->buttons & BT_SPIN && !LUA_HookPlayer(player, HOOK(ShieldSpecial)))) // Spin button effects
+		if ((!(player->charflags & SF_NOSHIELDABILITY)) && (cmd->buttons & BT_SHIELD && !LUA_HookPlayer(player, HOOK(ShieldSpecial)))) // Shield button effects
 		{
 			// Force stop
 			if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
@@ -5243,52 +5247,45 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 			;
 		else if (P_PlayerShieldThink(player, cmd, lockonthok, visual))
 			;
-		else if ((cmd->buttons & BT_SPIN))
+		else if ((cmd->buttons & BT_SPIN) && !LUA_HookPlayer(player, HOOK(JumpSpinSpecial)))
 		{
-			if (!(player->pflags & PF_SPINDOWN) && P_SuperReady(player))
+			switch (player->charability)
 			{
-				// If you can turn super and aren't already,
-				// and you don't have a shield, do it!
-				P_DoSuperTransformation(player, false);
-			}
-			else if (!LUA_HookPlayer(player, HOOK(JumpSpinSpecial)))
-				switch (player->charability)
-				{
-					case CA_THOK:
-						if (player->powers[pw_super]) // Super Sonic float
+				case CA_THOK:
+					if (player->powers[pw_super]) // Super Sonic float
+					{
+						if ((player->speed > 5*player->mo->scale) // FixedMul(5<<FRACBITS, player->mo->scale), but scale is FRACUNIT-based
+						&& (P_MobjFlip(player->mo)*player->mo->momz <= 0))
 						{
-							if ((player->speed > 5*player->mo->scale) // FixedMul(5<<FRACBITS, player->mo->scale), but scale is FRACUNIT-based
-							&& (P_MobjFlip(player->mo)*player->mo->momz <= 0))
+							if (player->panim != PA_RUN && player->panim != PA_WALK)
 							{
-								if (player->panim != PA_RUN && player->panim != PA_WALK)
-								{
-									if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
-										P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
-									else
-										P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
-								}
-
-								player->mo->momz = 0;
-								player->pflags &= ~(PF_STARTJUMP|PF_SPINNING);
-								player->secondjump = 1;
+								if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
+									P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
+								else
+									P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
 							}
+
+							player->mo->momz = 0;
+							player->pflags &= ~(PF_STARTJUMP|PF_SPINNING);
+							player->secondjump = 1;
 						}
-						break;
-					case CA_TELEKINESIS:
-						if (!(player->pflags & (PF_THOKKED|PF_SPINDOWN)) || (player->charflags & SF_MULTIABILITY))
-						{
-							P_Telekinesis(player,
-								-FixedMul(player->actionspd, player->mo->scale), // -ve thrust (pulling towards player)
-								FixedMul(384*FRACUNIT, player->mo->scale));
-						}
-						break;
-					case CA_TWINSPIN:
-						if ((player->charability2 == CA2_MELEE) && (!(player->pflags & (PF_THOKKED|PF_SPINDOWN)) || player->charflags & SF_MULTIABILITY))
-							P_DoTwinSpin(player);
-						break;
-					default:
-						break;
-				}
+					}
+					break;
+				case CA_TELEKINESIS:
+					if (!(player->pflags & (PF_THOKKED|PF_SPINDOWN)) || (player->charflags & SF_MULTIABILITY))
+					{
+						P_Telekinesis(player,
+							-FixedMul(player->actionspd, player->mo->scale), // -ve thrust (pulling towards player)
+							FixedMul(384*FRACUNIT, player->mo->scale));
+					}
+					break;
+				case CA_TWINSPIN:
+					if ((player->charability2 == CA2_MELEE) && (!(player->pflags & (PF_THOKKED|PF_SPINDOWN)) || player->charflags & SF_MULTIABILITY))
+						P_DoTwinSpin(player);
+					break;
+				default:
+					break;
+			}
 		}
 	}
 
@@ -5351,12 +5348,6 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		}
 		else if (player->pflags & PF_SLIDING || ((gametyperules & GTR_TEAMFLAGS) && player->gotflag) || player->pflags & PF_SHIELDABILITY)
 			;
-		/*else if (P_SuperReady(player))
-		{
-			// If you can turn super and aren't already,
-			// and you don't have a shield, do it!
-			P_DoSuperTransformation(player, false);
-		}*/
 		else if (player->pflags & PF_JUMPED)
 		{
 			if (!LUA_HookPlayer(player, HOOK(AbilitySpecial)))
@@ -8687,18 +8678,31 @@ void P_MovePlayer(player_t *player)
 	&& player->panim == PA_IDLE && !(player->powers[pw_carry]))
 		P_DoTeeter(player);
 
-	// Toss a flag
-	if (G_GametypeHasTeams() && (cmd->buttons & BT_TOSSFLAG) && !(player->powers[pw_super]) && !(player->tossdelay))
+	// Check for fire and shield buttons
+	if (!player->exiting && !(player->pflags & PF_STASIS))
 	{
-		if (!(player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
-			P_PlayerEmeraldBurst(player, true); // Toss emeralds
-		else
-			P_PlayerFlagBurst(player, true);
-	}
-
-	// check for fire
-	if (!player->exiting)
+		// Check for fire buttons
 		P_DoFiring(player, cmd);
+		
+		// Release the shield button
+		if (!(cmd->buttons & BT_SHIELD))
+			player->pflags &= ~PF_SHIELDDOWN;
+		
+		// Shield button behavior
+		// Check P_PlayerShieldThink for actual shields!
+		else if (!(player->pflags & PF_SHIELDDOWN))
+		{
+			// Transform into super if we can!
+			if (P_SuperReady(player, true))
+				P_DoSuperTransformation(player, false);
+			
+			// Detransform from super if we can!
+			else if (P_SuperReady(player, false))
+				P_DoSuperDetransformation(player);
+			
+			player->pflags |= PF_SHIELDDOWN;
+		}
+	}
 
 	{
 		boolean atspinheight = false;
diff --git a/src/r_bbox.c b/src/r_bbox.c
index cf417ec37639477b43a5a5e5035b059dc81490b4..8ccad2bb58186098810f299405fc0f4d23fa9b5a 100644
--- a/src/r_bbox.c
+++ b/src/r_bbox.c
@@ -267,18 +267,17 @@ static boolean is_tangible (mobj_t *thing)
 boolean R_ThingBoundingBoxVisible(mobj_t *thing)
 {
 	INT32 cvmode = cv_renderhitbox.value;
+	boolean ring = false;
 
 	if (multiplayer) // No hitboxes in multiplayer to avoid cheating
 		return false;
 
-	// Do not render bbox for these
 	switch (thing->type)
 	{
 		default:
-			// First person / awayviewmobj -- rendering
-			// a bbox too close to the viewpoint causes
-			// anomalies and these are exactly on the
-			// viewpoint!
+			// First person / awayviewmobj -- rendering a bbox 
+			// too close to the viewpoint causes anomalies
+			// and these are exactly on the viewpoint!
 			if (thing != r_viewmobj)
 			{
 				break;
@@ -290,6 +289,17 @@ boolean R_ThingBoundingBoxVisible(mobj_t *thing)
 			// are rendered using portals in Software,
 			// r_viewmobj does not point here.
 			return false;
+
+		case MT_RING:
+		case MT_BLUESPHERE:
+		case MT_NIGHTSSTAR:
+		case MT_NIGHTSCHIP:
+		case MT_COIN:
+			// Rings and similar objects are often placed
+			// in large amounts, so they are handled
+			// separately from other tangible objects.
+			ring = true;
+			break;
 	}
 
 	switch (cvmode)
@@ -304,16 +314,10 @@ boolean R_ThingBoundingBoxVisible(mobj_t *thing)
 			return !is_tangible(thing);
 
 		case RENDERHITBOX_TANGIBLE:
-			// Exclude rings from here, lots of them!
-			if (thing->type == MT_RING)
-			{
-				return false;
-			}
-
-			return is_tangible(thing);
+			return !ring && is_tangible(thing);
 
 		case RENDERHITBOX_RINGS:
-			return (thing->type == MT_RING || thing->type == MT_BLUESPHERE);
+			return ring;
 
 		default:
 			return false;
diff --git a/src/r_data.c b/src/r_data.c
index 08624b454ed39efa4cf2735dab39ae911aa3df14..0a13d27dbaf55f9e9092adeb0de4635523c96606 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -439,7 +439,7 @@ extracolormap_t *R_CreateDefaultColormap(boolean lighttable)
 	exc->fadeend = 31;
 	exc->flags = 0;
 	exc->rgba = 0;
-	exc->fadergba = 0x19000000;
+	exc->fadergba = 0xFF000000;
 	exc->colormap = lighttable ? R_CreateLightTable(exc) : NULL;
 #ifdef EXTRACOLORMAPLUMPS
 	exc->lump = LUMPERROR;
@@ -554,7 +554,7 @@ boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba,
 				&& !flags)
 			)
 		&& (!checkrgba ? true : rgba == 0)
-		&& (!checkfadergba ? true : fadergba == 0x19000000)
+		&& (!checkfadergba ? true : (unsigned)fadergba == 0xFF000000)
 #ifdef EXTRACOLORMAPLUMPS
 		&& lump == LUMPERROR
 		&& extra_colormap->lumpname[0] == 0
@@ -655,7 +655,7 @@ extracolormap_t *R_ColormapForName(char *name)
 	if (lump == LUMPERROR)
 		I_Error("R_ColormapForName: Cannot find colormap lump %.8s\n", name);
 
-	exc = R_GetColormapFromListByValues(0, 0x19000000, 0, 31, 0, lump);
+	exc = R_GetColormapFromListByValues(0, 0xFF000000, 0, 31, 0, lump);
 	if (exc)
 		return exc;
 
@@ -675,7 +675,7 @@ extracolormap_t *R_ColormapForName(char *name)
 	exc->fadeend = 31;
 	exc->flags = 0;
 	exc->rgba = 0;
-	exc->fadergba = 0x19000000;
+	exc->fadergba = 0xFF000000;
 
 	R_AddColormapToList(exc);
 
@@ -693,9 +693,26 @@ extracolormap_t *R_ColormapForName(char *name)
 //
 static double deltas[256][3], map[256][3];
 
-static int RoundUp(double number);
+static colorlookup_t lighttable_lut;
+
+static UINT8 LightTableNearest(UINT8 r, UINT8 g, UINT8 b)
+{
+	return NearestColor(r, g, b);
+}
+
+static UINT8 LightTableNearest_LUT(UINT8 r, UINT8 g, UINT8 b)
+{
+	return GetColorLUT(&lighttable_lut, r, g, b);
+}
 
 lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
+{
+	extra_colormap->colormap = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8);
+	R_GenerateLightTable(extra_colormap, false);
+	return extra_colormap->colormap;
+}
+
+void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup)
 {
 	double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb;
 	double maskamt = 0, othermask = 0;
@@ -712,7 +729,6 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 	UINT8 fadestart = extra_colormap->fadestart,
 		fadedist = extra_colormap->fadeend - extra_colormap->fadestart;
 
-	lighttable_t *lighttable = NULL;
 	size_t i;
 
 	/////////////////////
@@ -722,7 +738,7 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 	cmaskg = cg;
 	cmaskb = cb;
 
-	maskamt = (double)(ca/24.0l);
+	maskamt = (double)(ca/255.0l);
 	othermask = 1 - maskamt;
 	maskamt /= 0xff;
 
@@ -738,7 +754,7 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 	cdestb = cfb;
 
 	// fade alpha unused in software
-	// maskamt = (double)(cfa/24.0l);
+	// maskamt = (double)(cfa/255.0l);
 	// othermask = 1 - maskamt;
 	// maskamt /= 0xff;
 
@@ -754,6 +770,16 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 		int p;
 		char *colormap_p;
 
+		UINT8 (*NearestColorFunc)(UINT8, UINT8, UINT8);
+
+		if (uselookup)
+		{
+			InitColorLUT(&lighttable_lut, pMasterPalette, false);
+			NearestColorFunc = LightTableNearest_LUT;
+		}
+		else
+			NearestColorFunc = LightTableNearest;
+
 		// Initialise the map and delta arrays
 		// map[i] stores an RGB color (as double) for index i,
 		//  which is then converted to SRB2's palette later
@@ -784,8 +810,7 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 
 		// Now allocate memory for the actual colormap array itself!
 		// aligned on 8 bit for asm code
-		colormap_p = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8);
-		lighttable = (UINT8 *)colormap_p;
+		colormap_p = (char *)extra_colormap->colormap;
 
 		// Calculate the palette index for each palette index, for each light level
 		// (as well as the two unused colormap lines we inherited from Doom)
@@ -793,9 +818,9 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 		{
 			for (i = 0; i < 256; i++)
 			{
-				*colormap_p = NearestColor((UINT8)RoundUp(map[i][0]),
-					(UINT8)RoundUp(map[i][1]),
-					(UINT8)RoundUp(map[i][2]));
+				*colormap_p = NearestColorFunc((UINT8)M_RoundUp(map[i][0]),
+					(UINT8)M_RoundUp(map[i][1]),
+					(UINT8)M_RoundUp(map[i][2]));
 				colormap_p++;
 
 				if ((UINT32)p < fadestart)
@@ -819,8 +844,6 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 			}
 		}
 	}
-
-	return lighttable;
 }
 
 extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
@@ -829,7 +852,7 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
 	UINT8 cr = 0, cg = 0, cb = 0, ca = 0, cfr = 0, cfg = 0, cfb = 0, cfa = 25;
 	UINT32 fadestart = 0, fadeend = 31;
 	UINT8 flags = 0;
-	INT32 rgba = 0, fadergba = 0x19000000;
+	INT32 rgba = 0, fadergba = 0xFF000000;
 
 #define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0)
 #define ALPHA2INT(x) (x >= 'a' && x <= 'z' ? x - 'a' : x >= 'A' && x <= 'Z' ? x - 'A' : x >= '0' && x <= '9' ? 25 : 0)
@@ -837,13 +860,13 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
 	// Get base colormap value
 	// First alpha-only, then full value
 	if (p1[0] >= 'a' && p1[0] <= 'z' && !p1[1])
-		ca = (p1[0] - 'a');
+		ca = ((p1[0] - 'a') * 102) / 10;
 	else if (p1[0] == '#' && p1[1] >= 'a' && p1[1] <= 'z' && !p1[2])
-		ca = (p1[1] - 'a');
+		ca = ((p1[1] - 'a') * 102) / 10;
 	else if (p1[0] >= 'A' && p1[0] <= 'Z' && !p1[1])
-		ca = (p1[0] - 'A');
+		ca = ((p1[0] - 'A') * 102) / 10;
 	else if (p1[0] == '#' && p1[1] >= 'A' && p1[1] <= 'Z' && !p1[2])
-		ca = (p1[1] - 'A');
+		ca = ((p1[1] - 'A') * 102) / 10;
 	else if (p1[0] == '#')
 	{
 		// For each subsequent value, the value before it must exist
@@ -859,20 +882,20 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
 					cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6]));
 
 					if (p1[7] >= 'a' && p1[7] <= 'z')
-						ca = (p1[7] - 'a');
+						ca = ((p1[7] - 'a') * 102) / 10;
 					else if (p1[7] >= 'A' && p1[7] <= 'Z')
-						ca = (p1[7] - 'A');
+						ca = ((p1[7] - 'A') * 102) / 10;
 					else
-						ca = 25;
+						ca = 255;
 				}
 				else
-					ca = 25;
+					ca = 255;
 			}
 			else
-				ca = 25;
+				ca = 255;
 		}
 		else
-			ca = 25;
+			ca = 255;
 	}
 
 #define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0)
@@ -902,13 +925,13 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
 	// Get fade (dark) colormap value
 	// First alpha-only, then full value
 	if (p3[0] >= 'a' && p3[0] <= 'z' && !p3[1])
-		cfa = (p3[0] - 'a');
+		cfa = ((p3[0] - 'a') * 102) / 10;
 	else if (p3[0] == '#' && p3[1] >= 'a' && p3[1] <= 'z' && !p3[2])
-		cfa = (p3[1] - 'a');
+		cfa = ((p3[1] - 'a') * 102) / 10;
 	else if (p3[0] >= 'A' && p3[0] <= 'Z' && !p3[1])
-		cfa = (p3[0] - 'A');
+		cfa = ((p3[0] - 'A') * 102) / 10;
 	else if (p3[0] == '#' && p3[1] >= 'A' && p3[1] <= 'Z' && !p3[2])
-		cfa = (p3[1] - 'A');
+		cfa = ((p3[1] - 'A') * 102) / 10;
 	else if (p3[0] == '#')
 	{
 		// For each subsequent value, the value before it must exist
@@ -924,20 +947,20 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
 					cfb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6]));
 
 					if (p3[7] >= 'a' && p3[7] <= 'z')
-						cfa = (p3[7] - 'a');
+						cfa = ((p3[7] - 'a') * 102) / 10;
 					else if (p3[7] >= 'A' && p3[7] <= 'Z')
-						cfa = (p3[7] - 'A');
+						cfa = ((p3[7] - 'A') * 102) / 10;
 					else
-						cfa = 25;
+						cfa = 255;
 				}
 				else
-					cfa = 25;
+					cfa = 255;
 			}
 			else
-				cfa = 25;
+				cfa = 255;
 		}
 		else
-			cfa = 25;
+			cfa = 255;
 	}
 #undef ALPHA2INT
 #undef HEX2INT
@@ -1134,20 +1157,6 @@ UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette)
 	return (UINT8)bestcolor;
 }
 
-// Rounds off floating numbers and checks for 0 - 255 bounds
-static int RoundUp(double number)
-{
-	if (number > 255.0l)
-		return 255;
-	if (number < 0.0l)
-		return 0;
-
-	if ((int)number <= (int)(number - 0.5f))
-		return (int)number + 1;
-
-	return (int)number;
-}
-
 #ifdef EXTRACOLORMAPLUMPS
 const char *R_NameForColormap(extracolormap_t *extra_colormap)
 {
diff --git a/src/r_data.h b/src/r_data.h
index ef5c967e5ae41e4a726ae167716c05a931015dd6..364f85b6d4b0cbdd09448144f0546db9bc661bfc 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -92,6 +92,7 @@ typedef enum
 	TMCF_OVERRIDE     = 1<<13,
 } textmapcolormapflags_t;
 
+void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup);
 lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap);
 extracolormap_t * R_CreateColormapFromLinedef(char *p1, char *p2, char *p3);
 extracolormap_t* R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags);
diff --git a/src/r_defs.h b/src/r_defs.h
index abf10ab7c7faad45fb950df4b1fdf17ee640cb78..afe88ca28666855315ed8ca36bcac4315c3645ab 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -52,6 +52,9 @@ typedef struct
 // Could even use more than 32 levels.
 typedef UINT8 lighttable_t;
 
+#define NUM_PALETTE_ENTRIES 256
+#define DEFAULT_STARTTRANSCOLOR 96
+
 #define CMF_FADEFULLBRIGHTSPRITES  1
 #define CMF_FOG 4
 
@@ -511,6 +514,8 @@ typedef enum
 #define NUMLINEARGS 10
 #define NUMLINESTRINGARGS 2
 
+#define NO_SIDEDEF 0xFFFFFFFF
+
 typedef struct line_s
 {
 	// Vertices, from v1 to v2.
@@ -528,7 +533,7 @@ typedef struct line_s
 	char *stringargs[NUMLINESTRINGARGS];
 
 	// Visual appearance: sidedefs.
-	UINT16 sidenum[2]; // sidenum[1] will be 0xffff if one-sided
+	UINT32 sidenum[2]; // sidenum[1] will be NO_SIDEDEF if one-sided
 	fixed_t alpha; // translucency
 	UINT8 blendmode; // blendmode
 	INT32 executordelay;
@@ -921,7 +926,7 @@ typedef struct
 	UINT16 flip;
 
 #ifdef ROTSPRITE
-	rotsprite_t *rotated[2][16]; // Rotated patches
+	rotsprite_t *rotated[16]; // Rotated patches
 #endif
 } spriteframe_t;
 
diff --git a/src/r_main.c b/src/r_main.c
index 6c7bedbf1039fc47efe29f6fb0271c0ad35fd907..0cfccab8c2fbcbd7cdc35c678c379cdfb942cbcc 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -158,7 +158,7 @@ consvar_t cv_drawdist = CVAR_INIT ("drawdist", "Infinite", CV_SAVE, drawdist_con
 consvar_t cv_drawdist_nights = CVAR_INIT ("drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL);
 consvar_t cv_drawdist_precip = CVAR_INIT ("drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL);
 //consvar_t cv_precipdensity = CVAR_INIT ("precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL);
-consvar_t cv_fov = CVAR_INIT ("fov", "90", CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange);
+consvar_t cv_fov = CVAR_INIT ("fov", "90", CV_SAVE|CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange);
 
 // Okay, whoever said homremoval causes a performance hit should be shot.
 consvar_t cv_homremoval = CVAR_INIT ("homremoval", "No", CV_SAVE, homremoval_cons_t, NULL);
diff --git a/src/r_patch.h b/src/r_patch.h
index a0ab3e75ac0c81a7fa16b6129cd2deabb8d02d46..cc41639b3a1913706c21b3041044aab84d70ff06 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -37,7 +37,7 @@ patch_t *Patch_GetRotated(patch_t *patch, INT32 angle, boolean flip);
 patch_t *Patch_GetRotatedSprite(
 	spriteframe_t *sprite,
 	size_t frame, size_t spriteangle,
-	boolean flip, boolean adjustfeet,
+	boolean flip,
 	void *info, INT32 rotationangle);
 angle_t R_ModelRotationAngle(interpmobjstate_t *interp);
 angle_t R_SpriteRotationAngle(interpmobjstate_t *interp);
diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c
index b0cbeaa42911d1506d9928b906198afdf50ff0be..b9106984970f728e83e567627daa4c1b9c478011 100644
--- a/src/r_patchrotation.c
+++ b/src/r_patchrotation.c
@@ -10,7 +10,7 @@
 /// \brief Patch rotation.
 
 #include "r_patchrotation.h"
-#include "r_things.h" // FEETADJUST
+#include "r_things.h" // FEETADJUST (todo: is this needed anymore? -- Monster Iestyn 21 Sep 2023 )
 #include "z_zone.h"
 #include "w_wad.h"
 #include "r_main.h" // R_PointToAngle
@@ -66,23 +66,20 @@ patch_t *Patch_GetRotated(patch_t *patch, INT32 angle, boolean flip)
 patch_t *Patch_GetRotatedSprite(
 	spriteframe_t *sprite,
 	size_t frame, size_t spriteangle,
-	boolean flip, boolean adjustfeet,
+	boolean flip,
 	void *info, INT32 rotationangle)
 {
-	rotsprite_t *rotsprite;
+	rotsprite_t *rotsprite = sprite->rotated[spriteangle];
 	spriteinfo_t *sprinfo = (spriteinfo_t *)info;
 	INT32 idx = rotationangle;
-	UINT8 type = (adjustfeet ? 1 : 0);
 
 	if (rotationangle < 1 || rotationangle >= ROTANGLES)
 		return NULL;
 
-	rotsprite = sprite->rotated[type][spriteangle];
-
 	if (rotsprite == NULL)
 	{
 		rotsprite = RotatedPatch_Create(ROTANGLES);
-		sprite->rotated[type][spriteangle] = rotsprite;
+		sprite->rotated[spriteangle] = rotsprite;
 	}
 
 	if (flip)
@@ -111,10 +108,6 @@ patch_t *Patch_GetRotatedSprite(
 		}
 
 		RotatedPatch_DoRotation(rotsprite, patch, rotationangle, xpivot, ypivot, flip);
-
-		//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
-		if (adjustfeet)
-			((patch_t *)rotsprite->patches[idx])->topoffset += FEETADJUST>>FRACBITS;
 	}
 
 	return rotsprite->patches[idx];
diff --git a/src/r_things.c b/src/r_things.c
index 75c652ea10a5665da2b7ce088430b359002b58bc..fdc5015041b32981d67820df2b448164b3434ad6 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -139,8 +139,7 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 #ifdef ROTSPRITE
 	for (r = 0; r < 16; r++)
 	{
-		sprtemp[frame].rotated[0][r] = NULL;
-		sprtemp[frame].rotated[1][r] = NULL;
+		sprtemp[frame].rotated[r] = NULL;
 	}
 #endif
 
@@ -338,6 +337,11 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 			spritecachedinfo[numspritelumps].height = height<<FRACBITS;
 
 			// BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
+			// Monster Iestyn (21 Sep 2023): the above comment no longer makes sense in context!!! So I give an explanation here!
+			// FEETADJUST was originally an OpenGL-exclusive hack from Doom Legacy to avoid the player's feet being clipped as
+			// a result of rendering partially under the ground, but sometime before SRB2 2.1's release this was changed to apply
+			// to the software renderer as well.
+			// TODO: kill FEETADJUST altogether somehow and somehow fix OpenGL not to clip sprites that are partially underground (if possible)?
 			spritecachedinfo[numspritelumps].topoffset += FEETADJUST;
 
 			//----------------------------------------------------
@@ -1311,8 +1315,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	patch_t *patch;
 	fixed_t xscale, yscale, shadowxscale, shadowyscale, shadowskew, x1, x2;
 	INT32 heightsec, phs;
-	INT32 light = 0;
-	fixed_t scalemul; UINT8 trans;
+	fixed_t scalemul;
+	UINT8 trans;
 	fixed_t floordiff;
 	fixed_t groundz;
 	pslope_t *groundslope;
@@ -1429,27 +1433,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	if (thing->renderflags & RF_NOCOLORMAPS)
 		shadow->extra_colormap = NULL;
 	else
-	{
-		if (thing->subsector->sector->numlights)
-		{
-			INT32 lightnum;
-			light = thing->subsector->sector->numlights - 1;
-
-			// R_GetPlaneLight won't work on sloped lights!
-			for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
-				fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], interp.x, interp.y);
-				if (h <= shadow->gzt) {
-					light = lightnum - 1;
-					break;
-				}
-			}
-		}
-
-		if (thing->subsector->sector->numlights)
-			shadow->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
-		else
-			shadow->extra_colormap = thing->subsector->sector->extra_colormap;
-	}
+		shadow->extra_colormap = P_GetColormapFromSectorAt(thing->subsector->sector, interp.x, interp.y, shadow->gzt);
 
 	shadow->transmap = R_GetTranslucencyTable(trans + 1);
 	shadow->colormap = scalelight[0][0]; // full dark!
@@ -1790,7 +1774,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			rollangle = R_GetRollAngle(spriterotangle);
 		}
 
-		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
+		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, sprinfo, rollangle);
 
 		if (rotsprite != NULL)
 		{
@@ -2146,21 +2130,9 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (thing->subsector->sector->numlights)
 	{
-		INT32 lightnum;
-		fixed_t top = (splat) ? gz : gzt;
-		light = thing->subsector->sector->numlights - 1;
-
-		// R_GetPlaneLight won't work on sloped lights!
-		for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
-			fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], interp.x, interp.y);
-			if (h <= top) {
-				light = lightnum - 1;
-				break;
-			}
-		}
-		//light = R_GetPlaneLight(thing->subsector->sector, gzt, false);
-		lightnum = (*thing->subsector->sector->lightlist[light].lightlevel >> LIGHTSEGSHIFT);
+		light = P_GetSectorLightAt(thing->subsector->sector, interp.x, interp.y, splat ? gz : gzt);
 
+		INT32 lightnum = (*thing->subsector->sector->lightlist[light].lightlevel >> LIGHTSEGSHIFT);
 		if (lightnum < 0)
 			spritelights = scalelight[0];
 		else if (lightnum >= LIGHTLEVELS)
@@ -3203,8 +3175,8 @@ static boolean R_CheckSpriteVisible(vissprite_t *spr, INT32 x1, INT32 x2)
 	INT16 sz = spr->sz;
 	INT16 szt = spr->szt;
 
-	fixed_t texturemid, yscale, scalestep = spr->scalestep;
-	INT32 height;
+	fixed_t texturemid = 0, yscale = 0, scalestep = spr->scalestep; // "= 0" pleases the compiler
+	INT32 height = 0;
 
 	if (scalestep)
 	{
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index b05f40ee375376bd7c585c3952daad2446580fca..450237149b08c9225b7204a9d31c63e311a06c29 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -608,6 +608,7 @@ void I_GetConsoleEvents(void)
 		return;
 
 	ev.type = ev_console;
+	ev.key = 0;
 	if (read(STDIN_FILENO, &key, 1) == -1 || !key)
 		return;
 
@@ -634,7 +635,7 @@ void I_GetConsoleEvents(void)
 		}
 		else return;
 	}
-	else
+	else if (tty_con.cursor < sizeof (tty_con.buffer))
 	{
 		// push regular character
 		ev.key = tty_con.buffer[tty_con.cursor] = key;
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 0a39c7f286f8dc3e4b563709de240b0370e1b2b9..a5be3a754ce5a44b5c985f5881300d06a5ad07c7 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -759,8 +759,8 @@ static void mix_gme(void *udata, Uint8 *stream, int len)
 		music_volume = 18;
 
 	// apply volume to stream
-	for (i = 0, p = (short *)stream; i < len/2; i++, p++)
-		*p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40;
+	for (i = 0, p = (short *)stream; i < len / 2; i++, p++)
+		*p = ((INT32)*p) * music_volume * internal_volume / 100 / 20;
 }
 #endif
 
@@ -783,8 +783,8 @@ static void mix_openmpt(void *udata, Uint8 *stream, int len)
 		music_volume = 18;
 
 	// apply volume to stream
-	for (i = 0, p = (short *)stream; i < len/2; i++, p++)
-		*p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40;
+	for (i = 0, p = (short *)stream; i < len / 2; i++, p++)
+		*p = ((INT32)*p) * music_volume * internal_volume / 100 / 20;
 }
 #endif
 
@@ -1441,7 +1441,7 @@ void I_SetMusicVolume(UINT8 volume)
 	Mix_VolumeMusic(get_real_volume(music_volume));
 }
 
-boolean I_SetSongTrack(int track)
+boolean I_SetSongTrack(INT32 track)
 {
 #ifdef HAVE_GME
 	// If the specified track is within the number of tracks playing, then change it
diff --git a/src/sdl/sdl_sound.c b/src/sdl/sdl_sound.c
index 2ca35b95456e004ee53105165428529d79d16f0d..2705261d623e4d1109ecdbdd2aa7d6fb5072579b 100644
--- a/src/sdl/sdl_sound.c
+++ b/src/sdl/sdl_sound.c
@@ -1471,7 +1471,7 @@ void I_SetMusicVolume(UINT8 volume)
 	(void)volume;
 }
 
-boolean I_SetSongTrack(int track)
+boolean I_SetSongTrack(INT32 track)
 {
 	(void)track;
 	return false;
diff --git a/src/y_inter.c b/src/y_inter.c
index 369ec390442642b1a52c185bbf0804b3c0c1a106..cbe057582161e15d92504fdaf834095bb68fddd1 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -579,9 +579,9 @@ void Y_IntermissionDrawer(void)
 		{
 			if (LUA_HudEnabled(hud_intermissiontitletext))
 			{
-				const char *ringtext = "\x82" "50 rings, no shield";
-				const char *tut1text = "\x82" "press " "\x80" "spin";
-				const char *tut2text = "\x82" "mid-" "\x80" "jump";
+				const char *ringtext = "\x82" "get 50 rings then";
+				const char *tut1text = "\x82" "press " "\x80" "shield";
+				const char *tut2text = "\x82" "to " "\x80" "transform";
 				ttheight = 8;
 				V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
 				ttheight += V_LevelNameHeight(data.spec.passed3) + 2;