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/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/d_player.h b/src/d_player.h
index 5c0519e88aede51b1e0d5d93c806e73c88b78a9e..62383f53a0797d8536aee5c1524069062aaf4749 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 e596e166fb01d762a6bcc210634c599155fc7d4e..b8daa04309d39c0e4167d83797307838dec41a9d 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -589,7 +589,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 bf148d82b275801d0a0c142642e8819da3ca89df..b53cd00c8fc23149a9a9310776335d59e7edd27a 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -5576,7 +5576,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
@@ -5756,9 +5757,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/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 9d97a3eaf46f8d34bd93af8546c690ed34dbe35f..b71f5f61d98d505f90375d7764d55b26f4489851 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_main.c b/src/hardware/hw_main.c
index 7fd810935922fb394bee09443e074a676392da97..7153e21a9a0ff06847d457913d241328bcedb805 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -584,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;
@@ -1070,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);
@@ -1868,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);
@@ -2244,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);
@@ -5258,7 +5322,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)
 		{
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 4bea105a663191c7bfa0b80bb56fac795319fbcd..d3d2c39160b6cdd9a95d92037ca6a6df50227b8d 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1686,11 +1686,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;
 }
 
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 456d3b4c9ba2bbe9358c4fb786159e6e5a7c8e70..eaa8301f92a05791885cb03e04bfc575b2331da5 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/m_menu.c b/src/m_menu.c
index 597f9fb98f260dc47cd23d11bc0acbdd52683d29..2864df31ab84761adfad67a75b38e5e156eee7c4 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1070,6 +1070,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      },
@@ -13180,23 +13181,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
@@ -13212,23 +13213,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/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..1f1548b86595b183e92d6c1d0b3c355caeb68e06 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)
diff --git a/src/p_user.c b/src/p_user.c
index 618e84c2d24853391c5dc029d594eb4d2e5ff47e..3335c3126624214df805bf975d40e11001139463 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)
 {
@@ -4086,6 +4137,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)))
 	{
@@ -4095,9 +4156,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;
@@ -4109,7 +4171,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;
@@ -4287,34 +4350,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;
 		}
 
@@ -4341,69 +4377,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;
@@ -5094,7 +5098,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))
@@ -5121,7 +5125,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)
@@ -5242,52 +5246,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;
+			}
 		}
 	}
 
@@ -5350,12 +5347,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)))
@@ -8686,18 +8677,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_defs.h b/src/r_defs.h
index dfd2d6d708f8a5a9dae0da401719ad1d3eb24235..b862ad7a9705c8f15a7c841d655dc02c752d3d0a 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -922,7 +922,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_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 ded96bf0d1b4ff7d766ac5496108e548f38ec385..4686c47e6d9d7bb82e66f8c6f3b5f14b72ce62ca 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -138,8 +138,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
 
@@ -337,6 +336,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;
 
 			//----------------------------------------------------
@@ -1786,7 +1790,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)
 		{
@@ -3193,8 +3197,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/y_inter.c b/src/y_inter.c
index 2221e8c86f1b898d574d44b5157947e2d5adc57a..1f008eaf0363bb444b97fc24bc3e5f7872af605d 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;