From 10a137620ae094def738273824c147da6ddcfd21 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 23 Dec 2016 17:31:07 +0000
Subject: [PATCH 001/119] First commit, CA_BOUNCE almost completed.

---
 src/d_player.h |  8 +++++--
 src/dehacked.c |  6 +++++
 src/info.c     |  7 ++++++
 src/info.h     |  7 ++++++
 src/p_inter.c  |  6 ++---
 src/p_map.c    |  4 ++--
 src/p_mobj.c   | 28 ++++++++++++++++++++++
 src/p_user.c   | 63 ++++++++++++++++++++++++++++++++++++++++++++------
 8 files changed, 115 insertions(+), 14 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index 1c57e6167..229ab6570 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -66,6 +66,7 @@ typedef enum
 	CA_AIRDRILL,
 	CA_JUMPTHOK,
 	CA_DASHMODE,
+	CA_BOUNCE,
 	CA_TWINSPIN
 } charability_t;
 
@@ -158,9 +159,12 @@ typedef enum
 	PF_SHIELDABILITY     = 1<<28,
 
 	// Force jump damage?
-	PF_FORCEJUMPDAMAGE        = 1<<29
+	PF_FORCEJUMPDAMAGE   = 1<<29,
 
-	// free up to and including 1<<31
+	// Bouncing
+	PF_BOUNCING          = 1<<30
+
+	// 1<<31 is free
 } pflags_t;
 
 typedef enum
diff --git a/src/dehacked.c b/src/dehacked.c
index f45b05b04..6427d0602 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3816,6 +3816,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_CLING",
 	"S_PLAY_CLIMB",
 
+	// CA_BOUNCE
+	"S_PLAY_BOUNCE",
+	"S_PLAY_BOUNCE_LANDING",
+
 	// CA_TWINSPIN
 	"S_PLAY_TWINSPIN",
 
@@ -6790,6 +6794,7 @@ static const char *const PLAYERFLAG_LIST[] = {
 	"CANCARRY", // Can carry?
 	"SHIELDABILITY", // Thokked with shield ability
 	"FORCEJUMPDAMAGE", // Force jump damage
+	"BOUNCING",
 
 	NULL // stop loop here.
 };
@@ -7207,6 +7212,7 @@ struct {
 	{"CA_AIRDRILL",CA_AIRDRILL},
 	{"CA_JUMPTHOK",CA_JUMPTHOK},
 	{"CA_DASHMODE",CA_DASHMODE},
+	{"CA_BOUNCE",CA_BOUNCE},
 	{"CA_TWINSPIN",CA_TWINSPIN},
 	// Secondary
 	{"CA2_NONE",CA2_NONE}, // now slot 0!
diff --git a/src/info.c b/src/info.c
index ad4aec136..4edaa5737 100644
--- a/src/info.c
+++ b/src/info.c
@@ -402,6 +402,9 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"CLNG",
 	"CLMB",
 
+	"BNCE",
+	"BLND",
+
 	"TWIN",
 
 	"MLEE",
@@ -514,6 +517,10 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_CLNG|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},       // S_PLAY_CLING
 	{SPR_PLAY, SPR2_CLMB,                 5, {NULL}, 0,  0, S_PLAY_CLIMB}, // S_PLAY_CLIMB
 
+	// CA_BOUNCE
+	{SPR_PLAY, SPR2_BNCE|FF_ANIMATE,     -1, {NULL}, 0,  0, S_NULL},                            // S_PLAY_BOUNCE
+	{SPR_PLAY, SPR2_BLND|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_BOUNCE,  4, S_PLAY_BOUNCE_LANDING}, // S_PLAY_BOUNCE_LANDING
+
 	// CA_TWINSPIN
 	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
 
diff --git a/src/info.h b/src/info.h
index 932180ebf..0202a71c4 100644
--- a/src/info.h
+++ b/src/info.h
@@ -604,6 +604,9 @@ enum playersprite
 	SPR2_CLNG, // cling
 	SPR2_CLMB, // climb
 
+	SPR2_BNCE, // bounce
+	SPR2_BLND, // bounce landing
+
 	SPR2_TWIN, // twinspin
 
 	SPR2_MLEE, // melee
@@ -713,6 +716,10 @@ typedef enum state
 	S_PLAY_CLING,
 	S_PLAY_CLIMB,
 
+	// CA_BOUNCE
+	S_PLAY_BOUNCE,
+	S_PLAY_BOUNCE_LANDING,
+
 	// CA_TWINSPIN
 	S_PLAY_TWINSPIN,
 
diff --git a/src/p_inter.c b/src/p_inter.c
index 974ae1b8f..16f128da9 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -371,7 +371,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
-		|| ((player->charflags & SF_STOMPDAMAGE) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
+		|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
 		|| player->powers[pw_invulnerability] || player->powers[pw_super]
 		|| elementalpierce) // Do you possess the ability to subdue the object?
 		{
@@ -423,7 +423,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
-		|| ((player->charflags & SF_STOMPDAMAGE) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
+		|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
 		|| player->powers[pw_invulnerability] || player->powers[pw_super]) // Do you possess the ability to subdue the object?
 		{
 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
@@ -1395,7 +1395,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				}
 				else if (((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 						|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
-						|| ((player->charflags & SF_STOMPDAMAGE) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
+						|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
 						|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 						|| player->powers[pw_invulnerability] || player->powers[pw_super]) // Do you possess the ability to subdue the object?
 				{
diff --git a/src/p_map.c b/src/p_map.c
index 7afd266fd..d31f46ba0 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1063,7 +1063,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 					|| !(tmthing->player->charflags & SF_NOJUMPSPIN)
 					|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
 				|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
-				|| ((tmthing->player->charflags & SF_STOMPDAMAGE)
+				|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
 					&& (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height/2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0))
 				|| elementalpierce))
 			{
@@ -1105,7 +1105,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			|| !(tmthing->player->charflags & SF_NOJUMPSPIN)
 			|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
 		|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
-		|| ((tmthing->player->charflags & SF_STOMPDAMAGE)
+		|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
 			&& (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height/2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0)))
 	&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != 2)))
 		;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 97147d642..8d4ef4202 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -253,6 +253,13 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_CLMB;
 			break;
 
+		case SPR2_BNCE:
+			spr2 = SPR2_FALL;
+			break;
+		case SPR2_BLND:
+			spr2 = SPR2_SPIN;
+			break;
+
 		case SPR2_TWIN:
 			spr2 = SPR2_SPIN;
 			break;
@@ -526,6 +533,8 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 	case S_PLAY_FLY:
 	case S_PLAY_SWIM:
 	case S_PLAY_GLIDE:
+	case S_PLAY_BOUNCE:
+	case S_PLAY_BOUNCE_LANDING:
 	case S_PLAY_TWINSPIN:
 		player->panim = PA_ABILITY;
 		break;
@@ -3306,6 +3315,22 @@ static void P_PlayerZMovement(mobj_t *mo)
 							clipmomz = false;
 						}
 					}
+
+					if (mo->player->pflags & PF_BOUNCING)
+					{
+						fixed_t prevmomz = P_MobjFlip(mo)*abs(mo->momz);
+						if (mo->eflags & MFE_UNDERWATER)
+						{
+							prevmomz /= 2;
+						}
+						S_StartSound(mo, sfx_boingf);
+						P_DoJump(mo->player, false);
+						P_SetPlayerMobjState(mo, S_PLAY_BOUNCE_LANDING);
+						mo->player->pflags |= PF_BOUNCING;
+						mo->player->jumping = 0;
+						mo->momz = (FixedMul(mo->momz, 3*FRACUNIT/2) + prevmomz)/2;
+						clipmomz = false;
+					}
 				}
 			}
 			if (!(mo->player->pflags & PF_SPINNING))
@@ -4127,6 +4152,9 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	// momentum movement
 	mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN;
 
+	if (mobj->state-states == S_PLAY_BOUNCE_LANDING)
+		goto animonly;
+
 	// Zoom tube
 	if (mobj->tracer)
 	{
diff --git a/src/p_user.c b/src/p_user.c
index df9fbb2c1..d1ba7d5e9 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -870,7 +870,7 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 // Useful when you want to kill everything the player is doing.
 void P_ResetPlayer(player_t *player)
 {
-	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_FORCEJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY);
+	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_FORCEJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
 	player->powers[pw_carry] = CR_NONE;
 	player->jumping = 0;
 	player->secondjump = 0;
@@ -3934,7 +3934,7 @@ void P_DoJumpShield(player_t *player)
 	player->jumping = 0;
 	player->secondjump = 0;
 	player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-	player->pflags &= ~PF_SPINNING;
+	player->pflags &= ~(PF_SPINNING|PF_BOUNCING);
 	if (electric)
 	{
 		mobj_t *spark;
@@ -4278,6 +4278,16 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						S_StartSound(player->mo, sfx_spndsh);
 					}
 					break;
+				case CA_BOUNCE:
+					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					{
+						P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE);
+						player->pflags &= ~PF_JUMPED;
+						player->pflags |= PF_BOUNCING;
+						player->mo->momx >>= 1;
+						player->mo->momy >>= 1;
+					}
+					break;
 				case CA_TWINSPIN:
 					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
 					{
@@ -4785,11 +4795,16 @@ static void P_3dMovement(player_t *player)
 	}
 
 	// Better maneuverability while flying
-	if(player->powers[pw_tailsfly])
+	if (player->powers[pw_tailsfly])
 	{
 		thrustfactor = player->thrustfactor*2;
 		acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
 	}
+	else if (player->pflags & PF_BOUNCING)
+	{
+		thrustfactor = player->thrustfactor/2;
+		acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
+	}
 
 	// Forward movement
 	if (player->climbing)
@@ -6703,9 +6718,44 @@ static void P_MovePlayer(player_t *player)
 		player->climbing = 0;
 	}
 
+	if ((!(player->charability == CA_BOUNCE) || player->gotflag) // If you can't bounce, then why the heck would you be bouncing?
+		&& (player->pflags & PF_BOUNCING))
+	{
+		if (onground)
+			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+		else
+		{
+			player->pflags |= PF_JUMPED;
+			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+		}
+		player->pflags &= ~PF_BOUNCING;
+	}
+
+	// Bouncing...
+	if (player->pflags & PF_BOUNCING)
+	{
+		if (!(player->pflags & PF_JUMPDOWN)) // If not holding the jump button
+		{
+			P_ResetPlayer(player); // down, stop bouncing.
+			if (onground)
+				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+			else if (player->charability2 == CA2_MULTIABILITY)
+			{
+				player->pflags |= PF_JUMPED;
+				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+			}
+			else
+			{
+				player->pflags |= PF_THOKKED;
+				player->mo->momx >>= 1;
+				player->mo->momy >>= 1;
+				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+			}
+		}
+	}
 	// Glide MOMZ
 	// AKA my own gravity. =)
-	if (player->pflags & PF_GLIDING)
+	else if (player->pflags & PF_GLIDING)
 	{
 		fixed_t leeway;
 		fixed_t glidespeed = player->actionspd;
@@ -6749,8 +6799,7 @@ static void P_MovePlayer(player_t *player)
 			P_ResetPlayer(player); // down, stop gliding.
 			if (onground)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-			else if (player->charability2 == CA2_MULTIABILITY
-				&& player->charability == CA_GLIDEANDCLIMB)
+			else if (player->charability2 == CA2_MULTIABILITY)
 			{
 				player->pflags |= PF_JUMPED;
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
@@ -9330,7 +9379,7 @@ void P_PlayerThink(player_t *player)
 	player->pflags &= ~PF_SLIDING;
 
 #define dashmode player->dashmode
-	// Dash mode ability for Metal Sonic
+	// Dash mode ability
 	if ((player->charability == CA_DASHMODE) && !(player->gotflag) && !(maptol & TOL_NIGHTS)) // woo, dashmode! no nights tho.
 	{
 		if (player->speed >= FixedMul(player->runspeed, player->mo->scale) || (player->pflags & PF_STARTDASH))

From 0a7fe9569d350a4d59f21efa4d904b6aec3940cd Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 23 Dec 2016 23:13:31 +0000
Subject: [PATCH 002/119] * Fixed issue where CA_BOUNCE users can harmlessly
 bounce on deathpits (and not Cakewalk style!) * Minor quality-of-life
 improvements to CA_BOUNCE handling.

---
 src/p_mobj.c | 6 +++---
 src/p_user.c | 5 +++--
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index 8d4ef4202..a6e012c73 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3316,7 +3316,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 						}
 					}
 
-					if (mo->player->pflags & PF_BOUNCING)
+					if (mo->player->pflags & PF_BOUNCING && !P_CheckDeathPitCollide(mo))
 					{
 						fixed_t prevmomz = P_MobjFlip(mo)*abs(mo->momz);
 						if (mo->eflags & MFE_UNDERWATER)
@@ -3326,7 +3326,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 						S_StartSound(mo, sfx_boingf);
 						P_DoJump(mo->player, false);
 						P_SetPlayerMobjState(mo, S_PLAY_BOUNCE_LANDING);
-						mo->player->pflags |= PF_BOUNCING;
+						mo->player->pflags |= PF_BOUNCING|PF_THOKKED;
 						mo->player->jumping = 0;
 						mo->momz = (FixedMul(mo->momz, 3*FRACUNIT/2) + prevmomz)/2;
 						clipmomz = false;
@@ -4153,7 +4153,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN;
 
 	if (mobj->state-states == S_PLAY_BOUNCE_LANDING)
-		goto animonly;
+		goto animonly; // no need for checkposition - doesn't move at ALL
 
 	// Zoom tube
 	if (mobj->tracer)
diff --git a/src/p_user.c b/src/p_user.c
index d1ba7d5e9..666e8d72d 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4283,9 +4283,10 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					{
 						P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE);
 						player->pflags &= ~PF_JUMPED;
-						player->pflags |= PF_BOUNCING;
+						player->pflags |= PF_THOKKED|PF_BOUNCING;
 						player->mo->momx >>= 1;
 						player->mo->momy >>= 1;
+						player->mo->momz >>= 1;
 					}
 					break;
 				case CA_TWINSPIN:
@@ -6746,9 +6747,9 @@ static void P_MovePlayer(player_t *player)
 			}
 			else
 			{
-				player->pflags |= PF_THOKKED;
 				player->mo->momx >>= 1;
 				player->mo->momy >>= 1;
+				player->mo->momz >>= 1;
 				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 			}
 		}

From a61f5e452124d5e7c8187be76132a85eedba3853 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 23 Dec 2016 23:58:12 +0000
Subject: [PATCH 003/119] * CA_BOUNCE can now break downwards.

---
 src/p_map.c  |  6 ++++--
 src/p_user.c | 46 ++++++++++++++++++++++++++++++++++++----------
 2 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index d31f46ba0..943f20313 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1508,7 +1508,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 			else if (thing->type == MT_SKIM && (rover->flags & FF_SWIMMABLE))
 				;
 			else if (!((rover->flags & FF_BLOCKPLAYER && thing->player)
-				|| (rover->flags & FF_BLOCKOTHERS && !thing->player)
+			    || (rover->flags & FF_BLOCKOTHERS && !thing->player)
 				|| rover->flags & FF_QUICKSAND))
 				continue;
 
@@ -1533,7 +1533,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				+ ((topheight - bottomheight)/2));
 
 			if (topheight > tmfloorz && abs(delta1) < abs(delta2)
-				&& !(rover->flags & FF_REVERSEPLATFORM))
+				&& !(rover->flags & FF_REVERSEPLATFORM)
+				&& ((P_MobjFlip(tmthing)*tmthing->momz >= 0) || (!(rover->flags & FF_PLATFORM))))
 			{
 				tmfloorz = tmdropoffz = topheight;
 #ifdef ESLOPE
@@ -1542,6 +1543,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 			}
 			if (bottomheight < tmceilingz && abs(delta1) >= abs(delta2)
 				&& !(rover->flags & FF_PLATFORM)
+				&& ((P_MobjFlip(tmthing)*tmthing->momz >= 0) || (!(rover->flags & FF_REVERSEPLATFORM)))
 				&& !(thing->type == MT_SKIM && (rover->flags & FF_SWIMMABLE)))
 			{
 				tmceilingz = tmdrpoffceilz = bottomheight;
diff --git a/src/p_user.c b/src/p_user.c
index 666e8d72d..a71491c7d 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1746,10 +1746,13 @@ static void P_CheckBustableBlocks(player_t *player)
 	oldx = player->mo->x;
 	oldy = player->mo->y;
 
-	P_UnsetThingPosition(player->mo);
-	player->mo->x += player->mo->momx;
-	player->mo->y += player->mo->momy;
-	P_SetThingPosition(player->mo);
+	if (!(player->pflags & PF_BOUNCING)) // Bouncers only get to break downwards, not sideways
+	{
+		P_UnsetThingPosition(player->mo);
+		player->mo->x += player->mo->momx;
+		player->mo->y += player->mo->momy;
+		P_SetThingPosition(player->mo);
+	}
 
 	for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
 	{
@@ -1769,7 +1772,7 @@ static void P_CheckBustableBlocks(player_t *player)
 				{
 					// If it's an FF_SPINBUST, you have to either be jumping, or coming down
 					// onto the top from a spin.
-					if (rover->flags & FF_SPINBUST && ((!(player->pflags & PF_JUMPED) && !(player->pflags & PF_SPINNING)) || (player->pflags & PF_STARTDASH)))
+					if (rover->flags & FF_SPINBUST && ((!(player->pflags & PF_JUMPED) && !(player->pflags & PF_SPINNING) && !(player->pflags & PF_BOUNCING)) || (player->pflags & PF_STARTDASH)))
 						continue;
 
 					// if it's not an FF_SHATTER, you must be spinning (and not jumping)
@@ -1784,6 +1787,7 @@ static void P_CheckBustableBlocks(player_t *player)
 						&& !((player->pflags & PF_SPINNING) && !(player->pflags & PF_JUMPED))
 						&& !(player->powers[pw_super])
 						&& !(player->charability == CA_GLIDEANDCLIMB)
+						&& !(player->pflags & PF_BOUNCING)
 						&& !((player->charability == CA_DASHMODE) && (player->dashmode >= 3*TICRATE))
 						&& !((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
 						&& !(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
@@ -1794,6 +1798,7 @@ static void P_CheckBustableBlocks(player_t *player)
 					// Only players with CA_GLIDEANDCLIMB, or CA_TWINSPIN/CA2_MELEE users can break this rock...
 					if (!(rover->flags & FF_SHATTER) && (rover->flags & FF_ONLYKNUX)
 						&& !(player->charability == CA_GLIDEANDCLIMB
+						|| (player->pflags & PF_BOUNCING)
 						|| ((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
 						|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)))
 						continue;
@@ -1801,6 +1806,24 @@ static void P_CheckBustableBlocks(player_t *player)
 					topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 					bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 
+					if (player->pflags & PF_BOUNCING)
+					{
+						if (player->mo->eflags & MFE_VERTICALFLIP)
+						{
+							if (player->mo->momz <= 0)
+								continue;
+							topheight += player->mo->momz;
+							bottomheight += player->mo->momz;
+						}
+						else
+						{
+							if (player->mo->momz >= 0)
+								continue;
+							topheight -= player->mo->momz;
+							bottomheight -= player->mo->momz;
+						}
+					}
+
 					// Height checks
 					if (rover->flags & FF_SHATTERBOTTOM)
 					{
@@ -1859,10 +1882,13 @@ static void P_CheckBustableBlocks(player_t *player)
 		}
 	}
 bustupdone:
-	P_UnsetThingPosition(player->mo);
-	player->mo->x = oldx;
-	player->mo->y = oldy;
-	P_SetThingPosition(player->mo);
+	if (!(player->pflags & PF_BOUNCING))
+	{
+		P_UnsetThingPosition(player->mo);
+		player->mo->x = oldx;
+		player->mo->y = oldy;
+		P_SetThingPosition(player->mo);
+	}
 }
 
 static void P_CheckBouncySectors(player_t *player)
@@ -6735,7 +6761,7 @@ static void P_MovePlayer(player_t *player)
 	// Bouncing...
 	if (player->pflags & PF_BOUNCING)
 	{
-		if (!(player->pflags & PF_JUMPDOWN)) // If not holding the jump button
+		if (!(player->pflags & PF_JUMPDOWN) || onground) // If not holding the jump button
 		{
 			P_ResetPlayer(player); // down, stop bouncing.
 			if (onground)

From b84ad05061951e729bd09cd34c7ce7ddc6425fc1 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 24 Dec 2016 00:11:54 +0000
Subject: [PATCH 004/119] Okay, failed experiment fixed. (This has nothing to
 do with the ability to pierce bustable FOFs, I just modified it for kicks to
 see what would happen.)

---
 src/p_map.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index 943f20313..a490c7f3b 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1533,8 +1533,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				+ ((topheight - bottomheight)/2));
 
 			if (topheight > tmfloorz && abs(delta1) < abs(delta2)
-				&& !(rover->flags & FF_REVERSEPLATFORM)
-				&& ((P_MobjFlip(tmthing)*tmthing->momz >= 0) || (!(rover->flags & FF_PLATFORM))))
+				&& !(rover->flags & FF_REVERSEPLATFORM))
 			{
 				tmfloorz = tmdropoffz = topheight;
 #ifdef ESLOPE
@@ -1543,7 +1542,6 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 			}
 			if (bottomheight < tmceilingz && abs(delta1) >= abs(delta2)
 				&& !(rover->flags & FF_PLATFORM)
-				&& ((P_MobjFlip(tmthing)*tmthing->momz >= 0) || (!(rover->flags & FF_REVERSEPLATFORM)))
 				&& !(thing->type == MT_SKIM && (rover->flags & FF_SWIMMABLE)))
 			{
 				tmceilingz = tmdrpoffceilz = bottomheight;

From 942065ba9f0b6ca469ab305482362e9ac48eafc4 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 24 Dec 2016 00:11:54 +0000
Subject: [PATCH 005/119] * CA_BOUNCE users now play animation/sound when
 bouncing on enemies, monitors. * CA_BOUNCE users harmlessly bounce off
 Sharps.

---
 src/p_inter.c | 13 +++++++++++--
 src/p_local.h |  1 +
 src/p_map.c   |  4 ++++
 src/p_mobj.c  | 13 ++-----------
 src/p_user.c  | 26 ++++++++++++++++++++++++++
 5 files changed, 44 insertions(+), 13 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 16f128da9..dc2f973b2 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -382,6 +382,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				else
 					toucher->momz = -toucher->momz;
 			}
+			if (player->pflags & PF_BOUNCING)
+				P_DoAbilityBounce(player, false);
 			toucher->momx = -toucher->momx;
 			toucher->momy = -toucher->momy;
 			P_DamageMobj(special, toucher, toucher, 1, 0);
@@ -416,8 +418,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		else if (special->type == MT_SHARP
 		&& ((special->state == &states[special->info->xdeathstate]) || (toucher->z > special->z + special->height/2)))
 		{
-			// Cannot hit sharp from above or when red and angry
-			P_DamageMobj(toucher, special, special, 1, 0);
+			if (player->pflags & PF_BOUNCING)
+			{
+				toucher->momz = -toucher->momz;
+				P_DoAbilityBounce(player, false);
+			}
+			else // Cannot hit sharp from above or when red and angry
+				P_DamageMobj(toucher, special, special, 1, 0);
 		}
 		else if (((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 		|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
@@ -433,6 +440,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				else
 					toucher->momz = -toucher->momz;
 			}
+			if (player->pflags & PF_BOUNCING)
+				P_DoAbilityBounce(player, false);
 
 			P_DamageMobj(special, toucher, toucher, 1, 0);
 		}
diff --git a/src/p_local.h b/src/p_local.h
index df9b20a54..ea97acb89 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -155,6 +155,7 @@ boolean P_AutoPause(void);
 
 void P_DoJumpShield(player_t *player);
 void P_DoBubbleBounce(player_t *player);
+void P_DoAbilityBounce(player_t *player, boolean changemomz);
 void P_BlackOw(player_t *player);
 void P_ElementalFire(player_t *player, boolean cropcircle);
 
diff --git a/src/p_map.c b/src/p_map.c
index a490c7f3b..a80e906ef 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1084,7 +1084,11 @@ static boolean PIT_CheckThing(mobj_t *thing)
 						*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
 				}
 				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
+				{
+					if (player->pflags & PF_BOUNCING)
+						P_DoAbilityBounce(player);
 					return false;
+				}
 				else
 					*z -= *momz; // to ensure proper collision.
 			}
diff --git a/src/p_mobj.c b/src/p_mobj.c
index a6e012c73..c9225c081 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3318,17 +3318,8 @@ static void P_PlayerZMovement(mobj_t *mo)
 
 					if (mo->player->pflags & PF_BOUNCING && !P_CheckDeathPitCollide(mo))
 					{
-						fixed_t prevmomz = P_MobjFlip(mo)*abs(mo->momz);
-						if (mo->eflags & MFE_UNDERWATER)
-						{
-							prevmomz /= 2;
-						}
-						S_StartSound(mo, sfx_boingf);
-						P_DoJump(mo->player, false);
-						P_SetPlayerMobjState(mo, S_PLAY_BOUNCE_LANDING);
-						mo->player->pflags |= PF_BOUNCING|PF_THOKKED;
-						mo->player->jumping = 0;
-						mo->momz = (FixedMul(mo->momz, 3*FRACUNIT/2) + prevmomz)/2;
+						mo->momz *= -1;
+						P_DoAbilityBounce(mo->player, true);
 						clipmomz = false;
 					}
 				}
diff --git a/src/p_user.c b/src/p_user.c
index a71491c7d..cdd87af3e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4005,6 +4005,32 @@ void P_DoBubbleBounce(player_t *player)
 	player->mo->momz = FixedMul(player->mo->momz, 5*FRACUNIT/4);
 }
 
+//
+// P_DoAbilityBounce
+//
+// CA_BOUNCE landing handling
+//
+void P_DoAbilityBounce(player_t *player, boolean changemomz)
+{
+	fixed_t prevmomz;
+	if (player->mo->state-states == S_PLAY_BOUNCE_LANDING)
+		return;
+	if (changemomz)
+	{
+		prevmomz = P_MobjFlip(player->mo)*player->mo->momz;
+		if (prevmomz < 0)
+			prevmomz = 0;
+		else if (player->mo->eflags & MFE_UNDERWATER)
+			prevmomz /= 2;
+		P_DoJump(player, false);
+		player->jumping = 0;
+		player->mo->momz = (FixedMul(player->mo->momz, 3*FRACUNIT/2) + prevmomz)/2;
+	}
+	S_StartSound(player->mo, sfx_boingf);
+	P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE_LANDING);
+	player->pflags |= PF_BOUNCING|PF_THOKKED;
+}
+
 //
 // P_Telekinesis
 //

From f60233027d71827696b1cb905e4377a3a42ad169 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 24 Dec 2016 00:11:54 +0000
Subject: [PATCH 006/119] On Rob's suggestion: Allow your controls to turn
 CA_BOUNCE users to turn on a dime when coiling for the next bounce.

---
 src/p_mobj.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index c9225c081..d2268e2f6 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -4144,7 +4144,15 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN;
 
 	if (mobj->state-states == S_PLAY_BOUNCE_LANDING)
+	{
+		angle_t dashangle = mobj->angle;
+		if (mobj->player->cmd.forwardmove || mobj->player->cmd.sidemove)
+		{
+			dashangle += R_PointToAngle2(0, 0, mobj->player->cmd.forwardmove<<FRACBITS, -mobj->player->cmd.sidemove<<FRACBITS);
+		}
+		P_InstaThrust(mobj, dashangle, mobj->player->speed);
 		goto animonly; // no need for checkposition - doesn't move at ALL
+	}
 
 	// Zoom tube
 	if (mobj->tracer)

From 102f26182b2d9cb9c5d2fbdd5acdedc3d8d2a351 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 24 Dec 2016 00:11:54 +0000
Subject: [PATCH 007/119] * Tweaks to CA_BOUNCE. * Tweaks to drowning number
 placement for altered sizes.

---
 src/p_mobj.c | 9 ++++-----
 src/p_user.c | 6 +++---
 2 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index d2268e2f6..ca5cc3126 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -4145,12 +4145,11 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 
 	if (mobj->state-states == S_PLAY_BOUNCE_LANDING)
 	{
-		angle_t dashangle = mobj->angle;
 		if (mobj->player->cmd.forwardmove || mobj->player->cmd.sidemove)
-		{
-			dashangle += R_PointToAngle2(0, 0, mobj->player->cmd.forwardmove<<FRACBITS, -mobj->player->cmd.sidemove<<FRACBITS);
-		}
-		P_InstaThrust(mobj, dashangle, mobj->player->speed);
+			P_InstaThrust(
+			mobj,
+			R_PointToAngle(mobj->x, mobj->y) + R_PointToAngle2(0, 0, mobj->player->cmd.forwardmove<<FRACBITS, -mobj->player->cmd.sidemove<<FRACBITS),
+			mobj->player->speed);
 		goto animonly; // no need for checkposition - doesn't move at ALL
 	}
 
diff --git a/src/p_user.c b/src/p_user.c
index cdd87af3e..f9f300d97 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2111,8 +2111,8 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 	 || (timeleft ==  1*TICRATE + 1) // 0
 	) {
 		fixed_t height = (player->mo->eflags & MFE_VERTICALFLIP)
-		? player->mo->z - FixedMul(8*FRACUNIT - mobjinfo[MT_DROWNNUMBERS].height, player->mo->scale)
-		: player->mo->z + player->mo->height + FixedMul(8*FRACUNIT, player->mo->scale);
+		? player->mo->z - FixedMul(8*FRACUNIT + mobjinfo[MT_DROWNNUMBERS].height, FixedMul(player->mo->scale, player->shieldscale))
+		: player->mo->z + player->mo->height + FixedMul(8*FRACUNIT, FixedMul(player->mo->scale, player->shieldscale));
 
 		mobj_t *numbermobj = P_SpawnMobj(player->mo->x, player->mo->y, height, MT_DROWNNUMBERS);
 
@@ -6787,7 +6787,7 @@ static void P_MovePlayer(player_t *player)
 	// Bouncing...
 	if (player->pflags & PF_BOUNCING)
 	{
-		if (!(player->pflags & PF_JUMPDOWN) || onground) // If not holding the jump button
+		if (!(player->pflags & PF_JUMPDOWN) || (onground && P_MobjFlip(player->mo)*player->mo->momz <= 0)) // If not holding the jump button OR on flat ground
 		{
 			P_ResetPlayer(player); // down, stop bouncing.
 			if (onground)

From 578b8095572ceefcf9f626e32f15e4b7d00a34de Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 24 Dec 2016 00:11:54 +0000
Subject: [PATCH 008/119] * Turned CA_DASHMODE into SF_DASHMODE. * SF_DASHMODE
 users can now dash on water. * Gave CA_HOVER its own states/sprite2s. * Gave
 Super Sonic's hover his own hover-run state/sprite2.

---
 src/d_player.h |   2 +-
 src/dehacked.c |   7 +++-
 src/info.c     |  11 ++++-
 src/info.h     |  13 +++++-
 src/p_map.c    |   6 +--
 src/p_mobj.c   |  43 ++++++++++++++-----
 src/p_user.c   | 109 +++++++++++++++++++++++--------------------------
 7 files changed, 115 insertions(+), 76 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index 229ab6570..9c14a5e37 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -45,6 +45,7 @@ typedef enum
 	SF_MARIODAMAGE      = SF_NOJUMPDAMAGE|SF_STOMPDAMAGE, // The Mario method of being able to damage enemies, etc.
 	SF_MACHINE          = 1<<10, // Beep boop. Are you a robot?
 	SF_NOSPINDASHDUST   = 1<<11, // Don't spawn dust particles when charging a spindash
+	SF_DASHMODE         = 1<<12, // Sonic Advance 2 style top speed increase?
 	// free up to and including 1<<31
 } skinflags_t;
 
@@ -65,7 +66,6 @@ typedef enum
 	CA_JUMPBOOST,
 	CA_AIRDRILL,
 	CA_JUMPTHOK,
-	CA_DASHMODE,
 	CA_BOUNCE,
 	CA_TWINSPIN
 } charability_t;
diff --git a/src/dehacked.c b/src/dehacked.c
index 6427d0602..1f133c3d6 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3816,6 +3816,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_CLING",
 	"S_PLAY_CLIMB",
 
+	// CA_FLOAT/CA_SLOWFALL
+	"S_PLAY_FLOAT",
+	"S_PLAY_FLOAT_RUN",
+
 	// CA_BOUNCE
 	"S_PLAY_BOUNCE",
 	"S_PLAY_BOUNCE_LANDING",
@@ -3844,6 +3848,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_SUPER_EDGE",
 	"S_PLAY_SUPER_RIDE",
 	"S_PLAY_SUPER_FLOAT",
+	"S_PLAY_SUPER_FLOAT_RUN",
 
 	// SF_SUPER
 	"S_PLAY_SUPERTRANS1",
@@ -7194,6 +7199,7 @@ struct {
 	{"SF_MARIODAMAGE",SF_MARIODAMAGE},
 	{"SF_MACHINE",SF_MACHINE},
 	{"SF_NOSPINDASHDUST",SF_NOSPINDASHDUST},
+	{"SF_DASHMODE",SF_DASHMODE},
 
 	// Character abilities!
 	// Primary
@@ -7211,7 +7217,6 @@ struct {
 	{"CA_JUMPBOOST",CA_JUMPBOOST},
 	{"CA_AIRDRILL",CA_AIRDRILL},
 	{"CA_JUMPTHOK",CA_JUMPTHOK},
-	{"CA_DASHMODE",CA_DASHMODE},
 	{"CA_BOUNCE",CA_BOUNCE},
 	{"CA_TWINSPIN",CA_TWINSPIN},
 	// Secondary
diff --git a/src/info.c b/src/info.c
index 4edaa5737..68fd4b4b0 100644
--- a/src/info.c
+++ b/src/info.c
@@ -402,6 +402,9 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"CLNG",
 	"CLMB",
 
+	"FLT_",
+	"FRUN",
+
 	"BNCE",
 	"BLND",
 
@@ -426,6 +429,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"SEDG",
 	"SRID",
 	"SFLT",
+	"SFRN",
 
 	"NTRN",
 	"NSTD",
@@ -517,8 +521,12 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_CLNG|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},       // S_PLAY_CLING
 	{SPR_PLAY, SPR2_CLMB,                 5, {NULL}, 0,  0, S_PLAY_CLIMB}, // S_PLAY_CLIMB
 
+	// CA_FLOAT/CA_SLOWFALL
+	{SPR_PLAY, SPR2_FLT ,                 7, {NULL}, 0,  0, S_PLAY_FLOAT}, // S_PLAY_FLOAT
+	{SPR_PLAY, SPR2_FRUN,                 7, {NULL}, 0,  0, S_PLAY_FLOAT_RUN},  // S_PLAY_FLOAT_RUN
+
 	// CA_BOUNCE
-	{SPR_PLAY, SPR2_BNCE|FF_ANIMATE,     -1, {NULL}, 0,  0, S_NULL},                            // S_PLAY_BOUNCE
+	{SPR_PLAY, SPR2_BNCE|FF_ANIMATE,     -1, {NULL},             0,  0, S_NULL},                // S_PLAY_BOUNCE
 	{SPR_PLAY, SPR2_BLND|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_BOUNCE,  4, S_PLAY_BOUNCE_LANDING}, // S_PLAY_BOUNCE_LANDING
 
 	// CA_TWINSPIN
@@ -545,6 +553,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_SEDG|FF_ANIMATE,     -1, {NULL}, 0, 12, S_NULL},            // S_PLAY_SUPER_EDGE
 	{SPR_PLAY, SPR2_SRID,                 4, {NULL}, 0,  0, S_PLAY_SUPER_RIDE}, // S_PLAY_SUPER_RIDE
 	{SPR_PLAY, SPR2_SFLT,                 7, {NULL}, 0,  0, S_PLAY_SUPER_FLOAT}, // S_PLAY_SUPER_FLOAT
+	{SPR_PLAY, SPR2_SFRN,                 7, {NULL}, 0,  0, S_PLAY_SUPER_FLOAT_RUN},  // S_PLAY_SUPER_FLOAT_RUN
 
 	// SF_SUPER
 	{SPR_PLAY, SPR2_TRNS,                 4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS2}, // S_PLAY_SUPER_TRANS
diff --git a/src/info.h b/src/info.h
index 0202a71c4..f49dd599a 100644
--- a/src/info.h
+++ b/src/info.h
@@ -604,6 +604,9 @@ enum playersprite
 	SPR2_CLNG, // cling
 	SPR2_CLMB, // climb
 
+	SPR2_FLT , // float
+	SPR2_FRUN, // float run
+
 	SPR2_BNCE, // bounce
 	SPR2_BLND, // bounce landing
 
@@ -628,6 +631,7 @@ enum playersprite
 	SPR2_SEDG, // super edge
 	SPR2_SRID, // super ride
 	SPR2_SFLT, // super float
+	SPR2_SFRN, // super float run
 
 	SPR2_NTRN, // NiGHTS transformation
 	SPR2_NSTD, // NiGHTS stand
@@ -700,7 +704,7 @@ typedef enum state
 	S_PLAY_SPIN,
 	S_PLAY_DASH,
 	S_PLAY_GASP,
-	S_PLAY_JUMP, // spin jump
+	S_PLAY_JUMP,
 	S_PLAY_SPRING,
 	S_PLAY_FALL,
 	S_PLAY_EDGE,
@@ -716,6 +720,10 @@ typedef enum state
 	S_PLAY_CLING,
 	S_PLAY_CLIMB,
 
+	// CA_FLOAT/CA_SLOWFALL
+	S_PLAY_FLOAT,
+	S_PLAY_FLOAT_RUN,
+
 	// CA_BOUNCE
 	S_PLAY_BOUNCE,
 	S_PLAY_BOUNCE_LANDING,
@@ -738,12 +746,13 @@ typedef enum state
 	S_PLAY_SUPER_DRWN,
 	S_PLAY_SUPER_SPIN,
 	S_PLAY_SUPER_GASP,
-	S_PLAY_SUPER_JUMP, // see note above
+	S_PLAY_SUPER_JUMP,
 	S_PLAY_SUPER_SPRING,
 	S_PLAY_SUPER_FALL,
 	S_PLAY_SUPER_EDGE,
 	S_PLAY_SUPER_RIDE,
 	S_PLAY_SUPER_FLOAT,
+	S_PLAY_SUPER_FLOAT_RUN,
 
 	// SF_SUPER
 	S_PLAY_SUPER_TRANS,
diff --git a/src/p_map.c b/src/p_map.c
index a80e906ef..53c90e7b0 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -460,9 +460,9 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
-	// CA_DASHMODE users destroy spikes and monitors, CA_TWINSPIN users and CA2_MELEE users destroy spikes.
+	// SF_DASHMODE users destroy spikes and monitors, CA_TWINSPIN users and CA2_MELEE users destroy spikes.
 	if ((tmthing->player)
-		&& (((tmthing->player->charability == CA_DASHMODE) && (tmthing->player->dashmode >= 3*TICRATE)
+		&& (((tmthing->player->charflags & SF_DASHMODE) && (tmthing->player->dashmode >= 3*TICRATE)
 		&& (thing->flags & (MF_MONITOR) || thing->type == MT_SPIKE))
 	|| ((((tmthing->player->charability == CA_TWINSPIN) && (tmthing->player->panim == PA_ABILITY))
 	|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2))
@@ -1086,7 +1086,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
 				{
 					if (player->pflags & PF_BOUNCING)
-						P_DoAbilityBounce(player);
+						P_DoAbilityBounce(player, false);
 					return false;
 				}
 				else
diff --git a/src/p_mobj.c b/src/p_mobj.c
index ca5cc3126..0edb5f9b3 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -233,11 +233,11 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_FALL;
 			break;
 
-		case SPR2_FLY:
+		case SPR2_FLY :
 			spr2 = SPR2_SPNG;
 			break;
 		case SPR2_SWIM:
-			spr2 = SPR2_FLY;
+			spr2 = SPR2_FLY ;
 			break;
 		case SPR2_TIRE:
 			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
@@ -253,6 +253,13 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_CLMB;
 			break;
 
+		case SPR2_FLT :
+			spr2 = SPR2_WALK;
+			break;
+		case SPR2_FRUN:
+			spr2 = SPR2_RUN ;
+			break;
+
 		case SPR2_BNCE:
 			spr2 = SPR2_FALL;
 			break;
@@ -317,6 +324,9 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 		case SPR2_SFLT:
 			spr2 = SPR2_SWLK;
 			break;
+		case SPR2_SFRN:
+			spr2 = SPR2_SRUN;
+			break;
 
 		// NiGHTS sprites.
 		case SPR2_NTRN:
@@ -470,6 +480,10 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_FALL);
 		case S_PLAY_EDGE:
 			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_EDGE);
+		case S_PLAY_FLOAT:
+			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_FLOAT);
+		case S_PLAY_FLOAT_RUN:
+			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_FLOAT_RUN);
 		default:
 			break;
 		}
@@ -496,12 +510,15 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		player->panim = PA_EDGE;
 		break;
 	case S_PLAY_WALK:
+	case S_PLAY_FLOAT:
 	case S_PLAY_SUPER_WALK:
 	case S_PLAY_SUPER_FLOAT:
 		player->panim = PA_WALK;
 		break;
 	case S_PLAY_RUN:
+	case S_PLAY_FLOAT_RUN:
 	case S_PLAY_SUPER_RUN:
+	case S_PLAY_SUPER_FLOAT_RUN:
 		player->panim = PA_RUN;
 		break;
 	case S_PLAY_PEEL:
@@ -607,7 +624,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 					else
 						mobj->tics = 2;
 				}
-				else if (P_IsObjectOnGround(mobj) || player->powers[pw_super]) // Only if on the ground or superflying.
+				else if (P_IsObjectOnGround(mobj) || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]) // Only if on the ground or superflying.
 				{
 					if (player->panim == PA_WALK)
 					{
@@ -3250,22 +3267,26 @@ static void P_PlayerZMovement(mobj_t *mo)
 					{
 						if (mo->player->cmomx || mo->player->cmomy)
 						{
-							if (mo->player->charability == CA_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_PEEL)
+							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_PEEL)
 								P_SetPlayerMobjState(mo, S_PLAY_PEEL);
-							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale) && mo->player->panim != PA_RUN)
+							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
+							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->state-states == S_PLAY_SUPER_FLOAT_RUN))
 								P_SetPlayerMobjState(mo, S_PLAY_RUN);
-							else if ((mo->player->rmomx || mo->player->rmomy) && (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_SUPER_FLOAT))
+							else if ((mo->player->rmomx || mo->player->rmomy)
+							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->state-states == S_PLAY_SUPER_FLOAT))
 								P_SetPlayerMobjState(mo, S_PLAY_WALK);
 							else if (!mo->player->rmomx && !mo->player->rmomy && mo->player->panim != PA_IDLE)
 								P_SetPlayerMobjState(mo, S_PLAY_STND);
 						}
 						else
 						{
-							if (mo->player->charability == CA_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_PEEL)
+							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_PEEL)
 								P_SetPlayerMobjState(mo, S_PLAY_PEEL);
-							if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale) && mo->player->panim != PA_RUN)
+							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
+							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->state-states == S_PLAY_SUPER_FLOAT_RUN))
 								P_SetPlayerMobjState(mo, S_PLAY_RUN);
-							else if ((mo->momx || mo->momy) && (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_SUPER_FLOAT))
+							else if ((mo->momx || mo->momy)
+							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->state-states == S_PLAY_SUPER_FLOAT))
 								P_SetPlayerMobjState(mo, S_PLAY_WALK);
 							else if (!mo->momx && !mo->momy && mo->player->panim != PA_IDLE)
 								P_SetPlayerMobjState(mo, S_PLAY_STND);
@@ -3582,7 +3603,7 @@ static boolean P_SceneryZMovement(mobj_t *mo)
 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
 {
 	if (!(player->pflags & PF_NIGHTSMODE) && !player->homing
-		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER) && player->mo->ceilingz-*rover->topheight >= player->mo->height)
+		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= 3*TICRATE) && player->mo->ceilingz-*rover->topheight >= player->mo->height)
 		&& (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
 		&& !(player->pflags & PF_SLIDING)
 		&& abs(player->mo->z - *rover->topheight) < FixedMul(30*FRACUNIT, player->mo->scale))
@@ -4148,7 +4169,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 		if (mobj->player->cmd.forwardmove || mobj->player->cmd.sidemove)
 			P_InstaThrust(
 			mobj,
-			R_PointToAngle(mobj->x, mobj->y) + R_PointToAngle2(0, 0, mobj->player->cmd.forwardmove<<FRACBITS, -mobj->player->cmd.sidemove<<FRACBITS),
+			mobj->angle + R_PointToAngle2(0, 0, mobj->player->cmd.forwardmove<<FRACBITS, -mobj->player->cmd.sidemove<<FRACBITS),
 			mobj->player->speed);
 		goto animonly; // no need for checkposition - doesn't move at ALL
 	}
diff --git a/src/p_user.c b/src/p_user.c
index f9f300d97..5a8703ad9 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1778,7 +1778,7 @@ static void P_CheckBustableBlocks(player_t *player)
 					// if it's not an FF_SHATTER, you must be spinning (and not jumping)
 					// or be super
 					// or have CA_GLIDEANDCLIMB
-					// or be in dashmode with CA_DASHMODE
+					// or be in dashmode with SF_DASHMODE
 					// or be using CA_TWINSPIN
 					// or be using CA2_MELEE
 					// or are drilling in NiGHTS
@@ -1788,7 +1788,7 @@ static void P_CheckBustableBlocks(player_t *player)
 						&& !(player->powers[pw_super])
 						&& !(player->charability == CA_GLIDEANDCLIMB)
 						&& !(player->pflags & PF_BOUNCING)
-						&& !((player->charability == CA_DASHMODE) && (player->dashmode >= 3*TICRATE))
+						&& !((player->charflags & SF_DASHMODE) && (player->dashmode >= 3*TICRATE))
 						&& !((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
 						&& !(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
 						&& !(player->pflags & PF_DRILLING)
@@ -3673,18 +3673,10 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			player->mo->momz = 24*FRACUNIT;
 		else if (player->powers[pw_super])
 		{
-			if (player->charability == CA_FLOAT)
-				player->mo->momz = 28*FRACUNIT; //Obscene jump height anyone?
-			else if (player->charability == CA_SLOWFALL)
-				player->mo->momz = 37*(FRACUNIT/2); //Less obscene because during super, floating propells oneself upward.
-			else // Default super jump momentum.
-				player->mo->momz = 13*FRACUNIT;
+			player->mo->momz = 13*FRACUNIT;
 
 			// Add a boost for super characters with float/slowfall and multiability.
-			if (player->charability2 == CA2_MULTIABILITY &&
-				(player->charability == CA_FLOAT || player->charability == CA_SLOWFALL))
-				player->mo->momz += 2*FRACUNIT;
-			else if (player->charability == CA_JUMPBOOST)
+			if (player->charability == CA_JUMPBOOST)
 			{
 				if (player->charability2 == CA2_MULTIABILITY)
 					player->mo->momz += FixedMul(FRACUNIT/4, dist6);
@@ -3692,12 +3684,6 @@ void P_DoJump(player_t *player, boolean soundandstate)
 					player->mo->momz += FixedMul(FRACUNIT/8, dist6);
 			}
 		}
-		else if (player->charability2 == CA2_MULTIABILITY &&
-			(player->charability == CA_FLOAT || player->charability == CA_SLOWFALL))
-		{
-			// Multiability exceptions, since some abilities cannot effectively use it and need a boost.
-			player->mo->momz = 12*FRACUNIT; // Increased jump height due to ineffective repeat.
-		}
 		else
 		{
 			player->mo->momz = 39*(FRACUNIT/4); // Default jump momentum.
@@ -4205,7 +4191,6 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				case CA_THOK:
 				case CA_HOMINGTHOK:
 				case CA_JUMPTHOK: // Credit goes to CZ64 and Sryder13 for the original
-				case CA_DASHMODE: // Credit goes to Iceman404
 					// Now it's Sonic's abilities turn!
 					// THOK!
 					if (!(player->pflags & PF_THOKKED) || (player->charability2 == CA2_MULTIABILITY))
@@ -4213,7 +4198,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						// Catapult the player
 						fixed_t actionspd = player->actionspd;
 
-						if (player->charability == CA_DASHMODE)
+						if (player->charflags & SF_DASHMODE)
 							actionspd = max(player->normalspeed, FixedDiv(player->speed, player->mo->scale));
 
 						if (player->mo->eflags & MFE_UNDERWATER)
@@ -4290,12 +4275,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				case CA_DOUBLEJUMP: // Double-Jump
 					if (!(player->pflags & PF_THOKKED) || ((player->charability2 == CA2_MULTIABILITY) && (player->secondjump < (player->actionspd >> FRACBITS))))
 					{
-						// Allow infinite double jumping if super.
-						if (!player->powers[pw_super])
-							player->pflags |= PF_THOKKED;
-						else
-							player->secondjump = 0;
-
+						player->pflags |= PF_THOKKED;
 						player->pflags &= ~PF_JUMPED;
 						P_DoJump(player, true);
 						player->secondjump++;
@@ -4303,8 +4283,13 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					break;
 				case CA_FLOAT: // Float
 				case CA_SLOWFALL: // Slow descent hover
-					if (!player->secondjump)
+					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					{
+						P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
+						player->pflags |= PF_THOKKED;
+						player->pflags &= ~(PF_JUMPED|PF_SPINNING);
 						player->secondjump = 1;
+					}
 					break;
 				case CA_TELEKINESIS:
 					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
@@ -4400,12 +4385,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					player->mo->momz = 0;
 				else if (player->charability == CA_SLOWFALL)
 				{
-					if (player->powers[pw_super])
-					{
-						if (P_MobjFlip(player->mo)*player->mo->momz < gravity*16)
-						player->mo->momz = P_MobjFlip(player->mo)*gravity*16; //Float upward 4x as fast while super.
-					}
-					else if (P_MobjFlip(player->mo)*player->mo->momz < -gravity*4)
+					if (P_MobjFlip(player->mo)*player->mo->momz < -gravity*4)
 						player->mo->momz = P_MobjFlip(player->mo)*-gravity*4;
 				}
 				player->pflags &= ~PF_SPINNING;
@@ -4417,11 +4397,20 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		player->pflags &= ~PF_JUMPDOWN;
 
 		// Repeat abilities, but not double jump!
-		if (player->charability2 == CA2_MULTIABILITY && player->charability != CA_DOUBLEJUMP)
-			player->secondjump = 0;
-		else if (player->charability == CA_FLOAT && player->secondjump == 1)
-			player->secondjump = 2;
-
+		if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP)
+		{
+			if (player->charability2 == CA2_MULTIABILITY)
+			{
+				player->pflags |= PF_JUMPED;
+				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+				player->secondjump = 0;
+			}
+			else
+			{
+				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+				player->secondjump = 2;
+			}
+		}
 
 		// If letting go of the jump button while still on ascent, cut the jump height.
 		if (player->pflags & PF_JUMPED && P_MobjFlip(player->mo)*player->mo->momz > 0 && player->jumping == 1)
@@ -6696,16 +6685,22 @@ static void P_MovePlayer(player_t *player)
 	if ((cmd->forwardmove != 0 || cmd->sidemove != 0) || (player->powers[pw_super] && !onground))
 	{
 		// If the player is in dashmode, here's their peelout.
-		if (player->charability == CA_DASHMODE && player->dashmode >= 3*TICRATE && player->panim == PA_RUN && !player->skidtime && (onground || player->powers[pw_super]))
+		if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim == PA_RUN && !player->skidtime && (onground || player->powers[pw_super]))
 			P_SetPlayerMobjState (player->mo, S_PLAY_PEEL);
 		// If the player is moving fast enough,
 		// break into a run!
-		else if (player->speed >= runspd && player->panim == PA_WALK && !player->skidtime && (onground || player->powers[pw_super]))
-			P_SetPlayerMobjState (player->mo, S_PLAY_RUN);
+		else if (player->speed >= runspd && player->panim == PA_WALK && !player->skidtime
+		&& (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
+		{
+			if (!onground)
+				P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
+			else
+				P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+		}
 
-		// Super floating at slow speeds has its own special animation.
-		else if (player->powers[pw_super] && player->panim == PA_IDLE && !onground)
-			P_SetPlayerMobjState (player->mo, S_PLAY_SUPER_FLOAT);
+		// Floating at slow speeds has its own special animation.
+		else if ((((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]) && player->panim == PA_IDLE && !onground)
+			P_SetPlayerMobjState (player->mo, S_PLAY_FLOAT);
 
 		// Otherwise, just walk.
 		else if ((player->rmomx || player->rmomy) && player->panim == PA_IDLE)
@@ -6714,15 +6709,15 @@ static void P_MovePlayer(player_t *player)
 
 	// If your peelout animation is playing, and you're
 	// going too slow, switch back to the run.
-	if (player->charability == CA_DASHMODE && player->panim == PA_PEEL && player->dashmode < 3*TICRATE)
+	if (player->charflags & SF_DASHMODE && player->panim == PA_PEEL && player->dashmode < 3*TICRATE)
 		P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
 
 	// If your running animation is playing, and you're
 	// going too slow, switch back to the walking frames.
 	if (player->panim == PA_RUN && player->speed < runspd)
 	{
-		if (!onground && player->powers[pw_super])
-			P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_FLOAT);
+		if (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super])
+			P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
 		else
 			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 	}
@@ -6790,6 +6785,7 @@ static void P_MovePlayer(player_t *player)
 		if (!(player->pflags & PF_JUMPDOWN) || (onground && P_MobjFlip(player->mo)*player->mo->momz <= 0)) // If not holding the jump button OR on flat ground
 		{
 			P_ResetPlayer(player); // down, stop bouncing.
+			player->pflags |= PF_THOKKED;
 			if (onground)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 			else if (player->charability2 == CA2_MULTIABILITY)
@@ -9432,12 +9428,13 @@ void P_PlayerThink(player_t *player)
 	player->pflags &= ~PF_SLIDING;
 
 #define dashmode player->dashmode
-	// Dash mode ability
-	if ((player->charability == CA_DASHMODE) && !(player->gotflag) && !(maptol & TOL_NIGHTS)) // woo, dashmode! no nights tho.
+	// Dash mode - thanks be to Iceman404
+	if ((player->charflags & SF_DASHMODE) && !(player->gotflag) && !(maptol & TOL_NIGHTS)) // woo, dashmode! no nights tho.
 	{
-		if (player->speed >= FixedMul(player->runspeed, player->mo->scale) || (player->pflags & PF_STARTDASH))
+		if (player->secondjump != 1 && (player->speed >= FixedMul(player->runspeed, player->mo->scale) || (player->pflags & PF_STARTDASH)))
 		{
-			dashmode++; // Counter. Adds 1 to dash mode per tic in top speed.
+			if (dashmode < 3*TICRATE + 3)
+				dashmode++; // Counter. Adds 1 to dash mode per tic in top speed.
 			if (dashmode == 3*TICRATE) // This isn't in the ">=" equation because it'd cause the sound to play infinitely.
 				S_StartSound(player->mo, sfx_s3ka2); // If the player enters dashmode, play this sound on the the tic it starts.
 		}
@@ -9456,16 +9453,14 @@ void P_PlayerThink(player_t *player)
 		}
 		else if (P_IsObjectOnGround(player->mo)) // Activate dash mode if we're on the ground.
 		{
-			if (player->normalspeed < skins[player->skin].actionspd) // If the player normalspeed is not currently at actionspd in dash mode, add speed each tic
-				player->normalspeed = player->normalspeed + 1*FRACUNIT/5; // Enter Dash Mode smoothly.
+			if (player->normalspeed < skins[player->skin].normalspeed*2) // If the player normalspeed is not currently at normalspeed*2 in dash mode, add speed each tic
+				player->normalspeed += FRACUNIT/5; // Enter Dash Mode smoothly.
 
 			if (player->jumpfactor < FixedMul(skins[player->skin].jumpfactor, 5*FRACUNIT/4)) // Boost jump height.
-				player->jumpfactor = player->jumpfactor + 1*FRACUNIT/300;
+				player->jumpfactor += FRACUNIT/300;
 		}
 
-		dashmode = min(dashmode, 3*TICRATE + 3);
-
-		if (player->normalspeed >= skins[player->skin].actionspd)
+		if (player->normalspeed >= skins[player->skin].normalspeed*2)
 		{
 			mobj_t *ghost = P_SpawnGhostMobj(player->mo); // Spawns afterimages
 			ghost->fuse = 2; // Makes the images fade quickly

From 5164ee7fc98fca43e98a57191e7d5f8856a13ad8 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 24 Dec 2016 00:11:54 +0000
Subject: [PATCH 009/119] Fun gameplay tweak: Turn CA_FLOAT into a limited
 CA_SLOWFALL when moving slowly, to double down on it being for speedrun
 characters and punishing slowing down. Rob-approved.

---
 src/p_user.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 5a8703ad9..03ea99158 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4381,13 +4381,15 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		{
 			if (player->secondjump == 1)
 			{
-				if (player->charability == CA_FLOAT)
-					player->mo->momz = 0;
-				else if (player->charability == CA_SLOWFALL)
-				{
-					if (P_MobjFlip(player->mo)*player->mo->momz < -gravity*4)
-						player->mo->momz = P_MobjFlip(player->mo)*-gravity*4;
-				}
+				fixed_t potentialmomz;
+				if (player->charability == CA_SLOWFALL)
+					potentialmomz = -gravity*4;
+				else
+					potentialmomz = ((player->speed < 10*player->mo->scale)
+					? (player->speed - 10*player->mo->scale)/5
+					: 0);
+				if (P_MobjFlip(player->mo)*player->mo->momz < potentialmomz)
+					player->mo->momz = P_MobjFlip(player->mo)*potentialmomz;
 				player->pflags &= ~PF_SPINNING;
 			}
 		}

From 877065250efaaa47c3412dc25a62f650d0e8e76f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 25 Dec 2016 19:56:33 +0000
Subject: [PATCH 010/119] Some NiGHTS change oversights I forgot to correct
 before.

---
 src/d_clisrv.c |  5 -----
 src/g_game.c   | 12 +++---------
 2 files changed, 3 insertions(+), 14 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 7ea95cfb8..e8e266019 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2200,12 +2200,7 @@ static void ResetNode(INT32 node);
 void CL_ClearPlayer(INT32 playernum)
 {
 	if (players[playernum].mo)
-	{
-		// Don't leave a NiGHTS ghost!
-		if ((players[playernum].pflags & PF_NIGHTSMODE) && players[playernum].mo->tracer)
-			P_RemoveMobj(players[playernum].mo->tracer);
 		P_RemoveMobj(players[playernum].mo);
-	}
 	players[playernum].mo = NULL;
 	memset(&players[playernum], 0, sizeof (player_t));
 }
diff --git a/src/g_game.c b/src/g_game.c
index c5488e0b7..ca8f6f02a 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3920,12 +3920,8 @@ void G_WriteGhostTic(mobj_t *ghost)
 	if (!(demoflags & DF_GHOST))
 		return; // No ghost data to write.
 
-	if (ghost->player && ghost->player->pflags & PF_NIGHTSMODE && ghost->tracer)
-	{
-		// We're talking about the NiGHTS thing, not the normal platforming thing!
+	if (ghost->player && ghost->player->pflags & PF_NIGHTSMODE) // We're talking about the NiGHTS thing, not the normal platforming thing!
 		ziptic |= GZT_NIGHTS;
-		ghost = ghost->tracer;
-	}
 
 	ziptic_p = demo_p++; // the ziptic, written at the end of this function
 
@@ -4107,11 +4103,9 @@ void G_ConsGhostTic(void)
 		demo_p++;
 	if (ziptic & GZT_SPR2)
 		demo_p++;
-	if(ziptic & GZT_NIGHTS) {
-		if (!testmo->player || !(testmo->player->pflags & PF_NIGHTSMODE) || !testmo->tracer)
+	if (ziptic & GZT_NIGHTS) {
+		if (!testmo->player || !(testmo->player->pflags & PF_NIGHTSMODE))
 			nightsfail = true;
-		else
-			testmo = testmo->tracer;
 	}
 
 	if (ziptic & GZT_EXTRA)

From 90758b47ec2a779ece8d7f6f3776689b8f542067 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 25 Dec 2016 22:20:20 +0000
Subject: [PATCH 011/119] * Limit spawning of endsign head to if a sprite for
 it exists. * Rearrange colouropposite so lavender's opposite is crimson,
 considering gamegear.

---
 src/p_enemy.c | 15 +++++++++------
 src/r_draw.c  |  8 ++++----
 2 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/src/p_enemy.c b/src/p_enemy.c
index 76a86d8b9..c86b96f46 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -4149,12 +4149,15 @@ void A_SignPlayer(mobj_t *actor)
 		actor->frame += Color_Opposite[actor->target->player->skincolor*2+1];
 	}
 
-	// spawn an overlay of the player's face.
-	ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
-	P_SetTarget(&ov->target, actor);
-	ov->color = actor->target->player->skincolor;
-	ov->skin = skin;
-	P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
+	if (skin->sprites[SPR2_SIGN].numframes)
+	{
+		// spawn an overlay of the player's face.
+		ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
+		P_SetTarget(&ov->target, actor);
+		ov->color = actor->target->player->skincolor;
+		ov->skin = skin;
+		P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
+	}
 }
 
 // Function: A_OverlayThink
diff --git a/src/r_draw.c b/src/r_draw.c
index 2c1f18826..9cd0d2944 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -194,10 +194,10 @@ const UINT8 Color_Opposite[MAXSKINCOLORS*2] =
 	SKINCOLOR_BROWN,8,   	// SKINCOLOR_PEACH - ditto
 	SKINCOLOR_PEACH,8,   	// SKINCOLOR_BROWN - ditto
 	SKINCOLOR_GREEN,5,  	// SKINCOLOR_RED
-	SKINCOLOR_CYAN,8,   	// SKINCOLOR_CRIMSON - ditto
+	SKINCOLOR_LAVENDER,8,   // SKINCOLOR_CRIMSON - ditto
 	SKINCOLOR_BLUE,12,  	// SKINCOLOR_ORANGE
 	SKINCOLOR_TAN,8,   		// SKINCOLOR_RUST - ditto
-	SKINCOLOR_LAVENDER,8,   // SKINCOLOR_GOLD - ditto
+	SKINCOLOR_CYAN,8,       // SKINCOLOR_GOLD - ditto
 	SKINCOLOR_TEAL,8,   	// SKINCOLOR_YELLOW - ditto
 	SKINCOLOR_RUST,8,   	// SKINCOLOR_TAN - ditto
 	SKINCOLOR_MAGENTA,3, 	// SKINCOLOR_MOSS
@@ -206,12 +206,12 @@ const UINT8 Color_Opposite[MAXSKINCOLORS*2] =
 	SKINCOLOR_PASTEL,8,   	// SKINCOLOR_EMERALD - ditto
 	SKINCOLOR_ROSY,8,   	// SKINCOLOR_AQUA - ditto
 	SKINCOLOR_YELLOW,8,   	// SKINCOLOR_TEAL - ditto
-	SKINCOLOR_CRIMSON,8,   	// SKINCOLOR_CYAN - ditto
+	SKINCOLOR_GOLD,8,   	// SKINCOLOR_CYAN - ditto
 	SKINCOLOR_ORANGE,9, 	// SKINCOLOR_BLUE
 	SKINCOLOR_PINK,8,   	// SKINCOLOR_AZURE - ditto
 	SKINCOLOR_EMERALD,8,   	// SKINCOLOR_PASTEL - ditto
 	SKINCOLOR_PERIDOT,10,   // SKINCOLOR_PURPLE - ditto
-	SKINCOLOR_GOLD,8,   	// SKINCOLOR_LAVENDER - ditto
+	SKINCOLOR_CRIMSON,12,   // SKINCOLOR_LAVENDER - ditto
 	SKINCOLOR_MOSS,8,   	// SKINCOLOR_MAGENTA - ditto
 	SKINCOLOR_AZURE,8,   	// SKINCOLOR_PINK - ditto
 	SKINCOLOR_AQUA,14   	// SKINCOLOR_ROSY - ditto

From 2ba3afaeda903c9774a42616daa4c98667635992 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 26 Dec 2016 13:02:26 +0000
Subject: [PATCH 012/119] Implemented unique attract shield sound (patch.dta).

---
 src/info.c    | 2 +-
 src/s_sound.c | 1 +
 src/sounds.c  | 1 +
 src/sounds.h  | 1 +
 4 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/info.c b/src/info.c
index 68fd4b4b0..c0f205516 100644
--- a/src/info.c
+++ b/src/info.c
@@ -7033,7 +7033,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_ATTRACT_ICON1, // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_s3k41,      // seesound
+		sfx_attrsg,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
diff --git a/src/s_sound.c b/src/s_sound.c
index 971961897..d3189d7b4 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -611,6 +611,7 @@ void S_StartSound(const void *origin, sfxenum_t sfx_id)
 			case sfx_forcsg:
 			case sfx_elemsg:
 			case sfx_armasg:
+			case sfx_attrsg:
 			case sfx_s3k3e:
 			case sfx_s3k3f:
 			case sfx_s3k41:
diff --git a/src/sounds.c b/src/sounds.c
index b551b73b5..b2758923d 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -170,6 +170,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"forcsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Force GET!
   {"elemsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Elemental GET!
   {"armasg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Armaggeddon GET!
+  {"attrsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Attract GET!
   {"shldls", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // You LOSE!
   {"spdpad", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"spkdth", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
diff --git a/src/sounds.h b/src/sounds.h
index 42fa4c308..42eeee31f 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -233,6 +233,7 @@ typedef enum
 	sfx_forcsg,
 	sfx_elemsg,
 	sfx_armasg,
+	sfx_attrsg,
 	sfx_shldls,
 	sfx_spdpad,
 	sfx_spkdth,

From 4a53d96099e34c9960b796fd91464a1ae95090fa Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 26 Dec 2016 18:36:57 +0000
Subject: [PATCH 013/119] * Fixed changing skins during differing-length sprite
 animations resulting in sprite errors. * Added PF_BOUNCING to a place where
 it would likely be wanted.

---
 src/b_bot.c    | 2 +-
 src/r_things.c | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/b_bot.c b/src/b_bot.c
index 0f2c80d55..9565b0e09 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -212,7 +212,7 @@ boolean B_CheckRespawn(player_t *player)
 
 	// Check if Sonic is busy first.
 	// If he's doing any of these things, he probably doesn't want to see us.
-	if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_NIGHTSMODE)
+	if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_BOUNCING|PF_NIGHTSMODE)
 	|| (sonic->player->panim != PA_IDLE && sonic->player->panim != PA_WALK)
 	|| (sonic->player->powers[pw_carry]))
 		return false;
diff --git a/src/r_things.c b/src/r_things.c
index fcc4d3103..d5e368959 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2627,16 +2627,16 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 				player->mo->color = newcolor;
 			P_SetScale(player->mo, player->mo->scale);
 			player->mo->radius = FixedMul(skin->radius, player->mo->scale);
+
+			P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames
 		}
 		return;
 	}
-	else if (skinnum >= 0 && skinnum < numskins)
-		skinnum = 255; // Cheeky emulation.
 
 	if (P_IsLocalPlayer(player))
-		CONS_Alert(CONS_WARNING, M_GetText("Skin %d not found\n"), skinnum);
+		CONS_Alert(CONS_WARNING, M_GetText("Requested skin not found\n"));
 	else if(server || adminplayer == consoleplayer)
-		CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum);
+		CONS_Alert(CONS_WARNING, "Player %d (%s) skin not found\n", playernum, player_names[playernum]);
 	SetPlayerSkinByNum(playernum, 0); // not found put the sonic skin
 }
 

From d2d88a919b9aa29a16d11b125febe0c0de199a35 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 26 Jan 2017 12:47:47 +0000
Subject: [PATCH 014/119] * Allowing for changing skins on command line startup
 again. * Fixing an inconsistency with being able to change skin colours when
 you shouldn't be able to, much like the previous skin change issue that was
 fixed.

---
 src/d_netcmd.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index f61c80cb2..8f6c45b3e 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -4102,7 +4102,8 @@ static void Skin_OnChange(void)
 	if (!Playing())
 		return; // do whatever you want
 
-	if (!(cv_debug || devparm) && !(multiplayer || netgame)) // In single player.
+	if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
+		&& (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y
 	{
 		CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
 		return;
@@ -4145,8 +4146,7 @@ static void Color_OnChange(void)
 	if (!Playing())
 		return; // do whatever you want
 
-	if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
-		&& (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CONTINUING))
+	if (!(cv_debug || devparm) && !(multiplayer || netgame)) // In single player.
 	{
 		CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
 		return;

From 4a68f191c8d4e1197b30dbcf2060d10e97987c8f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 26 Jan 2017 19:14:52 +0000
Subject: [PATCH 015/119] Mostly-complete structural work, very much incomplete
 rendering work.

---
 src/dehacked.c |   6 +
 src/doomstat.h |   2 +
 src/m_menu.c   | 300 +++++++++++++++++++++++++++++++++++++++++++++++--
 src/m_menu.h   |  17 +++
 src/p_setup.c  |   2 +
 5 files changed, 320 insertions(+), 7 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index c71c55ac1..e5d08fdb0 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1211,6 +1211,12 @@ static void readlevelheader(MYFILE *f, INT32 num)
 			{
 				deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
 					sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
+				strlcpy(mapheaderinfo[num-1]->selectheading, word2, sizeof(mapheaderinfo[num-1]->selectheading)); // not deh_ so only complains once
+			}
+			else if (fastcmp(word, "SELECTHEADING"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->selectheading, word2,
+					sizeof(mapheaderinfo[num-1]->selectheading), va("Level header %d: selectheading", num));
 			}
 			else if (fastcmp(word, "SCRIPTNAME"))
 			{
diff --git a/src/doomstat.h b/src/doomstat.h
index f1b7d2169..a3b95c95b 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -241,6 +241,8 @@ typedef struct
 	UINT8 levelflags;     ///< LF_flags:  merged eight booleans into one UINT8 for space, see below
 	UINT8 menuflags;      ///< LF2_flags: options that affect record attack / nights mode menus
 
+	char selectheading[22+5]; ///< Level select heading. Allows for controllable grouping.
+
 	// Freed animals stuff.
 	UINT8 numFlickies;     ///< Internal. For freed flicky support.
 	mobjtype_t *flickies;  ///< List of freeable flickies in this level. Allocated dynamically for space reasons. Be careful.
diff --git a/src/m_menu.c b/src/m_menu.c
index f682cd1b5..36e5c9e1a 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -53,6 +53,7 @@
 #include "byteptr.h"
 #include "st_stuff.h"
 #include "i_sound.h"
+#include "fastcmp.h"
 
 // Condition Sets
 #include "m_cond.h"
@@ -346,6 +347,7 @@ static void M_HandleLevelStats(INT32 choice);
 static void M_HandleConnectIP(INT32 choice);
 #endif
 static void M_HandleSetupMultiPlayer(INT32 choice);
+static void M_HandleNewLevelSelect(INT32 choice);
 #ifdef HWRENDER
 static void M_HandleFogColor(INT32 choice);
 #endif
@@ -642,9 +644,10 @@ static menuitem_t SR_MainMenu[] =
 
 static menuitem_t SR_LevelSelectMenu[] =
 {
-	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        60},
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleNewLevelSelect, '\0'},     // dummy menuitem for the control func
+/*	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        60},
 
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_LevelSelectWarp,     120},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_LevelSelectWarp,     120},*/
 };
 
 static menuitem_t SR_UnlockChecklistMenu[] =
@@ -3438,6 +3441,281 @@ static void M_PatchSkinNameTable(void)
 	return;
 }
 
+// Handle Level Select
+static levelselect_t levelselect = {0, NULL};
+static UINT8 levelselectselect[2];
+
+#define lsrow levelselectselect[0]
+#define lscol levelselectselect[1]
+
+//
+// M_CanShowLevelInNewList
+//
+// Determines whether to show a given map in the various level-select lists.
+// Set gt = -1 to ignore gametype.
+//
+boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
+{
+	// Does the map exist?
+	if (!mapheaderinfo[mapnum])
+		return false;
+
+	// Does the map have a name?
+	if (!mapheaderinfo[mapnum]->lvlttl[0])
+		return false;
+
+	switch (levellistmode)
+	{
+		case LLM_CREATESERVER:
+			// Should the map be hidden?
+			if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU)
+				return false;
+
+			/*if (M_MapLocked(mapnum+1))
+				return false; // not unlocked*/
+
+			if (gt == GT_COOP && (mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
+				return true;
+
+			if (gt == GT_COMPETITION && (mapheaderinfo[mapnum]->typeoflevel & TOL_COMPETITION))
+				return true;
+
+			if (gt == GT_CTF && (mapheaderinfo[mapnum]->typeoflevel & TOL_CTF))
+				return true;
+
+			if ((gt == GT_MATCH || gt == GT_TEAMMATCH) && (mapheaderinfo[mapnum]->typeoflevel & TOL_MATCH))
+				return true;
+
+			if ((gt == GT_TAG || gt == GT_HIDEANDSEEK) && (mapheaderinfo[mapnum]->typeoflevel & TOL_TAG))
+				return true;
+
+			if (gt == GT_RACE && (mapheaderinfo[mapnum]->typeoflevel & TOL_RACE))
+				return true;
+
+			return false;
+
+		case LLM_LEVELSELECT:
+			if (mapheaderinfo[mapnum]->levelselect != maplistoption)
+				return false;
+
+			/*if (M_MapLocked(mapnum+1))
+				return false; // not unlocked*/
+
+			return true;
+		case LLM_RECORDATTACK:
+			if (!(mapheaderinfo[mapnum]->menuflags & LF2_RECORDATTACK))
+				return false;
+
+			/*if (M_MapLocked(mapnum+1))
+				return false; // not unlocked*/
+
+			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
+				return true;
+
+			/*if (!mapvisited[mapnum])
+				return false;*/
+
+			return true;
+		case LLM_NIGHTSATTACK:
+			if (!(mapheaderinfo[mapnum]->menuflags & LF2_NIGHTSATTACK))
+				return false;
+
+			/*if (M_MapLocked(mapnum+1))
+				return false; // not unlocked*/
+
+			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
+				return true;
+
+			/*if (!mapvisited[mapnum])
+				return false;*/
+
+			return true;
+	}
+
+	// Hmm? Couldn't decide?
+	return false;
+}
+
+/*static INT32 M_CountLevelsToShowInNewList(INT32 gt)
+{
+	INT32 mapnum, count = 0;
+
+	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+		if (M_CanShowLevelInList(mapnum, gt))
+			count++;
+
+	return count;
+}*/
+
+static INT32 M_CountRowsToShowInNewList(INT32 gt)
+{
+	INT32 mapnum, prevmapnum, col = 0, rows = 0;
+
+	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+	{
+		if (M_CanShowLevelInNewList(mapnum, gt))
+		{
+			if (rows == 0)
+				rows++;
+			else
+			{
+				if (col == 2
+					|| !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading)))
+				{
+					col = 0;
+					rows++;
+				}
+				else
+					col++;
+			}
+			prevmapnum = mapnum;
+		}
+	}
+
+	return rows;
+}
+
+static boolean M_PrepareNewLevelSelect(INT32 gt)
+{
+	INT32 numrows = M_CountRowsToShowInNewList(gt);
+	INT32 mapnum, col = 0, row = 0;
+
+	if (!numrows)
+		return false;
+
+	if (levelselect.rows)
+		Z_Free(levelselect.rows);
+	levelselect.rows = NULL;
+
+	levelselect.numrows = numrows;
+	levelselect.rows = Z_Realloc(levelselect.rows, numrows*sizeof(levelselectrow_t), PU_STATIC, NULL);
+	if (!levelselect.rows)
+		I_Error("Insufficient memory to prepare level select platter");
+
+	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+	{
+		if (M_CanShowLevelInNewList(mapnum, gt))
+		{
+			const INT32 actnum = mapheaderinfo[mapnum]->actnum;
+
+			// preparing next position to drop mapnum into
+			if (levelselect.rows[0].maplist[0])
+			{
+				if (col == 2 // no more space on the row?
+					|| (levelselect.rows[row].maplist[0] && !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row].maplist[0]-1]->selectheading)))) // a new heading is starting?
+				{
+					col = 0;
+					row++;
+				}
+				else
+					col++;
+			}
+
+			levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
+			levelselect.rows[row].mapavailable[col] = true; /*(!M_MapLocked(mapnum+1)
+				&& (mapvisited[mapnum] || mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED));*/
+
+			// individual map name
+			if (!levelselect.rows[row].mapavailable[col])
+				sprintf(levelselect.rows[row].mapnames[col], "???");
+			else if (actnum)
+				sprintf(levelselect.rows[row].mapnames[col], "ACT %d", actnum);
+			else
+			{
+				sprintf(levelselect.rows[row].mapnames[col], "%s", mapheaderinfo[mapnum]->lvlttl);
+			}
+
+			// creating header text
+			if (!col && (!row || (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row].maplist[0]-1]->selectheading))))
+			{
+				if (!levelselect.rows[row].mapavailable[col])
+					sprintf(levelselect.rows[row].header, "???");
+				else
+				{
+					sprintf(levelselect.rows[row].header, "%s", mapheaderinfo[mapnum]->selectheading);
+					if (!(mapheaderinfo[mapnum]->levelflags & LF_NOZONE) && (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[mapnum]->lvlttl)))
+					{
+						sprintf(levelselect.rows[row].header + strlen(levelselect.rows[row].header), " ZONE");
+					}
+				}
+			}
+		}
+	}
+
+	lsrow = lscol = 0;
+
+	return true;
+}
+
+static void M_HandleNewLevelSelect(INT32 choice)
+{
+	boolean exitmenu = false;  // exit to previous menu
+	INT32 selectval;
+
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			lsrow++;
+			if (lsrow == levelselect.numrows)
+				lsrow = 0;
+			S_StartSound(NULL,sfx_s3kb7);
+			break;
+
+		case KEY_UPARROW:
+			lsrow--;
+			if (lsrow == UINT8_MAX)
+				lsrow = levelselect.numrows-1;
+			S_StartSound(NULL,sfx_s3kb7);
+			break;
+
+		case KEY_LEFTARROW:
+			if (lscol > 0)
+			{
+				lscol--;
+				S_StartSound(NULL,sfx_s3kb7);
+			}
+			break;
+
+		case KEY_RIGHTARROW:
+			if (lscol < 2)
+			{
+				lscol++;
+				S_StartSound(NULL,sfx_s3kb7);
+			}
+			break;
+
+		case KEY_ENTER:
+			selectval = levelselect.rows[lsrow].maplist[lscol];
+			if (selectval && levelselect.rows[lsrow].mapavailable[lscol])
+			{
+				CV_SetValue(&cv_nextmap, selectval);
+				M_LevelSelectWarp(0);
+				S_StartSound(NULL,sfx_s3kb7);
+			}
+			else
+				S_StartSound(NULL,sfx_s3kb2);
+			break;
+
+		case KEY_ESCAPE:
+			exitmenu = true;
+			break;
+
+		default:
+			break;
+	}
+
+	if (exitmenu)
+	{
+		if (currentMenu->prevMenu)
+			M_SetupNextMenu (currentMenu->prevMenu);
+		else
+			M_ClearMenus(true);
+	}
+}
+
+#undef lsrow
+#undef lscol
+
 // Call before showing any level-select menus
 static void M_PrepareLevelSelect(void)
 {
@@ -4089,7 +4367,16 @@ static void M_DrawLevelSelectMenu(void)
 {
 	M_DrawGenericMenu();
 
-	if (cv_nextmap.value)
+	V_DrawCenteredString(160, 40, V_YELLOWMAP, levelselect.rows[levelselectselect[0]].header);
+	V_DrawCenteredString(160, 48,           0, levelselect.rows[levelselectselect[0]].mapnames[levelselectselect[1]]);
+
+	/*if (levelselect.rows[levelselectselect[0]].maplist[levelselectselect[1]])
+		V_DrawCenteredString(160, 48, V_YELLOWMAP,
+		va("%s\n", G_BuildMapTitle(levelselect.rows[levelselectselect[0]].maplist[levelselectselect[1]])));
+	else
+		V_DrawCenteredString(160, 48, V_YELLOWMAP,
+		va("none\n"));*/
+	/*if (cv_nextmap.value)
 	{
 		lumpnum_t lumpnum;
 		patch_t *PictureOfLevel;
@@ -4103,7 +4390,7 @@ static void M_DrawLevelSelectMenu(void)
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
 
 		V_DrawSmallScaledPatch(200, 110, 0, PictureOfLevel);
-	}
+	}*/
 }
 
 static void M_DrawSkyRoom(void)
@@ -4288,13 +4575,12 @@ static void M_CustomLevelSelect(INT32 choice)
 	SR_LevelSelectDef.prevMenu = currentMenu;
 	levellistmode = LLM_LEVELSELECT;
 	maplistoption = (UINT8)(unlockables[ul].variable);
-	if (M_CountLevelsToShowInList() == 0)
+	if (!M_PrepareNewLevelSelect(-1))
 	{
 		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
 		return;
 	}
 
-	M_PrepareLevelSelect();
 	M_SetupNextMenu(&SR_LevelSelectDef);
 }
 
@@ -7474,4 +7760,4 @@ static void M_HandleFogColor(INT32 choice)
 			M_ClearMenus(true);
 	}
 }
-#endif
+#endif
\ No newline at end of file
diff --git a/src/m_menu.h b/src/m_menu.h
index 9cac52ed5..76eabd729 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -68,6 +68,7 @@ void M_QuitResponse(INT32 ch);
 
 // Determines whether to show a level in the list
 boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt);
+boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt);
 
 
 // flags for items in the menu
@@ -182,6 +183,22 @@ typedef struct
 	UINT8 next;
 } description_t;
 
+// experimental level select -- remember to use M_HandleSetupMultiPlayer
+typedef struct
+{
+	char header[22+5]; // mapheader_t lvltttl max length + " ZONE"
+	INT32 maplist[3];
+	char mapnames[3][22];
+	boolean mapavailable[3];
+} levelselectrow_t;
+
+typedef struct
+{
+	UINT8 numrows;
+	levelselectrow_t *rows;
+} levelselect_t;
+// experimental level select end
+
 // mode descriptions for video mode menu
 typedef struct
 {
diff --git a/src/p_setup.c b/src/p_setup.c
index 6df103255..5004ebdfa 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -199,6 +199,8 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	const INT16 num = (INT16)(i-1);
 	DEH_WriteUndoline("LEVELNAME", mapheaderinfo[num]->lvlttl, UNDO_NONE);
 	mapheaderinfo[num]->lvlttl[0] = '\0';
+	DEH_WriteUndoline("SELECTHEADING", mapheaderinfo[num]->selectheading, UNDO_NONE);
+	mapheaderinfo[num]->selectheading[0] = '\0';
 	DEH_WriteUndoline("SUBTITLE", mapheaderinfo[num]->subttl, UNDO_NONE);
 	mapheaderinfo[num]->subttl[0] = '\0';
 	DEH_WriteUndoline("ACT", va("%d", mapheaderinfo[num]->actnum), UNDO_NONE);

From 521ab3ca1a96d0077ce0ef30d077205e7fc8b048 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 27 Jan 2017 00:02:47 +0000
Subject: [PATCH 016/119] The visuals now work nicely. Future work involves
 porting this with minimal code repetition to the other sections of the game
 that use the old level select system.

---
 src/doomstat.h |   2 +-
 src/m_menu.c   | 196 ++++++++++++++++++++++++++++++++++++-------------
 2 files changed, 146 insertions(+), 52 deletions(-)

diff --git a/src/doomstat.h b/src/doomstat.h
index a3b95c95b..391f57a14 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -241,7 +241,7 @@ typedef struct
 	UINT8 levelflags;     ///< LF_flags:  merged eight booleans into one UINT8 for space, see below
 	UINT8 menuflags;      ///< LF2_flags: options that affect record attack / nights mode menus
 
-	char selectheading[22+5]; ///< Level select heading. Allows for controllable grouping.
+	char selectheading[22]; ///< Level select heading. Allows for controllable grouping.
 
 	// Freed animals stuff.
 	UINT8 numFlickies;     ///< Internal. For freed flicky support.
diff --git a/src/m_menu.c b/src/m_menu.c
index 36e5c9e1a..072c29e84 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3443,10 +3443,49 @@ static void M_PatchSkinNameTable(void)
 
 // Handle Level Select
 static levelselect_t levelselect = {0, NULL};
-static UINT8 levelselectselect[2];
+static UINT8 levelselectselect[3];
+static patch_t *levselp[4];
+static INT32 lsoffs[2];
 
 #define lsrow levelselectselect[0]
 #define lscol levelselectselect[1]
+#define lstic levelselectselect[2]
+
+#define hseperation 101
+#define vseperation 82
+
+static boolean M_LevelUnlockedInNewList(INT32 mapnum)
+{
+	if (M_MapLocked(mapnum+1))
+		return false; // not unlocked
+
+	switch (levellistmode)
+	{
+		case LLM_CREATESERVER:
+			return true;
+
+		case LLM_LEVELSELECT:
+			return true;
+
+		case LLM_RECORDATTACK:
+			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
+				return true;
+
+			if (!mapvisited[mapnum])
+				return false;
+
+			return true;
+		case LLM_NIGHTSATTACK:
+			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
+				return true;
+
+			if (!mapvisited[mapnum])
+				return false;
+
+			return true;
+	}
+	return true;
+}
 
 //
 // M_CanShowLevelInNewList
@@ -3464,6 +3503,9 @@ boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
 	if (!mapheaderinfo[mapnum]->lvlttl[0])
 		return false;
 
+	/*if (M_MapLocked(mapnum+1))
+		return false; // not unlocked*/
+
 	switch (levellistmode)
 	{
 		case LLM_CREATESERVER:
@@ -3471,9 +3513,6 @@ boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
 			if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU)
 				return false;
 
-			/*if (M_MapLocked(mapnum+1))
-				return false; // not unlocked*/
-
 			if (gt == GT_COOP && (mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
 				return true;
 
@@ -3498,21 +3537,15 @@ boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
 			if (mapheaderinfo[mapnum]->levelselect != maplistoption)
 				return false;
 
-			/*if (M_MapLocked(mapnum+1))
-				return false; // not unlocked*/
-
 			return true;
 		case LLM_RECORDATTACK:
 			if (!(mapheaderinfo[mapnum]->menuflags & LF2_RECORDATTACK))
 				return false;
 
-			/*if (M_MapLocked(mapnum+1))
-				return false; // not unlocked*/
-
-			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
+			/*if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
 				return true;
 
-			/*if (!mapvisited[mapnum])
+			if (!mapvisited[mapnum])
 				return false;*/
 
 			return true;
@@ -3520,13 +3553,10 @@ boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
 			if (!(mapheaderinfo[mapnum]->menuflags & LF2_NIGHTSATTACK))
 				return false;
 
-			/*if (M_MapLocked(mapnum+1))
-				return false; // not unlocked*/
-
-			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
+			/*if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
 				return true;
 
-			/*if (!mapvisited[mapnum])
+			if (!mapvisited[mapnum])
 				return false;*/
 
 			return true;
@@ -3612,8 +3642,7 @@ static boolean M_PrepareNewLevelSelect(INT32 gt)
 			}
 
 			levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
-			levelselect.rows[row].mapavailable[col] = true; /*(!M_MapLocked(mapnum+1)
-				&& (mapvisited[mapnum] || mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED));*/
+			levelselect.rows[row].mapavailable[col] = M_LevelUnlockedInNewList(mapnum);
 
 			// individual map name
 			if (!levelselect.rows[row].mapavailable[col])
@@ -3642,7 +3671,20 @@ static boolean M_PrepareNewLevelSelect(INT32 gt)
 		}
 	}
 
-	lsrow = lscol = 0;
+	lsrow = lscol = lstic = lsoffs[0] = lsoffs[1] = 0;
+
+	if (levselp[0]) // never going to have some provided but not all, saves individually checking
+	{
+		W_UnlockCachedPatch(levselp[0]);
+		W_UnlockCachedPatch(levselp[1]);
+		W_UnlockCachedPatch(levselp[2]);
+		W_UnlockCachedPatch(levselp[3]);
+	}
+
+	levselp[0] = W_CachePatchName("SLCT1LVL", PU_STATIC);
+	levselp[1] = W_CachePatchName("SLCT2LVL", PU_STATIC);
+	levselp[2] = W_CachePatchName("BLANKLVL", PU_STATIC);
+	levselp[3] = W_CachePatchName("STATCLVL", PU_STATIC);
 
 	return true;
 }
@@ -3658,6 +3700,7 @@ static void M_HandleNewLevelSelect(INT32 choice)
 			lsrow++;
 			if (lsrow == levelselect.numrows)
 				lsrow = 0;
+			lsoffs[0] = vseperation;
 			S_StartSound(NULL,sfx_s3kb7);
 			break;
 
@@ -3665,6 +3708,7 @@ static void M_HandleNewLevelSelect(INT32 choice)
 			lsrow--;
 			if (lsrow == UINT8_MAX)
 				lsrow = levelselect.numrows-1;
+			lsoffs[0] = -vseperation;
 			S_StartSound(NULL,sfx_s3kb7);
 			break;
 
@@ -3672,6 +3716,7 @@ static void M_HandleNewLevelSelect(INT32 choice)
 			if (lscol > 0)
 			{
 				lscol--;
+				lsoffs[1] = hseperation;
 				S_StartSound(NULL,sfx_s3kb7);
 			}
 			break;
@@ -3680,6 +3725,7 @@ static void M_HandleNewLevelSelect(INT32 choice)
 			if (lscol < 2)
 			{
 				lscol++;
+				lsoffs[1] = -hseperation;
 				S_StartSound(NULL,sfx_s3kb7);
 			}
 			break;
@@ -3690,10 +3736,13 @@ static void M_HandleNewLevelSelect(INT32 choice)
 			{
 				CV_SetValue(&cv_nextmap, selectval);
 				M_LevelSelectWarp(0);
-				S_StartSound(NULL,sfx_s3kb7);
+				S_StartSound(NULL,sfx_menu1);
 			}
 			else
+			{
+				lsoffs[0] = -8;
 				S_StartSound(NULL,sfx_s3kb2);
+			}
 			break;
 
 		case KEY_ESCAPE:
@@ -3713,6 +3762,81 @@ static void M_HandleNewLevelSelect(INT32 choice)
 	}
 }
 
+static void M_DrawLevelSelectRow(UINT8 row, INT32 y)
+{
+	UINT8 col;
+	const boolean highlight = (row == lsrow);
+	y -= 16;
+	if (levelselect.rows[row].header[0])
+	{
+		V_DrawString(19, y-4, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].header);
+		if ((y > 0) && (y < 200))
+		{
+			V_DrawFill(19, y+5, 282, 2, 26);
+			V_DrawFill(19, y+5, 281, 1, (highlight ? yellowmap[3] : 3));
+		}
+	}
+	y += 8;
+	for (col = 0; col < 3; col++)
+	{
+		INT32 x = 19+(col*hseperation);
+		patch_t *patch;
+
+		INT32 map = levelselect.rows[row].maplist[col];
+		if (!map)
+			continue;
+
+		//  A 160x100 image of the level as entry MAPxxP
+		if (!(levelselect.rows[row].mapavailable[col]))
+			patch = ((lstic & 1) ? levselp[2] : levselp[3]); // static - make secret maps look ENTICING
+		else if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
+			patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
+		else
+			patch = levselp[2]; // don't flash to indicate that it's just a normal level
+
+		V_DrawSmallScaledPatch(x, y, 0, patch);
+		W_UnlockCachedPatch(patch);
+
+		if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "EGG ROCK CORE"
+			V_DrawThinString(x, y+50, ((highlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+		else // "ACT 19"
+			V_DrawString(x, y+50, ((highlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+	}
+}
+
+static void M_DrawLevelSelectMenu(void)
+{
+	UINT8 prev = ((lsrow == 0) ? levelselect.numrows-1 : lsrow-1);
+	UINT8 next = ((lsrow == levelselect.numrows-1) ? 0 : lsrow+1);
+
+	if (++lstic == 32)
+		lstic = 0;
+
+	M_DrawLevelSelectRow(prev, lsoffs[0]);
+	M_DrawLevelSelectRow(lsrow, vseperation + lsoffs[0]);
+	M_DrawLevelSelectRow(next, 2*vseperation + lsoffs[0]);
+
+	if (lsoffs[0] > vseperation/3)
+		M_DrawLevelSelectRow( ((prev == 0) ? levelselect.numrows-1 : prev-1), -vseperation + lsoffs[0]);
+	else if (lsoffs[0] < -vseperation/3)
+		M_DrawLevelSelectRow( ((next == levelselect.numrows-1) ? 0 : next+1), 3*vseperation + lsoffs[0]);
+
+	if (abs(lsoffs[0]) > 1)
+		lsoffs[0] = 2*lsoffs[0]/3;
+	else
+		lsoffs[0] = 0;
+
+	if (abs(lsoffs[1]) > 1)
+		lsoffs[1] >>= 2;
+	else
+		lsoffs[1] = 0;
+
+	V_DrawSmallScaledPatch(19+(lscol*hseperation) + lsoffs[1], vseperation-8, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
+}
+
+#undef hseperation
+#undef vseperation
+
 #undef lsrow
 #undef lscol
 
@@ -4363,36 +4487,6 @@ static void M_DrawEmblemHints(void)
 	M_DrawGenericMenu();
 }
 
-static void M_DrawLevelSelectMenu(void)
-{
-	M_DrawGenericMenu();
-
-	V_DrawCenteredString(160, 40, V_YELLOWMAP, levelselect.rows[levelselectselect[0]].header);
-	V_DrawCenteredString(160, 48,           0, levelselect.rows[levelselectselect[0]].mapnames[levelselectselect[1]]);
-
-	/*if (levelselect.rows[levelselectselect[0]].maplist[levelselectselect[1]])
-		V_DrawCenteredString(160, 48, V_YELLOWMAP,
-		va("%s\n", G_BuildMapTitle(levelselect.rows[levelselectselect[0]].maplist[levelselectselect[1]])));
-	else
-		V_DrawCenteredString(160, 48, V_YELLOWMAP,
-		va("none\n"));*/
-	/*if (cv_nextmap.value)
-	{
-		lumpnum_t lumpnum;
-		patch_t *PictureOfLevel;
-
-		//  A 160x100 image of the level as entry MAPxxP
-		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
-
-		if (lumpnum != LUMPERROR)
-			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
-		else
-			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
-
-		V_DrawSmallScaledPatch(200, 110, 0, PictureOfLevel);
-	}*/
-}
-
 static void M_DrawSkyRoom(void)
 {
 	INT32 i, y = 0;

From 7178ae5916afdf233127096a3696ddb412be03e0 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 27 Jan 2017 14:42:57 +0000
Subject: [PATCH 017/119] Hefty refactor.

* Named all the new functions/etc "Platter" instead of "NewList", to make maintenence easier and also because it sounds delicious.
* The code, as a whole, is much cleaner.
* Allows for multiple items under the same heading now, with highlights and spacing consistently handled.
* Allows for picking up where you left off between menus now (assuming the same map is available on multiple screens).
* Defined M_CanShowLevelInList in terms of the two compartmentalised functions cleaved from it, in order to ensure consistency and prevent code duplication.
---
 src/m_menu.c | 309 ++++++++++++++++++++++++---------------------------
 src/m_menu.h |   3 +-
 2 files changed, 146 insertions(+), 166 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 072c29e84..97f396207 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -309,7 +309,7 @@ static void M_DrawChecklist(void);
 static void M_DrawEmblemHints(void);
 static void M_DrawPauseMenu(void);
 static void M_DrawServerMenu(void);
-static void M_DrawLevelSelectMenu(void);
+static void M_DrawLevelPlatterMenu(void);
 static void M_DrawImageDef(void);
 static void M_DrawLoad(void);
 static void M_DrawLevelStats(void);
@@ -338,6 +338,7 @@ static boolean M_CancelConnect(void);
 #endif
 static boolean M_ExitPandorasBox(void);
 static boolean M_QuitMultiPlayerMenu(void);
+static void M_HandleLevelPlatter(INT32 choice);
 static void M_HandleSoundTest(INT32 choice);
 static void M_HandleImageDef(INT32 choice);
 static void M_HandleLoadSave(INT32 choice);
@@ -347,7 +348,6 @@ static void M_HandleLevelStats(INT32 choice);
 static void M_HandleConnectIP(INT32 choice);
 #endif
 static void M_HandleSetupMultiPlayer(INT32 choice);
-static void M_HandleNewLevelSelect(INT32 choice);
 #ifdef HWRENDER
 static void M_HandleFogColor(INT32 choice);
 #endif
@@ -644,7 +644,7 @@ static menuitem_t SR_MainMenu[] =
 
 static menuitem_t SR_LevelSelectMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleNewLevelSelect, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
 /*	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        60},
 
 	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_LevelSelectWarp,     120},*/
@@ -1437,7 +1437,7 @@ menu_t SR_LevelSelectDef =
 	sizeof (SR_LevelSelectMenu)/sizeof (menuitem_t),
 	&SR_MainDef,
 	SR_LevelSelectMenu,
-	M_DrawLevelSelectMenu,
+	M_DrawLevelPlatterMenu,
 	40, 40,
 	0,
 	NULL
@@ -3443,38 +3443,36 @@ static void M_PatchSkinNameTable(void)
 
 // Handle Level Select
 static levelselect_t levelselect = {0, NULL};
-static UINT8 levelselectselect[3];
+static UINT8 levelselectselect[4];
 static patch_t *levselp[4];
 static INT32 lsoffs[2];
 
 #define lsrow levelselectselect[0]
 #define lscol levelselectselect[1]
 #define lstic levelselectselect[2]
+#define lshli levelselectselect[3]
 
 #define hseperation 101
-#define vseperation 82
+#define basevseperation 62
+#define headingheight 16
+#define getheadingoffset(row) (levelselect.rows[row].header[0] ? headingheight : 0)
+#define vseperation(row) basevseperation + getheadingoffset(row)
 
-static boolean M_LevelUnlockedInNewList(INT32 mapnum)
+//
+// M_LevelAvailableOnPlatter
+//
+// Okay, you know that the level SHOULD show up on the platter already.
+// The only question is whether it should be as a question mark,
+// (hinting as to its existence), or as its pure, unfettered self.
+//
+static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 {
 	if (M_MapLocked(mapnum+1))
 		return false; // not unlocked
 
 	switch (levellistmode)
 	{
-		case LLM_CREATESERVER:
-			return true;
-
-		case LLM_LEVELSELECT:
-			return true;
-
 		case LLM_RECORDATTACK:
-			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
-				return true;
-
-			if (!mapvisited[mapnum])
-				return false;
-
-			return true;
 		case LLM_NIGHTSATTACK:
 			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
 				return true;
@@ -3482,18 +3480,22 @@ static boolean M_LevelUnlockedInNewList(INT32 mapnum)
 			if (!mapvisited[mapnum])
 				return false;
 
+			// intentional fallthrough
+		case LLM_CREATESERVER:
+		case LLM_LEVELSELECT:
+		default:
 			return true;
 	}
 	return true;
 }
 
 //
-// M_CanShowLevelInNewList
+// M_CanShowLevelOnPlatter
 //
 // Determines whether to show a given map in the various level-select lists.
 // Set gt = -1 to ignore gametype.
 //
-boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
+static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 {
 	// Does the map exist?
 	if (!mapheaderinfo[mapnum])
@@ -3542,23 +3544,11 @@ boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
 			if (!(mapheaderinfo[mapnum]->menuflags & LF2_RECORDATTACK))
 				return false;
 
-			/*if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
-				return true;
-
-			if (!mapvisited[mapnum])
-				return false;*/
-
 			return true;
 		case LLM_NIGHTSATTACK:
 			if (!(mapheaderinfo[mapnum]->menuflags & LF2_NIGHTSATTACK))
 				return false;
 
-			/*if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
-				return true;
-
-			if (!mapvisited[mapnum])
-				return false;*/
-
 			return true;
 	}
 
@@ -3566,24 +3556,24 @@ boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt)
 	return false;
 }
 
-/*static INT32 M_CountLevelsToShowInNewList(INT32 gt)
+/*static INT32 M_CountLevelsToShowOnPlatter(INT32 gt)
 {
 	INT32 mapnum, count = 0;
 
 	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
-		if (M_CanShowLevelInList(mapnum, gt))
+		if (M_CanShowLevelInPlatter(mapnum, gt))
 			count++;
 
 	return count;
 }*/
 
-static INT32 M_CountRowsToShowInNewList(INT32 gt)
+static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 {
 	INT32 mapnum, prevmapnum, col = 0, rows = 0;
 
 	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
 	{
-		if (M_CanShowLevelInNewList(mapnum, gt))
+		if (M_CanShowLevelOnPlatter(mapnum, gt))
 		{
 			if (rows == 0)
 				rows++;
@@ -3605,9 +3595,9 @@ static INT32 M_CountRowsToShowInNewList(INT32 gt)
 	return rows;
 }
 
-static boolean M_PrepareNewLevelSelect(INT32 gt)
+static boolean M_PrepareLevelPlatter(INT32 gt)
 {
-	INT32 numrows = M_CountRowsToShowInNewList(gt);
+	INT32 numrows = M_CountRowsToShowOnPlatter(gt);
 	INT32 mapnum, col = 0, row = 0;
 
 	if (!numrows)
@@ -3620,13 +3610,17 @@ static boolean M_PrepareNewLevelSelect(INT32 gt)
 	levelselect.numrows = numrows;
 	levelselect.rows = Z_Realloc(levelselect.rows, numrows*sizeof(levelselectrow_t), PU_STATIC, NULL);
 	if (!levelselect.rows)
-		I_Error("Insufficient memory to prepare level select platter");
+		I_Error("Insufficient memory to prepare level platter");
+
+	// done here so lsrow and lscol can be set if cv_nextmap is on the platter
+	lsrow = lscol = lstic = lshli = lsoffs[0] = lsoffs[1] = 0;
 
 	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
 	{
-		if (M_CanShowLevelInNewList(mapnum, gt))
+		if (M_CanShowLevelOnPlatter(mapnum, gt))
 		{
 			const INT32 actnum = mapheaderinfo[mapnum]->actnum;
+			const boolean headingisname = (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[mapnum]->lvlttl));
 
 			// preparing next position to drop mapnum into
 			if (levelselect.rows[0].maplist[0])
@@ -3642,27 +3636,33 @@ static boolean M_PrepareNewLevelSelect(INT32 gt)
 			}
 
 			levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
-			levelselect.rows[row].mapavailable[col] = M_LevelUnlockedInNewList(mapnum);
+			levelselect.rows[row].mapavailable[col] = M_LevelAvailableOnPlatter(mapnum);
+
+			if (cv_nextmap.value == mapnum+1) // A little quality of life improvement.
+			{
+				lsrow = row;
+				lscol = col;
+			}
 
 			// individual map name
 			if (!levelselect.rows[row].mapavailable[col])
 				sprintf(levelselect.rows[row].mapnames[col], "???");
 			else if (actnum)
 				sprintf(levelselect.rows[row].mapnames[col], "ACT %d", actnum);
+			else if (headingisname)
+				sprintf(levelselect.rows[row].mapnames[col], "THE ACT", actnum);
 			else
-			{
 				sprintf(levelselect.rows[row].mapnames[col], "%s", mapheaderinfo[mapnum]->lvlttl);
-			}
 
 			// creating header text
-			if (!col && (!row || (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row].maplist[0]-1]->selectheading))))
+			if (!col && (!row || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row-1].maplist[0]-1]->selectheading))))
 			{
 				if (!levelselect.rows[row].mapavailable[col])
 					sprintf(levelselect.rows[row].header, "???");
 				else
 				{
 					sprintf(levelselect.rows[row].header, "%s", mapheaderinfo[mapnum]->selectheading);
-					if (!(mapheaderinfo[mapnum]->levelflags & LF_NOZONE) && (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[mapnum]->lvlttl)))
+					if (!(mapheaderinfo[mapnum]->levelflags & LF_NOZONE) && headingisname)
 					{
 						sprintf(levelselect.rows[row].header + strlen(levelselect.rows[row].header), " ZONE");
 					}
@@ -3671,8 +3671,6 @@ static boolean M_PrepareNewLevelSelect(INT32 gt)
 		}
 	}
 
-	lsrow = lscol = lstic = lsoffs[0] = lsoffs[1] = 0;
-
 	if (levselp[0]) // never going to have some provided but not all, saves individually checking
 	{
 		W_UnlockCachedPatch(levselp[0]);
@@ -3689,7 +3687,14 @@ static boolean M_PrepareNewLevelSelect(INT32 gt)
 	return true;
 }
 
-static void M_HandleNewLevelSelect(INT32 choice)
+#define selectvalnextmapnobrace(column) selectval = levelselect.rows[lsrow].maplist[column];\
+			if (selectval && levelselect.rows[lsrow].mapavailable[column])\
+			{\
+				CV_SetValue(&cv_nextmap, selectval);
+
+#define selectvalnextmap(column) selectvalnextmapnobrace(column)}
+
+static void M_HandleLevelPlatter(INT32 choice)
 {
 	boolean exitmenu = false;  // exit to previous menu
 	INT32 selectval;
@@ -3700,24 +3705,50 @@ static void M_HandleNewLevelSelect(INT32 choice)
 			lsrow++;
 			if (lsrow == levelselect.numrows)
 				lsrow = 0;
-			lsoffs[0] = vseperation;
+
+			lsoffs[0] = vseperation(lsrow);
+
+			if (levelselect.rows[lsrow].header[0])
+				lshli = lsrow;
+			// no else needed - headerless lines associate upwards, so moving down to a row without a header is identity
+
 			S_StartSound(NULL,sfx_s3kb7);
+
+			selectvalnextmap(lscol) else selectvalnextmap(0)
 			break;
 
 		case KEY_UPARROW:
+			lsoffs[0] = -vseperation(lsrow);
+
 			lsrow--;
 			if (lsrow == UINT8_MAX)
 				lsrow = levelselect.numrows-1;
-			lsoffs[0] = -vseperation;
+
+			if (levelselect.rows[lsrow].header[0])
+				lshli = lsrow;
+			else
+			{
+				UINT8 iter = lsrow;
+				do
+					iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
+				while ((iter != lsrow) && !(levelselect.rows[iter].header[0]));
+				lshli = iter;
+			}
+
 			S_StartSound(NULL,sfx_s3kb7);
+
+			selectvalnextmap(lscol) else selectvalnextmap(0)
 			break;
 
 		case KEY_LEFTARROW:
 			if (lscol > 0)
 			{
 				lscol--;
+
 				lsoffs[1] = hseperation;
 				S_StartSound(NULL,sfx_s3kb7);
+
+				selectvalnextmap(lscol) else selectvalnextmap(0)
 			}
 			break;
 
@@ -3725,20 +3756,23 @@ static void M_HandleNewLevelSelect(INT32 choice)
 			if (lscol < 2)
 			{
 				lscol++;
+
 				lsoffs[1] = -hseperation;
 				S_StartSound(NULL,sfx_s3kb7);
+
+				selectvalnextmap(lscol) else selectvalnextmap(0)
 			}
 			break;
 
 		case KEY_ENTER:
-			selectval = levelselect.rows[lsrow].maplist[lscol];
-			if (selectval && levelselect.rows[lsrow].mapavailable[lscol])
-			{
-				CV_SetValue(&cv_nextmap, selectval);
+			selectvalnextmapnobrace(lscol)
+
 				M_LevelSelectWarp(0);
+
+				lsoffs[0] = lsoffs[1] = 0;
 				S_StartSound(NULL,sfx_menu1);
 			}
-			else
+			else if (!lsoffs[0]) //  prevent sound spam
 			{
 				lsoffs[0] = -8;
 				S_StartSound(NULL,sfx_s3kb2);
@@ -3762,21 +3796,29 @@ static void M_HandleNewLevelSelect(INT32 choice)
 	}
 }
 
-static void M_DrawLevelSelectRow(UINT8 row, INT32 y)
+static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 {
 	UINT8 col;
-	const boolean highlight = (row == lsrow);
-	y -= 16;
+	const boolean rowhighlight = (row == lsrow);
 	if (levelselect.rows[row].header[0])
 	{
-		V_DrawString(19, y-4, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].header);
-		if ((y > 0) && (y < 200))
+		const boolean headerhighlight = (rowhighlight || (row == lshli));
+
+		y += headingheight - 12;
+		V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0), levelselect.rows[row].header);
+		y += 9;
+		if ((y >= 0) && (y < 200))
 		{
-			V_DrawFill(19, y+5, 282, 2, 26);
-			V_DrawFill(19, y+5, 281, 1, (highlight ? yellowmap[3] : 3));
+			V_DrawFill(19, y, 281, 1, (headerhighlight ? yellowmap[3] : 3));
+			V_DrawFill(300, y, 1, 1, 26);
 		}
+		y++;
+		if ((y >= 0) && (y < 200))
+		{
+			V_DrawFill(19, y, 282, 1, 26);
+		}
+		y += 2;
 	}
-	y += 8;
 	for (col = 0; col < 3; col++)
 	{
 		INT32 x = 19+(col*hseperation);
@@ -3795,50 +3837,66 @@ static void M_DrawLevelSelectRow(UINT8 row, INT32 y)
 			patch = levselp[2]; // don't flash to indicate that it's just a normal level
 
 		V_DrawSmallScaledPatch(x, y, 0, patch);
-		W_UnlockCachedPatch(patch);
 
-		if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "EGG ROCK CORE"
-			V_DrawThinString(x, y+50, ((highlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
-		else // "ACT 19"
-			V_DrawString(x, y+50, ((highlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+		if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
+			V_DrawThinString(x, y+50, ((rowhighlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+		else
+			V_DrawString(x, y+50, ((rowhighlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
 	}
 }
 
-static void M_DrawLevelSelectMenu(void)
+#define basey 59+headingheight
+
+static void M_DrawLevelPlatterMenu(void)
 {
-	UINT8 prev = ((lsrow == 0) ? levelselect.numrows-1 : lsrow-1);
-	UINT8 next = ((lsrow == levelselect.numrows-1) ? 0 : lsrow+1);
+	UINT8 iter = lsrow;
+	INT32 y = basey + lsoffs[0] - getheadingoffset(lsrow);
 
 	if (++lstic == 32)
 		lstic = 0;
 
-	M_DrawLevelSelectRow(prev, lsoffs[0]);
-	M_DrawLevelSelectRow(lsrow, vseperation + lsoffs[0]);
-	M_DrawLevelSelectRow(next, 2*vseperation + lsoffs[0]);
+	// finds row at top of the screen
+	while (y > 0)
+	{
+		iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
+		y -= vseperation(iter);
+	}
 
-	if (lsoffs[0] > vseperation/3)
-		M_DrawLevelSelectRow( ((prev == 0) ? levelselect.numrows-1 : prev-1), -vseperation + lsoffs[0]);
-	else if (lsoffs[0] < -vseperation/3)
-		M_DrawLevelSelectRow( ((next == levelselect.numrows-1) ? 0 : next+1), 3*vseperation + lsoffs[0]);
+	// draw from top to bottom
+	while (y < 200)
+	{
+		M_DrawLevelPlatterRow(iter, y);
+		y += vseperation(iter);
+		iter = ((iter == levelselect.numrows-1) ? 0 : iter+1);
+	}
 
+	// handle movement of cursor box
 	if (abs(lsoffs[0]) > 1)
 		lsoffs[0] = 2*lsoffs[0]/3;
 	else
 		lsoffs[0] = 0;
 
 	if (abs(lsoffs[1]) > 1)
-		lsoffs[1] >>= 2;
+		lsoffs[1] = 2*lsoffs[1]/3;
 	else
 		lsoffs[1] = 0;
 
-	V_DrawSmallScaledPatch(19+(lscol*hseperation) + lsoffs[1], vseperation-8, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
+	// draw cursor box
+	V_DrawSmallScaledPatch(19+(lscol*hseperation) + lsoffs[1], basey, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
 }
 
-#undef hseperation
-#undef vseperation
+#undef basey
 
 #undef lsrow
 #undef lscol
+#undef lstic
+#undef lshli
+
+#undef hseperation
+#undef basevseperation
+#undef headingheight
+#undef getheadingoffset
+#undef vseperation
 
 // Call before showing any level-select menus
 static void M_PrepareLevelSelect(void)
@@ -3852,89 +3910,12 @@ static void M_PrepareLevelSelect(void)
 //
 // M_CanShowLevelInList
 //
-// Determines whether to show a given map in the various level-select lists.
+// Determines whether to show a given map in level-select lists where you don't want to see locked levels.
 // Set gt = -1 to ignore gametype.
 //
 boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
 {
-	// Does the map exist?
-	if (!mapheaderinfo[mapnum])
-		return false;
-
-	// Does the map have a name?
-	if (!mapheaderinfo[mapnum]->lvlttl[0])
-		return false;
-
-	switch (levellistmode)
-	{
-		case LLM_CREATESERVER:
-			// Should the map be hidden?
-			if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU)
-				return false;
-
-			if (M_MapLocked(mapnum+1))
-				return false; // not unlocked
-
-			if (gt == GT_COOP && (mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
-				return true;
-
-			if (gt == GT_COMPETITION && (mapheaderinfo[mapnum]->typeoflevel & TOL_COMPETITION))
-				return true;
-
-			if (gt == GT_CTF && (mapheaderinfo[mapnum]->typeoflevel & TOL_CTF))
-				return true;
-
-			if ((gt == GT_MATCH || gt == GT_TEAMMATCH) && (mapheaderinfo[mapnum]->typeoflevel & TOL_MATCH))
-				return true;
-
-			if ((gt == GT_TAG || gt == GT_HIDEANDSEEK) && (mapheaderinfo[mapnum]->typeoflevel & TOL_TAG))
-				return true;
-
-			if (gt == GT_RACE && (mapheaderinfo[mapnum]->typeoflevel & TOL_RACE))
-				return true;
-
-			return false;
-
-		case LLM_LEVELSELECT:
-			if (mapheaderinfo[mapnum]->levelselect != maplistoption)
-				return false;
-
-			if (M_MapLocked(mapnum+1))
-				return false; // not unlocked
-
-			return true;
-		case LLM_RECORDATTACK:
-			if (!(mapheaderinfo[mapnum]->menuflags & LF2_RECORDATTACK))
-				return false;
-
-			if (M_MapLocked(mapnum+1))
-				return false; // not unlocked
-
-			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
-				return true;
-
-			if (!mapvisited[mapnum])
-				return false;
-
-			return true;
-		case LLM_NIGHTSATTACK:
-			if (!(mapheaderinfo[mapnum]->menuflags & LF2_NIGHTSATTACK))
-				return false;
-
-			if (M_MapLocked(mapnum+1))
-				return false; // not unlocked
-
-			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
-				return true;
-
-			if (!mapvisited[mapnum])
-				return false;
-
-			return true;
-	}
-
-	// Hmm? Couldn't decide?
-	return false;
+	return (M_CanShowLevelOnPlatter(mapnum, gt) && M_LevelAvailableOnPlatter(mapnum));
 }
 
 static INT32 M_CountLevelsToShowInList(void)
@@ -4669,7 +4650,7 @@ static void M_CustomLevelSelect(INT32 choice)
 	SR_LevelSelectDef.prevMenu = currentMenu;
 	levellistmode = LLM_LEVELSELECT;
 	maplistoption = (UINT8)(unlockables[ul].variable);
-	if (!M_PrepareNewLevelSelect(-1))
+	if (!M_PrepareLevelPlatter(-1))
 	{
 		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
 		return;
diff --git a/src/m_menu.h b/src/m_menu.h
index 76eabd729..5c07d8ae8 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -66,9 +66,8 @@ void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtyp
 // Called by linux_x/i_video_xshm.c
 void M_QuitResponse(INT32 ch);
 
-// Determines whether to show a level in the list
+// Determines whether to show a level in the list (platter version does not need to be exposed)
 boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt);
-boolean M_CanShowLevelInNewList(INT32 mapnum, INT32 gt);
 
 
 // flags for items in the menu

From 2737d08107bcee1a536c063e27b1e43c70090d2f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 27 Jan 2017 23:16:35 +0000
Subject: [PATCH 018/119] * Singleplayer save-complete level select now
 following new system. * More comprehensive "pick up where you left off"
 system. * Made individual map name selection actually take into account the
 map name AND the act number if the heading isn't the zone name.

---
 src/m_menu.c | 77 ++++++++++++++++++++++++++++++++++------------------
 1 file changed, 50 insertions(+), 27 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 97f396207..4c37bb3af 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -645,9 +645,6 @@ static menuitem_t SR_MainMenu[] =
 static menuitem_t SR_LevelSelectMenu[] =
 {
 	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
-/*	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        60},
-
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_LevelSelectWarp,     120},*/
 };
 
 static menuitem_t SR_UnlockChecklistMenu[] =
@@ -692,9 +689,7 @@ static menuitem_t SP_LoadGameMenu[] =
 // Single Player Level Select
 static menuitem_t SP_LevelSelectMenu[] =
 {
-	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        60},
-
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_LevelSelectWarp,     120},
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
 };
 
 // Single Player Time Attack
@@ -1433,12 +1428,12 @@ menu_t SR_MainDef =
 };
 menu_t SR_LevelSelectDef =
 {
-	0,
+	NULL,
 	sizeof (SR_LevelSelectMenu)/sizeof (menuitem_t),
 	&SR_MainDef,
 	SR_LevelSelectMenu,
 	M_DrawLevelPlatterMenu,
-	40, 40,
+	0, 0,
 	0,
 	NULL
 };
@@ -1478,7 +1473,18 @@ menu_t SP_LoadDef =
 	0,
 	NULL
 };
-menu_t SP_LevelSelectDef = MAPICONMENUSTYLE(NULL, SP_LevelSelectMenu, &SP_LoadDef);
+
+menu_t SP_LevelSelectDef =
+{
+	NULL,
+	sizeof (SP_LevelSelectMenu)/sizeof (menuitem_t),
+	&SP_LoadDef,
+	SP_LevelSelectMenu,
+	M_DrawLevelPlatterMenu,
+	0, 0,
+	0,
+	NULL
+};
 
 menu_t SP_GameStatsDef =
 {
@@ -3556,22 +3562,24 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 	return false;
 }
 
-/*static INT32 M_CountLevelsToShowOnPlatter(INT32 gt)
+#if 0
+static INT32 M_CountLevelsToShowOnPlatter(INT32 gt)
 {
 	INT32 mapnum, count = 0;
 
 	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
-		if (M_CanShowLevelInPlatter(mapnum, gt))
+		if (M_CanShowLevelOnPlatter(mapnum, gt))
 			count++;
 
 	return count;
-}*/
+}
+#endif
 
 static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 {
-	INT32 mapnum, prevmapnum, col = 0, rows = 0;
+	INT32 mapnum = 0, prevmapnum = 0, col = 0, rows = 0;
 
-	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+	while (mapnum < NUMMAPS)
 	{
 		if (M_CanShowLevelOnPlatter(mapnum, gt))
 		{
@@ -3590,6 +3598,7 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 			}
 			prevmapnum = mapnum;
 		}
+		mapnum++;
 	}
 
 	return rows;
@@ -3598,7 +3607,7 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 static boolean M_PrepareLevelPlatter(INT32 gt)
 {
 	INT32 numrows = M_CountRowsToShowOnPlatter(gt);
-	INT32 mapnum, col = 0, row = 0;
+	INT32 mapnum, desiredmap, col = 0, row = 0;
 
 	if (!numrows)
 		return false;
@@ -3615,6 +3624,8 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 	// done here so lsrow and lscol can be set if cv_nextmap is on the platter
 	lsrow = lscol = lstic = lshli = lsoffs[0] = lsoffs[1] = 0;
 
+	desiredmap = ((Playing()) ? gamemap: cv_nextmap.value);
+
 	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
 	{
 		if (M_CanShowLevelOnPlatter(mapnum, gt))
@@ -3638,21 +3649,32 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 			levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
 			levelselect.rows[row].mapavailable[col] = M_LevelAvailableOnPlatter(mapnum);
 
-			if (cv_nextmap.value == mapnum+1) // A little quality of life improvement.
+			if (desiredmap == mapnum+1) // A little quality of life improvement.
 			{
 				lsrow = row;
 				lscol = col;
 			}
 
 			// individual map name
-			if (!levelselect.rows[row].mapavailable[col])
-				sprintf(levelselect.rows[row].mapnames[col], "???");
-			else if (actnum)
-				sprintf(levelselect.rows[row].mapnames[col], "ACT %d", actnum);
-			else if (headingisname)
-				sprintf(levelselect.rows[row].mapnames[col], "THE ACT", actnum);
+			if (levelselect.rows[row].mapavailable[col])
+			{
+				if (headingisname)
+				{
+					if (actnum)
+						sprintf(levelselect.rows[row].mapnames[col], "ACT %d", actnum);
+					else
+						sprintf(levelselect.rows[row].mapnames[col], "THE ACT");
+				}
+				else
+				{
+					if (actnum)
+						sprintf(levelselect.rows[row].mapnames[col], "%s %d", mapheaderinfo[mapnum]->lvlttl, actnum);
+					else
+						sprintf(levelselect.rows[row].mapnames[col], "%s", mapheaderinfo[mapnum]->lvlttl);
+				}
+			}
 			else
-				sprintf(levelselect.rows[row].mapnames[col], "%s", mapheaderinfo[mapnum]->lvlttl);
+				sprintf(levelselect.rows[row].mapnames[col], "???");
 
 			// creating header text
 			if (!col && (!row || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row-1].maplist[0]-1]->selectheading))))
@@ -4650,6 +4672,7 @@ static void M_CustomLevelSelect(INT32 choice)
 	SR_LevelSelectDef.prevMenu = currentMenu;
 	levellistmode = LLM_LEVELSELECT;
 	maplistoption = (UINT8)(unlockables[ul].variable);
+
 	if (!M_PrepareLevelPlatter(-1))
 	{
 		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
@@ -4677,17 +4700,17 @@ static void M_SinglePlayerMenu(INT32 choice)
 static void M_LoadGameLevelSelect(INT32 choice)
 {
 	(void)choice;
+
+	SP_LevelSelectDef.prevMenu = currentMenu;
 	levellistmode = LLM_LEVELSELECT;
 	maplistoption = 1;
-	if (M_CountLevelsToShowInList() == 0)
+
+	if (!M_PrepareLevelPlatter(-1))
 	{
 		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
 		return;
 	}
 
-	SP_LevelSelectDef.prevMenu = currentMenu;
-
-	M_PrepareLevelSelect();
 	M_SetupNextMenu(&SP_LevelSelectDef);
 }
 

From 70068c664d40bf7ff144c57c29112be885dee82c Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 28 Jan 2017 00:56:28 +0000
Subject: [PATCH 019/119] * Dark blue background behind text underneath map
 icons. * Traditional menu cursor now added.

---
 src/m_menu.c | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 4c37bb3af..d2751cf9d 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3841,6 +3841,7 @@ static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 		}
 		y += 2;
 	}
+
 	for (col = 0; col < 3; col++)
 	{
 		INT32 x = 19+(col*hseperation);
@@ -3860,6 +3861,21 @@ static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 
 		V_DrawSmallScaledPatch(x, y, 0, patch);
 
+		if ((y+50) < 200)
+		{
+			INT32 topy = (y+50), h = 8;
+
+			if (topy < 0)
+			{
+				h += topy;
+				topy = 0;
+			}
+			else if (topy + h >= 200)
+				h = 200 - y;
+			if (h > 0)
+				V_DrawFill(x, topy, 80, h, 159);
+		}
+
 		if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
 			V_DrawThinString(x, y+50, ((rowhighlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
 		else
@@ -3873,6 +3889,7 @@ static void M_DrawLevelPlatterMenu(void)
 {
 	UINT8 iter = lsrow;
 	INT32 y = basey + lsoffs[0] - getheadingoffset(lsrow);
+	const UINT32 cursorx = 19+(lscol*hseperation);
 
 	if (++lstic == 32)
 		lstic = 0;
@@ -3892,6 +3909,12 @@ static void M_DrawLevelPlatterMenu(void)
 		iter = ((iter == levelselect.numrows-1) ? 0 : iter+1);
 	}
 
+	// draw cursor box
+	V_DrawSmallScaledPatch(cursorx + lsoffs[1], basey, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
+
+	if (levelselect.rows[lsrow].maplist[lscol])
+		V_DrawScaledPatch(cursorx-17, basey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+
 	// handle movement of cursor box
 	if (abs(lsoffs[0]) > 1)
 		lsoffs[0] = 2*lsoffs[0]/3;
@@ -3902,9 +3925,6 @@ static void M_DrawLevelPlatterMenu(void)
 		lsoffs[1] = 2*lsoffs[1]/3;
 	else
 		lsoffs[1] = 0;
-
-	// draw cursor box
-	V_DrawSmallScaledPatch(19+(lscol*hseperation) + lsoffs[1], basey, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
 }
 
 #undef basey

From 3574b598d62bf074493dba81af4beb2f20bc273a Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 28 Jan 2017 10:34:58 +0000
Subject: [PATCH 020/119] * Make unlockable levels (as opposed to ones which
 are part of the main campaign) have a different coloured (dark orange) text
 background. * Better comments.

---
 src/m_menu.c | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index d2751cf9d..c9d8d0ca9 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3604,6 +3604,12 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 	return rows;
 }
 
+//
+// M_PrepareLevelPlatter
+//
+// Prepares a tasty dish of zones and acts!
+// Call before any attempt to access a level platter.
+//
 static boolean M_PrepareLevelPlatter(INT32 gt)
 {
 	INT32 numrows = M_CountRowsToShowOnPlatter(gt);
@@ -3716,6 +3722,11 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 
 #define selectvalnextmap(column) selectvalnextmapnobrace(column)}
 
+//
+// M_HandleLevelPlatter
+//
+// Reacts to your key inputs. Basically a mini menu thinker.
+//
 static void M_HandleLevelPlatter(INT32 choice)
 {
 	boolean exitmenu = false;  // exit to previous menu
@@ -3772,6 +3783,11 @@ static void M_HandleLevelPlatter(INT32 choice)
 
 				selectvalnextmap(lscol) else selectvalnextmap(0)
 			}
+			else if (!lsoffs[1]) //  prevent sound spam
+			{
+				lsoffs[1] = -8;
+				S_StartSound(NULL,sfx_s3kb7);
+			}
 			break;
 
 		case KEY_RIGHTARROW:
@@ -3784,6 +3800,11 @@ static void M_HandleLevelPlatter(INT32 choice)
 
 				selectvalnextmap(lscol) else selectvalnextmap(0)
 			}
+			else if (!lsoffs[1]) //  prevent sound spam
+			{
+				lsoffs[1] = 8;
+				S_StartSound(NULL,sfx_s3kb7);
+			}
 			break;
 
 		case KEY_ENTER:
@@ -3873,7 +3894,9 @@ static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 			else if (topy + h >= 200)
 				h = 200 - y;
 			if (h > 0)
-				V_DrawFill(x, topy, 80, h, 159);
+				V_DrawFill(x, topy, 80, h,
+				((mapheaderinfo[map-1]->unlockrequired < 0)
+				? 159 : 63));
 		}
 
 		if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
@@ -3940,7 +3963,7 @@ static void M_DrawLevelPlatterMenu(void)
 #undef getheadingoffset
 #undef vseperation
 
-// Call before showing any level-select menus
+// Call before showing any level-select menus (Not necessary for platter-based ones)
 static void M_PrepareLevelSelect(void)
 {
 	if (levellistmode != LLM_CREATESERVER)

From a23da9ae43a903538853b67c6017a030d1bb8b02 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 28 Jan 2017 13:13:03 +0000
Subject: [PATCH 021/119] * Level Platter system added to Record/NiGHTS Attack
 modes. * Made quality-of-life improvement for starting the player off on the
 "right" map more reliable.

---
 src/d_netcmd.c      |   2 +
 src/m_menu.c        | 218 ++++++++++++++++++++++++++++++++++++--------
 src/sdl/i_video.c   |   2 +-
 src/sdl12/i_video.c |   2 +-
 src/win32/win_vid.c |   2 +-
 5 files changed, 183 insertions(+), 43 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 55a3b30f9..1cc1adf8b 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1532,6 +1532,8 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	// The supplied data are assumed to be good.
 	I_Assert(delay >= 0 && delay <= 2);
 
+	CV_SetValue(&cv_nextmap, mapnum);
+
 	CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
 	           mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene);
 
diff --git a/src/m_menu.c b/src/m_menu.c
index c9d8d0ca9..6895f674f 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -238,7 +238,9 @@ menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
 
 // Single Player
 static void M_LoadGame(INT32 choice);
+static void M_TimeAttackLevelSelect(INT32 choice);
 static void M_TimeAttack(INT32 choice);
+static void M_NightsAttackLevelSelect(INT32 choice);
 static void M_NightsAttack(INT32 choice);
 static void M_Statistics(INT32 choice);
 static void M_ReplayTimeAttack(INT32 choice);
@@ -354,6 +356,7 @@ static void M_HandleFogColor(INT32 choice);
 static void M_HandleVideoMode(INT32 choice);
 
 // Consvar onchange functions
+static boolean M_SetNextMapOnPlatter(void);
 static void Nextmap_OnChange(void);
 static void Newgametype_OnChange(void);
 static void Dummymares_OnChange(void);
@@ -666,10 +669,10 @@ static menuitem_t SR_EmblemHintMenu[] =
 // Single Player Main
 static menuitem_t SP_MainMenu[] =
 {
-	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,        92},
-	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,     100},
-	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,   108},
-	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics",    M_Statistics,     116},
+	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 92},
+	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttackLevelSelect,   100},
+	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttackLevelSelect, 108},
+	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics",    M_Statistics,              116},
 };
 
 enum
@@ -692,6 +695,12 @@ static menuitem_t SP_LevelSelectMenu[] =
 	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
 };
 
+// Single Player Time Attack Level Select
+static menuitem_t SP_TimeAttackLevelSelectMenu[] =
+{
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
+};
+
 // Single Player Time Attack
 static menuitem_t SP_TimeAttackMenu[] =
 {
@@ -784,6 +793,12 @@ static menuitem_t SP_NightsGhostMenu[] =
 	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",       &SP_NightsAttackDef,  50}
 };
 
+// Single Player Nights Attack Level Select
+static menuitem_t SP_NightsAttackLevelSelectMenu[] =
+{
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
+};
+
 // Single Player Nights Attack
 static menuitem_t SP_NightsAttackMenu[] =
 {
@@ -1478,7 +1493,7 @@ menu_t SP_LevelSelectDef =
 {
 	NULL,
 	sizeof (SP_LevelSelectMenu)/sizeof (menuitem_t),
-	&SP_LoadDef,
+	&MainDef,  // Doesn't matter.
 	SP_LevelSelectMenu,
 	M_DrawLevelPlatterMenu,
 	0, 0,
@@ -1509,6 +1524,17 @@ menu_t SP_LevelStatsDef =
 	NULL
 };
 
+menu_t SP_TimeAttackLevelSelectDef =
+{
+	"M_ATTACK",
+	sizeof (SP_TimeAttackLevelSelectMenu)/sizeof (menuitem_t),
+	&MainDef,  // Doesn't matter.
+	SP_TimeAttackLevelSelectMenu,
+	M_DrawLevelPlatterMenu,
+	0, 0,
+	0,
+	NULL
+};
 static menu_t SP_TimeAttackDef =
 {
 	"M_ATTACK",
@@ -1518,7 +1544,7 @@ static menu_t SP_TimeAttackDef =
 	M_DrawTimeAttackMenu,
 	32, 40,
 	0,
-	NULL
+	M_SetNextMapOnPlatter
 };
 static menu_t SP_ReplayDef =
 {
@@ -1554,6 +1580,17 @@ static menu_t SP_GhostDef =
 	NULL
 };
 
+menu_t SP_NightsAttackLevelSelectDef =
+{
+	"M_NIGHTS", // HAMALAYAN
+	sizeof (SP_NightsAttackLevelSelectMenu)/sizeof (menuitem_t),
+	&MainDef,  // Doesn't matter.
+	SP_NightsAttackLevelSelectMenu,
+	M_DrawLevelPlatterMenu,
+	0, 0,
+	0,
+	NULL
+};
 static menu_t SP_NightsAttackDef =
 {
 	"M_NIGHTS",
@@ -1563,7 +1600,7 @@ static menu_t SP_NightsAttackDef =
 	M_DrawNightsAttackMenu,
 	32, 40,
 	0,
-	NULL
+	M_SetNextMapOnPlatter
 };
 static menu_t SP_NightsReplayDef =
 {
@@ -1775,6 +1812,7 @@ static void Nextmap_OnChange(void)
 
 	if (currentMenu == &SP_NightsAttackDef)
 	{
+		M_SetNextMapOnPlatter();
 		CV_StealthSetValue(&cv_dummymares, 0);
 		// Hide the record changing CVAR if only one mare is available.
 		if (!nightsrecords[cv_nextmap.value-1] || nightsrecords[cv_nextmap.value-1]->nummares < 2)
@@ -2414,7 +2452,7 @@ boolean M_Responder(event_t *ev)
 					multiplayer = false;
 				}
 
-				if (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef)
+				if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef))
 				{
 					// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
 					menuactive = false;
@@ -3604,6 +3642,27 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 	return rows;
 }
 
+static boolean M_SetNextMapOnPlatter(void)
+{
+	INT32 row, col = 0;
+	while (col < 3)
+	{
+		row = 0;
+		while (row < levelselect.numrows)
+		{
+			if (levelselect.rows[row].maplist[col] == cv_nextmap.value)
+			{
+				lsrow = row;
+				lscol = col;
+				return true;
+			}
+			row++;
+		}
+		col++;
+	}
+	return true;
+}
+
 //
 // M_PrepareLevelPlatter
 //
@@ -3613,7 +3672,7 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 static boolean M_PrepareLevelPlatter(INT32 gt)
 {
 	INT32 numrows = M_CountRowsToShowOnPlatter(gt);
-	INT32 mapnum, desiredmap, col = 0, row = 0;
+	INT32 mapnum, col = 0, row = 0;
 
 	if (!numrows)
 		return false;
@@ -3630,8 +3689,6 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 	// done here so lsrow and lscol can be set if cv_nextmap is on the platter
 	lsrow = lscol = lstic = lshli = lsoffs[0] = lsoffs[1] = 0;
 
-	desiredmap = ((Playing()) ? gamemap: cv_nextmap.value);
-
 	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
 	{
 		if (M_CanShowLevelOnPlatter(mapnum, gt))
@@ -3655,7 +3712,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 			levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
 			levelselect.rows[row].mapavailable[col] = M_LevelAvailableOnPlatter(mapnum);
 
-			if (desiredmap == mapnum+1) // A little quality of life improvement.
+			if (cv_nextmap.value == mapnum+1) // A little quality of life improvement.
 			{
 				lsrow = row;
 				lscol = col;
@@ -3810,10 +3867,17 @@ static void M_HandleLevelPlatter(INT32 choice)
 		case KEY_ENTER:
 			selectvalnextmapnobrace(lscol)
 
-				M_LevelSelectWarp(0);
-
 				lsoffs[0] = lsoffs[1] = 0;
 				S_StartSound(NULL,sfx_menu1);
+				if (gamestate == GS_TIMEATTACK)
+				{
+					if (currentMenu == &SP_TimeAttackLevelSelectDef)
+						M_TimeAttack(-1);
+					else
+						M_NightsAttack(-1);
+				}
+				else
+					M_LevelSelectWarp(0);
 			}
 			else if (!lsoffs[0]) //  prevent sound spam
 			{
@@ -3833,7 +3897,16 @@ static void M_HandleLevelPlatter(INT32 choice)
 	if (exitmenu)
 	{
 		if (currentMenu->prevMenu)
-			M_SetupNextMenu (currentMenu->prevMenu);
+		{
+			if (gamestate == GS_TIMEATTACK)
+			{
+				// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
+				menuactive = false;
+				D_StartTitle();
+			}
+			else
+				M_SetupNextMenu (currentMenu->prevMenu);
+		}
 		else
 			M_ClearMenus(true);
 	}
@@ -3917,6 +3990,9 @@ static void M_DrawLevelPlatterMenu(void)
 	if (++lstic == 32)
 		lstic = 0;
 
+	if (gamestate == GS_TIMEATTACK)
+		V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+
 	// finds row at top of the screen
 	while (y > 0)
 	{
@@ -3948,6 +4024,8 @@ static void M_DrawLevelPlatterMenu(void)
 		lsoffs[1] = 2*lsoffs[1]/3;
 	else
 		lsoffs[1] = 0;
+
+	M_DrawMenuTitle();
 }
 
 #undef basey
@@ -5825,33 +5903,63 @@ void M_DrawTimeAttackMenu(void)
 	}
 }
 
-// Going to Time Attack menu...
-static void M_TimeAttack(INT32 choice)
+static void M_TimeAttackLevelSelect(INT32 choice)
 {
 	(void)choice;
+	levellistmode = LLM_RECORDATTACK;
 
-	memset(skins_cons_t, 0, sizeof (skins_cons_t));
-
-	levellistmode = LLM_RECORDATTACK; // Don't be dependent on cv_newgametype
-
-	if (M_CountLevelsToShowInList() == 0)
+	if (!M_PrepareLevelPlatter(-1))
 	{
 		M_StartMessage(M_GetText("No record-attackable levels found.\n"),NULL,MM_NOTHING);
 		return;
 	}
 
+	memset(skins_cons_t, 0, sizeof (skins_cons_t));
 	M_PatchSkinNameTable();
 
-	M_PrepareLevelSelect();
-	M_SetupNextMenu(&SP_TimeAttackDef);
-	Nextmap_OnChange();
-
-	itemOn = tastart; // "Start" is selected.
+	M_SetupNextMenu(&SP_TimeAttackLevelSelectDef);
 
 	G_SetGamestate(GS_TIMEATTACK);
 	S_ChangeMusicInternal("_inter", true);
 }
 
+// Going to Time Attack menu...
+static void M_TimeAttack(INT32 choice)
+{
+	const boolean direct = (choice != -1); // Are we coming from SP_TimeAtttackLevelSelect?
+
+	if (direct)
+	{
+		SP_TimeAttackDef.prevMenu = &MainDef;
+		levellistmode = LLM_RECORDATTACK; // Don't be dependent on cv_newgametype
+
+		if (M_CountLevelsToShowInList() == 0)
+		{
+			M_StartMessage(M_GetText("No record-attackable levels found.\n"),NULL,MM_NOTHING);
+			return;
+		}
+
+		memset(skins_cons_t, 0, sizeof (skins_cons_t));
+		M_PatchSkinNameTable();
+
+		M_PrepareLevelSelect();
+	}
+	else
+		SP_TimeAttackDef.prevMenu = currentMenu;
+
+	M_SetupNextMenu(&SP_TimeAttackDef);
+
+	if (direct)
+	{
+		Nextmap_OnChange();
+
+		G_SetGamestate(GS_TIMEATTACK);
+		S_ChangeMusicInternal("_inter", true);
+	}
+
+	itemOn = tastart; // "Start" is selected.
+}
+
 // Drawing function for Nights Attack
 void M_DrawNightsAttackMenu(void)
 {
@@ -5957,34 +6065,64 @@ void M_DrawNightsAttackMenu(void)
 	}
 }
 
-// Going to Nights Attack menu...
-static void M_NightsAttack(INT32 choice)
+static void M_NightsAttackLevelSelect(INT32 choice)
 {
 	(void)choice;
+	levellistmode = LLM_NIGHTSATTACK;
 
-	memset(skins_cons_t, 0, sizeof (skins_cons_t));
-
-	levellistmode = LLM_NIGHTSATTACK; // Don't be dependent on cv_newgametype
-
-	if (M_CountLevelsToShowInList() == 0)
+	if (!M_PrepareLevelPlatter(-1))
 	{
 		M_StartMessage(M_GetText("No NiGHTS-attackable levels found.\n"),NULL,MM_NOTHING);
 		return;
 	}
 
-	// This is really just to make sure Sonic is the played character, just in case
+	memset(skins_cons_t, 0, sizeof (skins_cons_t));
 	M_PatchSkinNameTable();
 
-	M_PrepareLevelSelect();
-	M_SetupNextMenu(&SP_NightsAttackDef);
-	Nextmap_OnChange();
-
-	itemOn = nastart; // "Start" is selected.
+	M_SetupNextMenu(&SP_NightsAttackLevelSelectDef);
 
 	G_SetGamestate(GS_TIMEATTACK);
 	S_ChangeMusicInternal("_inter", true);
 }
 
+// Going to Nights Attack menu...
+static void M_NightsAttack(INT32 choice)
+{
+	const boolean direct = (choice != -1); // Are we coming from SP_TimeAtttackLevelSelect?
+
+	if (direct)
+	{
+		SP_NightsAttackDef.prevMenu = &MainDef;
+		levellistmode = LLM_NIGHTSATTACK; // Don't be dependent on cv_newgametype
+
+		if (M_CountLevelsToShowInList() == 0)
+		{
+			M_StartMessage(M_GetText("No NiGHTS-attackable levels found.\n"),NULL,MM_NOTHING);
+			return;
+		}
+
+		// This is really just to make sure Sonic is the played character, just in case
+		memset(skins_cons_t, 0, sizeof (skins_cons_t));
+		M_PatchSkinNameTable();
+
+		M_PrepareLevelSelect();
+	}
+	else
+		SP_NightsAttackDef.prevMenu = currentMenu;
+
+	M_SetupNextMenu(&SP_NightsAttackDef);
+
+	if (direct)
+	{
+		Nextmap_OnChange();
+
+		G_SetGamestate(GS_TIMEATTACK);
+		S_ChangeMusicInternal("_inter", true);
+	}
+
+	itemOn = nastart; // "Start" is selected.
+}
+
 // Player has selected the "START" from the nights attack screen
 static void M_ChooseNightsAttack(INT32 choice)
 {
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index aa572e6e0..7d33f2554 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -909,7 +909,7 @@ static inline boolean I_SkipFrame(void)
 		case GS_LEVEL:
 			if (!paused)
 				return false;
-		case GS_TIMEATTACK:
+		//case GS_TIMEATTACK: -- sorry optimisation but now we have a cool level platter and that being laggardly looks terrible
 		case GS_WAITINGPLAYERS:
 			return skip; // Skip odd frames
 		default:
diff --git a/src/sdl12/i_video.c b/src/sdl12/i_video.c
index 197924eda..1fa80e7d4 100644
--- a/src/sdl12/i_video.c
+++ b/src/sdl12/i_video.c
@@ -1311,7 +1311,7 @@ static inline boolean I_SkipFrame(void)
 		case GS_LEVEL:
 			if (!paused)
 				return false;
-		case GS_TIMEATTACK:
+		//case GS_TIMEATTACK: -- sorry optimisation but now we have a cool level platter and that being laggardly looks terrible
 		case GS_WAITINGPLAYERS:
 			return skip; // Skip odd frames
 		default:
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index 0960bb6dd..31d1b8120 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -322,7 +322,7 @@ static inline boolean I_SkipFrame(void)
 		case GS_LEVEL:
 			if (!paused)
 				return false;
-		case GS_TIMEATTACK:
+		//case GS_TIMEATTACK: -- sorry optimisation but now we have a cool level platter and that being laggardly looks terrible
 #ifndef CLIENT_LOADINGSCREEN
 		case GS_WAITINGPLAYERS:
 #endif

From df3ba302634c32553b59530dd3d27abd077a3e4f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 29 Jan 2017 19:25:43 +0000
Subject: [PATCH 022/119] * Revamped Time and Nights attack.

Still need to figure out the way to sort pressing esc on reselect level sending you back to the title screen. This will need further whittling before I can call this a solved section and move onto MP stuff.
---
 src/m_menu.c | 469 +++++++++++++++++++++++++++------------------------
 1 file changed, 247 insertions(+), 222 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 6895f674f..d2406b655 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -188,6 +188,7 @@ static INT32 vidm_column_size;
 // PROTOTYPES
 //
 
+static void M_GoBack(INT32 choice);
 static void M_StopMessage(INT32 choice);
 
 #ifndef NONET
@@ -205,6 +206,23 @@ menu_t MessageDef;
 
 menu_t SPauseDef;
 
+// Level Select
+static levelselect_t levelselect = {0, NULL};
+static UINT8 levelselectselect[4];
+static patch_t *levselp[4];
+static INT32 lsoffs[2];
+
+#define lsrow levelselectselect[0]
+#define lscol levelselectselect[1]
+#define lstic levelselectselect[2]
+#define lshli levelselectselect[3]
+
+#define lshseperation 101
+#define lsbasevseperation 62
+#define lsheadingheight 16
+#define getheadingoffset(row) (levelselect.rows[row].header[0] ? lsheadingheight : 0)
+#define lsvseperation(row) lsbasevseperation + getheadingoffset(row)
+
 // Sky Room
 static void M_CustomLevelSelect(INT32 choice);
 static void M_CustomWarp(INT32 choice);
@@ -356,7 +374,6 @@ static void M_HandleFogColor(INT32 choice);
 static void M_HandleVideoMode(INT32 choice);
 
 // Consvar onchange functions
-static boolean M_SetNextMapOnPlatter(void);
 static void Nextmap_OnChange(void);
 static void Newgametype_OnChange(void);
 static void Dummymares_OnChange(void);
@@ -704,18 +721,18 @@ static menuitem_t SP_TimeAttackLevelSelectMenu[] =
 // Single Player Time Attack
 static menuitem_t SP_TimeAttackMenu[] =
 {
-	{IT_STRING|IT_CVAR,        NULL, "Level",      &cv_nextmap,          52},
-	{IT_STRING|IT_CVAR,        NULL, "Player",     &cv_chooseskin,       62},
+	{IT_STRING|IT_CALL,        NULL, "Reselect Level",  &M_GoBack,           52},
+	{IT_STRING|IT_CVAR,        NULL, "Character",       &cv_chooseskin,      62},
 
 	{IT_DISABLED,              NULL, "Guest Option...", &SP_GuestReplayDef, 100},
-	{IT_DISABLED,              NULL, "Replay...",     &SP_ReplayDef,        110},
-	{IT_DISABLED,              NULL, "Ghosts...",     &SP_GhostDef,         120},
+	{IT_DISABLED,              NULL, "Replay...",       &SP_ReplayDef,      110},
+	{IT_DISABLED,              NULL, "Ghosts...",       &SP_GhostDef,       120},
 	{IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED,   NULL, "Start",         M_ChooseTimeAttack,   130},
 };
 
 enum
 {
-	talevel,
+	talevelback,
 	taplayer,
 
 	taguest,
@@ -802,18 +819,18 @@ static menuitem_t SP_NightsAttackLevelSelectMenu[] =
 // Single Player Nights Attack
 static menuitem_t SP_NightsAttackMenu[] =
 {
-	{IT_STRING|IT_CVAR,        NULL, "Level",            &cv_nextmap,          44},
-	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,       54},
+	{IT_STRING|IT_CALL,        NULL, "Reselect Level",   &M_GoBack,                   52},
+	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,              62},
 
-	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,   108},
-	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,        118},
-	{IT_DISABLED,              NULL, "Ghosts...",        &SP_NightsGhostDef,         128},
-	{IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED,   NULL, "Start",            M_ChooseNightsAttack, 138},
+	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,   100},
+	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,        110},
+	{IT_DISABLED,              NULL, "Ghosts...",        &SP_NightsGhostDef,         120},
+	{IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED,   NULL, "Start",            M_ChooseNightsAttack, 130},
 };
 
 enum
 {
-	nalevel,
+	nalevelback,
 	narecords,
 
 	naguest,
@@ -1544,7 +1561,7 @@ static menu_t SP_TimeAttackDef =
 	M_DrawTimeAttackMenu,
 	32, 40,
 	0,
-	M_SetNextMapOnPlatter
+	NULL
 };
 static menu_t SP_ReplayDef =
 {
@@ -1600,7 +1617,7 @@ static menu_t SP_NightsAttackDef =
 	M_DrawNightsAttackMenu,
 	32, 40,
 	0,
-	M_SetNextMapOnPlatter
+	NULL
 };
 static menu_t SP_NightsReplayDef =
 {
@@ -1812,7 +1829,6 @@ static void Nextmap_OnChange(void)
 
 	if (currentMenu == &SP_NightsAttackDef)
 	{
-		M_SetNextMapOnPlatter();
 		CV_StealthSetValue(&cv_dummymares, 0);
 		// Hide the record changing CVAR if only one mare is available.
 		if (!nightsrecords[cv_nextmap.value-1] || nightsrecords[cv_nextmap.value-1]->nummares < 2)
@@ -2023,6 +2039,34 @@ menu_t *currentMenu = &MainDef;
 // BASIC MENU HANDLING
 // =========================================================================
 
+static void M_GoBack(INT32 choice)
+{
+	(void)choice;
+
+	if (currentMenu->prevMenu)
+	{
+		//If we entered the game search menu, but didn't enter a game,
+		//make sure the game doesn't still think we're in a netgame.
+		if (!Playing() && netgame && multiplayer)
+		{
+			MSCloseUDPSocket();		// Clean up so we can re-open the connection later.
+			netgame = false;
+			multiplayer = false;
+		}
+
+		if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef))
+		{
+			// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
+			menuactive = false;
+			D_StartTitle();
+		}
+		else
+			M_SetupNextMenu(currentMenu->prevMenu);
+	}
+	else
+		M_ClearMenus(true);
+}
+
 static void M_ChangeCvar(INT32 choice)
 {
 	consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
@@ -2441,28 +2485,8 @@ boolean M_Responder(event_t *ev)
 		case KEY_ESCAPE:
 			noFurtherInput = true;
 			currentMenu->lastOn = itemOn;
-			if (currentMenu->prevMenu)
-			{
-				//If we entered the game search menu, but didn't enter a game,
-				//make sure the game doesn't still think we're in a netgame.
-				if (!Playing() && netgame && multiplayer)
-				{
-					MSCloseUDPSocket();		// Clean up so we can re-open the connection later.
-					netgame = false;
-					multiplayer = false;
-				}
 
-				if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef))
-				{
-					// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
-					menuactive = false;
-					D_StartTitle();
-				}
-				else
-					M_SetupNextMenu(currentMenu->prevMenu);
-			}
-			else
-				M_ClearMenus(true);
+			M_GoBack(0);
 
 			return true;
 
@@ -3485,23 +3509,6 @@ static void M_PatchSkinNameTable(void)
 	return;
 }
 
-// Handle Level Select
-static levelselect_t levelselect = {0, NULL};
-static UINT8 levelselectselect[4];
-static patch_t *levselp[4];
-static INT32 lsoffs[2];
-
-#define lsrow levelselectselect[0]
-#define lscol levelselectselect[1]
-#define lstic levelselectselect[2]
-#define lshli levelselectselect[3]
-
-#define hseperation 101
-#define basevseperation 62
-#define headingheight 16
-#define getheadingoffset(row) (levelselect.rows[row].header[0] ? headingheight : 0)
-#define vseperation(row) basevseperation + getheadingoffset(row)
-
 //
 // M_LevelAvailableOnPlatter
 //
@@ -3642,6 +3649,7 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 	return rows;
 }
 
+#if 0
 static boolean M_SetNextMapOnPlatter(void)
 {
 	INT32 row, col = 0;
@@ -3662,6 +3670,7 @@ static boolean M_SetNextMapOnPlatter(void)
 	}
 	return true;
 }
+#endif
 
 //
 // M_PrepareLevelPlatter
@@ -3796,7 +3805,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 			if (lsrow == levelselect.numrows)
 				lsrow = 0;
 
-			lsoffs[0] = vseperation(lsrow);
+			lsoffs[0] = lsvseperation(lsrow);
 
 			if (levelselect.rows[lsrow].header[0])
 				lshli = lsrow;
@@ -3808,7 +3817,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 			break;
 
 		case KEY_UPARROW:
-			lsoffs[0] = -vseperation(lsrow);
+			lsoffs[0] = -lsvseperation(lsrow);
 
 			lsrow--;
 			if (lsrow == UINT8_MAX)
@@ -3835,7 +3844,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 			{
 				lscol--;
 
-				lsoffs[1] = hseperation;
+				lsoffs[1] = lshseperation;
 				S_StartSound(NULL,sfx_s3kb7);
 
 				selectvalnextmap(lscol) else selectvalnextmap(0)
@@ -3852,7 +3861,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 			{
 				lscol++;
 
-				lsoffs[1] = -hseperation;
+				lsoffs[1] = -lshseperation;
 				S_StartSound(NULL,sfx_s3kb7);
 
 				selectvalnextmap(lscol) else selectvalnextmap(0)
@@ -3912,80 +3921,86 @@ static void M_HandleLevelPlatter(INT32 choice)
 	}
 }
 
+static void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlight)
+{
+	y += lsheadingheight - 12;
+	V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0), header);
+	y += 9;
+	if ((y >= 0) && (y < 200))
+	{
+		V_DrawFill(19, y, 281, 1, (headerhighlight ? yellowmap[3] : 3));
+		V_DrawFill(300, y, 1, 1, 26);
+	}
+	y++;
+	if ((y >= 0) && (y < 200))
+	{
+		V_DrawFill(19, y, 282, 1, 26);
+	}
+	y += 2;
+}
+
+static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
+{
+	patch_t *patch;
+
+	INT32 map = levelselect.rows[row].maplist[col];
+	if (!map)
+		return;
+
+	//  A 160x100 image of the level as entry MAPxxP
+	if (!(levelselect.rows[row].mapavailable[col]))
+		patch = ((lstic & 1) ? levselp[2] : levselp[3]); // static - make secret maps look ENTICING
+	else if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
+		patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
+	else
+		patch = levselp[2]; // don't flash to indicate that it's just a normal level
+
+	V_DrawSmallScaledPatch(x, y, 0, patch);
+
+	if ((y+50) < 200)
+	{
+		INT32 topy = (y+50), h = 8;
+
+		if (topy < 0)
+		{
+			h += topy;
+			topy = 0;
+		}
+		else if (topy + h >= 200)
+			h = 200 - y;
+		if (h > 0)
+			V_DrawFill(x, topy, 80, h,
+			((mapheaderinfo[map-1]->unlockrequired < 0)
+			? 159 : 63));
+	}
+
+	if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
+		V_DrawThinString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+	else
+		V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+}
+
 static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 {
 	UINT8 col;
 	const boolean rowhighlight = (row == lsrow);
 	if (levelselect.rows[row].header[0])
 	{
-		const boolean headerhighlight = (rowhighlight || (row == lshli));
-
-		y += headingheight - 12;
-		V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0), levelselect.rows[row].header);
-		y += 9;
-		if ((y >= 0) && (y < 200))
-		{
-			V_DrawFill(19, y, 281, 1, (headerhighlight ? yellowmap[3] : 3));
-			V_DrawFill(300, y, 1, 1, 26);
-		}
-		y++;
-		if ((y >= 0) && (y < 200))
-		{
-			V_DrawFill(19, y, 282, 1, 26);
-		}
-		y += 2;
+		M_DrawLevelPlatterHeader(y, levelselect.rows[row].header, (rowhighlight || (row == lshli)));
+		y += lsheadingheight;
 	}
 
 	for (col = 0; col < 3; col++)
-	{
-		INT32 x = 19+(col*hseperation);
-		patch_t *patch;
-
-		INT32 map = levelselect.rows[row].maplist[col];
-		if (!map)
-			continue;
-
-		//  A 160x100 image of the level as entry MAPxxP
-		if (!(levelselect.rows[row].mapavailable[col]))
-			patch = ((lstic & 1) ? levselp[2] : levselp[3]); // static - make secret maps look ENTICING
-		else if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
-			patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
-		else
-			patch = levselp[2]; // don't flash to indicate that it's just a normal level
-
-		V_DrawSmallScaledPatch(x, y, 0, patch);
-
-		if ((y+50) < 200)
-		{
-			INT32 topy = (y+50), h = 8;
-
-			if (topy < 0)
-			{
-				h += topy;
-				topy = 0;
-			}
-			else if (topy + h >= 200)
-				h = 200 - y;
-			if (h > 0)
-				V_DrawFill(x, topy, 80, h,
-				((mapheaderinfo[map-1]->unlockrequired < 0)
-				? 159 : 63));
-		}
-
-		if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
-			V_DrawThinString(x, y+50, ((rowhighlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
-		else
-			V_DrawString(x, y+50, ((rowhighlight && col == lscol) ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
-	}
+		M_DrawLevelPlatterMap(row, col, 19+(col*lshseperation), y, (rowhighlight && col == lscol));
 }
 
-#define basey 59+headingheight
+#define lsbasey 59+lsheadingheight
 
 static void M_DrawLevelPlatterMenu(void)
 {
 	UINT8 iter = lsrow;
-	INT32 y = basey + lsoffs[0] - getheadingoffset(lsrow);
-	const UINT32 cursorx = 19+(lscol*hseperation);
+	INT32 y = lsbasey + lsoffs[0] - getheadingoffset(lsrow);
+	const UINT32 cursorx = 19+(lscol*lshseperation);
 
 	if (++lstic == 32)
 		lstic = 0;
@@ -3997,22 +4012,22 @@ static void M_DrawLevelPlatterMenu(void)
 	while (y > 0)
 	{
 		iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
-		y -= vseperation(iter);
+		y -= lsvseperation(iter);
 	}
 
 	// draw from top to bottom
 	while (y < 200)
 	{
 		M_DrawLevelPlatterRow(iter, y);
-		y += vseperation(iter);
+		y += lsvseperation(iter);
 		iter = ((iter == levelselect.numrows-1) ? 0 : iter+1);
 	}
 
 	// draw cursor box
-	V_DrawSmallScaledPatch(cursorx + lsoffs[1], basey, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
+	V_DrawSmallScaledPatch(cursorx + lsoffs[1], lsbasey, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
 
 	if (levelselect.rows[lsrow].maplist[lscol])
-		V_DrawScaledPatch(cursorx-17, basey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+		V_DrawScaledPatch(cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
 
 	// handle movement of cursor box
 	if (abs(lsoffs[0]) > 1)
@@ -4028,18 +4043,7 @@ static void M_DrawLevelPlatterMenu(void)
 	M_DrawMenuTitle();
 }
 
-#undef basey
-
-#undef lsrow
-#undef lscol
-#undef lstic
-#undef lshli
-
-#undef hseperation
-#undef basevseperation
-#undef headingheight
-#undef getheadingoffset
-#undef vseperation
+#undef lsbasey
 
 // Call before showing any level-select menus (Not necessary for platter-based ones)
 static void M_PrepareLevelSelect(void)
@@ -5814,7 +5818,7 @@ void M_DrawTimeAttackMenu(void)
 	else
 		PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
 
-	V_DrawSmallScaledPatch(208, 32, 0, PictureOfLevel);
+	V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
 
 	// Character face!
 	if (W_CheckNumForName(skins[cv_chooseskin.value-1].charsel) != LUMPERROR)
@@ -5832,15 +5836,17 @@ void M_DrawTimeAttackMenu(void)
 		emblem_t *em;
 		INT32 yHeight;
 
-		V_DrawCenteredString(104, 32, 0, "* LEVEL RECORDS *");
+		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
+
+		V_DrawCenteredString(104, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
 			sprintf(beststr, "(none)");
 		else
 			sprintf(beststr, "%u", mainrecords[cv_nextmap.value-1]->score);
 
-		V_DrawString(104-72, 48, V_YELLOWMAP, "SCORE:");
-		V_DrawRightAlignedString(104+72, 48, V_ALLOWLOWERCASE, beststr);
+		V_DrawString(104-72, 48+lsheadingheight/2, V_YELLOWMAP, "SCORE:");
+		V_DrawRightAlignedString(104+72, 48+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time)
 			sprintf(beststr, "(none)");
@@ -5849,16 +5855,16 @@ void M_DrawTimeAttackMenu(void)
 			                                 G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time),
 			                                 G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time));
 
-		V_DrawString(104-72, 58, V_YELLOWMAP, "TIME:");
-		V_DrawRightAlignedString(104+72, 58, V_ALLOWLOWERCASE, beststr);
+		V_DrawString(104-72, 58+lsheadingheight/2, V_YELLOWMAP, "TIME:");
+		V_DrawRightAlignedString(104+72, 58+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->rings)
 			sprintf(beststr, "(none)");
 		else
 			sprintf(beststr, "%hu", mainrecords[cv_nextmap.value-1]->rings);
 
-		V_DrawString(104-72, 68, V_YELLOWMAP, "RINGS:");
-		V_DrawRightAlignedString(104+72, 68, V_ALLOWLOWERCASE, beststr);
+		V_DrawString(104-72, 68+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
+		V_DrawRightAlignedString(104+72, 68+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
 
 		// Draw record emblems.
 		em = M_GetLevelEmblems(cv_nextmap.value);
@@ -5874,17 +5880,17 @@ void M_DrawTimeAttackMenu(void)
 			}
 
 			if (em->collected)
-				V_DrawSmallMappedPatch(104+76, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
+				V_DrawSmallMappedPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 			else
-				V_DrawSmallScaledPatch(104+76, yHeight, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+				V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
 
 			skipThisOne:
 			em = M_GetLevelEmblems(-1);
 		}
 	}
 
-	// ALWAYS DRAW level name and skin even when not on this menu!
+	// ALWAYS DRAW level and skin even when not on this menu!
 	if (currentMenu != &SP_TimeAttackDef)
 	{
 		consvar_t *ncv;
@@ -5892,14 +5898,11 @@ void M_DrawTimeAttackMenu(void)
 		x = SP_TimeAttackDef.x;
 		y = SP_TimeAttackDef.y;
 
-		for (i = 0; i < 2; ++i)
-		{
-			ncv = (consvar_t *)SP_TimeAttackMenu[i].itemaction;
+		V_DrawString(x, y + SP_TimeAttackMenu[talevelback].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[talevelback].text);
 
-			V_DrawString(x, y + SP_TimeAttackMenu[i].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[i].text);
-			V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0),
-			             y + SP_TimeAttackMenu[i].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
-		}
+		ncv = (consvar_t *)SP_TimeAttackMenu[taplayer].itemaction;
+		V_DrawString(x, y + SP_TimeAttackMenu[taplayer].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[taplayer].text);
+		V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0), y + SP_TimeAttackMenu[taplayer].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
 	}
 }
 
@@ -5914,7 +5917,6 @@ static void M_TimeAttackLevelSelect(INT32 choice)
 		return;
 	}
 
-	memset(skins_cons_t, 0, sizeof (skins_cons_t));
 	M_PatchSkinNameTable();
 
 	M_SetupNextMenu(&SP_TimeAttackLevelSelectDef);
@@ -5939,7 +5941,6 @@ static void M_TimeAttack(INT32 choice)
 			return;
 		}
 
-		memset(skins_cons_t, 0, sizeof (skins_cons_t));
 		M_PatchSkinNameTable();
 
 		M_PrepareLevelSelect();
@@ -5948,11 +5949,10 @@ static void M_TimeAttack(INT32 choice)
 		SP_TimeAttackDef.prevMenu = currentMenu;
 
 	M_SetupNextMenu(&SP_TimeAttackDef);
+	Nextmap_OnChange();
 
 	if (direct)
 	{
-		Nextmap_OnChange();
-
 		G_SetGamestate(GS_TIMEATTACK);
 		S_ChangeMusicInternal("_inter", true);
 	}
@@ -5963,6 +5963,8 @@ static void M_TimeAttack(INT32 choice)
 // Drawing function for Nights Attack
 void M_DrawNightsAttackMenu(void)
 {
+	INT32 i, x, y, cursory = 0;
+	UINT16 dispstatus;
 	patch_t *PictureOfLevel;
 	lumpnum_t lumpnum;
 	char beststr[40];
@@ -5971,8 +5973,43 @@ void M_DrawNightsAttackMenu(void)
 
 	V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
 
+	M_DrawMenuTitle();
+
 	// draw menu (everything else goes on top of it)
-	M_DrawGenericMenu();
+	// Sadly we can't just use generic mode menus because we need some extra hacks
+	x = currentMenu->x;
+	y = currentMenu->y;
+
+	for (i = 0; i < currentMenu->numitems; ++i)
+	{
+		dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY);
+		if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING)
+			continue;
+
+		y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
+		if (i == itemOn)
+			cursory = y;
+
+		V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? V_YELLOWMAP : 0 , currentMenu->menuitems[i].text);
+
+		// Cvar specific handling
+		if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
+		{
+			consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+			INT32 soffset = 0;
+
+			// hack to keep the menu from overlapping the overall grade icon
+			if (currentMenu != &SP_NightsAttackDef)
+				soffset = 80;
+
+			// Should see nothing but strings
+			V_DrawString(BASEVIDWIDTH - x - soffset - V_StringWidth(cv->string, 0), y, V_YELLOWMAP, cv->string);
+		}
+	}
+
+	// DRAW THE SKULL CURSOR
+	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 
 	//  A 160x100 image of the level as entry MAPxxP
 	lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
@@ -5982,7 +6019,7 @@ void M_DrawNightsAttackMenu(void)
 	else
 		PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
 
-	V_DrawSmallScaledPatch(90, 28, 0, PictureOfLevel);
+	V_DrawSmallScaledPatch(208, 32+lsheadingheight/2, 0, PictureOfLevel);
 
 	// Level record list
 	if (cv_nextmap.value)
@@ -5995,74 +6032,65 @@ void M_DrawNightsAttackMenu(void)
 		UINT32 bestscore	= G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value);
 		tic_t besttime		= G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value);
 
+		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
+
 		if (P_HasGrades(cv_nextmap.value, 0))
-			V_DrawScaledPatch(200, 28 + 8, 0, ngradeletters[bestoverall]);
+			V_DrawScaledPatch(224, 120, 0, ngradeletters[bestoverall]);
 
-		if (currentMenu == &SP_NightsAttackDef)
-		{
-			if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
+		if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
 			{
-				V_DrawString(160-88, 112, V_YELLOWMAP, "BEST GRADE:");
-				V_DrawSmallScaledPatch(160 + 86 - (ngradeletters[bestgrade]->width/2),
-					112 + 8 - (ngradeletters[bestgrade]->height/2),
-					0, ngradeletters[bestgrade]);
-			}
-
-			if (!bestscore)
-				sprintf(beststr, "(none)");
-			else
-				sprintf(beststr, "%u", bestscore);
-
-			V_DrawString(160 - 88, 122, V_YELLOWMAP, "BEST SCORE:");
-			V_DrawRightAlignedString(160 + 88, 122, V_ALLOWLOWERCASE, beststr);
-
-			if (besttime == UINT32_MAX)
-				sprintf(beststr, "(none)");
-			else
-				sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(besttime, true),
-																				 G_TicsToSeconds(besttime),
-																				 G_TicsToCentiseconds(besttime));
-
-			V_DrawString(160-88, 132, V_YELLOWMAP, "BEST TIME:");
-			V_DrawRightAlignedString(160+88, 132, V_ALLOWLOWERCASE, beststr);
-
-			if (cv_dummymares.value == 0) {
-				// Draw record emblems.
-				em = M_GetLevelEmblems(cv_nextmap.value);
-				while (em)
-				{
-					switch (em->type)
-					{
-						case ET_NGRADE: yHeight = 112; break;
-						case ET_NTIME:  yHeight = 132; break;
-						default:
-							goto skipThisOne;
-					}
-
-					if (em->collected)
-						V_DrawSmallMappedPatch(160+88, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
-																	 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
-					else
-						V_DrawSmallScaledPatch(160+88, yHeight, 0, W_CachePatchName("NEEDIT", PU_CACHE));
-
-					skipThisOne:
-					em = M_GetLevelEmblems(-1);
-				}
-			}
+			V_DrawString(104 - 72, 48+lsheadingheight/2, V_YELLOWMAP, "BEST GRADE:");
+			V_DrawSmallScaledPatch(104 + 72 - (ngradeletters[bestgrade]->width/2),
+				48+lsheadingheight/2 + 8 - (ngradeletters[bestgrade]->height/2),
+				0, ngradeletters[bestgrade]);
 		}
-		// ALWAYS DRAW level name even when not on this menu!
-		else
-		{
-			consvar_t *ncv;
-			INT32 x = SP_NightsAttackDef.x;
-			INT32 y = SP_NightsAttackDef.y;
 
-			ncv = (consvar_t *)SP_NightsAttackMenu[0].itemaction;
-			V_DrawString(x, y + SP_NightsAttackMenu[0].alphaKey, V_TRANSLUCENT, SP_NightsAttackMenu[0].text);
-			V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0),
-									 y + SP_NightsAttackMenu[0].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
+		if (!bestscore)
+			sprintf(beststr, "(none)");
+		else
+			sprintf(beststr, "%u", bestscore);
+
+		V_DrawString(104 - 72, 58+lsheadingheight/2, V_YELLOWMAP, "BEST SCORE:");
+		V_DrawRightAlignedString(104 + 72, 58+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+
+		if (besttime == UINT32_MAX)
+			sprintf(beststr, "(none)");
+		else
+			sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(besttime, true),
+																			 G_TicsToSeconds(besttime),
+																			 G_TicsToCentiseconds(besttime));
+
+		V_DrawString(104 - 72, 68+lsheadingheight/2, V_YELLOWMAP, "BEST TIME:");
+		V_DrawRightAlignedString(104 + 72, 68+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+
+		if (cv_dummymares.value == 0) {
+			// Draw record emblems.
+			em = M_GetLevelEmblems(cv_nextmap.value);
+			while (em)
+			{
+				switch (em->type)
+				{
+					case ET_NGRADE: yHeight = 48; break;
+					case ET_NTIME:  yHeight = 68; break;
+					default:
+						goto skipThisOne;
+				}
+
+				if (em->collected)
+					V_DrawSmallMappedPatch(104+76, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
+																 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
+				else
+					V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+
+				skipThisOne:
+				em = M_GetLevelEmblems(-1);
+			}
 		}
 	}
+
+	// ALWAYS DRAW level even when not on this menu!
+	if (currentMenu != &SP_NightsAttackDef)
+		V_DrawString(SP_TimeAttackDef.x, SP_TimeAttackDef.y + SP_TimeAttackMenu[nalevelback].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[nalevelback].text);
 }
 
 static void M_NightsAttackLevelSelect(INT32 choice)
@@ -6076,7 +6104,6 @@ static void M_NightsAttackLevelSelect(INT32 choice)
 		return;
 	}
 
-	memset(skins_cons_t, 0, sizeof (skins_cons_t));
 	M_PatchSkinNameTable();
 
 	M_SetupNextMenu(&SP_NightsAttackLevelSelectDef);
@@ -6102,7 +6129,6 @@ static void M_NightsAttack(INT32 choice)
 		}
 
 		// This is really just to make sure Sonic is the played character, just in case
-		memset(skins_cons_t, 0, sizeof (skins_cons_t));
 		M_PatchSkinNameTable();
 
 		M_PrepareLevelSelect();
@@ -6111,11 +6137,10 @@ static void M_NightsAttack(INT32 choice)
 		SP_NightsAttackDef.prevMenu = currentMenu;
 
 	M_SetupNextMenu(&SP_NightsAttackDef);
+	Nextmap_OnChange();
 
 	if (direct)
 	{
-		Nextmap_OnChange();
-
 		G_SetGamestate(GS_TIMEATTACK);
 		S_ChangeMusicInternal("_inter", true);
 	}

From 8d3804a2016429dd65a9dd7f6063e48ca1a43afe Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 29 Jan 2017 22:00:07 +0000
Subject: [PATCH 023/119] * Renamed the confusing "Reselect Level" to "Back to
 Level Select". * Updated layout of NiGHTS attack page to match Records
 attack. * Improved code.

---
 src/m_menu.c | 69 +++++++++++++++++++++++++++-------------------------
 1 file changed, 36 insertions(+), 33 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index d2406b655..40a729906 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -721,7 +721,7 @@ static menuitem_t SP_TimeAttackLevelSelectMenu[] =
 // Single Player Time Attack
 static menuitem_t SP_TimeAttackMenu[] =
 {
-	{IT_STRING|IT_CALL,        NULL, "Reselect Level",  &M_GoBack,           52},
+	{IT_STRING|IT_CALL,        NULL, "Back to Level Select", &M_GoBack,      52},
 	{IT_STRING|IT_CVAR,        NULL, "Character",       &cv_chooseskin,      62},
 
 	{IT_DISABLED,              NULL, "Guest Option...", &SP_GuestReplayDef, 100},
@@ -819,13 +819,13 @@ static menuitem_t SP_NightsAttackLevelSelectMenu[] =
 // Single Player Nights Attack
 static menuitem_t SP_NightsAttackMenu[] =
 {
-	{IT_STRING|IT_CALL,        NULL, "Reselect Level",   &M_GoBack,                   52},
+	{IT_STRING|IT_CALL,        NULL, "Back to Level Select",  &M_GoBack,              52},
 	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,              62},
 
 	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,   100},
 	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,        110},
 	{IT_DISABLED,              NULL, "Ghosts...",        &SP_NightsGhostDef,         120},
-	{IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED,   NULL, "Start",            M_ChooseNightsAttack, 130},
+	{IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED, NULL, "Start", M_ChooseNightsAttack, 130},
 };
 
 enum
@@ -5764,9 +5764,7 @@ void M_DrawTimeAttackMenu(void)
 {
 	INT32 i, x, y, cursory = 0;
 	UINT16 dispstatus;
-	patch_t *PictureOfLevel, *PictureOfUrFace;
-	lumpnum_t lumpnum;
-	char beststr[40];
+	patch_t *PictureOfUrFace;
 
 	S_ChangeMusicInternal("_inter", true); // Eww, but needed for when user hits escape during demo playback
 
@@ -5810,16 +5808,6 @@ void M_DrawTimeAttackMenu(void)
 	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
 	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 
-	//  A 160x100 image of the level as entry MAPxxP
-	lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
-
-	if (lumpnum != LUMPERROR)
-		PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
-	else
-		PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
-
-	V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
-
 	// Character face!
 	if (W_CheckNumForName(skins[cv_chooseskin.value-1].charsel) != LUMPERROR)
 	{
@@ -5835,9 +5823,22 @@ void M_DrawTimeAttackMenu(void)
 	{
 		emblem_t *em;
 		INT32 yHeight;
+		patch_t *PictureOfLevel;
+		lumpnum_t lumpnum;
+		char beststr[40];
 
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
 
+		//  A 160x100 image of the level as entry MAPxxP
+		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+
+		if (lumpnum != LUMPERROR)
+			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
+		else
+			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+
+		V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
+
 		V_DrawCenteredString(104, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
@@ -5965,9 +5966,6 @@ void M_DrawNightsAttackMenu(void)
 {
 	INT32 i, x, y, cursory = 0;
 	UINT16 dispstatus;
-	patch_t *PictureOfLevel;
-	lumpnum_t lumpnum;
-	char beststr[40];
 
 	S_ChangeMusicInternal("_inter", true); // Eww, but needed for when user hits escape during demo playback
 
@@ -6011,21 +6009,14 @@ void M_DrawNightsAttackMenu(void)
 	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
 	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 
-	//  A 160x100 image of the level as entry MAPxxP
-	lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
-
-	if (lumpnum != LUMPERROR)
-		PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
-	else
-		PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
-
-	V_DrawSmallScaledPatch(208, 32+lsheadingheight/2, 0, PictureOfLevel);
-
 	// Level record list
 	if (cv_nextmap.value)
 	{
 		emblem_t *em;
 		INT32 yHeight;
+		patch_t *PictureOfLevel;
+		lumpnum_t lumpnum;
+		char beststr[40];
 
 		UINT8 bestoverall	= G_GetBestNightsGrade(cv_nextmap.value, 0);
 		UINT8 bestgrade		= G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value);
@@ -6034,14 +6025,26 @@ void M_DrawNightsAttackMenu(void)
 
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
 
+		//  A 160x100 image of the level as entry MAPxxP
+		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+
+		if (lumpnum != LUMPERROR)
+			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
+		else
+			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+
+		V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
+
+		V_DrawCenteredString(104, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
+
 		if (P_HasGrades(cv_nextmap.value, 0))
-			V_DrawScaledPatch(224, 120, 0, ngradeletters[bestoverall]);
+			V_DrawScaledPatch(235, 135, 0, ngradeletters[bestoverall]);
 
 		if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
 			{
 			V_DrawString(104 - 72, 48+lsheadingheight/2, V_YELLOWMAP, "BEST GRADE:");
-			V_DrawSmallScaledPatch(104 + 72 - (ngradeletters[bestgrade]->width/2),
-				48+lsheadingheight/2 + 8 - (ngradeletters[bestgrade]->height/2),
+			V_DrawTinyScaledPatch(104 + 72 - (ngradeletters[bestgrade]->width/4) - 1,
+				48+lsheadingheight/2 - 1,
 				0, ngradeletters[bestgrade]);
 		}
 
@@ -6090,7 +6093,7 @@ void M_DrawNightsAttackMenu(void)
 
 	// ALWAYS DRAW level even when not on this menu!
 	if (currentMenu != &SP_NightsAttackDef)
-		V_DrawString(SP_TimeAttackDef.x, SP_TimeAttackDef.y + SP_TimeAttackMenu[nalevelback].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[nalevelback].text);
+		V_DrawString(SP_NightsAttackDef.x, SP_NightsAttackDef.y + SP_TimeAttackMenu[nalevelback].alphaKey, V_TRANSLUCENT, SP_NightsAttackMenu[nalevelback].text);
 }
 
 static void M_NightsAttackLevelSelect(INT32 choice)

From f56f76692c0c4b5639b44be2e596d9617472db36 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 30 Jan 2017 20:55:31 +0000
Subject: [PATCH 024/119] Put size of BEST GRADE back to 2.1 levels. To make
 space for it, I left-aligned * LEVEL RECORDS *. http://imgur.com/a/3i7GT

---
 src/m_menu.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 40a729906..705882659 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -5839,7 +5839,7 @@ void M_DrawTimeAttackMenu(void)
 
 		V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
 
-		V_DrawCenteredString(104, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
+		V_DrawString(104 - 72, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
 			sprintf(beststr, "(none)");
@@ -6035,16 +6035,16 @@ void M_DrawNightsAttackMenu(void)
 
 		V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
 
-		V_DrawCenteredString(104, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
+		V_DrawString(104 - 72, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
 
 		if (P_HasGrades(cv_nextmap.value, 0))
 			V_DrawScaledPatch(235, 135, 0, ngradeletters[bestoverall]);
 
 		if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
-			{
+			{//make bigger again
 			V_DrawString(104 - 72, 48+lsheadingheight/2, V_YELLOWMAP, "BEST GRADE:");
-			V_DrawTinyScaledPatch(104 + 72 - (ngradeletters[bestgrade]->width/4) - 1,
-				48+lsheadingheight/2 - 1,
+			V_DrawSmallScaledPatch(104 + 72 - (ngradeletters[bestgrade]->width/2),
+				48+lsheadingheight/2 + 8 - (ngradeletters[bestgrade]->height/2),
 				0, ngradeletters[bestgrade]);
 		}
 

From b5fef5f46a57eb70fe37e3bdd34f30120fb3373c Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 31 Jan 2017 11:04:50 +0000
Subject: [PATCH 025/119] Forgot to vertically align collected emblems on
 nights page

---
 src/m_menu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 705882659..1322d93b5 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -6080,7 +6080,7 @@ void M_DrawNightsAttackMenu(void)
 				}
 
 				if (em->collected)
-					V_DrawSmallMappedPatch(104+76, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
+					V_DrawSmallMappedPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
 																 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 				else
 					V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));

From 3cb2f178c6d89d2312623b5d194d2b0e7b5c2189 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 31 Jan 2017 18:48:50 +0000
Subject: [PATCH 026/119] Getting closer to done.

Updated the mid-game gametype/level select menu! Just need to combine it with the server creation menu, and then I'll be ready to make a merge request...
---
 src/doomstat.h |  2 +-
 src/m_menu.c   | 97 ++++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 80 insertions(+), 19 deletions(-)

diff --git a/src/doomstat.h b/src/doomstat.h
index 391f57a14..801e79edd 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -313,7 +313,7 @@ enum GameType
 
 	NUMGAMETYPES
 };
-// If you alter this list, update gametype_cons_t in m_menu.c
+// If you alter this list, update dehacked.c, and gametype_cons_t and MISC_ChangeGameTypeMenu in m_menu.c
 
 extern tic_t totalplaytime;
 
diff --git a/src/m_menu.c b/src/m_menu.c
index 1322d93b5..90b5d4901 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -242,6 +242,7 @@ static void M_Options(INT32 choice);
 static void M_SelectableClearMenus(INT32 choice);
 static void M_Retry(INT32 choice);
 static void M_EndGame(INT32 choice);
+static void M_GameTypeChange(INT32 choice);
 static void M_MapChange(INT32 choice);
 static void M_ChangeLevel(INT32 choice);
 static void M_ConfirmSpectate(INT32 choice);
@@ -328,6 +329,7 @@ static void M_DrawSkyRoom(void);
 static void M_DrawChecklist(void);
 static void M_DrawEmblemHints(void);
 static void M_DrawPauseMenu(void);
+static void M_DrawGameTypeMenu(void);
 static void M_DrawServerMenu(void);
 static void M_DrawLevelPlatterMenu(void);
 static void M_DrawImageDef(void);
@@ -392,7 +394,7 @@ static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}};
 consvar_t cv_chooseskin = {"chooseskin", DEFAULTSKIN, CV_HIDEN|CV_CALL, skins_cons_t, Nextmap_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 // This gametype list is integral for many different reasons.
-// When you add gametypes here, don't forget to update them in CV_AddValue!
+// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h!
 CV_PossibleValue_t gametype_cons_t[] =
 {
 	{GT_COOP, "Co-op"},
@@ -505,7 +507,7 @@ typedef enum
 static menuitem_t MPauseMenu[] =
 {
 	{IT_STRING  | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16},
-	{IT_STRING  | IT_CALL,    NULL, "Switch Map..."    , M_MapChange,           24},
+	{IT_STRING  | IT_CALL,    NULL, "Switch Map..."    , M_GameTypeChange,           24},
 
 	{IT_CALL | IT_STRING,    NULL, "Continue",             M_SelectableClearMenus,40},
 	{IT_CALL | IT_STRING,    NULL, "Player 1 Setup",       M_SetupMultiPlayer,    48}, // splitscreen
@@ -586,11 +588,25 @@ static menuitem_t MISC_ChangeTeamMenu[] =
 	{IT_WHITESTRING|IT_CALL,         NULL, "Confirm",           M_ConfirmTeamChange,    90},
 };
 
+static menuitem_t MISC_ChangeGameTypeMenu[] =
+{
+	{IT_STRING|IT_CALL,              NULL, "Co-op",            M_MapChange,  0},
+
+	{IT_STRING|IT_CALL,              NULL, "Competition",      M_MapChange, 12},
+	{IT_STRING|IT_CALL,              NULL, "Race",             M_MapChange, 20},
+
+	{IT_STRING|IT_CALL,              NULL, "Match",            M_MapChange, 32},
+	{IT_STRING|IT_CALL,              NULL, "Team Match",       M_MapChange, 40},
+
+	{IT_STRING|IT_CALL,              NULL, "Tag",              M_MapChange, 52},
+	{IT_STRING|IT_CALL,              NULL, "Hide and Seek",    M_MapChange, 60},
+
+	{IT_STRING|IT_CALL,              NULL, "Capture the Flag", M_MapChange, 72},
+};
+
 static menuitem_t MISC_ChangeLevelMenu[] =
 {
-	{IT_STRING|IT_CVAR,              NULL, "Game Type",             &cv_newgametype,    30},
-	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        60},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Change Level",          M_ChangeLevel,     120},
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0},     // dummy menuitem for the control func
 };
 
 static menuitem_t MISC_HelpMenu[] =
@@ -664,7 +680,7 @@ static menuitem_t SR_MainMenu[] =
 
 static menuitem_t SR_LevelSelectMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0},     // dummy menuitem for the control func
 };
 
 static menuitem_t SR_UnlockChecklistMenu[] =
@@ -703,19 +719,19 @@ enum
 // Single Player Load Game
 static menuitem_t SP_LoadGameMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLoadSave, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLoadSave, 0},     // dummy menuitem for the control func
 };
 
 // Single Player Level Select
 static menuitem_t SP_LevelSelectMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0},     // dummy menuitem for the control func
 };
 
 // Single Player Time Attack Level Select
 static menuitem_t SP_TimeAttackLevelSelectMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0},     // dummy menuitem for the control func
 };
 
 // Single Player Time Attack
@@ -813,7 +829,7 @@ static menuitem_t SP_NightsGhostMenu[] =
 // Single Player Nights Attack Level Select
 static menuitem_t SP_NightsAttackLevelSelectMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0},     // dummy menuitem for the control func
 };
 
 // Single Player Nights Attack
@@ -842,12 +858,12 @@ enum
 // Statistics
 static menuitem_t SP_GameStatsMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleGameStats, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleGameStats, 0},     // dummy menuitem for the control func
 };
 
 static menuitem_t SP_LevelStatsMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelStats, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelStats, 0},     // dummy menuitem for the control func
 };
 
 // A rare case.
@@ -1199,7 +1215,7 @@ static menuitem_t OP_VideoOptionsMenu[] =
 
 static menuitem_t OP_VideoModeMenu[] =
 {
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleVideoMode, '\0'},     // dummy menuitem for the control func
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleVideoMode, 0},     // dummy menuitem for the control func
 };
 
 #ifdef HWRENDER
@@ -1432,7 +1448,31 @@ menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72);
 // Misc Main Menu
 menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40);
 menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40);
-menu_t MISC_ChangeLevelDef = MAPICONMENUSTYLE(NULL, MISC_ChangeLevelMenu, &MPauseDef);
+
+// MP Gametype and map change menu
+menu_t MISC_ChangeGameTypeDef =
+{
+	NULL,
+	sizeof (MISC_ChangeGameTypeMenu)/sizeof (menuitem_t),
+	&MainDef,  // Doesn't matter.
+	MISC_ChangeGameTypeMenu,
+	M_DrawGameTypeMenu,
+	30, 104 - ((80 - lsheadingheight/2)/2), // vertically centering
+	0,
+	NULL
+};
+menu_t MISC_ChangeLevelDef =
+{
+	NULL,
+	sizeof (MISC_ChangeLevelMenu)/sizeof (menuitem_t),
+	&MISC_ChangeGameTypeDef,
+	MISC_ChangeLevelMenu,
+	M_DrawLevelPlatterMenu,
+	0, 0,
+	0,
+	NULL
+};
+
 menu_t MISC_HelpDef = IMAGEDEF(MISC_HelpMenu);
 
 // Sky Room
@@ -3885,6 +3925,8 @@ static void M_HandleLevelPlatter(INT32 choice)
 					else
 						M_NightsAttack(-1);
 				}
+				else if (currentMenu == &MISC_ChangeLevelDef)
+					M_ChangeLevel(0);
 				else
 					M_LevelSelectWarp(0);
 			}
@@ -6845,16 +6887,35 @@ static void M_DrawServerMenu(void)
 	V_DrawSmallScaledPatch((BASEVIDWIDTH*3/4)-(SHORT(PictureOfLevel->width)/4), ((BASEVIDHEIGHT*3/4)-(SHORT(PictureOfLevel->height)/4)+10), 0, PictureOfLevel);
 }
 
-static void M_MapChange(INT32 choice)
+static void M_GameTypeChange(INT32 choice)
 {
 	(void)choice;
 
+	MISC_ChangeGameTypeDef.prevMenu = currentMenu;
+	M_SetupNextMenu(&MISC_ChangeGameTypeDef);
+	itemOn = gametype;
+}
+
+// Drawing function for Nights Attack
+void M_DrawGameTypeMenu(void)
+{
+	M_DrawGenericMenu();
+	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight, "SELECT GAMETYPE", true);
+}
+
+static void M_MapChange(INT32 choice)
+{
+	MISC_ChangeLevelDef.prevMenu = currentMenu;
 	levellistmode = LLM_CREATESERVER;
 
-	CV_SetValue(&cv_newgametype, gametype);
-	CV_SetValue(&cv_nextmap, gamemap);
+	CV_SetValue(&cv_newgametype, choice);
+
+	if (!M_PrepareLevelPlatter(choice))
+	{
+		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
+		return;
+	}
 
-	M_PrepareLevelSelect();
 	M_SetupNextMenu(&MISC_ChangeLevelDef);
 }
 

From 873e768a446d6cb73a272c078e7dccbd37ea5803 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 31 Jan 2017 23:56:09 +0000
Subject: [PATCH 027/119] Server page (and splitscreen server page) started.

http://i.imgur.com/jJ3YZUd.png

Will be discussing what to do with the space freed under "server options" the heading on IRC tomorrow.

(MI, if you're looking - the mistake was that I set up an IT_CALL menu line as IT_CVAR by mistake, which of course caused a crash when trying to get ((consvar_t *)M_GameTypeChange)->string. ;P )
---
 src/m_menu.c | 139 ++++++++++++++++++++++++++++++++-------------------
 1 file changed, 88 insertions(+), 51 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 90b5d4901..fc2b894fe 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -406,7 +406,7 @@ CV_PossibleValue_t gametype_cons_t[] =
 	{GT_TEAMMATCH, "Team Match"},
 
 	{GT_TAG, "Tag"},
-	{GT_HIDEANDSEEK, "Hide and Seek"},
+	{GT_HIDEANDSEEK, "Hide & Seek"},
 
 	{GT_CTF, "CTF"},
 	{0, NULL}
@@ -506,21 +506,21 @@ typedef enum
 // ---------------------
 static menuitem_t MPauseMenu[] =
 {
-	{IT_STRING  | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16},
-	{IT_STRING  | IT_CALL,    NULL, "Switch Map..."    , M_GameTypeChange,           24},
+	{IT_STRING  | IT_SUBMENU, NULL, "Scramble Teams...",        &MISC_ScrambleTeamDef, 16},
+	{IT_STRING  | IT_CALL,    NULL, "Switch Gametype/Level...", M_GameTypeChange,      24},
 
-	{IT_CALL | IT_STRING,    NULL, "Continue",             M_SelectableClearMenus,40},
-	{IT_CALL | IT_STRING,    NULL, "Player 1 Setup",       M_SetupMultiPlayer,    48}, // splitscreen
-	{IT_CALL | IT_STRING,    NULL, "Player 2 Setup",       M_SetupMultiPlayer2,   56}, // splitscreen
+	{IT_CALL | IT_STRING,    NULL, "Continue",                  M_SelectableClearMenus,40},
+	{IT_CALL | IT_STRING,    NULL, "Player 1 Setup",            M_SetupMultiPlayer,    48}, // splitscreen
+	{IT_CALL | IT_STRING,    NULL, "Player 2 Setup",            M_SetupMultiPlayer2,   56}, // splitscreen
 
-	{IT_STRING | IT_CALL,    NULL, "Spectate",             M_ConfirmSpectate,     48},
-	{IT_STRING | IT_CALL,    NULL, "Enter Game",           M_ConfirmEnterGame,    48},
-	{IT_STRING | IT_SUBMENU, NULL, "Switch Team...",       &MISC_ChangeTeamDef,   48},
-	{IT_CALL | IT_STRING,    NULL, "Player Setup",         M_SetupMultiPlayer,    56}, // alone
-	{IT_CALL | IT_STRING,    NULL, "Options",              M_Options,             64},
+	{IT_STRING | IT_CALL,    NULL, "Spectate",                  M_ConfirmSpectate,     48},
+	{IT_STRING | IT_CALL,    NULL, "Enter Game",                M_ConfirmEnterGame,    48},
+	{IT_STRING | IT_SUBMENU, NULL, "Switch Team...",            &MISC_ChangeTeamDef,   48},
+	{IT_CALL | IT_STRING,    NULL, "Player Setup",              M_SetupMultiPlayer,    56}, // alone
+	{IT_CALL | IT_STRING,    NULL, "Options",                   M_Options,             64},
 
-	{IT_CALL | IT_STRING,    NULL, "Return to Title",      M_EndGame,            80},
-	{IT_CALL | IT_STRING,    NULL, "Quit Game",            M_QuitSRB2,           88},
+	{IT_CALL | IT_STRING,    NULL, "Return to Title",           M_EndGame,             80},
+	{IT_CALL | IT_STRING,    NULL, "Quit Game",                 M_QuitSRB2,            88},
 };
 
 typedef enum
@@ -599,7 +599,7 @@ static menuitem_t MISC_ChangeGameTypeMenu[] =
 	{IT_STRING|IT_CALL,              NULL, "Team Match",       M_MapChange, 40},
 
 	{IT_STRING|IT_CALL,              NULL, "Tag",              M_MapChange, 52},
-	{IT_STRING|IT_CALL,              NULL, "Hide and Seek",    M_MapChange, 60},
+	{IT_STRING|IT_CALL,              NULL, "Hide & Seek",      M_MapChange, 60},
 
 	{IT_STRING|IT_CALL,              NULL, "Capture the Flag", M_MapChange, 72},
 };
@@ -924,28 +924,33 @@ static menuitem_t MP_MainMenu[] =
 
 static menuitem_t MP_ServerMenu[] =
 {
-	{IT_STRING|IT_CVAR,              NULL, "Game Type",             &cv_newgametype,    10},
+	{IT_DISABLED|IT_NOTHING, NULL, "", NULL, 0},
 #ifndef NONET
-	{IT_STRING|IT_CALL,              NULL, "Room...",               M_RoomMenu,         20},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",           &cv_servername,     30},
+	{IT_STRING|IT_CALL,              NULL, "Room...",                   M_RoomMenu,         10},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",               &cv_servername,     20},
 #endif
-
-	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        80},
-
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_StartServer,     130},
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange,   90},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                     M_StartServer,     130},
 };
 
 enum
 {
-	mp_server_gametype = 0,
+	mp_server_dummy = 0, // exists solely so numbering is consistent between NONET and not NONET
 #ifndef NONET
 	mp_server_room,
 	mp_server_name,
 #endif
-	mp_server_level,
+	mp_server_levelgt,
 	mp_server_start
 };
 
+// Separated splitscreen and normal servers.
+static menuitem_t MP_SplitServerMenu[] =
+{
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange,   90},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                     M_StartServer,     130},
+};
+
 #ifndef NONET
 static menuitem_t MP_ConnectMenu[] =
 {
@@ -1004,14 +1009,6 @@ static menuitem_t MP_ConnectIPMenu[] =
 };
 #endif
 
-// Separated splitscreen and normal servers.
-static menuitem_t MP_SplitServerMenu[] =
-{
-	{IT_STRING|IT_CVAR,              NULL, "Game Type",             &cv_newgametype,    10},
-	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,        80},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_StartServer,     130},
-};
-
 static menuitem_t MP_PlayerSetupMenu[] =
 {
 	{IT_KEYHANDLER | IT_STRING,   NULL, "Your name",   M_HandleSetupMultiPlayer,   0},
@@ -1708,7 +1705,31 @@ menu_t SP_PlayerDef =
 
 // Multiplayer
 menu_t MP_MainDef = DEFAULTMENUSTYLE("M_MULTI", MP_MainMenu, &MainDef, 60, 40);
-menu_t MP_ServerDef = MAPICONMENUSTYLE("M_MULTI", MP_ServerMenu, &MP_MainDef);
+
+menu_t MP_ServerDef =
+{
+	"M_MULTI",
+	sizeof (MP_ServerMenu)/sizeof (menuitem_t),
+	&MP_MainDef,
+	MP_ServerMenu,
+	M_DrawServerMenu,
+	27, 40,
+	0,
+	NULL
+};
+
+menu_t MP_SplitServerDef =
+{
+	"M_MULTI",
+	sizeof (MP_SplitServerMenu)/sizeof (menuitem_t),
+	&MP_MainDef,
+	MP_SplitServerMenu,
+	M_DrawServerMenu,
+	27, 40,
+	0,
+	NULL
+};
+
 #ifndef NONET
 menu_t MP_ConnectDef =
 {
@@ -1744,7 +1765,7 @@ menu_t MP_RoomDef =
 	NULL
 };
 #endif
-menu_t MP_SplitServerDef = MAPICONMENUSTYLE("M_MULTI", MP_SplitServerMenu, &MP_MainDef);
+
 menu_t MP_PlayerSetupDef =
 {
 	"M_SPLAYR",
@@ -3926,7 +3947,12 @@ static void M_HandleLevelPlatter(INT32 choice)
 						M_NightsAttack(-1);
 				}
 				else if (currentMenu == &MISC_ChangeLevelDef)
-					M_ChangeLevel(0);
+				{
+					if (currentMenu->prevMenu && currentMenu->prevMenu->prevMenu != &MPauseDef)
+						M_SetupNextMenu(currentMenu->prevMenu->prevMenu);
+					else
+						M_ChangeLevel(0);
+				}
 				else
 					M_LevelSelectWarp(0);
 			}
@@ -3956,7 +3982,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 				D_StartTitle();
 			}
 			else
-				M_SetupNextMenu (currentMenu->prevMenu);
+				M_SetupNextMenu(currentMenu->prevMenu);
 		}
 		else
 			M_ClearMenus(true);
@@ -6858,11 +6884,10 @@ static void M_StartServer(INT32 choice)
 
 static void M_DrawServerMenu(void)
 {
-	lumpnum_t lumpnum;
-	patch_t *PictureOfLevel;
-
 	M_DrawGenericMenu();
 
+	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true);
+
 #ifndef NONET
 	// Room name
 	if (currentMenu == &MP_ServerDef)
@@ -6876,15 +6901,26 @@ static void M_DrawServerMenu(void)
 	}
 #endif
 
-	//  A 160x100 image of the level as entry MAPxxP
-	lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+	if (cv_nextmap.value)
+	{
+		patch_t *PictureOfLevel;
+		lumpnum_t lumpnum;
+		char headerstr[40];
 
-	if (lumpnum != LUMPERROR)
-		PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
-	else
-		PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+		sprintf(headerstr, "%s - %s", cv_newgametype.string, cv_nextmap.string);
 
-	V_DrawSmallScaledPatch((BASEVIDWIDTH*3/4)-(SHORT(PictureOfLevel->width)/4), ((BASEVIDHEIGHT*3/4)-(SHORT(PictureOfLevel->height)/4)+10), 0, PictureOfLevel);
+		M_DrawLevelPlatterHeader(currentMenu->y + 80 - lsheadingheight/2, (const char *)headerstr, true);
+
+		//  A 160x100 image of the level as entry MAPxxP
+		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+
+		if (lumpnum != LUMPERROR)
+			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
+		else
+			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+
+		V_DrawSmallScaledPatch(319 - (currentMenu->x + (SHORT(PictureOfLevel->width)/2)), currentMenu->y + 90, 0, PictureOfLevel);
+	}
 }
 
 static void M_GameTypeChange(INT32 choice)
@@ -6893,14 +6929,14 @@ static void M_GameTypeChange(INT32 choice)
 
 	MISC_ChangeGameTypeDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&MISC_ChangeGameTypeDef);
-	itemOn = gametype;
+	if (Playing())
+		itemOn = gametype;
 }
 
-// Drawing function for Nights Attack
 void M_DrawGameTypeMenu(void)
 {
 	M_DrawGenericMenu();
-	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight, "SELECT GAMETYPE", true);
+	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight, "Select Gametype", true);
 }
 
 static void M_MapChange(INT32 choice)
@@ -6923,7 +6959,7 @@ static void M_StartSplitServerMenu(INT32 choice)
 {
 	(void)choice;
 	levellistmode = LLM_CREATESERVER;
-	M_PrepareLevelSelect();
+	Newgametype_OnChange();
 	M_SetupNextMenu(&MP_SplitServerDef);
 }
 
@@ -6931,10 +6967,11 @@ static void M_StartSplitServerMenu(INT32 choice)
 static void M_StartServerMenu(INT32 choice)
 {
 	(void)choice;
-	levellistmode = LLM_CREATESERVER;
-	M_PrepareLevelSelect();
 	ms_RoomId = -1;
+	levellistmode = LLM_CREATESERVER;
+	Newgametype_OnChange();
 	M_SetupNextMenu(&MP_ServerDef);
+	itemOn = 1;
 
 }
 

From 4efeb029783006b3841f5ed4455561a76ed863aa Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 1 Feb 2017 17:21:04 +0000
Subject: [PATCH 028/119] Added some useful options to the Server setup menu,
 including a jump-link to the relevant Options screen. Think it should be a
 little smarter designed, though, but I'd rather commit what I have then let
 it languish.

---
 src/m_menu.c | 52 ++++++++++++++++++++++++++++++++++------------------
 1 file changed, 34 insertions(+), 18 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index fc2b894fe..bfc6b15b0 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -281,6 +281,7 @@ static void M_ConnectIPMenu(INT32 choice);
 #endif
 static void M_StartSplitServerMenu(INT32 choice);
 static void M_StartServer(INT32 choice);
+static void M_ServerOptions(INT32 choice);
 #ifndef NONET
 static void M_Refresh(INT32 choice);
 static void M_Connect(INT32 choice);
@@ -926,29 +927,36 @@ static menuitem_t MP_ServerMenu[] =
 {
 	{IT_DISABLED|IT_NOTHING, NULL, "", NULL, 0},
 #ifndef NONET
-	{IT_STRING|IT_CALL,              NULL, "Room...",                   M_RoomMenu,         10},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",               &cv_servername,     20},
+	{IT_STRING|IT_CALL,              NULL, "Room...",               M_RoomMenu,        10},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",           &cv_servername,    20},
+	{IT_STRING|IT_CVAR,              NULL, "Max Players",           &cv_maxplayers,    46},
+	{IT_STRING|IT_CVAR,              NULL, "Allow WAD Downloading", &cv_downloading,   56},
 #endif
-	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange,   90},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                     M_StartServer,     130},
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange, 100},
+	{IT_STRING|IT_CALL,              NULL, "More Options...",       M_ServerOptions,  130},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_StartServer,    140},
 };
 
 enum
 {
-	mp_server_dummy = 0, // exists solely so numbering is consistent between NONET and not NONET
+	mp_server_dummy = 0, // exists solely so zero-indexed in both NONET and not NONET
 #ifndef NONET
 	mp_server_room,
 	mp_server_name,
+	mp_server_maxpl,
+	mp_server_waddl,
 #endif
 	mp_server_levelgt,
+	mp_server_options,
 	mp_server_start
 };
 
 // Separated splitscreen and normal servers.
 static menuitem_t MP_SplitServerMenu[] =
 {
-	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange,   90},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                     M_StartServer,     130},
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange, 100},
+	{IT_STRING|IT_CALL,              NULL, "More Options...",       M_ServerOptions,  130},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_StartServer,    140},
 };
 
 #ifndef NONET
@@ -1022,14 +1030,14 @@ static menuitem_t MP_PlayerSetupMenu[] =
 // Prefix: OP_
 static menuitem_t OP_MainMenu[] =
 {
-	{IT_SUBMENU | IT_STRING, NULL, "Setup Controls...",     &OP_ControlsDef,      10},
+	{IT_SUBMENU | IT_STRING, NULL, "Setup Controls...", &OP_ControlsDef,      10},
 
-	{IT_SUBMENU | IT_STRING, NULL, "Video Options...",      &OP_VideoOptionsDef,  30},
-	{IT_SUBMENU | IT_STRING, NULL, "Sound Options...",      &OP_SoundOptionsDef,  40},
-	{IT_SUBMENU | IT_STRING, NULL, "Data Options...",       &OP_DataOptionsDef,   50},
+	{IT_SUBMENU | IT_STRING, NULL, "Video Options...",  &OP_VideoOptionsDef,  30},
+	{IT_SUBMENU | IT_STRING, NULL, "Sound Options...",  &OP_SoundOptionsDef,  40},
+	{IT_SUBMENU | IT_STRING, NULL, "Data Options...",   &OP_DataOptionsDef,   50},
 
-	{IT_SUBMENU | IT_STRING, NULL, "Game Options...",       &OP_GameOptionsDef,   70},
-	{IT_SUBMENU | IT_STRING, NULL, "Server Options...",     &OP_ServerOptionsDef, 80},
+	{IT_SUBMENU | IT_STRING, NULL, "Game Options...",   &OP_GameOptionsDef,   70},
+	{IT_CALL | IT_STRING,    NULL, "Server Options...", M_ServerOptions,      80},
 };
 
 static menuitem_t OP_ControlsMenu[] =
@@ -1713,7 +1721,7 @@ menu_t MP_ServerDef =
 	&MP_MainDef,
 	MP_ServerMenu,
 	M_DrawServerMenu,
-	27, 40,
+	27, 30,
 	0,
 	NULL
 };
@@ -1725,7 +1733,7 @@ menu_t MP_SplitServerDef =
 	&MP_MainDef,
 	MP_SplitServerMenu,
 	M_DrawServerMenu,
-	27, 40,
+	27, 30,
 	0,
 	NULL
 };
@@ -4507,7 +4515,7 @@ static void M_Options(INT32 choice)
 	(void)choice;
 
 	// if the player is not admin or server, disable server options
-	OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
+	OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL);
 
 	// if the player is playing _at all_, disable the erase data options
 	OP_DataOptionsMenu[1].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
@@ -6909,7 +6917,7 @@ static void M_DrawServerMenu(void)
 
 		sprintf(headerstr, "%s - %s", cv_newgametype.string, cv_nextmap.string);
 
-		M_DrawLevelPlatterHeader(currentMenu->y + 80 - lsheadingheight/2, (const char *)headerstr, true);
+		M_DrawLevelPlatterHeader(currentMenu->y + MP_ServerMenu[mp_server_levelgt].alphaKey - 10 - lsheadingheight/2, (const char *)headerstr, true);
 
 		//  A 160x100 image of the level as entry MAPxxP
 		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
@@ -6919,7 +6927,7 @@ static void M_DrawServerMenu(void)
 		else
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
 
-		V_DrawSmallScaledPatch(319 - (currentMenu->x + (SHORT(PictureOfLevel->width)/2)), currentMenu->y + 90, 0, PictureOfLevel);
+		V_DrawSmallScaledPatch(319 - (currentMenu->x + (SHORT(PictureOfLevel->width)/2)), currentMenu->y + MP_ServerMenu[mp_server_levelgt].alphaKey, 0, PictureOfLevel);
 	}
 }
 
@@ -6963,6 +6971,14 @@ static void M_StartSplitServerMenu(INT32 choice)
 	M_SetupNextMenu(&MP_SplitServerDef);
 }
 
+static void M_ServerOptions(INT32 choice)
+{
+	(void)choice;
+
+	OP_ServerOptionsDef.prevMenu = currentMenu;
+	M_SetupNextMenu(&OP_ServerOptionsDef);
+}
+
 #ifndef NONET
 static void M_StartServerMenu(INT32 choice)
 {

From 8ebb4a3c3f3c3a69b3366641ee055b07c0dc660a Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 1 Feb 2017 17:36:19 +0000
Subject: [PATCH 029/119] Making things nicer for the server creation menus of
 2P mode and NONET executables.

---
 src/m_menu.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index bfc6b15b0..03f97d750 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1721,7 +1721,11 @@ menu_t MP_ServerDef =
 	&MP_MainDef,
 	MP_ServerMenu,
 	M_DrawServerMenu,
-	27, 30,
+	27, 30
+#ifdef NONET
+	- 50
+#endif
+	,
 	0,
 	NULL
 };
@@ -1733,7 +1737,7 @@ menu_t MP_SplitServerDef =
 	&MP_MainDef,
 	MP_SplitServerMenu,
 	M_DrawServerMenu,
-	27, 30,
+	27, 30 - 50,
 	0,
 	NULL
 };
@@ -6894,12 +6898,11 @@ static void M_DrawServerMenu(void)
 {
 	M_DrawGenericMenu();
 
-	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true);
-
 #ifndef NONET
 	// Room name
 	if (currentMenu == &MP_ServerDef)
 	{
+		M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true);
 		if (ms_RoomId < 0)
 			V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
 			                         V_YELLOWMAP, (itemOn == mp_server_room) ? "<Select to change>" : "<Offline Mode>");

From 8625c8bfbd23b1da932a85cc1f7b8590dbc8ecfb Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 1 Feb 2017 21:30:10 +0000
Subject: [PATCH 030/119] Removed extraenous space.

---
 src/m_menu.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 03f97d750..740d7b834 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -6991,7 +6991,6 @@ static void M_StartServerMenu(INT32 choice)
 	Newgametype_OnChange();
 	M_SetupNextMenu(&MP_ServerDef);
 	itemOn = 1;
-
 }
 
 // ==============

From 05ebc93f6cc1feaa4c961d0d9e92ce07edadddb0 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 1 Feb 2017 21:31:35 +0000
Subject: [PATCH 031/119] After discussion with Mystic and Rob, you now keep
 your lives, score and emerald count when changing maps in Co-op mode. Reset
 when you declare -force.

---
 src/d_netcmd.c | 38 ++++++++++++++++++--------------------
 src/g_game.c   | 12 ++++++++----
 src/g_game.h   |  2 +-
 3 files changed, 27 insertions(+), 25 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1cc1adf8b..c36477fc2 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1537,7 +1537,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
 	           mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene);
 
-	if (netgame || multiplayer)
+	if ((netgame || multiplayer) && !((gametype == newgametype) && (newgametype == GT_COOP)))
 		FLS = false;
 
 	if (delay != 2)
@@ -1703,9 +1703,19 @@ static void Command_Map_f(void)
 		}
 	}
 
+	// Prevent warping to locked levels
+	// ... unless you're in a dedicated server.  Yes, technically this means you can view any level by
+	// running a dedicated server and joining it yourself, but that's better than making dedicated server's
+	// lives hell.
+	if (!dedicated && M_MapLocked(newmapnum))
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
+		return;
+	}
+
 	// don't use a gametype the map doesn't support
 	if (cv_debug || COM_CheckParm("-force") || cv_skipmapcheck.value)
-		; // The player wants us to trek on anyway.  Do so.
+		fromlevelselect = false; // The player wants us to trek on anyway.  Do so.
 	// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
 	// Alternatively, bail if the map header is completely missing anyway.
 	else if (!mapheaderinfo[newmapnum-1]
@@ -1724,19 +1734,10 @@ static void Command_Map_f(void)
 		CONS_Alert(CONS_WARNING, M_GetText("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, gametypestring);
 		return;
 	}
+	else
+		fromlevelselect = ((netgame || multiplayer) && ((gametype == newgametype) && (newgametype == GT_COOP)));
 
-	// Prevent warping to locked levels
-	// ... unless you're in a dedicated server.  Yes, technically this means you can view any level by
-	// running a dedicated server and joining it yourself, but that's better than making dedicated server's
-	// lives hell.
-	if (!dedicated && M_MapLocked(newmapnum))
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
-		return;
-	}
-
-	fromlevelselect = false;
-	D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, false);
+	D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
 }
 
 /** Receives a map command and changes the map.
@@ -1802,17 +1803,14 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	if (demoplayback && !timingdemo)
 		precache = false;
 
-	if (resetplayer)
-	{
-		if (!FLS || (netgame || multiplayer))
-			emeralds = 0;
-	}
+	if (resetplayer && !FLS)
+		emeralds = 0;
 
 #ifdef HAVE_BLUA
 	LUAh_MapChange();
 #endif
 
-	G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene);
+	G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene, FLS);
 	if (demoplayback && !timingdemo)
 		precache = true;
 	CON_ToggleOff();
diff --git a/src/g_game.c b/src/g_game.c
index 5e04af496..1de4c17a7 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3541,7 +3541,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b
 // This is the map command interpretation something like Command_Map_f
 //
 // called at: map cmd execution, doloadgame, doplaydemo
-void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean skipprecutscene)
+void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean skipprecutscene, boolean FLS)
 {
 	INT32 i;
 
@@ -3571,7 +3571,8 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 
 			if (netgame || multiplayer)
 			{
-				players[i].lives = cv_startinglives.value;
+				if (!FLS || (players[i].lives < cv_startinglives.value))
+					players[i].lives = cv_startinglives.value;
 				players[i].continues = 0;
 			}
 			else if (pultmode)
@@ -3585,13 +3586,16 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 				players[i].continues = 1;
 			}
 
+			if (!((netgame || multiplayer) && (FLS)))
+				players[i].score = 0;
+
 			// The latter two should clear by themselves, but just in case
 			players[i].pflags &= ~(PF_TAGIT|PF_TAGGED|PF_FULLSTASIS);
 
 			// Clear cheatcodes too, just in case.
 			players[i].pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS);
 
-			players[i].score = players[i].xtralife = 0;
+			players[i].xtralife = 0;
 		}
 
 		// Reset unlockable triggers
@@ -5142,7 +5146,7 @@ void G_DoPlayDemo(char *defdemoname)
 	memset(playeringame,0,sizeof(playeringame));
 	playeringame[0] = true;
 	P_SetRandSeed(randseed);
-	G_InitNew(false, G_BuildMapName(gamemap), true, true);
+	G_InitNew(false, G_BuildMapName(gamemap), true, true, false);
 
 	// Set skin
 	SetPlayerSkin(0, skin);
diff --git a/src/g_game.h b/src/g_game.h
index 6d4125517..bfde7698a 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -89,7 +89,7 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo);
 void G_DoReborn(INT32 playernum);
 void G_PlayerReborn(INT32 player);
 void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer,
-	boolean skipprecutscene);
+	boolean skipprecutscene, boolean FLS);
 char *G_BuildMapTitle(INT32 mapnum);
 
 // XMOD spawning

From 55e736250a54d7e00a2284c6d7a99dae45e7df63 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 2 Feb 2017 12:32:02 +0000
Subject: [PATCH 032/119] Clear out some irrelevant/redundant functions now
 there are no non-platter Level Selects available.

---
 src/m_menu.c | 43 ++++++-------------------------------------
 1 file changed, 6 insertions(+), 37 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 740d7b834..70380a973 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1884,8 +1884,7 @@ menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOp
 // (there's only a couple anyway)
 
 // Prototypes
-static INT32 M_FindFirstMap(INT32 gtype);
-static INT32 M_GetFirstLevelInList(void);
+static INT32 M_GetFirstLevelInList(INT32 gt);
 
 // Nextmap.  Used for Time Attack.
 static void Nextmap_OnChange(void)
@@ -2065,9 +2064,7 @@ static void Newgametype_OnChange(void)
 					break;
 			}
 
-			CV_SetValue(&cv_nextmap, M_FindFirstMap(value));
-			CV_AddValue(&cv_nextmap, -1);
-			CV_AddValue(&cv_nextmap, 1);
+			CV_SetValue(&cv_nextmap, M_GetFirstLevelInList(value));
 		}
 	}
 }
@@ -4125,15 +4122,6 @@ static void M_DrawLevelPlatterMenu(void)
 
 #undef lsbasey
 
-// Call before showing any level-select menus (Not necessary for platter-based ones)
-static void M_PrepareLevelSelect(void)
-{
-	if (levellistmode != LLM_CREATESERVER)
-		CV_SetValue(&cv_nextmap, M_GetFirstLevelInList());
-	else
-		Newgametype_OnChange(); // Make sure to start on an appropriate map if wads have been added
-}
-
 //
 // M_CanShowLevelInList
 //
@@ -4156,12 +4144,12 @@ static INT32 M_CountLevelsToShowInList(void)
 	return count;
 }
 
-static INT32 M_GetFirstLevelInList(void)
+static INT32 M_GetFirstLevelInList(INT32 gt)
 {
 	INT32 mapnum;
 
 	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
-		if (M_CanShowLevelInList(mapnum, -1))
+		if (M_CanShowLevelInList(mapnum, gt))
 			return mapnum + 1;
 
 	return 1;
@@ -6024,7 +6012,7 @@ static void M_TimeAttack(INT32 choice)
 
 		M_PatchSkinNameTable();
 
-		M_PrepareLevelSelect();
+		Newgametype_OnChange();
 	}
 	else
 		SP_TimeAttackDef.prevMenu = currentMenu;
@@ -6214,7 +6202,7 @@ static void M_NightsAttack(INT32 choice)
 		// This is really just to make sure Sonic is the played character, just in case
 		M_PatchSkinNameTable();
 
-		M_PrepareLevelSelect();
+		Newgametype_OnChange();
 	}
 	else
 		SP_NightsAttackDef.prevMenu = currentMenu;
@@ -6837,25 +6825,6 @@ static void M_ChooseRoom(INT32 choice)
 // Start Server Menu
 //===========================================================================
 
-//
-// FindFirstMap
-//
-// Finds the first map of a particular gametype
-// Defaults to 1 if nothing found.
-//
-static INT32 M_FindFirstMap(INT32 gtype)
-{
-	INT32 i;
-
-	for (i = 0; i < NUMMAPS; i++)
-	{
-		if (mapheaderinfo[i] && (mapheaderinfo[i]->typeoflevel & gtype))
-			return i + 1;
-	}
-
-	return 1;
-}
-
 static void M_StartServer(INT32 choice)
 {
 	boolean StartSplitScreenGame = (currentMenu == &MP_SplitServerDef);

From 590ffae391713ea90fa1e4b89582ebd365e35b51 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 2 Feb 2017 12:52:39 +0000
Subject: [PATCH 033/119] Maximum length for name strings on level platters.
 http://i.imgur.com/FFXmVlY.png

---
 src/m_menu.c | 11 +++++++++--
 src/m_menu.h |  2 +-
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 70380a973..250c82765 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3809,10 +3809,17 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 				}
 				else
 				{
+					char mapname[22+3]; // lvlttl[22] + " 19"
+
 					if (actnum)
-						sprintf(levelselect.rows[row].mapnames[col], "%s %d", mapheaderinfo[mapnum]->lvlttl, actnum);
+						sprintf(mapname, "%s %d", mapheaderinfo[mapnum]->lvlttl, actnum);
 					else
-						sprintf(levelselect.rows[row].mapnames[col], "%s", mapheaderinfo[mapnum]->lvlttl);
+						sprintf(mapname, "%s", mapheaderinfo[mapnum]->lvlttl);
+
+					if (strlen(mapname) >= 17)
+						sprintf(mapname+17-3, "...");
+
+					strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
 				}
 			}
 			else
diff --git a/src/m_menu.h b/src/m_menu.h
index 5c07d8ae8..d5a3600a1 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -187,7 +187,7 @@ typedef struct
 {
 	char header[22+5]; // mapheader_t lvltttl max length + " ZONE"
 	INT32 maplist[3];
-	char mapnames[3][22];
+	char mapnames[3][17+1];
 	boolean mapavailable[3];
 } levelselectrow_t;
 

From 42518a1759da2b89cc79af975c0ac6a8275c4694 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 2 Feb 2017 16:02:07 +0000
Subject: [PATCH 034/119] For consistency purposes, added trailing ellipses to
 level selection prompts I added.

---
 src/m_menu.c | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 250c82765..827a5ac22 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -738,8 +738,8 @@ static menuitem_t SP_TimeAttackLevelSelectMenu[] =
 // Single Player Time Attack
 static menuitem_t SP_TimeAttackMenu[] =
 {
-	{IT_STRING|IT_CALL,        NULL, "Back to Level Select", &M_GoBack,      52},
-	{IT_STRING|IT_CVAR,        NULL, "Character",       &cv_chooseskin,      62},
+	{IT_STRING|IT_CALL,        NULL, "Back to Level Select...", &M_GoBack,   52},
+	{IT_STRING|IT_CVAR,        NULL, "Character",          &cv_chooseskin,   62},
 
 	{IT_DISABLED,              NULL, "Guest Option...", &SP_GuestReplayDef, 100},
 	{IT_DISABLED,              NULL, "Replay...",       &SP_ReplayDef,      110},
@@ -836,8 +836,8 @@ static menuitem_t SP_NightsAttackLevelSelectMenu[] =
 // Single Player Nights Attack
 static menuitem_t SP_NightsAttackMenu[] =
 {
-	{IT_STRING|IT_CALL,        NULL, "Back to Level Select",  &M_GoBack,              52},
-	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,              62},
+	{IT_STRING|IT_CALL,        NULL, "Back to Level Select...",  &M_GoBack,           52},
+	{IT_STRING|IT_CVAR,        NULL, "Show Records For",    &cv_dummymares,           62},
 
 	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,   100},
 	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,        110},
@@ -927,14 +927,14 @@ static menuitem_t MP_ServerMenu[] =
 {
 	{IT_DISABLED|IT_NOTHING, NULL, "", NULL, 0},
 #ifndef NONET
-	{IT_STRING|IT_CALL,              NULL, "Room...",               M_RoomMenu,        10},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",           &cv_servername,    20},
-	{IT_STRING|IT_CVAR,              NULL, "Max Players",           &cv_maxplayers,    46},
-	{IT_STRING|IT_CVAR,              NULL, "Allow WAD Downloading", &cv_downloading,   56},
+	{IT_STRING|IT_CALL,              NULL, "Room...",                  M_RoomMenu,        10},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",              &cv_servername,    20},
+	{IT_STRING|IT_CVAR,              NULL, "Max Players",              &cv_maxplayers,    46},
+	{IT_STRING|IT_CVAR,              NULL, "Allow WAD Downloading",    &cv_downloading,   56},
 #endif
-	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange, 100},
-	{IT_STRING|IT_CALL,              NULL, "More Options...",       M_ServerOptions,  130},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_StartServer,    140},
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level...", M_GameTypeChange, 100},
+	{IT_STRING|IT_CALL,              NULL, "More Options...",          M_ServerOptions,  130},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                    M_StartServer,    140},
 };
 
 enum
@@ -954,9 +954,9 @@ enum
 // Separated splitscreen and normal servers.
 static menuitem_t MP_SplitServerMenu[] =
 {
-	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level", M_GameTypeChange, 100},
-	{IT_STRING|IT_CALL,              NULL, "More Options...",       M_ServerOptions,  130},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_StartServer,    140},
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level...", M_GameTypeChange, 100},
+	{IT_STRING|IT_CALL,              NULL, "More Options...",          M_ServerOptions,  130},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                    M_StartServer,    140},
 };
 
 #ifndef NONET

From 3aa7573c86294d302358b88371472615446e8e32 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 2 Feb 2017 16:10:37 +0000
Subject: [PATCH 035/119] Made code repetition less.

---
 src/m_menu.c | 52 ++++++++--------------------------------------------
 src/m_menu.h |  8 ++++----
 2 files changed, 12 insertions(+), 48 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 827a5ac22..3eed3cf27 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1503,17 +1503,9 @@ menu_t SR_MainDef =
 	0,
 	NULL
 };
-menu_t SR_LevelSelectDef =
-{
-	NULL,
-	sizeof (SR_LevelSelectMenu)/sizeof (menuitem_t),
-	&SR_MainDef,
-	SR_LevelSelectMenu,
-	M_DrawLevelPlatterMenu,
-	0, 0,
-	0,
-	NULL
-};
+
+menu_t SR_LevelSelectDef = MAPPLATTERMENUSTYLE(NULL, SR_LevelSelectMenu);
+
 menu_t SR_UnlockChecklistDef =
 {
 	NULL,
@@ -1551,17 +1543,7 @@ menu_t SP_LoadDef =
 	NULL
 };
 
-menu_t SP_LevelSelectDef =
-{
-	NULL,
-	sizeof (SP_LevelSelectMenu)/sizeof (menuitem_t),
-	&MainDef,  // Doesn't matter.
-	SP_LevelSelectMenu,
-	M_DrawLevelPlatterMenu,
-	0, 0,
-	0,
-	NULL
-};
+menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(NULL, SP_LevelSelectMenu);
 
 menu_t SP_GameStatsDef =
 {
@@ -1586,17 +1568,8 @@ menu_t SP_LevelStatsDef =
 	NULL
 };
 
-menu_t SP_TimeAttackLevelSelectDef =
-{
-	"M_ATTACK",
-	sizeof (SP_TimeAttackLevelSelectMenu)/sizeof (menuitem_t),
-	&MainDef,  // Doesn't matter.
-	SP_TimeAttackLevelSelectMenu,
-	M_DrawLevelPlatterMenu,
-	0, 0,
-	0,
-	NULL
-};
+menu_t SP_TimeAttackLevelSelectDef = MAPPLATTERMENUSTYLE("M_ATTACK", SP_TimeAttackLevelSelectMenu);
+
 static menu_t SP_TimeAttackDef =
 {
 	"M_ATTACK",
@@ -1642,17 +1615,8 @@ static menu_t SP_GhostDef =
 	NULL
 };
 
-menu_t SP_NightsAttackLevelSelectDef =
-{
-	"M_NIGHTS", // HAMALAYAN
-	sizeof (SP_NightsAttackLevelSelectMenu)/sizeof (menuitem_t),
-	&MainDef,  // Doesn't matter.
-	SP_NightsAttackLevelSelectMenu,
-	M_DrawLevelPlatterMenu,
-	0, 0,
-	0,
-	NULL
-};
+menu_t SP_NightsAttackLevelSelectDef = MAPPLATTERMENUSTYLE("M_NIGHTS", SP_NightsAttackLevelSelectMenu);
+
 static menu_t SP_NightsAttackDef =
 {
 	"M_NIGHTS",
diff --git a/src/m_menu.h b/src/m_menu.h
index d5a3600a1..076fae1dc 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -279,14 +279,14 @@ void Screenshot_option_Onchange(void);
 	NULL\
 }
 
-#define MAPICONMENUSTYLE(header, source, prev)\
+#define MAPPLATTERMENUSTYLE(header, source)\
 {\
 	header,\
 	sizeof (source)/sizeof (menuitem_t),\
-	prev,\
+	&MainDef,\
 	source,\
-	M_DrawServerMenu,\
-	27,40,\
+	M_DrawLevelPlatterMenu,\
+	0,0,\
 	0,\
 	NULL\
 }

From ba652864a6394198b9ddfb55b114297fe76504fc Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 4 Feb 2017 17:08:14 +0000
Subject: [PATCH 036/119] Revamped roll-on-landing to work even from jumping,
 for the purposes of slopes! (But not from PF_THOKKED.)

Salt has wanted this for a while, and this was the only branch I had open which it was appropriate for. :P
---
 src/p_mobj.c | 10 +++++++---
 src/p_user.c |  2 +-
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index 04655814c..fa59f7663 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3322,9 +3322,13 @@ static void P_PlayerZMovement(mobj_t *mo)
 						}
 					}
 
-					if (mo->player->pflags & PF_JUMPED)
-						mo->player->pflags &= ~PF_SPINNING;
-					else if (!(mo->player->pflags & PF_USEDOWN))
+					if ((mo->player->charability2 == CA2_SPINDASH) && !(mo->player->pflags & PF_THOKKED) && (mo->player->cmd.buttons & BT_USE) && (FixedHypot(mo->momx, mo->momy) > (5*mo->scale)))
+					{
+						mo->player->pflags |= PF_SPINNING;
+						P_SetPlayerMobjState(mo, S_PLAY_SPIN);
+						S_StartSound(mo, sfx_spin);
+					}
+					else
 						mo->player->pflags &= ~PF_SPINNING;
 
 					if (!(mo->player->pflags & PF_GLIDING))
diff --git a/src/p_user.c b/src/p_user.c
index 662479a89..7ea32612e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3843,7 +3843,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					}
 				}
 
-				// If not moving up or down, and travelling faster than a speed of four while not holding
+				// If not moving up or down, and travelling faster than a speed of five while not holding
 				// down the spin button and not spinning.
 				// AKA Just go into a spin on the ground, you idiot. ;)
 				else if ((cmd->buttons & BT_USE || ((twodlevel || (player->mo->flags2 & MF2_TWOD)) && cmd->forwardmove < -20))

From 5170fafcac472d45a5c6879f6ceb5a597cd061c5 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 4 Feb 2017 18:25:16 +0000
Subject: [PATCH 037/119] * CA_BOUNCE is now more pleasant to control. *
 CA_MELEE can now break downwards like CA_BOUNCE, and CA_TWINSPIN can break
 both up and downwards.

---
 src/p_mobj.c |  7 -------
 src/p_user.c | 42 +++++++++++++++++++++---------------------
 2 files changed, 21 insertions(+), 28 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index fa59f7663..cd610ec85 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -4200,14 +4200,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN;
 
 	if (mobj->state-states == S_PLAY_BOUNCE_LANDING)
-	{
-		if (mobj->player->cmd.forwardmove || mobj->player->cmd.sidemove)
-			P_InstaThrust(
-			mobj,
-			mobj->angle + R_PointToAngle2(0, 0, mobj->player->cmd.forwardmove<<FRACBITS, -mobj->player->cmd.sidemove<<FRACBITS),
-			mobj->player->speed);
 		goto animonly; // no need for checkposition - doesn't move at ALL
-	}
 
 	// Zoom tube
 	if (mobj->tracer)
diff --git a/src/p_user.c b/src/p_user.c
index 7ea32612e..9c83713c6 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1812,22 +1812,11 @@ static void P_CheckBustableBlocks(player_t *player)
 					topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 					bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 
-					if (player->pflags & PF_BOUNCING)
+					if (((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
+					|| ((P_MobjFlip(player->mo)*player->mo->momz < 0) && (player->pflags & PF_BOUNCING || ((player->charability2 == CA2_MELEE) && (player->panim == PA_ABILITY2)))))
 					{
-						if (player->mo->eflags & MFE_VERTICALFLIP)
-						{
-							if (player->mo->momz <= 0)
-								continue;
-							topheight += player->mo->momz;
-							bottomheight += player->mo->momz;
-						}
-						else
-						{
-							if (player->mo->momz >= 0)
-								continue;
-							topheight -= player->mo->momz;
-							bottomheight -= player->mo->momz;
-						}
+						topheight -= player->mo->momz;
+						bottomheight -= player->mo->momz;
 					}
 
 					// Height checks
@@ -4850,14 +4839,25 @@ static void P_3dMovement(player_t *player)
 		thrustfactor = player->thrustfactor*2;
 		acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
 	}
-	else if (player->pflags & PF_BOUNCING)
+	else
 	{
-		thrustfactor = player->thrustfactor/2;
-		acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
-	}
+		if (player->pflags & PF_BOUNCING)
+		{
+			if (player->mo->state-states == S_PLAY_BOUNCE_LANDING)
+			{
+				thrustfactor = player->thrustfactor/2;
+				acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
+			}
+			else
+			{
+				thrustfactor = player->thrustfactor*2;
+				acceleration = player->accelstart/2 + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration/2;
+			}
+		}
 
-	if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration...
-		acceleration = FixedMul(acceleration<<FRACBITS, player->mo->movefactor)>>FRACBITS;
+		if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration...
+			acceleration = FixedMul(acceleration<<FRACBITS, player->mo->movefactor)>>FRACBITS;
+	}
 
 	// Forward movement
 	if (player->climbing)

From abc218de389f3b698a6320d269b7d3245ae136f0 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 4 Feb 2017 21:41:43 +0000
Subject: [PATCH 038/119] I am bad at conditions.

---
 src/p_user.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 9c83713c6..99bb8594b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4845,13 +4845,13 @@ static void P_3dMovement(player_t *player)
 		{
 			if (player->mo->state-states == S_PLAY_BOUNCE_LANDING)
 			{
-				thrustfactor = player->thrustfactor/2;
-				acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
+				thrustfactor = player->thrustfactor*8;
+				acceleration = player->accelstart/8 + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration/8;
 			}
 			else
 			{
-				thrustfactor = player->thrustfactor*2;
-				acceleration = player->accelstart/2 + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration/2;
+				thrustfactor = (3*player->thrustfactor)/4;
+				acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
 			}
 		}
 

From d670189c89fcd54e2a3a731a1d189704e7d99894 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 5 Feb 2017 21:20:32 +0000
Subject: [PATCH 039/119] * Fixed the Mode Attack cv_nextmap issue Salt found.
 * Improved the defaulting system to work cross-gametype. * Cleaned up the
 code. Less hacks!

---
 src/d_netcmd.c |  3 ++-
 src/g_game.c   |  3 +--
 src/m_menu.c   | 18 ++++++++----------
 src/m_menu.h   |  3 +++
 src/y_inter.c  |  3 +--
 5 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index c36477fc2..0fc5ddc85 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1532,7 +1532,8 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	// The supplied data are assumed to be good.
 	I_Assert(delay >= 0 && delay <= 2);
 
-	CV_SetValue(&cv_nextmap, mapnum);
+	if (mapnum != -1)
+		CV_SetValue(&cv_nextmap, mapnum);
 
 	CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
 	           mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene);
diff --git a/src/g_game.c b/src/g_game.c
index 1de4c17a7..7182ce51c 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -697,8 +697,7 @@ void G_SetNightsRecords(void)
 	free(gpath);
 
 	// If the mare count changed, this will update the score display
-	CV_AddValue(&cv_nextmap, 1);
-	CV_AddValue(&cv_nextmap, -1);
+	Nextmap_OnChange();
 }
 
 // for consistency among messages: this modifies the game and removes savemoddata.
diff --git a/src/m_menu.c b/src/m_menu.c
index 3eed3cf27..d55fb865b 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -377,7 +377,6 @@ static void M_HandleFogColor(INT32 choice);
 static void M_HandleVideoMode(INT32 choice);
 
 // Consvar onchange functions
-static void Nextmap_OnChange(void);
 static void Newgametype_OnChange(void);
 static void Dummymares_OnChange(void);
 
@@ -1850,8 +1849,8 @@ menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOp
 // Prototypes
 static INT32 M_GetFirstLevelInList(INT32 gt);
 
-// Nextmap.  Used for Time Attack.
-static void Nextmap_OnChange(void)
+// Nextmap.  Used for Level select.
+void Nextmap_OnChange(void)
 {
 	char *leveltitle;
 	char tabase[256];
@@ -6303,8 +6302,7 @@ static void M_EraseGuest(INT32 choice)
 		M_SetupNextMenu(&SP_NightsAttackDef);
 	else
 		M_SetupNextMenu(&SP_TimeAttackDef);
-	CV_AddValue(&cv_nextmap, -1);
-	CV_AddValue(&cv_nextmap, 1);
+	Nextmap_OnChange();
 	M_StartMessage(M_GetText("Guest replay data erased.\n"),NULL,MM_NOTHING);
 }
 
@@ -6330,8 +6328,7 @@ static void M_OverwriteGuest(const char *which, boolean nights)
 		M_SetupNextMenu(&SP_NightsAttackDef);
 	else
 		M_SetupNextMenu(&SP_TimeAttackDef);
-	CV_AddValue(&cv_nextmap, -1);
-	CV_AddValue(&cv_nextmap, 1);
+	Nextmap_OnChange();
 	M_StartMessage(M_GetText("Guest replay data saved.\n"),NULL,MM_NOTHING);
 }
 
@@ -6422,9 +6419,7 @@ static void M_ModeAttackEndGame(INT32 choice)
 	G_SetGamestate(GS_TIMEATTACK);
 	modeattacking = ATTACKING_NONE;
 	S_ChangeMusicInternal("_inter", true);
-	// Update replay availability.
-	CV_AddValue(&cv_nextmap, 1);
-	CV_AddValue(&cv_nextmap, -1);
+	Nextmap_OnChange();
 }
 
 // ========
@@ -6897,6 +6892,9 @@ static void M_MapChange(INT32 choice)
 
 	CV_SetValue(&cv_newgametype, choice);
 
+	if (Playing() && !(M_CanShowLevelOnPlatter(cv_nextmap.value-1, choice)) && (M_CanShowLevelOnPlatter(gamemap-1, choice)))
+		CV_SetValue(&cv_nextmap, gamemap);
+
 	if (!M_PrepareLevelPlatter(choice))
 	{
 		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
diff --git a/src/m_menu.h b/src/m_menu.h
index 076fae1dc..bb76dbbb1 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -238,6 +238,9 @@ void M_ForceSaveSlotSelected(INT32 sslot);
 
 void M_CheatActivationResponder(INT32 ch);
 
+// Level select updating
+void Nextmap_OnChange(void);
+
 // Screenshot menu updating
 void Moviemode_mode_Onchange(void);
 void Screenshot_option_Onchange(void);
diff --git a/src/y_inter.c b/src/y_inter.c
index 3b14f2837..4fc32c01f 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -900,8 +900,7 @@ static void Y_UpdateRecordReplays(void)
 		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
 
 	// Update timeattack menu's replay availability.
-	CV_AddValue(&cv_nextmap, 1);
-	CV_AddValue(&cv_nextmap, -1);
+	Nextmap_OnChange();
 }
 
 //

From 29c48c1992342d249091ded2b6441c0834062f5f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 8 Feb 2017 16:08:36 +0000
Subject: [PATCH 040/119] As defined in spec, lose a bit of chain each time you
 bounce on land.

---
 src/p_mobj.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index cd610ec85..1dd2d8b4e 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3374,6 +3374,8 @@ static void P_PlayerZMovement(mobj_t *mo)
 					{
 						mo->momz *= -1;
 						P_DoAbilityBounce(mo->player, true);
+						if (mo->player->scoreadd)
+							mo->player->scoreadd--;
 						clipmomz = false;
 					}
 				}

From 7b4688732b0595340718300992a47974295bf086 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 8 Feb 2017 16:46:16 +0000
Subject: [PATCH 041/119] * Float ability now properly stacks with dashmode. *
 Corrected a scenario where you could get a CTF flag mid-dashmode and have
 dashmode properties forever.

---
 src/p_user.c | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 99bb8594b..30557c559 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4280,7 +4280,12 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				case CA_SLOWFALL: // Slow descent hover
 					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
 					{
-						P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
+						if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE)
+							P_SetPlayerMobjState(player->mo, S_PLAY_PEEL);
+						else 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->pflags |= PF_THOKKED;
 						player->pflags &= ~(PF_JUMPED|PF_SPINNING);
 						player->secondjump = 1;
@@ -6701,7 +6706,7 @@ static void P_MovePlayer(player_t *player)
 	if ((cmd->forwardmove != 0 || cmd->sidemove != 0) || (player->powers[pw_super] && !onground))
 	{
 		// If the player is in dashmode, here's their peelout.
-		if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim == PA_RUN && !player->skidtime && (onground || player->powers[pw_super]))
+		if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim == PA_RUN && !player->skidtime && (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
 			P_SetPlayerMobjState (player->mo, S_PLAY_PEEL);
 		// If the player is moving fast enough,
 		// break into a run!
@@ -9448,14 +9453,17 @@ void P_PlayerThink(player_t *player)
 	// Dash mode - thanks be to Iceman404
 	if ((player->charflags & SF_DASHMODE) && !(player->gotflag) && !(maptol & TOL_NIGHTS)) // woo, dashmode! no nights tho.
 	{
-		if (player->secondjump != 1 && (player->speed >= FixedMul(player->runspeed, player->mo->scale) || (player->pflags & PF_STARTDASH)))
+		boolean totallyradical = player->speed >= FixedMul(player->runspeed, player->mo->scale);
+		boolean floating = (player->secondjump == 1);
+
+		if ((totallyradical && !floating) || (player->pflags & PF_STARTDASH))
 		{
 			if (dashmode < 3*TICRATE + 3)
 				dashmode++; // Counter. Adds 1 to dash mode per tic in top speed.
 			if (dashmode == 3*TICRATE) // This isn't in the ">=" equation because it'd cause the sound to play infinitely.
 				S_StartSound(player->mo, sfx_s3ka2); // If the player enters dashmode, play this sound on the the tic it starts.
 		}
-		else if (!(player->pflags & PF_SPINNING))
+		else if ((!totallyradical || !floating) && !(player->pflags & PF_SPINNING))
 		{
 			if (dashmode > 3)
 				dashmode -= 3; // Rather than lose it all, it gently counts back down!
@@ -9483,8 +9491,15 @@ void P_PlayerThink(player_t *player)
 			ghost->fuse = 2; // Makes the images fade quickly
 		}
 	}
-	else
+	else if (dashmode)
+	{
+		if (dashmode >= 3*TICRATE) // catch getting the flag!
+		{
+			player->normalspeed = skins[player->skin].normalspeed;
+			player->jumpfactor = skins[player->skin].jumpfactor;
+		}
 		dashmode = 0;
+	}
 #undef dashmode
 /*
 //	Colormap verification

From 8cdcb2c4169d75d32c47bbac25f26e11f6d1480f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 9 Feb 2017 23:24:47 +0000
Subject: [PATCH 042/119] * You can now bounce off of springs without
 unbouncing. * You can now bounce off the bottom of goop areas. * Fixed that
 long-standing bug where you could accelerate whilst rolling.

---
 src/p_map.c  | 15 ++++++++-------
 src/p_mobj.c |  9 ++++++---
 src/p_user.c | 22 ++++++++++++++++++----
 3 files changed, 32 insertions(+), 14 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index 4c36e4465..7d34d73e1 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -203,7 +203,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			}
 		}
 
-		pflags = object->player->pflags & (PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_SHIELDABILITY); // I still need these.
+		pflags = object->player->pflags & (PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_SHIELDABILITY|PF_BOUNCING); // I still need these.
 		jumping = object->player->jumping;
 		secondjump = object->player->secondjump;
 		P_ResetPlayer(object->player);
@@ -213,13 +213,10 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			object->player->pflags |= PF_JUMPED;
 			P_SetPlayerMobjState(object, S_PLAY_JUMP);
 		}
-		else if (P_MobjFlip(object)*vertispeed > 0)
-			P_SetPlayerMobjState(object, S_PLAY_SPRING);
-		else if (P_MobjFlip(object)*vertispeed < 0)
-			P_SetPlayerMobjState(object, S_PLAY_FALL);
-		else // horizontal spring
+		else if (!vertispeed || (pflags & PF_BOUNCING)) // horizontal spring or bouncing
 		{
-			if (pflags & (PF_JUMPED|PF_SPINNING) && (object->player->panim == PA_ROLL || object->player->panim == PA_JUMP || object->player->panim == PA_FALL))
+			if ((pflags & PF_BOUNCING)
+			|| (pflags & (PF_JUMPED|PF_SPINNING) && (object->player->panim == PA_ROLL || object->player->panim == PA_JUMP || object->player->panim == PA_FALL)))
 			{
 				object->player->pflags |= pflags;
 				object->player->jumping = jumping;
@@ -228,6 +225,10 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			else
 				P_SetPlayerMobjState(object, S_PLAY_WALK);
 		}
+		else if (P_MobjFlip(object)*vertispeed > 0)
+			P_SetPlayerMobjState(object, S_PLAY_SPRING);
+		else
+			P_SetPlayerMobjState(object, S_PLAY_FALL);
 	}
 	return true;
 }
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 1dd2d8b4e..9d076478c 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1924,9 +1924,12 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy)
 		// spinning friction
 		if (player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH))
 		{
-			const fixed_t ns = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT);
-			mo->momx = FixedMul(mo->momx, ns);
-			mo->momy = FixedMul(mo->momy, ns);
+			if (twodlevel || player->mo->flags2 & MF2_TWOD) // Otherwise handled in P_3DMovement
+			{
+				const fixed_t ns = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT);
+				mo->momx = FixedMul(mo->momx, ns);
+				mo->momy = FixedMul(mo->momy, ns);
+			}
 		}
 		else if (abs(player->rmomx) < FixedMul(STOPSPEED, mo->scale)
 		    && abs(player->rmomy) < FixedMul(STOPSPEED, mo->scale)
diff --git a/src/p_user.c b/src/p_user.c
index 30557c559..f7f20ac85 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1185,7 +1185,7 @@ boolean P_IsObjectInGoop(mobj_t *mo)
 //
 boolean P_IsObjectOnGround(mobj_t *mo)
 {
-	if (P_IsObjectInGoop(mo))
+	if (P_IsObjectInGoop(mo) && !(mo->player && mo->player->pflags & PF_BOUNCING))
 	{
 /*
 		// It's a crazy hack that checking if you're on the ground
@@ -3998,8 +3998,8 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz)
 		return;
 	if (changemomz)
 	{
-		prevmomz = P_MobjFlip(player->mo)*player->mo->momz;
-		if (prevmomz < 0)
+		prevmomz = player->mo->momz;
+		if (P_MobjFlip(player->mo)*prevmomz < 0)
 			prevmomz = 0;
 		else if (player->mo->eflags & MFE_UNDERWATER)
 			prevmomz /= 2;
@@ -4532,6 +4532,8 @@ static void P_2dMovement(player_t *player)
 				player->skidtime = 0;
 			}
 		}
+		if (player->pflags & PF_BOUNCING)
+			player->pflags &= ~PF_BOUNCING;
 		if (player->pflags & PF_SPINNING && !player->exiting)
 		{
 			player->pflags &= ~PF_SPINNING;
@@ -4691,6 +4693,7 @@ static void P_3dMovement(player_t *player)
 	angle_t dangle; // replaces old quadrants bits
 	fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale);
 	boolean analogmove = false;
+	boolean spin = (player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
 	fixed_t oldMagnitude, newMagnitude;
 #ifdef ESLOPE
 	vector3_t totalthrust;
@@ -4720,6 +4723,8 @@ static void P_3dMovement(player_t *player)
 				player->skidtime = 0;
 			}
 		}
+		if (player->pflags & PF_BOUNCING)
+			player->pflags &= ~PF_BOUNCING;
 		if (player->pflags & PF_SPINNING && !player->exiting)
 		{
 			player->pflags &= ~PF_SPINNING;
@@ -4838,6 +4843,15 @@ static void P_3dMovement(player_t *player)
 			topspeed = normalspd;
 	}
 
+	if (spin) // Prevent gaining speed whilst rolling!
+	{
+		const fixed_t ns = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT); // P_XYFriction
+		if (onground)
+			topspeed = FixedMul(oldMagnitude, ns);
+		else
+			topspeed = oldMagnitude;
+	}
+
 	// Better maneuverability while flying
 	if (player->powers[pw_tailsfly])
 	{
@@ -5015,7 +5029,7 @@ static void P_3dMovement(player_t *player)
 	if (newMagnitude > topspeed)
 	{
 		fixed_t tempmomx, tempmomy;
-		if (oldMagnitude > topspeed)
+		if (oldMagnitude > topspeed && !spin)
 		{
 			if (newMagnitude > oldMagnitude)
 			{

From 9fc757f388e37094fb792296f8ec1b1bd69f4e14 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 10 Feb 2017 14:54:05 +0000
Subject: [PATCH 043/119] Inuyasha: You were right.

* Changed the order of Record/Nights Attack and its level select menu option so that you go from the main menu to the map page to level select, rather than main menu to level select to map page.
* Cleaned up a lot of code.
---
 src/command.c |  31 ++++------
 src/m_menu.c  | 165 +++++++++++++++-----------------------------------
 2 files changed, 61 insertions(+), 135 deletions(-)

diff --git a/src/command.c b/src/command.c
index 6534fed3d..36c759109 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1551,34 +1551,27 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 			if (var == &cv_chooseskin)
 			{
 				// Special case for the chooseskin variable, used only directly from the menu
-				if (increment > 0) // Going up!
+				newvalue = var->value - 1;
+				do
 				{
-					newvalue = var->value - 1;
-					do
+					if (increment > 0) // Going up!
 					{
 						newvalue++;
 						if (newvalue == MAXSKINS)
 							newvalue = 0;
-					} while (var->PossibleValue[newvalue].strvalue == NULL);
-					var->value = newvalue + 1;
-					var->string = var->PossibleValue[newvalue].strvalue;
-					var->func();
-					return;
-				}
-				else if (increment < 0) // Going down!
-				{
-					newvalue = var->value - 1;
-					do
+					}
+					else if (increment < 0) // Going down!
 					{
 						newvalue--;
 						if (newvalue == -1)
 							newvalue = MAXSKINS-1;
-					} while (var->PossibleValue[newvalue].strvalue == NULL);
-					var->value = newvalue + 1;
-					var->string = var->PossibleValue[newvalue].strvalue;
-					var->func();
-					return;
-				}
+					}
+				} while (var->PossibleValue[newvalue].strvalue == NULL);
+
+				var->value = newvalue + 1;
+				var->string = var->PossibleValue[newvalue].strvalue;
+				var->func();
+				return;
 			}
 #ifdef PARANOIA
 			if (currentindice == -1)
diff --git a/src/m_menu.c b/src/m_menu.c
index d55fb865b..20667eea5 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -703,8 +703,8 @@ static menuitem_t SR_EmblemHintMenu[] =
 static menuitem_t SP_MainMenu[] =
 {
 	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 92},
-	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttackLevelSelect,   100},
-	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttackLevelSelect, 108},
+	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,              100},
+	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,            108},
 	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics",    M_Statistics,              116},
 };
 
@@ -737,8 +737,8 @@ static menuitem_t SP_TimeAttackLevelSelectMenu[] =
 // Single Player Time Attack
 static menuitem_t SP_TimeAttackMenu[] =
 {
-	{IT_STRING|IT_CALL,        NULL, "Back to Level Select...", &M_GoBack,   52},
-	{IT_STRING|IT_CVAR,        NULL, "Character",          &cv_chooseskin,   62},
+	{IT_STRING|IT_CALL,        NULL, "Level Select...", &M_TimeAttackLevelSelect,   52},
+	{IT_STRING|IT_CVAR,        NULL, "Character",       &cv_chooseskin,             62},
 
 	{IT_DISABLED,              NULL, "Guest Option...", &SP_GuestReplayDef, 100},
 	{IT_DISABLED,              NULL, "Replay...",       &SP_ReplayDef,      110},
@@ -835,12 +835,12 @@ static menuitem_t SP_NightsAttackLevelSelectMenu[] =
 // Single Player Nights Attack
 static menuitem_t SP_NightsAttackMenu[] =
 {
-	{IT_STRING|IT_CALL,        NULL, "Back to Level Select...",  &M_GoBack,           52},
-	{IT_STRING|IT_CVAR,        NULL, "Show Records For",    &cv_dummymares,           62},
+	{IT_STRING|IT_CALL,        NULL, "Level Select...",  &M_NightsAttackLevelSelect,  52},
+	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,              62},
 
-	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,   100},
-	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,        110},
-	{IT_DISABLED,              NULL, "Ghosts...",        &SP_NightsGhostDef,         120},
+	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,    100},
+	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,         110},
+	{IT_DISABLED,              NULL, "Ghosts...",        &SP_NightsGhostDef,          120},
 	{IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED, NULL, "Start", M_ChooseNightsAttack, 130},
 };
 
@@ -1848,6 +1848,7 @@ menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOp
 
 // Prototypes
 static INT32 M_GetFirstLevelInList(INT32 gt);
+static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt);
 
 // Nextmap.  Used for Level select.
 void Nextmap_OnChange(void)
@@ -1994,12 +1995,7 @@ static void Newgametype_OnChange(void)
 		if(!mapheaderinfo[cv_nextmap.value-1])
 			P_AllocMapHeader((INT16)(cv_nextmap.value-1));
 
-		if ((cv_newgametype.value == GT_COOP && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_COOP)) ||
-			(cv_newgametype.value == GT_COMPETITION && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_COMPETITION)) ||
-			(cv_newgametype.value == GT_RACE && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_RACE)) ||
-			((cv_newgametype.value == GT_MATCH || cv_newgametype.value == GT_TEAMMATCH) && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_MATCH)) ||
-			((cv_newgametype.value == GT_TAG || cv_newgametype.value == GT_HIDEANDSEEK) && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_TAG)) ||
-			(cv_newgametype.value == GT_CTF && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_CTF)))
+		if (!M_CanShowLevelOnPlatter(cv_nextmap.value-1, cv_newgametype.value))
 		{
 			INT32 value = 0;
 
@@ -3533,11 +3529,8 @@ static void M_PatchSkinNameTable(void)
 		}
 	}
 
-	CV_SetValue(&cv_chooseskin, cv_chooseskin.value); // This causes crash sometimes?!
-
 	CV_SetValue(&cv_chooseskin, 1);
-	CV_AddValue(&cv_chooseskin, -1);
-	CV_AddValue(&cv_chooseskin, 1);
+	Nextmap_OnChange();
 
 	return;
 }
@@ -3919,12 +3912,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 				lsoffs[0] = lsoffs[1] = 0;
 				S_StartSound(NULL,sfx_menu1);
 				if (gamestate == GS_TIMEATTACK)
-				{
-					if (currentMenu == &SP_TimeAttackLevelSelectDef)
-						M_TimeAttack(-1);
-					else
-						M_NightsAttack(-1);
-				}
+					M_SetupNextMenu(currentMenu->prevMenu);
 				else if (currentMenu == &MISC_ChangeLevelDef)
 				{
 					if (currentMenu->prevMenu && currentMenu->prevMenu->prevMenu != &MPauseDef)
@@ -3934,6 +3922,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 				}
 				else
 					M_LevelSelectWarp(0);
+				Nextmap_OnChange();
 			}
 			else if (!lsoffs[0]) //  prevent sound spam
 			{
@@ -3954,14 +3943,8 @@ static void M_HandleLevelPlatter(INT32 choice)
 	{
 		if (currentMenu->prevMenu)
 		{
-			if (gamestate == GS_TIMEATTACK)
-			{
-				// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
-				menuactive = false;
-				D_StartTitle();
-			}
-			else
-				M_SetupNextMenu(currentMenu->prevMenu);
+			M_SetupNextMenu(currentMenu->prevMenu);
+			Nextmap_OnChange();
 		}
 		else
 			M_ClearMenus(true);
@@ -4103,17 +4086,6 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
 	return (M_CanShowLevelOnPlatter(mapnum, gt) && M_LevelAvailableOnPlatter(mapnum));
 }
 
-static INT32 M_CountLevelsToShowInList(void)
-{
-	INT32 mapnum, count = 0;
-
-	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
-		if (M_CanShowLevelInList(mapnum, -1))
-			count++;
-
-	return count;
-}
-
 static INT32 M_GetFirstLevelInList(INT32 gt)
 {
 	INT32 mapnum;
@@ -5948,7 +5920,17 @@ void M_DrawTimeAttackMenu(void)
 static void M_TimeAttackLevelSelect(INT32 choice)
 {
 	(void)choice;
-	levellistmode = LLM_RECORDATTACK;
+	SP_TimeAttackLevelSelectDef.prevMenu = currentMenu;
+	M_SetupNextMenu(&SP_TimeAttackLevelSelectDef);
+}
+
+// Going to Time Attack menu...
+static void M_TimeAttack(INT32 choice)
+{
+	(void)choice;
+
+	SP_TimeAttackDef.prevMenu = &MainDef;
+	levellistmode = LLM_RECORDATTACK; // Don't be dependent on cv_newgametype
 
 	if (!M_PrepareLevelPlatter(-1))
 	{
@@ -5958,43 +5940,14 @@ static void M_TimeAttackLevelSelect(INT32 choice)
 
 	M_PatchSkinNameTable();
 
-	M_SetupNextMenu(&SP_TimeAttackLevelSelectDef);
+	M_SetupNextMenu(&SP_TimeAttackDef);
+	if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
+		CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
+	else
+		Nextmap_OnChange();
 
 	G_SetGamestate(GS_TIMEATTACK);
 	S_ChangeMusicInternal("_inter", true);
-}
-
-// Going to Time Attack menu...
-static void M_TimeAttack(INT32 choice)
-{
-	const boolean direct = (choice != -1); // Are we coming from SP_TimeAtttackLevelSelect?
-
-	if (direct)
-	{
-		SP_TimeAttackDef.prevMenu = &MainDef;
-		levellistmode = LLM_RECORDATTACK; // Don't be dependent on cv_newgametype
-
-		if (M_CountLevelsToShowInList() == 0)
-		{
-			M_StartMessage(M_GetText("No record-attackable levels found.\n"),NULL,MM_NOTHING);
-			return;
-		}
-
-		M_PatchSkinNameTable();
-
-		Newgametype_OnChange();
-	}
-	else
-		SP_TimeAttackDef.prevMenu = currentMenu;
-
-	M_SetupNextMenu(&SP_TimeAttackDef);
-	Nextmap_OnChange();
-
-	if (direct)
-	{
-		G_SetGamestate(GS_TIMEATTACK);
-		S_ChangeMusicInternal("_inter", true);
-	}
 
 	itemOn = tastart; // "Start" is selected.
 }
@@ -6137,54 +6090,34 @@ void M_DrawNightsAttackMenu(void)
 static void M_NightsAttackLevelSelect(INT32 choice)
 {
 	(void)choice;
-	levellistmode = LLM_NIGHTSATTACK;
+	SP_NightsAttackLevelSelectDef.prevMenu = currentMenu;
+	M_SetupNextMenu(&SP_NightsAttackLevelSelectDef);
+}
+
+// Going to Nights Attack menu...
+static void M_NightsAttack(INT32 choice)
+{
+	(void)choice;
+
+	SP_NightsAttackDef.prevMenu = &MainDef;
+	levellistmode = LLM_NIGHTSATTACK; // Don't be dependent on cv_newgametype
 
 	if (!M_PrepareLevelPlatter(-1))
 	{
 		M_StartMessage(M_GetText("No NiGHTS-attackable levels found.\n"),NULL,MM_NOTHING);
 		return;
 	}
-
+	// This is really just to make sure Sonic is the played character, just in case
 	M_PatchSkinNameTable();
 
-	M_SetupNextMenu(&SP_NightsAttackLevelSelectDef);
+	M_SetupNextMenu(&SP_NightsAttackDef);
+	if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
+		CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
+	else
+		Nextmap_OnChange();
 
 	G_SetGamestate(GS_TIMEATTACK);
 	S_ChangeMusicInternal("_inter", true);
-}
-
-// Going to Nights Attack menu...
-static void M_NightsAttack(INT32 choice)
-{
-	const boolean direct = (choice != -1); // Are we coming from SP_TimeAtttackLevelSelect?
-
-	if (direct)
-	{
-		SP_NightsAttackDef.prevMenu = &MainDef;
-		levellistmode = LLM_NIGHTSATTACK; // Don't be dependent on cv_newgametype
-
-		if (M_CountLevelsToShowInList() == 0)
-		{
-			M_StartMessage(M_GetText("No NiGHTS-attackable levels found.\n"),NULL,MM_NOTHING);
-			return;
-		}
-
-		// This is really just to make sure Sonic is the played character, just in case
-		M_PatchSkinNameTable();
-
-		Newgametype_OnChange();
-	}
-	else
-		SP_NightsAttackDef.prevMenu = currentMenu;
-
-	M_SetupNextMenu(&SP_NightsAttackDef);
-	Nextmap_OnChange();
-
-	if (direct)
-	{
-		G_SetGamestate(GS_TIMEATTACK);
-		S_ChangeMusicInternal("_inter", true);
-	}
 
 	itemOn = nastart; // "Start" is selected.
 }

From a66567ea0f16544cf6d62f84d69d4878f09deddb Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 10 Feb 2017 23:24:06 +0000
Subject: [PATCH 044/119] borp https://gfycat.com/AcceptableWhiteJabiru

doesn't do anything in ogl sorry, inu might change that later but
---
 src/dehacked.c         |  1 +
 src/hardware/hw_draw.c |  8 ++++++--
 src/m_menu.c           | 17 +++++++++--------
 src/v_video.c          | 41 ++++++++++++++++++++++++++++-------------
 src/v_video.h          |  1 +
 5 files changed, 45 insertions(+), 23 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index e5d08fdb0..53687b705 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -7672,6 +7672,7 @@ struct {
 	{"V_70TRANS",V_70TRANS},
 	{"V_80TRANS",V_80TRANS},
 	{"V_90TRANS",V_90TRANS},
+	{"V_STATIC",V_STATIC},
 	{"V_HUDTRANSHALF",V_HUDTRANSHALF},
 	{"V_HUDTRANS",V_HUDTRANS},
 	{"V_HUDTRANSDOUBLE",V_HUDTRANSDOUBLE},
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index f23753ee5..a00bf3aeb 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -152,7 +152,9 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
 	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
 
-	if (alphalevel >= 10 && alphalevel < 13)
+	if (alphalevel == 12)
+		alphalevel = 0;
+	else if (alphalevel >= 10 && alphalevel < 13)
 		return;
 
 	// make patch ready in hardware cache
@@ -252,7 +254,9 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
 	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
 
-	if (alphalevel >= 10 && alphalevel < 13)
+	if (alphalevel == 12)
+		alphalevel = 0;
+	else if (alphalevel >= 10 && alphalevel < 13)
 		return;
 
 	// make patch ready in hardware cache
diff --git a/src/m_menu.c b/src/m_menu.c
index 20667eea5..fc8d09437 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -209,7 +209,7 @@ menu_t SPauseDef;
 // Level Select
 static levelselect_t levelselect = {0, NULL};
 static UINT8 levelselectselect[4];
-static patch_t *levselp[4];
+static patch_t *levselp[3];
 static INT32 lsoffs[2];
 
 #define lsrow levelselectselect[0]
@@ -3803,13 +3803,11 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 		W_UnlockCachedPatch(levselp[0]);
 		W_UnlockCachedPatch(levselp[1]);
 		W_UnlockCachedPatch(levselp[2]);
-		W_UnlockCachedPatch(levselp[3]);
 	}
 
 	levselp[0] = W_CachePatchName("SLCT1LVL", PU_STATIC);
 	levselp[1] = W_CachePatchName("SLCT2LVL", PU_STATIC);
 	levselp[2] = W_CachePatchName("BLANKLVL", PU_STATIC);
-	levselp[3] = W_CachePatchName("STATCLVL", PU_STATIC);
 
 	return true;
 }
@@ -3979,13 +3977,16 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 
 	//  A 160x100 image of the level as entry MAPxxP
 	if (!(levelselect.rows[row].mapavailable[col]))
-		patch = ((lstic & 1) ? levselp[2] : levselp[3]); // static - make secret maps look ENTICING
-	else if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
-		patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
+		V_DrawSmallScaledPatch(x, y, V_STATIC, levselp[2]); // static - make secret maps look ENTICING
 	else
-		patch = levselp[2]; // don't flash to indicate that it's just a normal level
+	{
+		if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
+			patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
+		else
+			patch = levselp[2]; // don't flash to indicate that it's just a normal level
 
-	V_DrawSmallScaledPatch(x, y, 0, patch);
+		V_DrawSmallScaledPatch(x, y, 0, patch);
+	}
 
 	if ((y+50) < 200)
 	{
diff --git a/src/v_video.c b/src/v_video.c
index 9109ce5cc..fb02dfc96 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -325,6 +325,13 @@ static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fix
 {
 	return *(v_translevel + (((*(v_colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff));
 }
+static inline UINT8 staticpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
+{
+	UINT8 val = source[ofs>>FRACBITS];
+	(void)dest;
+	if (val < 7) return val;
+	return M_RandomKey(7+1)+(val-7);//M_RandomByte();
+}
 
 // Draws a patch scaled to arbitrary size.
 void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
@@ -356,22 +363,30 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	patchdrawfunc = standardpdraw;
 
 	v_translevel = NULL;
-	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)) == 12) // static
 	{
-		if (alphalevel == 13)
-			alphalevel = hudminusalpha[cv_translucenthud.value];
-		else if (alphalevel == 14)
-			alphalevel = 10 - cv_translucenthud.value;
-		else if (alphalevel == 15)
-			alphalevel = hudplusalpha[cv_translucenthud.value];
-
-		if (alphalevel >= 10)
-			return; // invis
+		alphalevel = 0;
+		patchdrawfunc = staticpdraw;
 	}
-	if (alphalevel)
+	else
 	{
-		v_translevel = transtables + ((alphalevel-1)<<FF_TRANSSHIFT);
-		patchdrawfunc = translucentpdraw;
+		if (alphalevel)
+		{
+			if (alphalevel == 13)
+				alphalevel = hudminusalpha[cv_translucenthud.value];
+			else if (alphalevel == 14)
+				alphalevel = 10 - cv_translucenthud.value;
+			else if (alphalevel == 15)
+				alphalevel = hudplusalpha[cv_translucenthud.value];
+
+			if (alphalevel >= 10)
+				return; // invis
+		}
+		if (alphalevel)
+		{
+			v_translevel = transtables + ((alphalevel-1)<<FF_TRANSSHIFT);
+			patchdrawfunc = translucentpdraw;
+		}
 	}
 
 	v_colormap = NULL;
diff --git a/src/v_video.h b/src/v_video.h
index 353f84c1d..ca1f58a30 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -90,6 +90,7 @@ extern RGBA_t *pLocalPalette;
 #define V_70TRANS            0x00070000
 #define V_80TRANS            0x00080000 // used to be V_8020TRANS
 #define V_90TRANS            0x00090000
+#define V_STATIC             0x000C0000 // ogl unsupported kthnxbai
 #define V_HUDTRANSHALF       0x000D0000
 #define V_HUDTRANS           0x000E0000 // draw the hud translucent
 #define V_HUDTRANSDOUBLE     0x000F0000

From c882851f144c627498d0f706020b5f2d3e7f39cd Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 17 Feb 2017 01:45:21 +0000
Subject: [PATCH 045/119] Wide icons for the level select platter's exclusive
 usage.

https://gfycat.com/MenacingClearAngora

More for what's coming than what's here right now, but still quite pretty as a placeholder.
---
 src/dehacked.c |   8 +++
 src/doomstat.h |   1 +
 src/m_menu.c   | 184 ++++++++++++++++++++++++++++++++++---------------
 src/m_menu.h   |   2 +-
 4 files changed, 140 insertions(+), 55 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 53687b705..ed9bb490d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1424,6 +1424,13 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				else
 					mapheaderinfo[num-1]->menuflags &= ~LF2_NOVISITNEEDED;
 			}
+			else if (fastcmp(word, "WIDEICON"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->menuflags |= LF2_WIDEICON;
+				else
+					mapheaderinfo[num-1]->menuflags &= ~LF2_WIDEICON;
+			}
 			else
 				deh_warning("Level header %d: unknown word '%s'", num, word);
 		}
@@ -7309,6 +7316,7 @@ struct {
 	{"LF2_RECORDATTACK",LF2_RECORDATTACK},
 	{"LF2_NIGHTSATTACK",LF2_NIGHTSATTACK},
 	{"LF2_NOVISITNEEDED",LF2_NOVISITNEEDED},
+	{"LF2_WIDEICON",LF2_WIDEICON},
 
 	// NiGHTS grades
 	{"GRADE_F",GRADE_F},
diff --git a/src/doomstat.h b/src/doomstat.h
index 801e79edd..7ee0382b2 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -269,6 +269,7 @@ typedef struct
 #define LF2_RECORDATTACK   4 ///< Show this map in Time Attack
 #define LF2_NIGHTSATTACK   8 ///< Show this map in NiGHTS mode menu
 #define LF2_NOVISITNEEDED 16 ///< Available in time attack/nights mode without visiting the level
+#define LF2_WIDEICON      32 ///< If you're in a circumstance where it fits, use a wide map icon
 
 extern mapheader_t* mapheaderinfo[NUMMAPS];
 
diff --git a/src/m_menu.c b/src/m_menu.c
index fc8d09437..5b160362a 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -209,7 +209,7 @@ menu_t SPauseDef;
 // Level Select
 static levelselect_t levelselect = {0, NULL};
 static UINT8 levelselectselect[4];
-static patch_t *levselp[3];
+static patch_t *levselp[2][3];
 static INT32 lsoffs[2];
 
 #define lsrow levelselectselect[0]
@@ -222,6 +222,10 @@ static INT32 lsoffs[2];
 #define lsheadingheight 16
 #define getheadingoffset(row) (levelselect.rows[row].header[0] ? lsheadingheight : 0)
 #define lsvseperation(row) lsbasevseperation + getheadingoffset(row)
+#define lswide(row) levelselect.rows[row].mapavailable[3]
+
+#define lsbasex 19
+#define lsbasey 59+lsheadingheight
 
 // Sky Room
 static void M_CustomLevelSelect(INT32 choice);
@@ -3646,35 +3650,6 @@ static INT32 M_CountLevelsToShowOnPlatter(INT32 gt)
 }
 #endif
 
-static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
-{
-	INT32 mapnum = 0, prevmapnum = 0, col = 0, rows = 0;
-
-	while (mapnum < NUMMAPS)
-	{
-		if (M_CanShowLevelOnPlatter(mapnum, gt))
-		{
-			if (rows == 0)
-				rows++;
-			else
-			{
-				if (col == 2
-					|| !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading)))
-				{
-					col = 0;
-					rows++;
-				}
-				else
-					col++;
-			}
-			prevmapnum = mapnum;
-		}
-		mapnum++;
-	}
-
-	return rows;
-}
-
 #if 0
 static boolean M_SetNextMapOnPlatter(void)
 {
@@ -3698,6 +3673,37 @@ static boolean M_SetNextMapOnPlatter(void)
 }
 #endif
 
+static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
+{
+	INT32 mapnum = 0, prevmapnum = 0, col = 0, rows = 0;
+
+	while (mapnum < NUMMAPS)
+	{
+		if (M_CanShowLevelOnPlatter(mapnum, gt))
+		{
+			if (rows == 0)
+				rows++;
+			else
+			{
+				if (col == 2
+				|| (mapheaderinfo[prevmapnum]->menuflags & LF2_WIDEICON)
+				|| (mapheaderinfo[mapnum]->menuflags & LF2_WIDEICON)
+				|| !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading)))
+				{
+					col = 0;
+					rows++;
+				}
+				else
+					col++;
+			}
+			prevmapnum = mapnum;
+		}
+		mapnum++;
+	}
+
+	return rows;
+}
+
 //
 // M_PrepareLevelPlatter
 //
@@ -3707,7 +3713,7 @@ static boolean M_SetNextMapOnPlatter(void)
 static boolean M_PrepareLevelPlatter(INT32 gt)
 {
 	INT32 numrows = M_CountRowsToShowOnPlatter(gt);
-	INT32 mapnum, col = 0, row = 0;
+	INT32 mapnum = 0, prevmapnum = 0, col = 0, row = 0;
 
 	if (!numrows)
 		return false;
@@ -3724,18 +3730,21 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 	// done here so lsrow and lscol can be set if cv_nextmap is on the platter
 	lsrow = lscol = lstic = lshli = lsoffs[0] = lsoffs[1] = 0;
 
-	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+	while (mapnum < NUMMAPS)
 	{
 		if (M_CanShowLevelOnPlatter(mapnum, gt))
 		{
 			const INT32 actnum = mapheaderinfo[mapnum]->actnum;
 			const boolean headingisname = (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[mapnum]->lvlttl));
+			const boolean wide = (mapheaderinfo[mapnum]->menuflags & LF2_WIDEICON);
 
 			// preparing next position to drop mapnum into
 			if (levelselect.rows[0].maplist[0])
 			{
 				if (col == 2 // no more space on the row?
-					|| (levelselect.rows[row].maplist[0] && !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row].maplist[0]-1]->selectheading)))) // a new heading is starting?
+				|| wide
+				|| (mapheaderinfo[prevmapnum]->menuflags & LF2_WIDEICON)
+				|| !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading))) // a new heading is starting?
 				{
 					col = 0;
 					row++;
@@ -3747,6 +3756,12 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 			levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
 			levelselect.rows[row].mapavailable[col] = M_LevelAvailableOnPlatter(mapnum);
 
+			if ((levelselect.rows[row].mapavailable[3] = wide))
+			{
+				levelselect.rows[row].maplist[2] = levelselect.rows[row].maplist[1] = levelselect.rows[row].maplist[0];
+				levelselect.rows[row].mapavailable[2] = levelselect.rows[row].mapavailable[1] = levelselect.rows[row].mapavailable[0];
+			}
+
 			if (cv_nextmap.value == mapnum+1) // A little quality of life improvement.
 			{
 				lsrow = row;
@@ -3763,9 +3778,16 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 					else
 						sprintf(levelselect.rows[row].mapnames[col], "THE ACT");
 				}
+				else if (wide)
+				{
+					// Yes, with LF2_WIDEICON it'll continue on over into the next 17+1 char block. That's alright; col is always zero, the string is contiguous, and the maximum length is lvlttl[22] + ' ' + ZONE + ' ' + INT32, which is about 39 or so - barely crossing into the third column.
+					char* mapname = G_BuildMapTitle(mapnum+1);
+					strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
+					Z_Free(mapname);
+				}
 				else
 				{
-					char mapname[22+3]; // lvlttl[22] + " 19"
+					char mapname[22+1+11]; // lvlttl[22] + ' ' + INT32
 
 					if (actnum)
 						sprintf(mapname, "%s %d", mapheaderinfo[mapnum]->lvlttl, actnum);
@@ -3795,19 +3817,31 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 					}
 				}
 			}
+
+			prevmapnum = mapnum;
 		}
+
+		mapnum++;
 	}
 
 	if (levselp[0]) // never going to have some provided but not all, saves individually checking
 	{
-		W_UnlockCachedPatch(levselp[0]);
-		W_UnlockCachedPatch(levselp[1]);
-		W_UnlockCachedPatch(levselp[2]);
+		W_UnlockCachedPatch(levselp[0][0]);
+		W_UnlockCachedPatch(levselp[0][1]);
+		W_UnlockCachedPatch(levselp[0][2]);
+
+		W_UnlockCachedPatch(levselp[1][0]);
+		W_UnlockCachedPatch(levselp[1][1]);
+		W_UnlockCachedPatch(levselp[1][2]);
 	}
 
-	levselp[0] = W_CachePatchName("SLCT1LVL", PU_STATIC);
-	levselp[1] = W_CachePatchName("SLCT2LVL", PU_STATIC);
-	levselp[2] = W_CachePatchName("BLANKLVL", PU_STATIC);
+	levselp[0][0] = W_CachePatchName("SLCT1LVL", PU_STATIC);
+	levselp[0][1] = W_CachePatchName("SLCT2LVL", PU_STATIC);
+	levselp[0][2] = W_CachePatchName("BLANKLVL", PU_STATIC);
+
+	levselp[1][0] = W_CachePatchName("SLCT1LVW", PU_STATIC);
+	levselp[1][1] = W_CachePatchName("SLCT2LVW", PU_STATIC);
+	levselp[1][2] = W_CachePatchName("BLANKLVW", PU_STATIC);
 
 	return true;
 }
@@ -3875,7 +3909,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 			{
 				lscol--;
 
-				lsoffs[1] = lshseperation;
+				lsoffs[1] = (lswide(lsrow) ? -8 : lshseperation);
 				S_StartSound(NULL,sfx_s3kb7);
 
 				selectvalnextmap(lscol) else selectvalnextmap(0)
@@ -3892,7 +3926,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 			{
 				lscol++;
 
-				lsoffs[1] = -lshseperation;
+				lsoffs[1] = (lswide(lsrow) ? 8 : -lshseperation);
 				S_StartSound(NULL,sfx_s3kb7);
 
 				selectvalnextmap(lscol) else selectvalnextmap(0)
@@ -3967,6 +4001,47 @@ static void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean header
 	y += 2;
 }
 
+static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
+{
+	patch_t *patch;
+
+	INT32 map = levelselect.rows[row].maplist[col];
+	if (!map)
+		return;
+
+	//  A 160x100 image of the level as entry MAPxxP
+	if (!(levelselect.rows[row].mapavailable[col]))
+		V_DrawSmallScaledPatch(x, y, V_STATIC, levselp[1][2]); // static - make secret maps look ENTICING
+	else
+	{
+		if (W_CheckNumForName(va("%sW", G_BuildMapName(map))) != LUMPERROR)
+			patch = W_CachePatchName(va("%sW", G_BuildMapName(map)), PU_CACHE);
+		else
+			patch = levselp[1][2]; // don't static to indicate that it's just a normal level
+
+		V_DrawSmallScaledPatch(x, y, 0, patch);
+	}
+
+	if ((y+50) < 200)
+	{
+		INT32 topy = (y+50), h = 8;
+
+		if (topy < 0)
+		{
+			h += topy;
+			topy = 0;
+		}
+		else if (topy + h >= 200)
+			h = 200 - y;
+		if (h > 0)
+			V_DrawFill(x, topy, 282, h,
+			((mapheaderinfo[map-1]->unlockrequired < 0)
+			? 159 : 63));
+	}
+
+	V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+}
+
 static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
 {
 	patch_t *patch;
@@ -3977,13 +4052,13 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 
 	//  A 160x100 image of the level as entry MAPxxP
 	if (!(levelselect.rows[row].mapavailable[col]))
-		V_DrawSmallScaledPatch(x, y, V_STATIC, levselp[2]); // static - make secret maps look ENTICING
+		V_DrawSmallScaledPatch(x, y, V_STATIC, levselp[0][2]); // static - make secret maps look ENTICING
 	else
 	{
 		if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
 			patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
 		else
-			patch = levselp[2]; // don't flash to indicate that it's just a normal level
+			patch = levselp[0][2]; // don't static to indicate that it's just a normal level
 
 		V_DrawSmallScaledPatch(x, y, 0, patch);
 	}
@@ -4021,17 +4096,20 @@ static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 		y += lsheadingheight;
 	}
 
-	for (col = 0; col < 3; col++)
-		M_DrawLevelPlatterMap(row, col, 19+(col*lshseperation), y, (rowhighlight && col == lscol));
+	if (lswide(row))
+		M_DrawLevelPlatterWideMap(row, 0, lsbasex, y, rowhighlight);
+	else
+	{
+		for (col = 0; col < 3; col++)
+			M_DrawLevelPlatterMap(row, col, lsbasex+(col*lshseperation), y, (rowhighlight && (col == lscol)));
+	}
 }
 
-#define lsbasey 59+lsheadingheight
-
 static void M_DrawLevelPlatterMenu(void)
 {
-	UINT8 iter = lsrow;
+	UINT8 iter = lsrow, sizeselect = (lswide(lsrow) ? 1 : 0);
 	INT32 y = lsbasey + lsoffs[0] - getheadingoffset(lsrow);
-	const UINT32 cursorx = 19+(lscol*lshseperation);
+	const INT32 cursorx = (sizeselect ? 0 : (lscol*lshseperation));
 
 	if (++lstic == 32)
 		lstic = 0;
@@ -4055,10 +4133,10 @@ static void M_DrawLevelPlatterMenu(void)
 	}
 
 	// draw cursor box
-	V_DrawSmallScaledPatch(cursorx + lsoffs[1], lsbasey, 0, ((lstic & 8) ? levselp[0] : levselp[1]));
+	V_DrawSmallScaledPatch(lsbasex + cursorx + lsoffs[1], lsbasey, 0, ((lstic & 8) ? levselp[sizeselect][0] : levselp[sizeselect][1]));
 
 	if (levelselect.rows[lsrow].maplist[lscol])
-		V_DrawScaledPatch(cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+		V_DrawScaledPatch(lsbasex + cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
 
 	// handle movement of cursor box
 	if (abs(lsoffs[0]) > 1)
@@ -4074,8 +4152,6 @@ static void M_DrawLevelPlatterMenu(void)
 	M_DrawMenuTitle();
 }
 
-#undef lsbasey
-
 //
 // M_CanShowLevelInList
 //
diff --git a/src/m_menu.h b/src/m_menu.h
index bb76dbbb1..01adfff80 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -188,7 +188,7 @@ typedef struct
 	char header[22+5]; // mapheader_t lvltttl max length + " ZONE"
 	INT32 maplist[3];
 	char mapnames[3][17+1];
-	boolean mapavailable[3];
+	boolean mapavailable[4]; // mapavailable[3] == wide or not
 } levelselectrow_t;
 
 typedef struct

From 2f9db4486ad256fee4c3ca825030f789fbb8030d Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Feb 2017 14:03:49 +0000
Subject: [PATCH 046/119] Minor corrections.

---
 src/m_menu.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 5b160362a..9229fde0d 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3756,7 +3756,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 			levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
 			levelselect.rows[row].mapavailable[col] = M_LevelAvailableOnPlatter(mapnum);
 
-			if ((levelselect.rows[row].mapavailable[3] = wide))
+			if ((lswide(row) = wide)) // intentionally assignment
 			{
 				levelselect.rows[row].maplist[2] = levelselect.rows[row].maplist[1] = levelselect.rows[row].maplist[0];
 				levelselect.rows[row].mapavailable[2] = levelselect.rows[row].mapavailable[1] = levelselect.rows[row].mapavailable[0];
@@ -3824,7 +3824,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 		mapnum++;
 	}
 
-	if (levselp[0]) // never going to have some provided but not all, saves individually checking
+	if (levselp[0][0]) // never going to have some provided but not all, saves individually checking
 	{
 		W_UnlockCachedPatch(levselp[0][0]);
 		W_UnlockCachedPatch(levselp[0][1]);

From cf45067d9c7164ac46840e478a64c312d86f9b2f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Feb 2017 17:09:03 +0000
Subject: [PATCH 047/119] Gametype descriptions.

https://gfycat.com/RelievedUnselfishChinchilla (competition's desc is slightly reworded but I'm too lazy to retake the gfy)
---
 src/m_menu.c | 29 ++++++++++++++++++++++++++---
 src/m_menu.h |  6 ++++++
 2 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 9229fde0d..3f69dd064 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -608,6 +608,18 @@ static menuitem_t MISC_ChangeGameTypeMenu[] =
 	{IT_STRING|IT_CALL,              NULL, "Capture the Flag", M_MapChange, 72},
 };
 
+static const gtdesc_t gametypedesc[] =
+{
+	{"Play through the single-player campaign with your friends, teaming up to beat Dr Eggman's nefarious challenges!"},
+	{"Speed your way through the main acts, competing to get as much stuff as possible in the fastest time to see who's the best."},
+	{"There's not much to it - zoom through the level faster than everyone else."},
+	{"Sling rings at your foes in a free-for-all battle. Use the special weapon rings to your advantage!"},
+	{"Sling rings at your foes in a color-coded battle. Use the special weapon rings to your advantage!"},
+	{"Whoever's IT has to hunt down everyone else. If you get caught, you have to turn on your former friends!"},
+	{"Try and find a good hiding place in these maps - we dare you."},
+	{"Steal the flag from the enemy's base and bring it back to your own, but watch out - they could just as easily steal yours!"},
+};
+
 static menuitem_t MISC_ChangeLevelMenu[] =
 {
 	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0},     // dummy menuitem for the control func
@@ -1465,7 +1477,7 @@ menu_t MISC_ChangeGameTypeDef =
 	&MainDef,  // Doesn't matter.
 	MISC_ChangeGameTypeMenu,
 	M_DrawGameTypeMenu,
-	30, 104 - ((80 - lsheadingheight/2)/2), // vertically centering
+	30, (200 - (72+8))/2, // vertically centering
 	0,
 	NULL
 };
@@ -2444,7 +2456,8 @@ boolean M_Responder(event_t *ev)
 		case KEY_DOWNARROW:
 			M_NextOpt();
 			S_StartSound(NULL, sfx_menu1);
-			if (currentMenu == &SP_PlayerDef)
+			if (currentMenu == &SP_PlayerDef
+			|| currentMenu == &MISC_ChangeGameTypeDef)
 			{
 				Z_Free(char_notes);
 				char_notes = NULL;
@@ -2454,7 +2467,8 @@ boolean M_Responder(event_t *ev)
 		case KEY_UPARROW:
 			M_PrevOpt();
 			S_StartSound(NULL, sfx_menu1);
-			if (currentMenu == &SP_PlayerDef)
+			if (currentMenu == &SP_PlayerDef
+			|| currentMenu == &MISC_ChangeGameTypeDef)
 			{
 				Z_Free(char_notes);
 				char_notes = NULL;
@@ -6887,12 +6901,21 @@ static void M_GameTypeChange(INT32 choice)
 	M_SetupNextMenu(&MISC_ChangeGameTypeDef);
 	if (Playing())
 		itemOn = gametype;
+
+	Z_Free(char_notes);
+	char_notes = NULL;
 }
 
 void M_DrawGameTypeMenu(void)
 {
 	M_DrawGenericMenu();
 	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight, "Select Gametype", true);
+
+	if (!char_notes)
+		char_notes = V_WordWrap(0, (160 - 30) - 8, V_ALLOWLOWERCASE, gametypedesc[itemOn].notes);
+
+	V_DrawFill(160, currentMenu->y, (160 - 30), 72 + 8, 159);
+	V_DrawString(164, currentMenu->y + 4, V_RETURN8|V_ALLOWLOWERCASE, char_notes);
 }
 
 static void M_MapChange(INT32 choice)
diff --git a/src/m_menu.h b/src/m_menu.h
index 01adfff80..785a3785c 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -198,6 +198,12 @@ typedef struct
 } levelselect_t;
 // experimental level select end
 
+// descriptions for gametype select screen
+typedef struct
+{
+	char notes[441];
+} gtdesc_t;
+
 // mode descriptions for video mode menu
 typedef struct
 {

From 76c15a0e97ea732de900195f5efc88079591f0b8 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Feb 2017 21:17:20 +0000
Subject: [PATCH 048/119] Salt's suggestion.

---
 src/m_menu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 3f69dd064..86f7410cd 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -611,7 +611,7 @@ static menuitem_t MISC_ChangeGameTypeMenu[] =
 static const gtdesc_t gametypedesc[] =
 {
 	{"Play through the single-player campaign with your friends, teaming up to beat Dr Eggman's nefarious challenges!"},
-	{"Speed your way through the main acts, competing to get as much stuff as possible in the fastest time to see who's the best."},
+	{"Speed your way through the main acts, competing in several different categories to see who's the best."},
 	{"There's not much to it - zoom through the level faster than everyone else."},
 	{"Sling rings at your foes in a free-for-all battle. Use the special weapon rings to your advantage!"},
 	{"Sling rings at your foes in a color-coded battle. Use the special weapon rings to your advantage!"},

From 7f7d51a4748a5df6d394d927bb266f443ae02a4f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 6 Mar 2017 19:03:05 +0000
Subject: [PATCH 049/119] comment updates

---
 src/m_menu.c | 2 +-
 src/m_menu.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 86f7410cd..4273ed207 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4023,7 +4023,7 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 	if (!map)
 		return;
 
-	//  A 160x100 image of the level as entry MAPxxP
+	//  A 564x100 image of the level as entry MAPxxW
 	if (!(levelselect.rows[row].mapavailable[col]))
 		V_DrawSmallScaledPatch(x, y, V_STATIC, levselp[1][2]); // static - make secret maps look ENTICING
 	else
diff --git a/src/m_menu.h b/src/m_menu.h
index 785a3785c..004ebaeb3 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -182,7 +182,7 @@ typedef struct
 	UINT8 next;
 } description_t;
 
-// experimental level select -- remember to use M_HandleSetupMultiPlayer
+// level select platter
 typedef struct
 {
 	char header[22+5]; // mapheader_t lvltttl max length + " ZONE"

From 75ac351940111540dc65fd37cc0b48b43315c0cf Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 11 Mar 2017 02:09:01 +0000
Subject: [PATCH 050/119] It's 2am, I'll upload the exe and player.dta to the
 ftp later.

* Super sprites are now deliniated via an additional S_SUPER lump between S_START and S_END. Above are normal sprites, below are super sprites. Handled internally via FF_SPR2SUPER.
* Sprite2 numbers are now appropriately limited for the data type that stores them.
* SPR2_SPIN is now SPR2_ROLL, SPR2_DASH is now SPR2_SPIN and SPR2_PEEL is now SPR2_DASH. Makes more sense, right?
---
 src/d_player.h    |  15 ++--
 src/dehacked.c    |  34 ++------
 src/info.c        |  58 +++-----------
 src/info.h        |  62 ++++-----------
 src/lua_baselib.c |   2 +-
 src/p_inter.c     |   7 +-
 src/p_mobj.c      | 192 +++++++++++++---------------------------------
 src/p_pspr.h      |   6 +-
 src/p_spec.c      |  12 +--
 src/p_user.c      |  75 +++++-------------
 src/r_things.c    |  35 ++++++---
 src/r_things.h    |   2 +-
 12 files changed, 155 insertions(+), 345 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index 9c14a5e37..c7fc70777 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -32,20 +32,19 @@
 // Extra abilities/settings for skins (combinable stuff)
 typedef enum
 {
-	SF_SUPER            = 1, // Can turn super in singleplayer/co-op mode.
-	SF_SUPERANIMS       = 1<<1, // If super, use the super sonic animations
-	SF_SUPERSPIN        = 1<<2, // Should spin frames be played while super?
-	SF_HIRES            = 1<<3, // Draw the sprite 2x as small?
+	SF_SUPER            = 1,    // Can turn super in singleplayer/co-op mode?
+	SF_NOSUPERSPIN      = 1<<1, // Should spin frames be played while super?
+	SF_NOSPINDASHDUST   = 1<<2, // Spawn dust particles when charging a spindash?
+	SF_HIRES            = 1<<3, // Draw the sprite at different size?
 	SF_NOSKID           = 1<<4, // No skid particles etc
 	SF_NOSPEEDADJUST    = 1<<5, // Skin-specific version of disablespeedadjust
 	SF_RUNONWATER       = 1<<6, // Run on top of water FOFs?
-	SF_NOJUMPSPIN       = 1<<7, // SPR2_JUMP defaults to SPR2_SPRG instead of SPR2_SPIN, falling states used, and player height is full when jumping?
+	SF_NOJUMPSPIN       = 1<<7, // SPR2_JUMP defaults to SPR2_SPRG instead of SPR2_ROLL, falling states used, and player height is full when jumping?
 	SF_NOJUMPDAMAGE     = 1<<8, // Don't damage enemies, etc whilst jumping?
 	SF_STOMPDAMAGE      = 1<<9, // Always damage enemies, etc by landing on them, no matter your vunerability?
 	SF_MARIODAMAGE      = SF_NOJUMPDAMAGE|SF_STOMPDAMAGE, // The Mario method of being able to damage enemies, etc.
 	SF_MACHINE          = 1<<10, // Beep boop. Are you a robot?
-	SF_NOSPINDASHDUST   = 1<<11, // Don't spawn dust particles when charging a spindash
-	SF_DASHMODE         = 1<<12, // Sonic Advance 2 style top speed increase?
+	SF_DASHMODE         = 1<<11, // Sonic Advance 2 style top speed increase?
 	// free up to and including 1<<31
 } skinflags_t;
 
@@ -175,7 +174,7 @@ typedef enum
 	PA_EDGE,
 	PA_WALK,
 	PA_RUN,
-	PA_PEEL,
+	PA_DASH,
 	PA_PAIN,
 	PA_ROLL,
 	PA_JUMP,
diff --git a/src/dehacked.c b/src/dehacked.c
index 9c3c8bbbc..915986c50 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3910,12 +3910,13 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_WAIT",
 	"S_PLAY_WALK",
 	"S_PLAY_RUN",
-	"S_PLAY_PEEL",
+	"S_PLAY_DASH",
 	"S_PLAY_PAIN",
+	"S_PLAY_STUN",
 	"S_PLAY_DEAD",
 	"S_PLAY_DRWN",
-	"S_PLAY_SPIN",
-	"S_PLAY_DASH",
+	"S_PLAY_ROLL",
+	"S_PLAY_SPINDASH",
 	"S_PLAY_GASP",
 	"S_PLAY_JUMP",
 	"S_PLAY_SPRING",
@@ -3948,25 +3949,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_MELEE",
 	"S_PLAY_MELEE_FINISH",
 
-	// SF_SUPERANIMS
-	"S_PLAY_SUPER_STND",
-	"S_PLAY_SUPER_WALK",
-	"S_PLAY_SUPER_RUN",
-	"S_PLAY_SUPER_PEEL",
-	"S_PLAY_SUPER_PAIN",
-	"S_PLAY_SUPER_STUN",
-	"S_PLAY_SUPER_DEAD",
-	"S_PLAY_SUPER_DRWN",
-	"S_PLAY_SUPER_SPIN",
-	"S_PLAY_SUPER_GASP",
-	"S_PLAY_SUPER_JUMP",
-	"S_PLAY_SUPER_SPRING",
-	"S_PLAY_SUPER_FALL",
-	"S_PLAY_SUPER_EDGE",
-	"S_PLAY_SUPER_RIDE",
-	"S_PLAY_SUPER_FLOAT",
-	"S_PLAY_SUPER_FLOAT_RUN",
-
 	// SF_SUPER
 	"S_PLAY_SUPERTRANS1",
 	"S_PLAY_SUPERTRANS2",
@@ -7245,6 +7227,7 @@ struct {
 	{"FF_FRAMEMASK",FF_FRAMEMASK},
 	{"FF_VERTICALFLIP",FF_VERTICALFLIP},
 	{"FF_PAPERSPRITE",FF_PAPERSPRITE},
+	{"FF_SPR2SUPER",FF_SPR2SUPER},
 	{"FF_SPR2ENDSTATE",FF_SPR2ENDSTATE},
 	{"FF_SPR2MIDSTART",FF_SPR2MIDSTART},
 	{"FF_ANIMATE",FF_ANIMATE},
@@ -7389,8 +7372,8 @@ struct {
 
 	// Character flags (skinflags_t)
 	{"SF_SUPER",SF_SUPER},
-	{"SF_SUPERANIMS",SF_SUPERANIMS},
-	{"SF_SUPERSPIN",SF_SUPERSPIN},
+	{"SF_NOSUPERSPIN",SF_NOSUPERSPIN},
+	{"SF_NOSPINDASHDUST",SF_NOSPINDASHDUST},
 	{"SF_HIRES",SF_HIRES},
 	{"SF_NOSKID",SF_NOSKID},
 	{"SF_NOSPEEDADJUST",SF_NOSPEEDADJUST},
@@ -7400,7 +7383,6 @@ struct {
 	{"SF_STOMPDAMAGE",SF_STOMPDAMAGE},
 	{"SF_MARIODAMAGE",SF_MARIODAMAGE},
 	{"SF_MACHINE",SF_MACHINE},
-	{"SF_NOSPINDASHDUST",SF_NOSPINDASHDUST},
 	{"SF_DASHMODE",SF_DASHMODE},
 
 	// Character abilities!
@@ -7488,7 +7470,7 @@ struct {
 	{"PA_EDGE",PA_EDGE},
 	{"PA_WALK",PA_WALK},
 	{"PA_RUN",PA_RUN},
-	{"PA_PEEL",PA_PEEL},
+	{"PA_DASH",PA_DASH},
 	{"PA_PAIN",PA_PAIN},
 	{"PA_ROLL",PA_ROLL},
 	{"PA_JUMP",PA_JUMP},
diff --git a/src/info.c b/src/info.c
index 1a10a04f9..a961157b2 100644
--- a/src/info.c
+++ b/src/info.c
@@ -390,12 +390,13 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"WAIT",
 	"WALK",
 	"RUN_",
-	"PEEL",
+	"DASH",
 	"PAIN",
+	"STUN",
 	"DEAD",
 	"DRWN",
+	"ROLL",
 	"SPIN",
-	"DASH",
 	"GASP",
 	"JUMP",
 	"SPNG",
@@ -403,9 +404,6 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"EDGE",
 	"RIDE",
 
-	"SIGN",
-	"LIFE",
-
 	"FLY_",
 	"SWIM",
 	"TIRE",
@@ -425,23 +423,6 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"MLEE",
 
 	"TRNS",
-	"SSTD",
-	"SWLK",
-	"SRUN",
-	"SPEE",
-	"SPAN",
-	"SSTN",
-	"SDTH",
-	"SDRN",
-	"SSPN",
-	"SGSP",
-	"SJMP",
-	"SSPG",
-	"SFAL",
-	"SEDG",
-	"SRID",
-	"SFLT",
-	"SFRN",
 
 	"NTRN",
 	"NSTD",
@@ -476,7 +457,10 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"DRL9",
 	"DRLA",
 	"DRLB",
-	"DRLC"
+	"DRLC",
+
+	"SIGN",
+	"LIFE"
 };
 enum playersprite free_spr2 = SPR2_FIRSTFREESLOT;
 
@@ -510,12 +494,13 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_WAIT|FF_ANIMATE,     -1, {NULL}, 0, 16, S_NULL},      // S_PLAY_WAIT
 	{SPR_PLAY, SPR2_WALK,                 4, {NULL}, 0,  0, S_PLAY_WALK}, // S_PLAY_WALK
 	{SPR_PLAY, SPR2_RUN ,                 2, {NULL}, 0,  0, S_PLAY_RUN},  // S_PLAY_RUN
-	{SPR_PLAY, SPR2_PEEL,                 2, {NULL}, 0,  0, S_PLAY_PEEL}, // S_PLAY_PEEL
+	{SPR_PLAY, SPR2_DASH,                 2, {NULL}, 0,  0, S_PLAY_DASH}, // S_PLAY_DASH
 	{SPR_PLAY, SPR2_PAIN|FF_ANIMATE,    350, {NULL}, 0,  4, S_PLAY_FALL}, // S_PLAY_PAIN
+	{SPR_PLAY, SPR2_STUN|FF_ANIMATE,    350, {NULL}, 0,  4, S_PLAY_FALL}, // S_PLAY_STUN
 	{SPR_PLAY, SPR2_DEAD|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},      // S_PLAY_DEAD
 	{SPR_PLAY, SPR2_DRWN|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},      // S_PLAY_DRWN
-	{SPR_PLAY, SPR2_SPIN,                 1, {NULL}, 0,  0, S_PLAY_SPIN}, // S_PLAY_SPIN
-	{SPR_PLAY, SPR2_DASH,                 2, {NULL}, 0,  0, S_PLAY_DASH}, // S_PLAY_DASH
+	{SPR_PLAY, SPR2_ROLL,                 1, {NULL}, 0,  0, S_PLAY_ROLL}, // S_PLAY_ROLL
+	{SPR_PLAY, SPR2_SPIN,                 2, {NULL}, 0,  0, S_PLAY_SPINDASH}, // S_PLAY_SPINDASH
 	{SPR_PLAY, SPR2_GASP|FF_ANIMATE,     14, {NULL}, 0,  4, S_PLAY_WALK}, // S_PLAY_GASP
 	{SPR_PLAY, SPR2_JUMP,                 1, {NULL}, 0,  0, S_PLAY_JUMP}, // S_PLAY_JUMP
 	{SPR_PLAY, SPR2_SPNG,                 2, {NULL}, 0,  0, S_PLAY_SPRING}, // S_PLAY_SPRING
@@ -548,25 +533,6 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
 	{SPR_PLAY, SPR2_MLEE,                20, {NULL},                   0, 0, S_PLAY_FALL}, // S_PLAY_MELEE_FINISH
 
-	// SF_SUPERANIMS
-	{SPR_PLAY, SPR2_SSTD|FF_ANIMATE,     -1, {NULL}, 0,  7, S_NULL},            // S_PLAY_SUPER_STND
-	{SPR_PLAY, SPR2_SWLK,                 7, {NULL}, 0,  0, S_PLAY_SUPER_WALK}, // S_PLAY_SUPER_WALK
-	{SPR_PLAY, SPR2_SRUN,                 7, {NULL}, 0,  0, S_PLAY_SUPER_RUN},  // S_PLAY_SUPER_RUN
-	{SPR_PLAY, SPR2_SPEE,                 7, {NULL}, 0,  0, S_PLAY_SUPER_PEEL}, // S_PLAY_SUPER_PEEL
-	{SPR_PLAY, SPR2_SPAN|FF_ANIMATE,    350, {NULL}, 0,  4, S_PLAY_SUPER_FALL}, // S_PLAY_SUPER_PAIN
-	{SPR_PLAY, SPR2_SSTN|FF_ANIMATE,    350, {NULL}, 0,  4, S_PLAY_SUPER_FALL}, // S_PLAY_SUPER_STUN
-	{SPR_PLAY, SPR2_SDTH|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},            // S_PLAY_SUPER_DEAD
-	{SPR_PLAY, SPR2_SDRN|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},            // S_PLAY_SUPER_DRWN
-	{SPR_PLAY, SPR2_SSPN,                 1, {NULL}, 0,  0, S_PLAY_SUPER_SPIN}, // S_PLAY_SUPER_SPIN
-	{SPR_PLAY, SPR2_SGSP|FF_ANIMATE,     14, {NULL}, 0,  4, S_PLAY_SUPER_WALK}, // S_PLAY_SUPER_GASP
-	{SPR_PLAY, SPR2_SJMP,                 1, {NULL}, 0,  0, S_PLAY_SUPER_JUMP}, // S_PLAY_SUPER_JUMP
-	{SPR_PLAY, SPR2_SSPG,                 2, {NULL}, 0,  0, S_PLAY_SUPER_SPRING}, // S_PLAY_SUPER_SPRING
-	{SPR_PLAY, SPR2_SFAL,                 2, {NULL}, 0,  0, S_PLAY_SUPER_FALL}, // S_PLAY_SUPER_FALL
-	{SPR_PLAY, SPR2_SEDG|FF_ANIMATE,     -1, {NULL}, 0, 12, S_NULL},            // S_PLAY_SUPER_EDGE
-	{SPR_PLAY, SPR2_SRID,                 4, {NULL}, 0,  0, S_PLAY_SUPER_RIDE}, // S_PLAY_SUPER_RIDE
-	{SPR_PLAY, SPR2_SFLT,                 7, {NULL}, 0,  0, S_PLAY_SUPER_FLOAT}, // S_PLAY_SUPER_FLOAT
-	{SPR_PLAY, SPR2_SFRN,                 7, {NULL}, 0,  0, S_PLAY_SUPER_FLOAT_RUN},  // S_PLAY_SUPER_FLOAT_RUN
-
 	// SF_SUPER
 	{SPR_PLAY, SPR2_TRNS,                 4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS2}, // S_PLAY_SUPER_TRANS
 	{SPR_PLAY, SPR2_TRNS,                 4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS3}, // S_PLAY_SUPER_TRANS2
@@ -3078,7 +3044,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MT_THOK,        // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
-		S_PLAY_SPIN,    // missilestate
+		S_PLAY_ROLL,    // missilestate
 		S_PLAY_DEAD,    // deathstate
 		S_PLAY_DRWN,    // xdeathstate
 		sfx_None,       // deathsound
diff --git a/src/info.h b/src/info.h
index cf3a4f916..f7e61614a 100644
--- a/src/info.h
+++ b/src/info.h
@@ -594,8 +594,8 @@ typedef enum sprite
 	NUMSPRITES
 } spritenum_t;
 
-// Make sure to be conscious of FF_FRAMEMASK whenever you change this table.
-// Currently, FF_FRAMEMASK is 0x1ff, or 511 - and NUMSPRITEFREESLOTS is 256.
+// Make sure to be conscious of FF_FRAMEMASK and the fact sprite2 is stored as a UINT8 whenever you change this table.
+// Currently, FF_FRAMEMASK is 0x7f, or 127 - and the limitation of sprite2 storage is 255, so the second half is used by FF_SPR2SUPER.
 // Since this is zero-based, there can be at most 256 different SPR2_'s without changing that.
 enum playersprite
 {
@@ -603,12 +603,13 @@ enum playersprite
 	SPR2_WAIT,
 	SPR2_WALK,
 	SPR2_RUN ,
-	SPR2_PEEL,
+	SPR2_DASH,
 	SPR2_PAIN,
+	SPR2_STUN,
 	SPR2_DEAD,
 	SPR2_DRWN, // drown
-	SPR2_SPIN,
-	SPR2_DASH, // spindash charge
+	SPR2_ROLL,
+	SPR2_SPIN, // spindash charge
 	SPR2_GASP,
 	SPR2_JUMP,
 	SPR2_SPNG, // spring
@@ -616,9 +617,6 @@ enum playersprite
 	SPR2_EDGE,
 	SPR2_RIDE,
 
-	SPR2_SIGN, // end sign head
-	SPR2_LIFE, // life monitor icon
-
 	SPR2_FLY ,
 	SPR2_SWIM,
 	SPR2_TIRE, // tired
@@ -638,23 +636,6 @@ enum playersprite
 	SPR2_MLEE, // melee
 
 	SPR2_TRNS, // super transformation
-	SPR2_SSTD, // super stand
-	SPR2_SWLK, // super walk
-	SPR2_SRUN, // super run
-	SPR2_SPEE, // super peelout
-	SPR2_SPAN, // super pain
-	SPR2_SSTN, // super stun
-	SPR2_SDTH, // super death
-	SPR2_SDRN, // super drown
-	SPR2_SSPN, // super spin
-	SPR2_SGSP, // super gasp
-	SPR2_SJMP, // super jump
-	SPR2_SSPG, // super spring
-	SPR2_SFAL, // super fall
-	SPR2_SEDG, // super edge
-	SPR2_SRID, // super ride
-	SPR2_SFLT, // super float
-	SPR2_SFRN, // super float run
 
 	SPR2_NTRN, // NiGHTS transformation
 	SPR2_NSTD, // NiGHTS stand
@@ -693,8 +674,11 @@ enum playersprite
 	SPR2_DRLB,
 	SPR2_DRLC,
 
+	SPR2_SIGN, // end sign head
+	SPR2_LIFE, // life monitor icon
+
 	SPR2_FIRSTFREESLOT,
-	SPR2_LASTFREESLOT = SPR2_FIRSTFREESLOT + NUMSPRITEFREESLOTS - 1,
+	SPR2_LASTFREESLOT = 0x7f,
 	NUMPLAYERSPRITES
 };
 
@@ -720,12 +704,13 @@ typedef enum state
 	S_PLAY_WAIT,
 	S_PLAY_WALK,
 	S_PLAY_RUN,
-	S_PLAY_PEEL,
+	S_PLAY_DASH,
 	S_PLAY_PAIN,
+	S_PLAY_STUN,
 	S_PLAY_DEAD,
 	S_PLAY_DRWN,
-	S_PLAY_SPIN,
-	S_PLAY_DASH,
+	S_PLAY_ROLL,
+	S_PLAY_SPINDASH,
 	S_PLAY_GASP,
 	S_PLAY_JUMP,
 	S_PLAY_SPRING,
@@ -758,25 +743,6 @@ typedef enum state
 	S_PLAY_MELEE,
 	S_PLAY_MELEE_FINISH,
 
-	// SF_SUPERANIMS
-	S_PLAY_SUPER_STND,
-	S_PLAY_SUPER_WALK,
-	S_PLAY_SUPER_RUN,
-	S_PLAY_SUPER_PEEL,
-	S_PLAY_SUPER_PAIN,
-	S_PLAY_SUPER_STUN,
-	S_PLAY_SUPER_DEAD,
-	S_PLAY_SUPER_DRWN,
-	S_PLAY_SUPER_SPIN,
-	S_PLAY_SUPER_GASP,
-	S_PLAY_SUPER_JUMP,
-	S_PLAY_SUPER_SPRING,
-	S_PLAY_SUPER_FALL,
-	S_PLAY_SUPER_EDGE,
-	S_PLAY_SUPER_RIDE,
-	S_PLAY_SUPER_FLOAT,
-	S_PLAY_SUPER_FLOAT_RUN,
-
 	// SF_SUPER
 	S_PLAY_SUPER_TRANS,
 	S_PLAY_SUPER_TRANS2,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index def0ad1b3..de78fa2c0 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -352,7 +352,7 @@ static int lib_pIsValidSprite2(lua_State *L)
 	//HUDSAFE
 	if (!mobj)
 		return LUA_ErrInvalid(L, "mobj_t");
-	lua_pushboolean(L, (mobj->skin && (((skin_t *)mobj->skin)->sprites[spr2].numframes > 0)));
+	lua_pushboolean(L, (mobj->skin && (((skin_t *)mobj->skin)->sprites[spr2].numframes)));
 	return 1;
 }
 
diff --git a/src/p_inter.c b/src/p_inter.c
index 600874a4c..e456c59bc 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1472,7 +1472,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				player->powers[pw_carry] = CR_MACESPIN;
 				S_StartSound(toucher, sfx_spin);
-				P_SetPlayerMobjState(toucher, S_PLAY_SPIN);
+				P_SetPlayerMobjState(toucher, S_PLAY_ROLL);
 			}
 			else
 				player->powers[pw_carry] = CR_GENERIC;
@@ -2829,10 +2829,7 @@ static inline void P_SuperDamage(player_t *player, mobj_t *inflictor, mobj_t *so
 
 	P_InstaThrust(player->mo, ang, fallbackspeed);
 
-	if (player->charflags & SF_SUPERANIMS)
-		P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_STUN);
-	else
-		P_SetPlayerMobjState(player->mo, player->mo->info->painstate);
+	P_SetPlayerMobjState(player->mo, S_PLAY_STUN);
 
 	P_ResetPlayer(player);
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 9d076478c..272f3bfca 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -187,32 +187,49 @@ static void P_CyclePlayerMobjState(mobj_t *mobj)
 
 //
 // P_GetMobjSprite2
+// For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing.
+// For super players, does the same as above - but tries the super equivalent for each sprite2 before the non-super version.
 //
 
 UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 {
 	player_t *player = mobj->player;
 	skin_t *skin = ((skin_t *)mobj->skin);
+	boolean super = false;
 
 	if (!skin)
 		return 0;
 
-	while ((skin->sprites[spr2].numframes <= 0)
+	if ((super = (player // only manipulate output if player...
+	&& (player->powers[pw_super] // and (if they're super...
+	|| ((player->pflags & PF_NIGHTSMODE) && (skin->flags & SF_SUPER)))))) // or if they're in nights and are a skin that CAN go super...)
+		spr2 |= FF_SPR2SUPER;
+
+	while (!(skin->sprites[spr2].numframes)
 		&& spr2 != SPR2_STND)
 	{
+		if (spr2 & FF_SPR2SUPER)
+		{
+			spr2 &= ~FF_SPR2SUPER;
+			continue;
+		}
+
 		switch(spr2)
 		{
-		case SPR2_PEEL:
-			spr2 = SPR2_RUN;
+		case SPR2_DASH:
+			spr2 = SPR2_RUN ;
 			break;
 		case SPR2_RUN:
 			spr2 = SPR2_WALK;
 			break;
+		case SPR2_STUN:
+			spr2 = SPR2_PAIN;
+			break;
 		case SPR2_DRWN:
 			spr2 = SPR2_DEAD;
 			break;
-		case SPR2_DASH:
-			spr2 = SPR2_SPIN;
+		case SPR2_SPIN:
+			spr2 = SPR2_ROLL;
 			break;
 		case SPR2_GASP:
 			spr2 = SPR2_SPNG;
@@ -221,7 +238,7 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = ((player
 					? player->charflags
 					: skin->flags)
-					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_SPIN;
+					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
 			break;
 		case SPR2_SPNG: // spring
 			spr2 = SPR2_FALL;
@@ -247,7 +264,7 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_FLY;
 			break;
 		case SPR2_CLMB:
-			spr2 = SPR2_SPIN;
+			spr2 = SPR2_ROLL;
 			break;
 		case SPR2_CLNG:
 			spr2 = SPR2_CLMB;
@@ -264,79 +281,26 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_FALL;
 			break;
 		case SPR2_BLND:
-			spr2 = SPR2_SPIN;
+			spr2 = SPR2_ROLL;
 			break;
 
 		case SPR2_TWIN:
-			spr2 = SPR2_SPIN;
+			spr2 = SPR2_ROLL;
 			break;
 
 		case SPR2_MLEE:
 			spr2 = SPR2_TWIN;
 			break;
 
-		// Super sprites fallback to regular sprites
-		case SPR2_SWLK:
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_SRUN:
-			spr2 = SPR2_RUN;
-			break;
-		case SPR2_SPEE:
-			spr2 = SPR2_PEEL;
-			break;
-		case SPR2_SPAN:
-			spr2 = SPR2_PAIN;
-			break;
-		case SPR2_SSTN:
-			spr2 = SPR2_SPAN;
-			break;
-		case SPR2_SDTH:
-			spr2 = SPR2_DEAD;
-			break;
-		case SPR2_SDRN:
-			spr2 = SPR2_DRWN;
-			break;
-		case SPR2_SSPN:
-			spr2 = SPR2_SPIN;
-			break;
-		case SPR2_SGSP:
-			spr2 = SPR2_GASP;
-			break;
-		case SPR2_SJMP:
-			spr2 = ((player
-					? player->charflags
-					: skin->flags)
-					& SF_NOJUMPSPIN) ? SPR2_SSPG : SPR2_SSPN;
-			break;
-		case SPR2_SSPG:
-			spr2 = SPR2_SPNG;
-			break;
-		case SPR2_SFAL:
-			spr2 = SPR2_FALL;
-			break;
-		case SPR2_SEDG:
-			spr2 = SPR2_EDGE;
-			break;
-		case SPR2_SRID:
-			spr2 = SPR2_RIDE;
-			break;
-		case SPR2_SFLT:
-			spr2 = SPR2_SWLK;
-			break;
-		case SPR2_SFRN:
-			spr2 = SPR2_SRUN;
-			break;
-
 		// NiGHTS sprites.
 		case SPR2_NTRN:
 			spr2 = SPR2_TRNS;
 			break;
 		case SPR2_NSTD:
-			spr2 = SPR2_SSTD;
+			spr2 = SPR2_STND;
 			break;
 		case SPR2_NFLT:
-			spr2 = (skin->flags & SF_SUPERANIMS) ? SPR2_SFLT : SPR2_FALL; // This is skin-exclusive so the default NiGHTS skin changing system plays nice.
+			spr2 = SPR2_FLT ;
 			break;
 		case SPR2_NPUL:
 			spr2 = SPR2_NFLT;
@@ -345,10 +309,10 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_NPUL;
 			break;
 		case SPR2_NATK:
-			spr2 = SPR2_SSPN;
+			spr2 = SPR2_ROLL;
 			break;
 		/*case SPR2_NGT0:
-			spr2 = SPR2_STND;
+			spr2 = SPR2_NFLT;
 			break;*/
 		case SPR2_NGT1:
 		case SPR2_NGT7:
@@ -407,7 +371,11 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_STND;
 			break;
 		}
+
+		if (super)
+			spr2 |= FF_SPR2SUPER;
 	}
+
 	return spr2;
 }
 
@@ -444,49 +412,12 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 	else if (state == S_PLAY_SWIM && !(player->mo->eflags & MFE_UNDERWATER))
 		return P_SetPlayerMobjState(player->mo, S_PLAY_FLY);
 
-	// Catch state changes for Super Sonic
-	if (player->powers[pw_super] && (player->charflags & SF_SUPERANIMS))
+	// Catch SF_NOSUPERSPIN jumps for Supers
+	if (player->powers[pw_super])
 	{
-		switch (state)
-		{
-		case S_PLAY_STND:
-		case S_PLAY_WAIT:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_STND);
-		case S_PLAY_WALK:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_WALK);
-		case S_PLAY_RUN:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_RUN);
-		case S_PLAY_PEEL:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_PEEL);
-		case S_PLAY_PAIN:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_PAIN);
-		case S_PLAY_DEAD:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_DEAD);
-		case S_PLAY_DRWN:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_DRWN);
-		case S_PLAY_SPIN:
-			if (!(player->charflags & SF_SUPERSPIN))
-				return true;
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_SPIN);
-		case S_PLAY_GASP:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_GASP);
-		case S_PLAY_JUMP:
-			if (!(player->charflags & SF_SUPERSPIN))
-				return true;
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_JUMP);
-		case S_PLAY_SPRING:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_SPRING);
-		case S_PLAY_FALL:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_FALL);
-		case S_PLAY_EDGE:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_EDGE);
-		case S_PLAY_FLOAT:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_FLOAT);
-		case S_PLAY_FLOAT_RUN:
-			return P_SetPlayerMobjState(mobj, S_PLAY_SUPER_FLOAT_RUN);
-		default:
-			break;
-		}
+		if ((player->charflags & SF_NOSUPERSPIN)
+		&& (state == S_PLAY_ROLL || state == S_PLAY_JUMP))
+			return true;
 	}
 	// You were in pain state after taking a hit, and you're moving out of pain state now?
 	else if (mobj->state == &states[mobj->info->painstate] && player->powers[pw_flashing] == flashingtics && state != mobj->info->painstate)
@@ -502,49 +433,37 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 	{
 	case S_PLAY_STND:
 	case S_PLAY_WAIT:
-	case S_PLAY_SUPER_STND:
 		player->panim = PA_IDLE;
 		break;
 	case S_PLAY_EDGE:
-	case S_PLAY_SUPER_EDGE:
 		player->panim = PA_EDGE;
 		break;
 	case S_PLAY_WALK:
 	case S_PLAY_FLOAT:
-	case S_PLAY_SUPER_WALK:
-	case S_PLAY_SUPER_FLOAT:
 		player->panim = PA_WALK;
 		break;
 	case S_PLAY_RUN:
 	case S_PLAY_FLOAT_RUN:
-	case S_PLAY_SUPER_RUN:
-	case S_PLAY_SUPER_FLOAT_RUN:
 		player->panim = PA_RUN;
 		break;
-	case S_PLAY_PEEL:
-	case S_PLAY_SUPER_PEEL:
-		player->panim = PA_PEEL;
+	case S_PLAY_DASH:
+		player->panim = PA_DASH;
 		break;
 	case S_PLAY_PAIN:
-	case S_PLAY_SUPER_PAIN:
-	case S_PLAY_SUPER_STUN:
+	case S_PLAY_STUN:
 		player->panim = PA_PAIN;
 		break;
-	case S_PLAY_SPIN:
-	//case S_PLAY_DASH: -- everyone can ROLL thanks to zoom tubes...
-	case S_PLAY_SUPER_SPIN:
+	case S_PLAY_ROLL:
+	//case S_PLAY_SPINDASH: -- everyone can ROLL thanks to zoom tubes...
 		player->panim = PA_ROLL;
 		break;
 	case S_PLAY_JUMP:
-	case S_PLAY_SUPER_JUMP:
 		player->panim = PA_JUMP;
 		break;
 	case S_PLAY_SPRING:
-	case S_PLAY_SUPER_SPRING:
 		player->panim = PA_SPRING;
 		break;
 	case S_PLAY_FALL:
-	case S_PLAY_SUPER_FALL:
 		player->panim = PA_FALL;
 		break;
 	case S_PLAY_FLY:
@@ -555,13 +474,12 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 	case S_PLAY_TWINSPIN:
 		player->panim = PA_ABILITY;
 		break;
-	case S_PLAY_DASH: // ...but the act of SPINDASHING is charability2 specific.
+	case S_PLAY_SPINDASH: // ...but the act of SPINDASHING is charability2 specific.
 	case S_PLAY_MELEE:
 	case S_PLAY_MELEE_FINISH:
 		player->panim = PA_ABILITY2;
 		break;
 	case S_PLAY_RIDE:
-	case S_PLAY_SUPER_RIDE:
 		player->panim = PA_RIDE;
 		break;
 	default:
@@ -635,7 +553,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 						else
 							mobj->tics = 4;
 					}
-					else if ((player->panim == PA_RUN) || (player->panim == PA_PEEL))
+					else if ((player->panim == PA_RUN) || (player->panim == PA_DASH))
 					{
 						if (speed > 52<<FRACBITS)
 							mobj->tics = 1;
@@ -3299,26 +3217,26 @@ static void P_PlayerZMovement(mobj_t *mo)
 					{
 						if (mo->player->cmomx || mo->player->cmomy)
 						{
-							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_PEEL)
-								P_SetPlayerMobjState(mo, S_PLAY_PEEL);
+							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_DASH)
+								P_SetPlayerMobjState(mo, S_PLAY_DASH);
 							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
-							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->state-states == S_PLAY_SUPER_FLOAT_RUN))
+							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->sprite2 & FF_SPR2SUPER))
 								P_SetPlayerMobjState(mo, S_PLAY_RUN);
 							else if ((mo->player->rmomx || mo->player->rmomy)
-							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->state-states == S_PLAY_SUPER_FLOAT))
+							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->sprite2 & FF_SPR2SUPER))
 								P_SetPlayerMobjState(mo, S_PLAY_WALK);
 							else if (!mo->player->rmomx && !mo->player->rmomy && mo->player->panim != PA_IDLE)
 								P_SetPlayerMobjState(mo, S_PLAY_STND);
 						}
 						else
 						{
-							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_PEEL)
-								P_SetPlayerMobjState(mo, S_PLAY_PEEL);
+							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_DASH)
+								P_SetPlayerMobjState(mo, S_PLAY_DASH);
 							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
-							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->state-states == S_PLAY_SUPER_FLOAT_RUN))
+							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->sprite2 & FF_SPR2SUPER))
 								P_SetPlayerMobjState(mo, S_PLAY_RUN);
 							else if ((mo->momx || mo->momy)
-							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->state-states == S_PLAY_SUPER_FLOAT))
+							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->sprite2 & FF_SPR2SUPER))
 								P_SetPlayerMobjState(mo, S_PLAY_WALK);
 							else if (!mo->momx && !mo->momy && mo->player->panim != PA_IDLE)
 								P_SetPlayerMobjState(mo, S_PLAY_STND);
@@ -3328,7 +3246,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 					if ((mo->player->charability2 == CA2_SPINDASH) && !(mo->player->pflags & PF_THOKKED) && (mo->player->cmd.buttons & BT_USE) && (FixedHypot(mo->momx, mo->momy) > (5*mo->scale)))
 					{
 						mo->player->pflags |= PF_SPINNING;
-						P_SetPlayerMobjState(mo, S_PLAY_SPIN);
+						P_SetPlayerMobjState(mo, S_PLAY_ROLL);
 						S_StartSound(mo, sfx_spin);
 					}
 					else
diff --git a/src/p_pspr.h b/src/p_pspr.h
index 97d7adb94..fccfac3a3 100644
--- a/src/p_pspr.h
+++ b/src/p_pspr.h
@@ -35,9 +35,11 @@
 #pragma interface
 #endif
 
-/// \brief Frame flags: only the frame number - 0 to 511 (Frames from 0 to 63, Sprite2 number uses full range)
-#define FF_FRAMEMASK 0x1ff
+/// \brief Frame flags: only the frame number - 0 to 127 (Frames from 0 to 63, Sprite2 number uses full range)
+#define FF_FRAMEMASK 0x7f
 
+/// \brief Frame flags - SPR2: Super sprite2
+#define FF_SPR2SUPER 0x80
 /// \brief Frame flags - SPR2: A change of state at the end of Sprite2 animation
 #define FF_SPR2ENDSTATE 0x1000
 /// \brief Frame flags - SPR2: 50% of starting in middle of Sprite2 animation
diff --git a/src/p_spec.c b/src/p_spec.c
index b4380bb4b..577940094 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3747,7 +3747,7 @@ DoneSection2:
 					if (!(player->pflags & PF_SPINNING))
 						player->pflags |= PF_SPINNING;
 
-					P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 				}
 
 				player->powers[pw_flashing] = TICRATE/3;
@@ -3908,7 +3908,7 @@ DoneSection2:
 			if (!(player->pflags & PF_SPINNING) && P_IsObjectOnGround(player->mo) && (player->charability2 == CA2_SPINDASH))
 			{
 				player->pflags |= PF_SPINNING;
-				P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 				S_StartAttackSound(player->mo, sfx_spin);
 
 				if (abs(player->rmomx) < FixedMul(5*FRACUNIT, player->mo->scale)
@@ -3988,9 +3988,9 @@ DoneSection2:
 				player->pflags &= ~(PF_JUMPED|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 
-				if (player->mo->state-states != S_PLAY_SPIN)
+				if (player->mo->state-states != S_PLAY_ROLL)
 				{
-					P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 					S_StartSound(player->mo, sfx_spin);
 				}
 			}
@@ -4068,9 +4068,9 @@ DoneSection2:
 				player->pflags &= ~(PF_JUMPED|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 
-				if (player->mo->state-states != S_PLAY_SPIN)
+				if (player->mo->state-states != S_PLAY_ROLL)
 				{
-					P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 					S_StartSound(player->mo, sfx_spin);
 				}
 			}
diff --git a/src/p_user.c b/src/p_user.c
index f7f20ac85..5d3caf023 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3506,42 +3506,8 @@ static void P_DoSuperStuff(player_t *player)
 			if (gametype != GT_COOP)
 				player->powers[pw_flashing] = flashingtics-1;
 
-			if (player->mo->health > 0)
-			{
-				if (player->pflags & PF_JUMPED)
-					P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
-				else if (player->pflags & PF_SPINNING && player->mo->state-states != S_PLAY_DASH)
-					P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
-				else switch (player->mo->state-states)
-				{
-				default:
-					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
-					break;
-				case S_PLAY_DASH:
-					break;
-				case S_PLAY_SUPER_WALK:
-					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-					break;
-				case S_PLAY_SUPER_RUN:
-					P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
-					break;
-				case S_PLAY_SUPER_PEEL:
-					P_SetPlayerMobjState(player->mo, S_PLAY_PEEL);
-					break;
-				case S_PLAY_SUPER_PAIN:
-					P_SetPlayerMobjState(player->mo, S_PLAY_PAIN);
-					break;
-				case S_PLAY_SUPER_SPRING:
-					P_SetPlayerMobjState(player->mo, S_PLAY_SPRING);
-					break;
-				case S_PLAY_SUPER_FALL:
-					P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
-					break;
-				case S_PLAY_SUPER_RIDE:
-					P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
-					break;
-				}
-			}
+			if ((player->mo->health > 0) && (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 (gametype != GT_COOP)
@@ -3807,7 +3773,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					player->mo->momy = player->cmomy;
 					player->pflags |= PF_STARTDASH|PF_SPINNING;
 					player->dashspeed = player->mindash;
-					P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+					P_SetPlayerMobjState(player->mo, S_PLAY_SPINDASH);
 					player->pflags |= PF_USEDOWN;
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_s3kab); // Make the rev sound! Previously sfx_spndsh.
@@ -3840,7 +3806,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						|| !canstand) && !(player->pflags & (PF_USEDOWN|PF_SPINNING)))
 				{
 					player->pflags |= PF_SPINNING;
-					P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_spin);
 					player->pflags |= PF_USEDOWN;
@@ -3896,7 +3862,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 		{
 			if (player->dashspeed)
 			{
-				P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 				P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->dashspeed, player->mo->scale)); // catapult forward ho!!
 			}
 			else
@@ -3914,14 +3880,14 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 
 	if (onground && player->pflags & PF_STARTDASH)
 	{
-		if (player->mo->state-states != S_PLAY_DASH)
-			P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+		if (player->mo->state-states != S_PLAY_SPINDASH)
+			P_SetPlayerMobjState(player->mo, S_PLAY_SPINDASH);
 		// Spawn spin dash dust
 		if (!(player->charflags & SF_NOSPINDASHDUST) && !(player->mo->eflags & MFE_GOOWATER))
 			P_DoSpinDashDust(player);
 	}
 	else if (onground && player->pflags & PF_SPINNING && !(player->panim == PA_ROLL))
-		P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 }
 
 //
@@ -4281,7 +4247,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
 					{
 						if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE)
-							P_SetPlayerMobjState(player->mo, S_PLAY_PEEL);
+							P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
 						else if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
 							P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
 						else
@@ -6721,7 +6687,7 @@ static void P_MovePlayer(player_t *player)
 	{
 		// If the player is in dashmode, here's their peelout.
 		if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim == PA_RUN && !player->skidtime && (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
-			P_SetPlayerMobjState (player->mo, S_PLAY_PEEL);
+			P_SetPlayerMobjState (player->mo, S_PLAY_DASH);
 		// If the player is moving fast enough,
 		// break into a run!
 		else if (player->speed >= runspd && player->panim == PA_WALK && !player->skidtime
@@ -6744,7 +6710,7 @@ static void P_MovePlayer(player_t *player)
 
 	// If your peelout animation is playing, and you're
 	// going too slow, switch back to the run.
-	if (player->charflags & SF_DASHMODE && player->panim == PA_PEEL && player->dashmode < 3*TICRATE)
+	if (player->charflags & SF_DASHMODE && player->panim == PA_DASH && player->dashmode < 3*TICRATE)
 		P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
 
 	// If your running animation is playing, and you're
@@ -7106,7 +7072,7 @@ static void P_MovePlayer(player_t *player)
 #endif
 		}
 		// Otherwise, face the direction you're travelling.
-		else if (player->panim == PA_WALK || player->panim == PA_RUN || player->panim == PA_PEEL || player->panim == PA_ROLL || player->panim == PA_JUMP
+		else if (player->panim == PA_WALK || player->panim == PA_RUN || player->panim == PA_DASH || player->panim == PA_ROLL || player->panim == PA_JUMP
 		|| (player->panim == PA_ABILITY && player->mo->state-states == S_PLAY_GLIDE))
 			player->mo->angle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
 
@@ -7132,9 +7098,8 @@ static void P_MovePlayer(player_t *player)
 				&& (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_PAIN || player->panim == PA_JUMP || player->panim == PA_FALL
-					|| (player->panim == PA_WALK && player->mo->state-states != S_PLAY_SUPER_FLOAT))
-						P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_FLOAT);
+					if (player->panim != PA_RUN && player->mo->state-states != S_PLAY_FLOAT)
+						P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
 
 					player->mo->momz = 0;
 					player->pflags &= ~PF_SPINNING;
@@ -7177,7 +7142,7 @@ static void P_MovePlayer(player_t *player)
 								if (P_LookForEnemies(player, false) && player->mo->tracer)
 								{
 									player->pflags |= PF_FORCEJUMPDAMAGE;
-									P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 									S_StartSound(player->mo, sfx_s3k40);
 									player->homing = 3*TICRATE;
 								}
@@ -7188,7 +7153,7 @@ static void P_MovePlayer(player_t *player)
 							case SH_ELEMENTAL:
 							case SH_BUBBLEWRAP:
 								player->pflags |= PF_FORCEJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY;
-								P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+								P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 								player->secondjump = 0;
 								player->mo->momx = player->mo->momy = 0;
 								P_SetObjectMomZ(player->mo, -24*FRACUNIT, false);
@@ -7306,7 +7271,7 @@ static void P_MovePlayer(player_t *player)
 		fixed_t oldheight = player->mo->height;
 
 		// Less height while spinning. Good for spinning under things...?
-		if ((player->mo->state == &states[player->mo->info->painstate] || player->mo->state == &states[S_PLAY_SUPER_PAIN])
+		if ((player->mo->state == &states[player->mo->info->painstate])
 		|| (!(player->charflags & SF_NOJUMPSPIN) && (player->pflags & PF_JUMPED))
 		|| (player->pflags & PF_SPINNING)
 		|| player->powers[pw_tailsfly] || player->pflags & PF_GLIDING
@@ -7326,7 +7291,7 @@ static void P_MovePlayer(player_t *player)
 		if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_SPINNING))
 		{
 			player->pflags |= PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 		}
 		else if (player->mo->ceilingz - player->mo->floorz < player->mo->height)
 		{
@@ -9057,7 +9022,7 @@ void P_PlayerThink(player_t *player)
 		if (!(player->charflags & SF_NOJUMPSPIN))
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		else if (player->pflags & PF_FORCEJUMPDAMAGE)
-			P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 	}
 
 	if (player->flashcount)
@@ -9302,7 +9267,7 @@ void P_PlayerThink(player_t *player)
 		{
 			P_DoZoomTube(player);
 			if (!(player->panim == PA_ROLL))
-				P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 		}
 		player->rmomx = player->rmomy = 0; // no actual momentum from your controls
 		P_ResetScore(player);
diff --git a/src/r_things.c b/src/r_things.c
index 10b398f00..cd3c91f37 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2899,8 +2899,8 @@ void R_AddSkins(UINT16 wadnum)
 			// these are uppercase so they can be concatenated with SF_
 			// 1, true, yes are all valid values
 			GETFLAG(SUPER)
-			GETFLAG(SUPERANIMS)
-			GETFLAG(SUPERSPIN)
+			GETFLAG(NOSUPERSPIN)
+			GETFLAG(NOSPINDASHDUST)
 			GETFLAG(HIRES)
 			GETFLAG(NOSKID)
 			GETFLAG(NOSPEEDADJUST)
@@ -2910,7 +2910,7 @@ void R_AddSkins(UINT16 wadnum)
 			GETFLAG(STOMPDAMAGE)
 			GETFLAG(MARIODAMAGE)
 			GETFLAG(MACHINE)
-			GETFLAG(NOSPINDASHDUST)
+			GETFLAG(DASHMODE)
 #undef GETFLAG
 
 			else // let's check if it's a sound, otherwise error out
@@ -2956,20 +2956,35 @@ next_token:
 
 		// Add sprites
 		{
-			UINT16 z;
+			UINT16 newlastlump;
 			UINT8 sprite2;
 
 			lump++; // start after S_SKIN
 			lastlump = W_CheckNumForNamePwad("S_END",wadnum,lump); // stop at S_END
-			// old wadding practices die hard -- stop at S_SKIN or S_START if they come before S_END.
-			z = W_CheckNumForNamePwad("S_SKIN",wadnum,lump);
-			if (z < lastlump) lastlump = z;
-			z = W_CheckNumForNamePwad("S_START",wadnum,lump);
-			if (z < lastlump) lastlump = z;
 
-			// load all sprite sets we are aware of.
+			// old wadding practices die hard -- stop at S_SKIN or S_START if they come before S_END.
+			newlastlump = W_CheckNumForNamePwad("S_SKIN",wadnum,lump);
+			if (newlastlump < lastlump) lastlump = newlastlump;
+			newlastlump = W_CheckNumForNamePwad("S_START",wadnum,lump);
+			if (newlastlump < lastlump) lastlump = newlastlump;
+
+			// ...and let's handle super, too
+			newlastlump = W_CheckNumForNamePwad("S_SUPER",wadnum,lump);
+			if (newlastlump < lastlump)
+			{
+				newlastlump++;
+				// load all sprite sets we are aware of... for super!
+				for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
+					R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[FF_SPR2SUPER|sprite2], wadnum, newlastlump, lastlump);
+
+				newlastlump--;
+				lastlump = newlastlump; // okay, make the normal sprite set loading end there
+			}
+
+			// load all sprite sets we are aware of... for normal stuff.
 			for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
 				R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[sprite2], wadnum, lump, lastlump);
+
 		}
 
 		R_FlushTranslationColormapCache();
diff --git a/src/r_things.h b/src/r_things.h
index 572884d21..5684f8a89 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -118,7 +118,7 @@ typedef struct
 	// specific sounds per skin
 	sfxenum_t soundsid[NUMSKINSOUNDS]; // sound # in S_sfx table
 
-	spritedef_t sprites[NUMPLAYERSPRITES];
+	spritedef_t sprites[NUMPLAYERSPRITES*2]; // contains super versions too
 
 	UINT8 availability; // lock?
 } skin_t;

From e20292844d64e6a0000de5d404b49e8e62d7e8ed Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 11 Mar 2017 17:14:39 +0000
Subject: [PATCH 051/119] =?UTF-8?q?*=20SPR2=5FDRL0-C=20are=20gone.=20They'?=
 =?UTF-8?q?re=20now=20(FF=5FSPR2SUPER|SPR2=5FNGT0-C).=20This=20frees=20up?=
 =?UTF-8?q?=20a=20bunch=20of=20badly-used=20freeslots,=20considering=20tha?=
 =?UTF-8?q?t=20you=20can't=20be=20super=20AND=20NiGHTS=20at=20the=20same?=
 =?UTF-8?q?=20time.=20*=20Speaking=20of,=20actively=20enforced=20not=20bei?=
 =?UTF-8?q?ng=20able=20to=20be=20Super=20and=20NiGHTS=20at=20the=20same=20?=
 =?UTF-8?q?time.=20*=20Also=20on=20that=20note=20-=20SPR2=5FTRNS=20is=20no?=
 =?UTF-8?q?w=20the=20NiGHTS=20transformation.=20The=20Super=20transformati?=
 =?UTF-8?q?on=20is=20(FF=5FSPR2SUPER|SPR2=5FTRNS).=20*=20SPR2=5FNPAN=20is?=
 =?UTF-8?q?=20now=20SPR2=5FNSTN,=20since=20it=20matches=20Super=20Sonic's?=
 =?UTF-8?q?=20stun=20animation=20rather=20than=20the=20pain=20animation.?=
 =?UTF-8?q?=20*=20Fixed=20a=20bunch=20of=20things=20where=20Super=20float?=
 =?UTF-8?q?=20was=20handled=20badly=20with=202AM=20brain.=20*=20Fixed=20th?=
 =?UTF-8?q?e=20R=5FProjectSprite=20error=20going=20out=20of=20spr2names'?=
 =?UTF-8?q?=20bounds.=20*=20Fixed=20order=20of=20FF's=20in=20dehack=C3=A9d?=
 =?UTF-8?q?.=20*=20Fixed=20that=20thing=20where=20Super=20Sonic=20was=20bl?=
 =?UTF-8?q?ue=20for=201=20tic=20after=20transformation,=20and=20the=20life?=
 =?UTF-8?q?=20icon=20was=20a=20blue=20Super=20Sonic.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/dehacked.c |   6 +--
 src/hu_stuff.c |   4 +-
 src/info.c     | 119 +++++++++++++++++++++----------------------------
 src/info.h     |  28 +++---------
 src/p_inter.c  |   2 +-
 src/p_mobj.c   |  75 +++++++------------------------
 src/p_pspr.h   |   4 +-
 src/p_user.c   |  18 ++++++--
 src/r_things.c |   2 +-
 src/st_stuff.c |   2 +-
 10 files changed, 99 insertions(+), 161 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 915986c50..dcefb27da 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3986,7 +3986,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	"S_PLAY_NIGHTS_STAND",
 	"S_PLAY_NIGHTS_FLOAT",
-	"S_PLAY_NIGHTS_PAIN",
+	"S_PLAY_NIGHTS_STUN",
 	"S_PLAY_NIGHTS_PULL",
 	"S_PLAY_NIGHTS_ATTACK",
 
@@ -7225,8 +7225,6 @@ struct {
 
 	// Frame settings
 	{"FF_FRAMEMASK",FF_FRAMEMASK},
-	{"FF_VERTICALFLIP",FF_VERTICALFLIP},
-	{"FF_PAPERSPRITE",FF_PAPERSPRITE},
 	{"FF_SPR2SUPER",FF_SPR2SUPER},
 	{"FF_SPR2ENDSTATE",FF_SPR2ENDSTATE},
 	{"FF_SPR2MIDSTART",FF_SPR2MIDSTART},
@@ -7234,6 +7232,8 @@ struct {
 	{"FF_RANDOMANIM",FF_RANDOMANIM},
 	{"FF_GLOBALANIM",FF_GLOBALANIM},
 	{"FF_FULLBRIGHT",FF_FULLBRIGHT},
+	{"FF_VERTICALFLIP",FF_VERTICALFLIP},
+	{"FF_PAPERSPRITE",FF_PAPERSPRITE},
 	{"FF_TRANSMASK",FF_TRANSMASK},
 	{"FF_TRANSSHIFT",FF_TRANSSHIFT},
 	// new preshifted translucency (used in source)
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 01d4ed524..840af9c57 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1241,9 +1241,9 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		}
 		else
 		{
-			if (players[tab[i].num].powers[pw_super])
+			if (players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS] || players[tab[i].num].mo->state > &states[S_PLAY_SUPER_TRANS9]))
 			{
-				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
+				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
 				V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
 			}
 			else
diff --git a/src/info.c b/src/info.c
index a961157b2..2c117c5c9 100644
--- a/src/info.c
+++ b/src/info.c
@@ -424,10 +424,9 @@ char spr2names[NUMPLAYERSPRITES][5] =
 
 	"TRNS",
 
-	"NTRN",
 	"NSTD",
 	"NFLT",
-	"NPAN",
+	"NSTN",
 	"NPUL",
 	"NATK",
 
@@ -445,20 +444,6 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"NGTB",
 	"NGTC",
 
-	"DRL0",
-	"DRL1",
-	"DRL2",
-	"DRL3",
-	"DRL4",
-	"DRL5",
-	"DRL6",
-	"DRL7",
-	"DRL8",
-	"DRL9",
-	"DRLA",
-	"DRLB",
-	"DRLC",
-
 	"SIGN",
 	"LIFE"
 };
@@ -534,19 +519,19 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_MLEE,                20, {NULL},                   0, 0, S_PLAY_FALL}, // S_PLAY_MELEE_FINISH
 
 	// SF_SUPER
-	{SPR_PLAY, SPR2_TRNS,                 4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS2}, // S_PLAY_SUPER_TRANS
-	{SPR_PLAY, SPR2_TRNS,                 4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS3}, // S_PLAY_SUPER_TRANS2
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,   4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS4}, // S_PLAY_SUPER_TRANS3
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS5}, // S_PLAY_SUPER_TRANS4
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS6}, // S_PLAY_SUPER_TRANS5
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS7}, // S_PLAY_SUPER_TRANS6
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS8}, // S_PLAY_SUPER_TRANS7
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS9}, // S_PLAY_SUPER_TRANS8
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,  16, {NULL}, 0, 0, S_PLAY_WALK},         // S_PLAY_SUPER_TRANS9
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER,                4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS2}, // S_PLAY_SUPER_TRANS
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER,                4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS3}, // S_PLAY_SUPER_TRANS2
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS4}, // S_PLAY_SUPER_TRANS3
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS5}, // S_PLAY_SUPER_TRANS4
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS6}, // S_PLAY_SUPER_TRANS5
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS7}, // S_PLAY_SUPER_TRANS6
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS8}, // S_PLAY_SUPER_TRANS7
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  3, {NULL}, 0, 0, S_PLAY_SUPER_TRANS9}, // S_PLAY_SUPER_TRANS8
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT, 16, {NULL}, 0, 0, S_PLAY_WALK},         // S_PLAY_SUPER_TRANS9
 
 	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_OBJPLACE_DUMMY}, //S_OBJPLACE_DUMMY
 
-	// 1-Up Box Sprites (uses player sprite)
+	// 1-Up box sprites (uses player sprite)
 	{SPR_PLAY, SPR2_LIFE,  2, {NULL}, 0, 16, S_PLAY_BOX2},  // S_PLAY_BOX1
 	{SPR_NULL,         0,  1, {NULL}, 0,  0, S_PLAY_BOX1},  // S_PLAY_BOX2
 	{SPR_PLAY, SPR2_LIFE,  4, {NULL}, 0,  4, S_PLAY_ICON2}, // S_PLAY_ICON1
@@ -557,50 +542,50 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_SIGN, 1, {NULL}, 0, 24, S_PLAY_SIGN},         // S_PLAY_SIGN
 
 	// NiGHTS Player, transforming
-	{SPR_PLAY, SPR2_NTRN,                4, {A_Scream}, 0, 0, S_PLAY_NIGHTS_TRANS2}, // S_PLAY_NIGHTS_TRANS
-	{SPR_PLAY, SPR2_NTRN,                4, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS3}, // S_PLAY_NIGHTS_TRANS2
-	{SPR_PLAY, SPR2_NTRN|FF_FULLBRIGHT,  4, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS4}, // S_PLAY_NIGHTS_TRANS3
-	{SPR_PLAY, SPR2_NTRN,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS5}, // S_PLAY_NIGHTS_TRANS4
-	{SPR_PLAY, SPR2_NTRN,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS6}, // S_PLAY_NIGHTS_TRANS5
-	{SPR_PLAY, SPR2_NTRN,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS7}, // S_PLAY_NIGHTS_TRANS6
-	{SPR_PLAY, SPR2_NTRN,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS8}, // S_PLAY_NIGHTS_TRANS7
-	{SPR_PLAY, SPR2_NTRN,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS9}, // S_PLAY_NIGHTS_TRANS8
-	{SPR_PLAY, SPR2_NTRN,               16, {NULL},     0, 0, S_PLAY_NIGHTS_FLOAT}, // S_PLAY_NIGHTS_TRANS9
+	{SPR_PLAY, SPR2_TRNS,                4, {A_Scream}, 0, 0, S_PLAY_NIGHTS_TRANS2}, // S_PLAY_NIGHTS_TRANS
+	{SPR_PLAY, SPR2_TRNS,                4, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS3}, // S_PLAY_NIGHTS_TRANS2
+	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,  4, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS4}, // S_PLAY_NIGHTS_TRANS3
+	{SPR_PLAY, SPR2_TRNS,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS5}, // S_PLAY_NIGHTS_TRANS4
+	{SPR_PLAY, SPR2_TRNS,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS6}, // S_PLAY_NIGHTS_TRANS5
+	{SPR_PLAY, SPR2_TRNS,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS7}, // S_PLAY_NIGHTS_TRANS6
+	{SPR_PLAY, SPR2_TRNS,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS8}, // S_PLAY_NIGHTS_TRANS7
+	{SPR_PLAY, SPR2_TRNS,                3, {NULL},     0, 0, S_PLAY_NIGHTS_TRANS9}, // S_PLAY_NIGHTS_TRANS8
+	{SPR_PLAY, SPR2_TRNS,               16, {NULL},     0, 0, S_PLAY_NIGHTS_FLOAT},  // S_PLAY_NIGHTS_TRANS9
 
-	// NiGHTS Player, Stand, Floating, Pain, Pull and Attack
-	{SPR_PLAY, SPR2_NSTD, 7, {NULL}, 0, 0, S_PLAY_NIGHTS_STAND}, // S_PLAY_NIGHTS_STAND
-	{SPR_PLAY, SPR2_NFLT, 7, {NULL}, 0, 0, S_PLAY_NIGHTS_FLOAT}, // S_PLAY_NIGHTS_FLOAT
-	{SPR_PLAY, SPR2_NPAN, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_PAIN}, // S_PLAY_NIGHTS_PAIN
-	{SPR_PLAY, SPR2_NPUL, 1, {NULL}, 0, 0, S_PLAY_NIGHTS_PULL}, // S_PLAY_NIGHTS_PULL
+	// NiGHTS Player, stand, float, pain, pull and attack
+	{SPR_PLAY, SPR2_NSTD, 7, {NULL}, 0, 0, S_PLAY_NIGHTS_STAND},  // S_PLAY_NIGHTS_STAND
+	{SPR_PLAY, SPR2_NFLT, 7, {NULL}, 0, 0, S_PLAY_NIGHTS_FLOAT},  // S_PLAY_NIGHTS_FLOAT
+	{SPR_PLAY, SPR2_NSTN, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_STUN},   // S_PLAY_NIGHTS_STUN
+	{SPR_PLAY, SPR2_NPUL, 1, {NULL}, 0, 0, S_PLAY_NIGHTS_PULL},   // S_PLAY_NIGHTS_PULL
 	{SPR_PLAY, SPR2_NATK, 1, {NULL}, 0, 0, S_PLAY_NIGHTS_ATTACK}, // S_PLAY_NIGHTS_ATTACK
 
-	// NiGHTS Player, Flying and Drilling
-	{SPR_PLAY, SPR2_NGT0, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY0},  // S_PLAY_NIGHTS_FLY0
-	{SPR_PLAY, SPR2_DRL0, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL0},  // S_PLAY_NIGHTS_DRILL0
-	{SPR_PLAY, SPR2_NGT1, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY1},  // S_PLAY_NIGHTS_FLY1
-	{SPR_PLAY, SPR2_DRL1, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL1},  // S_PLAY_NIGHTS_DRILL1
-	{SPR_PLAY, SPR2_NGT2, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY2},  // S_PLAY_NIGHTS_FLY2
-	{SPR_PLAY, SPR2_DRL2, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL2},  // S_PLAY_NIGHTS_DRILL2
-	{SPR_PLAY, SPR2_NGT3, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY3},  // S_PLAY_NIGHTS_FLY3
-	{SPR_PLAY, SPR2_DRL3, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL3},  // S_PLAY_NIGHTS_DRILL3
-	{SPR_PLAY, SPR2_NGT4, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY4},  // S_PLAY_NIGHTS_FLY4
-	{SPR_PLAY, SPR2_DRL4, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL4},  // S_PLAY_NIGHTS_DRILL4
-	{SPR_PLAY, SPR2_NGT5, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY5},  // S_PLAY_NIGHTS_FLY5
-	{SPR_PLAY, SPR2_DRL5, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL5},  // S_PLAY_NIGHTS_DRILL5
-	{SPR_PLAY, SPR2_NGT6, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY6},  // S_PLAY_NIGHTS_FLY6
-	{SPR_PLAY, SPR2_DRL6, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL6},  // S_PLAY_NIGHTS_DRILL6
-	{SPR_PLAY, SPR2_NGT7, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY7},  // S_PLAY_NIGHTS_FLY7
-	{SPR_PLAY, SPR2_DRL7, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL7},  // S_PLAY_NIGHTS_DRILL7
-	{SPR_PLAY, SPR2_NGT8, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY8},  // S_PLAY_NIGHTS_FLY8
-	{SPR_PLAY, SPR2_DRL8, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL8},  // S_PLAY_NIGHTS_DRILL8
-	{SPR_PLAY, SPR2_NGT9, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY9},  // S_PLAY_NIGHTS_FLY9
-	{SPR_PLAY, SPR2_DRL9, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL9},  // S_PLAY_NIGHTS_DRILL9
-	{SPR_PLAY, SPR2_NGTA, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYA},  // S_PLAY_NIGHTS_FLYA
-	{SPR_PLAY, SPR2_DRLA, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLA},  // S_PLAY_NIGHTS_DRILLA
-	{SPR_PLAY, SPR2_NGTB, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYB},  // S_PLAY_NIGHTS_FLYB
-	{SPR_PLAY, SPR2_DRLB, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLB},  // S_PLAY_NIGHTS_DRILLB
-	{SPR_PLAY, SPR2_NGTC, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYC},  // S_PLAY_NIGHTS_FLYC
-	{SPR_PLAY, SPR2_DRLC, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLC},  // S_PLAY_NIGHTS_DRILLC
+	// NiGHTS Player, flying and drilling
+	{SPR_PLAY, SPR2_NGT0,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY0},   // S_PLAY_NIGHTS_FLY0
+	{SPR_PLAY, SPR2_NGT0|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL0}, // S_PLAY_NIGHTS_DRILL0
+	{SPR_PLAY, SPR2_NGT1,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY1},   // S_PLAY_NIGHTS_FLY1
+	{SPR_PLAY, SPR2_NGT1|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL1}, // S_PLAY_NIGHTS_DRILL1
+	{SPR_PLAY, SPR2_NGT2,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY2},   // S_PLAY_NIGHTS_FLY2
+	{SPR_PLAY, SPR2_NGT2|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL2}, // S_PLAY_NIGHTS_DRILL2
+	{SPR_PLAY, SPR2_NGT3,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY3},   // S_PLAY_NIGHTS_FLY3
+	{SPR_PLAY, SPR2_NGT3|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL3}, // S_PLAY_NIGHTS_DRILL3
+	{SPR_PLAY, SPR2_NGT4,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY4},   // S_PLAY_NIGHTS_FLY4
+	{SPR_PLAY, SPR2_NGT4|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL4}, // S_PLAY_NIGHTS_DRILL4
+	{SPR_PLAY, SPR2_NGT5,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY5},   // S_PLAY_NIGHTS_FLY5
+	{SPR_PLAY, SPR2_NGT5|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL5}, // S_PLAY_NIGHTS_DRILL5
+	{SPR_PLAY, SPR2_NGT6,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY6},   // S_PLAY_NIGHTS_FLY6
+	{SPR_PLAY, SPR2_NGT6|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL6}, // S_PLAY_NIGHTS_DRILL6
+	{SPR_PLAY, SPR2_NGT7,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY7},   // S_PLAY_NIGHTS_FLY7
+	{SPR_PLAY, SPR2_NGT7|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL7}, // S_PLAY_NIGHTS_DRILL7
+	{SPR_PLAY, SPR2_NGT8,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY8},   // S_PLAY_NIGHTS_FLY8
+	{SPR_PLAY, SPR2_NGT8|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL8}, // S_PLAY_NIGHTS_DRILL8
+	{SPR_PLAY, SPR2_NGT9,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY9},   // S_PLAY_NIGHTS_FLY9
+	{SPR_PLAY, SPR2_NGT9|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL9}, // S_PLAY_NIGHTS_DRILL9
+	{SPR_PLAY, SPR2_NGTA,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYA},   // S_PLAY_NIGHTS_FLYA
+	{SPR_PLAY, SPR2_NGTA|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLA}, // S_PLAY_NIGHTS_DRILLA
+	{SPR_PLAY, SPR2_NGTB,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYB},   // S_PLAY_NIGHTS_FLYB
+	{SPR_PLAY, SPR2_NGTB|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLB}, // S_PLAY_NIGHTS_DRILLB
+	{SPR_PLAY, SPR2_NGTC,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYC},   // S_PLAY_NIGHTS_FLYC
+	{SPR_PLAY, SPR2_NGTC|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLC}, // S_PLAY_NIGHTS_DRILLC
 
 	// Blue Crawla
 	{SPR_POSS, 0, 5, {A_Look}, 0, 0, S_POSS_STND},   // S_POSS_STND
diff --git a/src/info.h b/src/info.h
index f7e61614a..c293292e1 100644
--- a/src/info.h
+++ b/src/info.h
@@ -595,8 +595,8 @@ typedef enum sprite
 } spritenum_t;
 
 // Make sure to be conscious of FF_FRAMEMASK and the fact sprite2 is stored as a UINT8 whenever you change this table.
-// Currently, FF_FRAMEMASK is 0x7f, or 127 - and the limitation of sprite2 storage is 255, so the second half is used by FF_SPR2SUPER.
-// Since this is zero-based, there can be at most 256 different SPR2_'s without changing that.
+// Currently, FF_FRAMEMASK is 0xff, or 255 - but the second half is used by FF_SPR2SUPER, so the limitation is 0x7f.
+// Since this is zero-based, there can be at most 128 different SPR2_'s without changing that.
 enum playersprite
 {
 	SPR2_STND = 0,
@@ -635,16 +635,15 @@ enum playersprite
 
 	SPR2_MLEE, // melee
 
-	SPR2_TRNS, // super transformation
+	SPR2_TRNS, // transformation
 
-	SPR2_NTRN, // NiGHTS transformation
 	SPR2_NSTD, // NiGHTS stand
 	SPR2_NFLT, // NiGHTS float
-	SPR2_NPAN, // NiGHTS pain
+	SPR2_NSTN, // NiGHTS stun
 	SPR2_NPUL, // NiGHTS pull
 	SPR2_NATK, // NiGHTS attack
 
-	// NiGHTS flight.
+	// NiGHTS flight. bitwise | with FF_SPR2SUPER for drilling
 	SPR2_NGT0,
 	SPR2_NGT1,
 	SPR2_NGT2,
@@ -659,21 +658,6 @@ enum playersprite
 	SPR2_NGTB,
 	SPR2_NGTC,
 
-	// NiGHTS drill.
-	SPR2_DRL0,
-	SPR2_DRL1,
-	SPR2_DRL2,
-	SPR2_DRL3,
-	SPR2_DRL4,
-	SPR2_DRL5,
-	SPR2_DRL6,
-	SPR2_DRL7,
-	SPR2_DRL8,
-	SPR2_DRL9,
-	SPR2_DRLA,
-	SPR2_DRLB,
-	SPR2_DRLC,
-
 	SPR2_SIGN, // end sign head
 	SPR2_LIFE, // life monitor icon
 
@@ -780,7 +764,7 @@ typedef enum state
 
 	S_PLAY_NIGHTS_STAND,
 	S_PLAY_NIGHTS_FLOAT,
-	S_PLAY_NIGHTS_PAIN,
+	S_PLAY_NIGHTS_STUN,
 	S_PLAY_NIGHTS_PULL,
 	S_PLAY_NIGHTS_ATTACK,
 
diff --git a/src/p_inter.c b/src/p_inter.c
index e456c59bc..00c1cccaa 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2606,7 +2606,7 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 		}
 
 		player->powers[pw_flashing] = flashingtics;
-		P_SetPlayerMobjState(target, S_PLAY_NIGHTS_PAIN);
+		P_SetPlayerMobjState(target, S_PLAY_NIGHTS_STUN);
 		S_StartSound(target, sfx_nghurt);
 
 		if (oldnightstime > 10*TICRATE
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 272f3bfca..a9271e275 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -195,16 +195,11 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 {
 	player_t *player = mobj->player;
 	skin_t *skin = ((skin_t *)mobj->skin);
-	boolean super = false;
+	UINT8 super = (spr2 & FF_SPR2SUPER);
 
 	if (!skin)
 		return 0;
 
-	if ((super = (player // only manipulate output if player...
-	&& (player->powers[pw_super] // and (if they're super...
-	|| ((player->pflags & PF_NIGHTSMODE) && (skin->flags & SF_SUPER)))))) // or if they're in nights and are a skin that CAN go super...)
-		spr2 |= FF_SPR2SUPER;
-
 	while (!(skin->sprites[spr2].numframes)
 		&& spr2 != SPR2_STND)
 	{
@@ -293,77 +288,40 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			break;
 
 		// NiGHTS sprites.
-		case SPR2_NTRN:
-			spr2 = SPR2_TRNS;
-			break;
 		case SPR2_NSTD:
-			spr2 = SPR2_STND;
+			spr2 = FF_SPR2SUPER|SPR2_STND;
 			break;
 		case SPR2_NFLT:
-			spr2 = SPR2_FLT ;
+			spr2 = FF_SPR2SUPER|SPR2_FLT ;
 			break;
 		case SPR2_NPUL:
-			spr2 = SPR2_NFLT;
+			spr2 = SPR2_STUN;
 			break;
-		case SPR2_NPAN:
+		case SPR2_NSTN:
 			spr2 = SPR2_NPUL;
 			break;
 		case SPR2_NATK:
-			spr2 = SPR2_ROLL;
+			spr2 = FF_SPR2SUPER|SPR2_ROLL;
 			break;
 		/*case SPR2_NGT0:
 			spr2 = SPR2_NFLT;
 			break;*/
 		case SPR2_NGT1:
-		case SPR2_NGT7:
-		case SPR2_DRL0:
-			spr2 = SPR2_NGT0;
-			break;
 		case SPR2_NGT2:
-		case SPR2_DRL1:
-			spr2 = SPR2_NGT1;
-			break;
 		case SPR2_NGT3:
-		case SPR2_DRL2:
-			spr2 = SPR2_NGT2;
-			break;
 		case SPR2_NGT4:
-		case SPR2_DRL3:
-			spr2 = SPR2_NGT3;
-			break;
 		case SPR2_NGT5:
-		case SPR2_DRL4:
-			spr2 = SPR2_NGT4;
-			break;
 		case SPR2_NGT6:
-		case SPR2_DRL5:
-			spr2 = SPR2_NGT5;
-			break;
-		case SPR2_DRL6:
-			spr2 = SPR2_NGT6;
-			break;
+		// case SPR2_NGT7:
 		case SPR2_NGT8:
-		case SPR2_DRL7:
-			spr2 = SPR2_NGT7;
-			break;
 		case SPR2_NGT9:
-		case SPR2_DRL8:
-			spr2 = SPR2_NGT8;
-			break;
 		case SPR2_NGTA:
-		case SPR2_DRL9:
-			spr2 = SPR2_NGT9;
-			break;
 		case SPR2_NGTB:
-		case SPR2_DRLA:
-			spr2 = SPR2_NGTA;
-			break;
 		case SPR2_NGTC:
-		case SPR2_DRLB:
-			spr2 = SPR2_NGTB;
+			spr2--; // take an angle step towards horizontal
 			break;
-		case SPR2_DRLC:
-			spr2 = SPR2_NGTC;
+		case SPR2_NGT7:
+			spr2 = SPR2_NGT0; // needs to explicitly go directly to horizontal
 			break;
 
 		// Dunno? Just go to standing then.
@@ -372,8 +330,7 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			break;
 		}
 
-		if (super)
-			spr2 |= FF_SPR2SUPER;
+		spr2 |= super;
 	}
 
 	return spr2;
@@ -571,7 +528,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 			UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
 			UINT8 numframes;
 
-			UINT8 spr2 = P_GetMobjSprite2(mobj, st->frame & FF_FRAMEMASK);
+			UINT8 spr2 = P_GetMobjSprite2(mobj, (((player->powers[pw_super]) ? FF_SPR2SUPER : 0)|st->frame) & FF_FRAMEMASK);
 
 			if (skin)
 				numframes = skin->sprites[spr2].numframes;
@@ -3220,10 +3177,10 @@ static void P_PlayerZMovement(mobj_t *mo)
 							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_DASH)
 								P_SetPlayerMobjState(mo, S_PLAY_DASH);
 							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
-							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->sprite2 & FF_SPR2SUPER))
+							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN))
 								P_SetPlayerMobjState(mo, S_PLAY_RUN);
 							else if ((mo->player->rmomx || mo->player->rmomy)
-							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->sprite2 & FF_SPR2SUPER))
+							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT))
 								P_SetPlayerMobjState(mo, S_PLAY_WALK);
 							else if (!mo->player->rmomx && !mo->player->rmomy && mo->player->panim != PA_IDLE)
 								P_SetPlayerMobjState(mo, S_PLAY_STND);
@@ -3233,10 +3190,10 @@ static void P_PlayerZMovement(mobj_t *mo)
 							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_DASH)
 								P_SetPlayerMobjState(mo, S_PLAY_DASH);
 							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
-							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN || mo->sprite2 & FF_SPR2SUPER))
+							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN))
 								P_SetPlayerMobjState(mo, S_PLAY_RUN);
 							else if ((mo->momx || mo->momy)
-							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT || mo->sprite2 & FF_SPR2SUPER))
+							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT))
 								P_SetPlayerMobjState(mo, S_PLAY_WALK);
 							else if (!mo->momx && !mo->momy && mo->player->panim != PA_IDLE)
 								P_SetPlayerMobjState(mo, S_PLAY_STND);
diff --git a/src/p_pspr.h b/src/p_pspr.h
index fccfac3a3..0734b78ec 100644
--- a/src/p_pspr.h
+++ b/src/p_pspr.h
@@ -35,8 +35,8 @@
 #pragma interface
 #endif
 
-/// \brief Frame flags: only the frame number - 0 to 127 (Frames from 0 to 63, Sprite2 number uses full range)
-#define FF_FRAMEMASK 0x7f
+/// \brief Frame flags: only the frame number - 0 to 256 (Frames from 0 to 63, Sprite2 number uses 0 to 127 plus FF_SPR2SUPER)
+#define FF_FRAMEMASK 0xff
 
 /// \brief Frame flags - SPR2: Super sprite2
 #define FF_SPR2SUPER 0x80
diff --git a/src/p_user.c b/src/p_user.c
index 5d3caf023..67b5f7d60 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -643,7 +643,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 {
 	INT32 oldmare;
 
-	// Bots can't be super, silly!1 :P
+	// Bots can't be NiGHTSerized, silly!1 :P
 	if (player->bot)
 		return;
 
@@ -658,6 +658,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	player->secondjump = 0;
 
 	player->powers[pw_shield] = SH_NONE;
+	player->powers[pw_super] = 0;
 
 	player->mo->flags |= MF_NOGRAVITY;
 
@@ -3422,7 +3423,9 @@ static void P_DoSuperStuff(player_t *player)
 {
 	mobj_t *spark;
 	ticcmd_t *cmd = &player->cmd;
-	if (player->mo->state >= &states[S_PLAY_SUPER_TRANS] && player->mo->state <= &states[S_PLAY_SUPER_TRANS9])
+	if (player->mo->state >= &states[S_PLAY_SUPER_TRANS]
+	&& (player->mo->state < &states[S_PLAY_SUPER_TRANS9]
+	|| (player->mo->state == &states[S_PLAY_SUPER_TRANS9] && player->mo->tics > 1))) // needed to prevent one-frame old colour...
 		return; // don't do anything right now, we're in the middle of transforming!
 
 	if (player->pflags & PF_NIGHTSMODE)
@@ -6717,12 +6720,21 @@ static void P_MovePlayer(player_t *player)
 	// going too slow, switch back to the walking frames.
 	if (player->panim == PA_RUN && player->speed < runspd)
 	{
-		if (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super])
+		if (!onground && (((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
 			P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
 		else
 			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 	}
 
+	// Correct floating when ending up on the ground.
+	if (onground)
+	{
+		if (player->mo->state-states == S_PLAY_FLOAT)
+			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+		else if (player->mo->state-states == S_PLAY_FLOAT_RUN)
+			P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+	}
+
 	// If Springing (or nojumpspinning), but travelling DOWNWARD, change back!
 	if ((player->panim == PA_SPRING && P_MobjFlip(player->mo)*player->mo->momz < 0)
 		|| ((((player->charflags & SF_NOJUMPSPIN) && (player->pflags & PF_JUMPED) && player->panim == PA_JUMP))
diff --git a/src/r_things.c b/src/r_things.c
index cd3c91f37..1a7dc2ecb 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1176,7 +1176,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	{
 		sprdef = &((skin_t *)thing->skin)->sprites[thing->sprite2];
 		if (rot >= sprdef->numframes) {
-			CONS_Alert(CONS_ERROR, M_GetText("R_ProjectSprite: invalid skins[\"%s\"].sprites[SPR2_%s] frame %s\n"), ((skin_t *)thing->skin)->name, spr2names[thing->sprite2], sizeu5(rot));
+			CONS_Alert(CONS_ERROR, M_GetText("R_ProjectSprite: invalid skins[\"%s\"].sprites[%sSPR2_%s] frame %s\n"), ((skin_t *)thing->skin)->name, ((thing->sprite2 & FF_SPR2SUPER) ? "FF_SPR2SUPER|": ""), spr2names[(thing->sprite2 & ~FF_SPR2SUPER)], sizeu5(rot));
 			thing->sprite = states[S_UNKNOWN].sprite;
 			thing->frame = states[S_UNKNOWN].frame;
 			sprdef = &sprites[thing->sprite];
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 658c2c6d6..49f5ea2a5 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -714,7 +714,7 @@ static void ST_drawLives(void)
 		// skincolor face/super
 		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->mo->color, GTC_CACHE);
 		patch_t *face = faceprefix[stplyr->skin];
-		if (stplyr->powers[pw_super] || stplyr->pflags & PF_NIGHTSMODE)
+		if ((stplyr->powers[pw_super] && (stplyr->mo->state < &states[S_PLAY_SUPER_TRANS] || stplyr->mo->state > &states[S_PLAY_SUPER_TRANS9])) || stplyr->pflags & PF_NIGHTSMODE)
 			face = superprefix[stplyr->skin];
 		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
 			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,face, colormap);

From 5b77ca5fed709cf167820e0ccdbf636c0fc81d7e Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 11 Mar 2017 18:14:58 +0000
Subject: [PATCH 052/119] Reverted change where NiGHTS drilling sprites were
 technically the super version of flying sprites. It was not helpful to
 modders at all, and was only justifiable as a rutheless attempt at efficiency
 in storage.

---
 src/info.c   | 66 ++++++++++++++++++++++++++++++--------------------
 src/info.h   | 17 ++++++++++++-
 src/p_mobj.c | 68 +++++++++++++++++++++++++++++++++++++++-------------
 3 files changed, 107 insertions(+), 44 deletions(-)

diff --git a/src/info.c b/src/info.c
index 2c117c5c9..dadb4bfe0 100644
--- a/src/info.c
+++ b/src/info.c
@@ -444,6 +444,20 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"NGTB",
 	"NGTC",
 
+	"DRL0",
+	"DRL1",
+	"DRL2",
+	"DRL3",
+	"DRL4",
+	"DRL5",
+	"DRL6",
+	"DRL7",
+	"DRL8",
+	"DRL9",
+	"DRLA",
+	"DRLB",
+	"DRLC",
+
 	"SIGN",
 	"LIFE"
 };
@@ -560,32 +574,32 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_NATK, 1, {NULL}, 0, 0, S_PLAY_NIGHTS_ATTACK}, // S_PLAY_NIGHTS_ATTACK
 
 	// NiGHTS Player, flying and drilling
-	{SPR_PLAY, SPR2_NGT0,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY0},   // S_PLAY_NIGHTS_FLY0
-	{SPR_PLAY, SPR2_NGT0|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL0}, // S_PLAY_NIGHTS_DRILL0
-	{SPR_PLAY, SPR2_NGT1,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY1},   // S_PLAY_NIGHTS_FLY1
-	{SPR_PLAY, SPR2_NGT1|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL1}, // S_PLAY_NIGHTS_DRILL1
-	{SPR_PLAY, SPR2_NGT2,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY2},   // S_PLAY_NIGHTS_FLY2
-	{SPR_PLAY, SPR2_NGT2|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL2}, // S_PLAY_NIGHTS_DRILL2
-	{SPR_PLAY, SPR2_NGT3,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY3},   // S_PLAY_NIGHTS_FLY3
-	{SPR_PLAY, SPR2_NGT3|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL3}, // S_PLAY_NIGHTS_DRILL3
-	{SPR_PLAY, SPR2_NGT4,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY4},   // S_PLAY_NIGHTS_FLY4
-	{SPR_PLAY, SPR2_NGT4|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL4}, // S_PLAY_NIGHTS_DRILL4
-	{SPR_PLAY, SPR2_NGT5,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY5},   // S_PLAY_NIGHTS_FLY5
-	{SPR_PLAY, SPR2_NGT5|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL5}, // S_PLAY_NIGHTS_DRILL5
-	{SPR_PLAY, SPR2_NGT6,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY6},   // S_PLAY_NIGHTS_FLY6
-	{SPR_PLAY, SPR2_NGT6|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL6}, // S_PLAY_NIGHTS_DRILL6
-	{SPR_PLAY, SPR2_NGT7,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY7},   // S_PLAY_NIGHTS_FLY7
-	{SPR_PLAY, SPR2_NGT7|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL7}, // S_PLAY_NIGHTS_DRILL7
-	{SPR_PLAY, SPR2_NGT8,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY8},   // S_PLAY_NIGHTS_FLY8
-	{SPR_PLAY, SPR2_NGT8|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL8}, // S_PLAY_NIGHTS_DRILL8
-	{SPR_PLAY, SPR2_NGT9,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY9},   // S_PLAY_NIGHTS_FLY9
-	{SPR_PLAY, SPR2_NGT9|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL9}, // S_PLAY_NIGHTS_DRILL9
-	{SPR_PLAY, SPR2_NGTA,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYA},   // S_PLAY_NIGHTS_FLYA
-	{SPR_PLAY, SPR2_NGTA|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLA}, // S_PLAY_NIGHTS_DRILLA
-	{SPR_PLAY, SPR2_NGTB,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYB},   // S_PLAY_NIGHTS_FLYB
-	{SPR_PLAY, SPR2_NGTB|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLB}, // S_PLAY_NIGHTS_DRILLB
-	{SPR_PLAY, SPR2_NGTC,              2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYC},   // S_PLAY_NIGHTS_FLYC
-	{SPR_PLAY, SPR2_NGTC|FF_SPR2SUPER, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLC}, // S_PLAY_NIGHTS_DRILLC
+	{SPR_PLAY, SPR2_NGT0, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY0},   // S_PLAY_NIGHTS_FLY0
+	{SPR_PLAY, SPR2_DRL0, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL0}, // S_PLAY_NIGHTS_DRILL0
+	{SPR_PLAY, SPR2_NGT1, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY1},   // S_PLAY_NIGHTS_FLY1
+	{SPR_PLAY, SPR2_DRL1, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL1}, // S_PLAY_NIGHTS_DRILL1
+	{SPR_PLAY, SPR2_NGT2, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY2},   // S_PLAY_NIGHTS_FLY2
+	{SPR_PLAY, SPR2_DRL2, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL2}, // S_PLAY_NIGHTS_DRILL2
+	{SPR_PLAY, SPR2_NGT3, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY3},   // S_PLAY_NIGHTS_FLY3
+	{SPR_PLAY, SPR2_DRL3, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL3}, // S_PLAY_NIGHTS_DRILL3
+	{SPR_PLAY, SPR2_NGT4, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY4},   // S_PLAY_NIGHTS_FLY4
+	{SPR_PLAY, SPR2_DRL4, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL4}, // S_PLAY_NIGHTS_DRILL4
+	{SPR_PLAY, SPR2_NGT5, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY5},   // S_PLAY_NIGHTS_FLY5
+	{SPR_PLAY, SPR2_DRL5, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL5}, // S_PLAY_NIGHTS_DRILL5
+	{SPR_PLAY, SPR2_NGT6, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY6},   // S_PLAY_NIGHTS_FLY6
+	{SPR_PLAY, SPR2_DRL6, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL6}, // S_PLAY_NIGHTS_DRILL6
+	{SPR_PLAY, SPR2_NGT7, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY7},   // S_PLAY_NIGHTS_FLY7
+	{SPR_PLAY, SPR2_DRL7, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL7}, // S_PLAY_NIGHTS_DRILL7
+	{SPR_PLAY, SPR2_NGT8, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY8},   // S_PLAY_NIGHTS_FLY8
+	{SPR_PLAY, SPR2_DRL8, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL8}, // S_PLAY_NIGHTS_DRILL8
+	{SPR_PLAY, SPR2_NGT9, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLY9},   // S_PLAY_NIGHTS_FLY9
+	{SPR_PLAY, SPR2_DRL9, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILL9}, // S_PLAY_NIGHTS_DRILL9
+	{SPR_PLAY, SPR2_NGTA, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYA},   // S_PLAY_NIGHTS_FLYA
+	{SPR_PLAY, SPR2_DRLA, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLA}, // S_PLAY_NIGHTS_DRILLA
+	{SPR_PLAY, SPR2_NGTB, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYB},   // S_PLAY_NIGHTS_FLYB
+	{SPR_PLAY, SPR2_DRLB, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLB}, // S_PLAY_NIGHTS_DRILLB
+	{SPR_PLAY, SPR2_NGTC, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_FLYC},   // S_PLAY_NIGHTS_FLYC
+	{SPR_PLAY, SPR2_DRLC, 2, {NULL}, 0, 0, S_PLAY_NIGHTS_DRILLC}, // S_PLAY_NIGHTS_DRILLC
 
 	// Blue Crawla
 	{SPR_POSS, 0, 5, {A_Look}, 0, 0, S_POSS_STND},   // S_POSS_STND
diff --git a/src/info.h b/src/info.h
index c293292e1..54291cb3c 100644
--- a/src/info.h
+++ b/src/info.h
@@ -643,7 +643,7 @@ enum playersprite
 	SPR2_NPUL, // NiGHTS pull
 	SPR2_NATK, // NiGHTS attack
 
-	// NiGHTS flight. bitwise | with FF_SPR2SUPER for drilling
+	// NiGHTS flight
 	SPR2_NGT0,
 	SPR2_NGT1,
 	SPR2_NGT2,
@@ -658,6 +658,21 @@ enum playersprite
 	SPR2_NGTB,
 	SPR2_NGTC,
 
+	// NiGHTS drill
+	SPR2_DRL0,
+	SPR2_DRL1,
+	SPR2_DRL2,
+	SPR2_DRL3,
+	SPR2_DRL4,
+	SPR2_DRL5,
+	SPR2_DRL6,
+	SPR2_DRL7,
+	SPR2_DRL8,
+	SPR2_DRL9,
+	SPR2_DRLA,
+	SPR2_DRLB,
+	SPR2_DRLC,
+
 	SPR2_SIGN, // end sign head
 	SPR2_LIFE, // life monitor icon
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index a9271e275..bbecf84ad 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -294,11 +294,11 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 		case SPR2_NFLT:
 			spr2 = FF_SPR2SUPER|SPR2_FLT ;
 			break;
-		case SPR2_NPUL:
+		case SPR2_NSTN:
 			spr2 = SPR2_STUN;
 			break;
-		case SPR2_NSTN:
-			spr2 = SPR2_NPUL;
+		case SPR2_NPUL:
+			spr2 = SPR2_NSTN;
 			break;
 		case SPR2_NATK:
 			spr2 = FF_SPR2SUPER|SPR2_ROLL;
@@ -307,21 +307,55 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_NFLT;
 			break;*/
 		case SPR2_NGT1:
-		case SPR2_NGT2:
-		case SPR2_NGT3:
-		case SPR2_NGT4:
-		case SPR2_NGT5:
-		case SPR2_NGT6:
-		// case SPR2_NGT7:
-		case SPR2_NGT8:
-		case SPR2_NGT9:
-		case SPR2_NGTA:
-		case SPR2_NGTB:
-		case SPR2_NGTC:
-			spr2--; // take an angle step towards horizontal
-			break;
 		case SPR2_NGT7:
-			spr2 = SPR2_NGT0; // needs to explicitly go directly to horizontal
+		case SPR2_DRL0:
+			spr2 = SPR2_NGT0;
+			break;
+		case SPR2_NGT2:
+		case SPR2_DRL1:
+			spr2 = SPR2_NGT1;
+			break;
+		case SPR2_NGT3:
+		case SPR2_DRL2:
+			spr2 = SPR2_NGT2;
+			break;
+		case SPR2_NGT4:
+		case SPR2_DRL3:
+			spr2 = SPR2_NGT3;
+			break;
+		case SPR2_NGT5:
+		case SPR2_DRL4:
+			spr2 = SPR2_NGT4;
+			break;
+		case SPR2_NGT6:
+		case SPR2_DRL5:
+			spr2 = SPR2_NGT5;
+			break;
+		case SPR2_DRL6:
+			spr2 = SPR2_NGT6;
+			break;
+		case SPR2_NGT8:
+		case SPR2_DRL7:
+			spr2 = SPR2_NGT7;
+			break;
+		case SPR2_NGT9:
+		case SPR2_DRL8:
+			spr2 = SPR2_NGT8;
+			break;
+		case SPR2_NGTA:
+		case SPR2_DRL9:
+			spr2 = SPR2_NGT9;
+			break;
+		case SPR2_NGTB:
+		case SPR2_DRLA:
+			spr2 = SPR2_NGTA;
+			break;
+		case SPR2_NGTC:
+		case SPR2_DRLB:
+			spr2 = SPR2_NGTB;
+			break;
+		case SPR2_DRLC:
+			spr2 = SPR2_NGTC;
 			break;
 
 		// Dunno? Just go to standing then.

From b9ddc0226e4052d722d32d4ad4df0d799355fbde Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 11 Mar 2017 18:15:44 +0000
Subject: [PATCH 053/119] Fixed a thing where super float started on the float
 frame normally and then immediately went to the float run frame a tic later.

---
 src/p_user.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 67b5f7d60..62ad6d4e4 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -7110,8 +7110,13 @@ static void P_MovePlayer(player_t *player)
 				&& (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->mo->state-states != S_PLAY_FLOAT)
-						P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
+					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_SPINNING;

From 7aeeb278fe558d29e0aa1298f056a3432043731a Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 14 Mar 2017 18:11:17 +0000
Subject: [PATCH 054/119] Increased space in pflags (because we were getting
 VERY close to full capacity), which I will re-organise when this branch is
 closer to completion.

* PF_NIGHTSMODE is now CR_NIGHTSMODE as part of player->powers[pw_carry]. This is because it's mutually exclusive to every other "carry" type.
* PF_SUPERREADY is dead because it literally just checked for all 7 emeralds and 50 rings. That's it. You couldn't even appreciably alter its presence with Lua. That logic has been placed back in P_SuperReady.
---
 src/b_bot.c    |  2 +-
 src/d_player.h | 13 ++++----
 src/dehacked.c | 12 +++----
 src/g_game.c   | 18 +++++-----
 src/m_cheat.c  |  6 ++--
 src/p_enemy.c  | 10 +++---
 src/p_inter.c  | 44 ++++++++++++------------
 src/p_map.c    | 13 ++++----
 src/p_mobj.c   | 27 +++++++--------
 src/p_setup.c  |  2 --
 src/p_spec.c   |  8 ++---
 src/p_telept.c |  1 +
 src/p_user.c   | 90 ++++++++++++++++++++++++++++----------------------
 src/r_main.c   |  2 +-
 src/r_things.c |  2 +-
 src/st_stuff.c |  8 ++---
 16 files changed, 131 insertions(+), 127 deletions(-)

diff --git a/src/b_bot.c b/src/b_bot.c
index 9565b0e09..dc65c9c16 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -212,7 +212,7 @@ boolean B_CheckRespawn(player_t *player)
 
 	// Check if Sonic is busy first.
 	// If he's doing any of these things, he probably doesn't want to see us.
-	if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_BOUNCING|PF_NIGHTSMODE)
+	if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_BOUNCING)
 	|| (sonic->player->panim != PA_IDLE && sonic->player->panim != PA_WALK)
 	|| (sonic->player->powers[pw_carry]))
 		return false;
diff --git a/src/d_player.h b/src/d_player.h
index c7fc70777..c35c29903 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -118,8 +118,7 @@ typedef enum
 	// Did you get a time-over?
 	PF_TIMEOVER = 1<<10,
 
-	// Ready for Super?
-	PF_SUPERREADY = 1<<11,
+	PF_TEMPSLOT1 = 1<<11,
 
 	// Character action status
 	PF_JUMPED    = 1<<12,
@@ -133,12 +132,10 @@ typedef enum
 	// Sliding (usually in water) like Labyrinth/Oil Ocean
 	PF_SLIDING   = 1<<17,
 
-	/*** NIGHTS STUFF ***/
-	// Is the player in NiGHTS mode?
-	PF_NIGHTSMODE        = 1<<18,
-	PF_TRANSFERTOCLOSEST = 1<<19,
+	PF_TEMPSLOT2         = 1<<18,
 
-	// Spill rings after falling
+	/*** NIGHTS STUFF ***/
+	PF_TRANSFERTOCLOSEST = 1<<19,
 	PF_NIGHTSFALL        = 1<<20,
 	PF_DRILLING          = 1<<21,
 	PF_SKIDDOWN          = 1<<22,
@@ -226,6 +223,8 @@ typedef enum
 	CR_GENERIC,
 	// Tails carry.
 	CR_PLAYER,
+	// NiGHTS mode. Not technically a CARRYING, but doesn't stack with any of the others, so might as well go here.
+	CR_NIGHTSMODE,
 	// Specific level gimmicks.
 	CR_ZOOMTUBE,
 	CR_ROPEHANG,
diff --git a/src/dehacked.c b/src/dehacked.c
index dcefb27da..a611b1985 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6948,8 +6948,7 @@ static const char *const PLAYERFLAG_LIST[] = {
 	// Did you get a time-over?
 	"TIMEOVER",
 
-	// Ready for Super?
-	"SUPERREADY",
+	"TEMPSLOT1",
 
 	// Character action status
 	"JUMPED",
@@ -6963,12 +6962,10 @@ static const char *const PLAYERFLAG_LIST[] = {
 	// Sliding (usually in water) like Labyrinth/Oil Ocean
 	"SLIDING",
 
-	/*** NIGHTS STUFF ***/
-	// Is the player in NiGHTS mode?
-	"NIGHTSMODE",
-	"TRANSFERTOCLOSEST",
+	"TEMPSLOT2",
 
-	// Spill rings after falling
+	/*** NIGHTS STUFF ***/
+	"TRANSFERTOCLOSEST",
 	"NIGHTSFALL",
 	"DRILLING",
 	"SKIDDOWN",
@@ -7357,6 +7354,7 @@ struct {
 	{"CR_NONE",CR_NONE},
 	{"CR_GENERIC",CR_GENERIC},
 	{"CR_PLAYER",CR_PLAYER},
+	{"CR_NIGHTSMODE",CR_NIGHTSMODE},
 	{"CR_ZOOMTUBE",CR_ZOOMTUBE},
 	{"CR_ROPEHANG",CR_ROPEHANG},
 	{"CR_MACESPIN",CR_MACESPIN},
diff --git a/src/g_game.c b/src/g_game.c
index c79998d45..e801891fa 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1015,9 +1015,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	if (cv_analog.value || twodlevel
 		|| (player->mo && (player->mo->flags2 & MF2_TWOD))
 		|| (!demoplayback && (player->climbing
-		|| (player->pflags & PF_NIGHTSMODE)
-		|| (player->pflags & PF_SLIDING)
-		|| (player->pflags & PF_FORCESTRAFE)))) // Analog
+		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
+		|| (player->pflags & (PF_SLIDING|PF_FORCESTRAFE))))) // Analog
 			forcestrafe = true;
 	if (forcestrafe) // Analog
 	{
@@ -1120,7 +1119,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 		cmd->buttons |= BT_USE;
 
 	// Camera Controls
-	if (cv_debug || cv_analog.value || demoplayback || objectplacing || player->pflags & PF_NIGHTSMODE)
+	if (cv_debug || cv_analog.value || demoplayback || objectplacing || player->powers[pw_carry] == CR_NIGHTSMODE)
 	{
 		if (PLAYER1INPUTDOWN(gc_camleft))
 			cmd->buttons |= BT_CAMLEFT;
@@ -1306,9 +1305,8 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	if (cv_analog2.value || twodlevel
 		|| (player->mo && (player->mo->flags2 & MF2_TWOD))
 		|| player->climbing
-		|| (player->pflags & PF_NIGHTSMODE)
-		|| (player->pflags & PF_SLIDING)
-		|| (player->pflags & PF_FORCESTRAFE)) // Analog
+		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
+		|| (player->pflags & (PF_SLIDING|PF_FORCESTRAFE))) // Analog
 			forcestrafe = true;
 	if (forcestrafe) // Analog
 	{
@@ -1408,7 +1406,7 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		cmd->buttons |= BT_USE;
 
 	// Camera Controls
-	if (cv_debug || cv_analog2.value || player->pflags & PF_NIGHTSMODE)
+	if (cv_debug || cv_analog2.value || player->powers[pw_carry] == CR_NIGHTSMODE)
 	{
 		if (PLAYER2INPUTDOWN(gc_camleft))
 			cmd->buttons |= BT_CAMLEFT;
@@ -3920,7 +3918,7 @@ void G_WriteGhostTic(mobj_t *ghost)
 	if (!(demoflags & DF_GHOST))
 		return; // No ghost data to write.
 
-	if (ghost->player && ghost->player->pflags & PF_NIGHTSMODE) // We're talking about the NiGHTS thing, not the normal platforming thing!
+	if (ghost->player && ghost->player->powers[pw_carry] == CR_NIGHTSMODE) // We're talking about the NiGHTS thing, not the normal platforming thing!
 		ziptic |= GZT_NIGHTS;
 
 	ziptic_p = demo_p++; // the ziptic, written at the end of this function
@@ -4104,7 +4102,7 @@ void G_ConsGhostTic(void)
 	if (ziptic & GZT_SPR2)
 		demo_p++;
 	if (ziptic & GZT_NIGHTS) {
-		if (!testmo->player || !(testmo->player->pflags & PF_NIGHTSMODE))
+		if (!testmo->player || !(testmo->player->powers[pw_carry] == CR_NIGHTSMODE))
 			nightsfail = true;
 	}
 
diff --git a/src/m_cheat.c b/src/m_cheat.c
index ce9519799..8ae670662 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -942,7 +942,7 @@ boolean OP_FreezeObjectplace(void)
 	if (!objectplacing)
 		return false;
 
-	if ((maptol & TOL_NIGHTS) && (players[consoleplayer].pflags & PF_NIGHTSMODE))
+	if ((maptol & TOL_NIGHTS) && (players[consoleplayer].powers[pw_carry] == CR_NIGHTSMODE))
 		return false;
 
 	return true;
@@ -1255,7 +1255,7 @@ void Command_ObjectPlace_f(void)
 	{
 		objectplacing = true;
 
-		if ((players[0].pflags & PF_NIGHTSMODE))
+		if ((players[0].powers[pw_carry] == CR_NIGHTSMODE))
 			return;
 
 		if (!COM_CheckParm("-silent"))
@@ -1326,7 +1326,7 @@ void Command_ObjectPlace_f(void)
 
 		// Don't touch the NiGHTS Objectplace stuff.
 		// ... or if the mo mysteriously vanished.
-		if (!players[0].mo || (players[0].pflags & PF_NIGHTSMODE))
+		if (!players[0].mo || (players[0].powers[pw_carry] == CR_NIGHTSMODE))
 			return;
 
 		// If still in dummy state, get out of it.
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 650ace336..e11c67686 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -5611,7 +5611,7 @@ void A_MixUp(mobj_t *actor)
 	// and grab their xyz coords
 	for (i = 0; i < MAXPLAYERS; i++)
 		if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE
-			&& !players[i].exiting && !players[i].powers[pw_super])
+			&& !players[i].exiting && !players[i].powers[pw_super] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
 		{
 			if ((netgame || multiplayer) && players[i].spectator) // Ignore spectators
 				continue;
@@ -5730,7 +5730,7 @@ void A_MixUp(mobj_t *actor)
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			if (playeringame[i] && players[i].playerstate == PST_LIVE
-				&& players[i].mo && players[i].mo->health > 0 && !players[i].exiting && !players[i].powers[pw_super])
+				&& players[i].mo && players[i].mo->health > 0 && !players[i].exiting && !players[i].powers[pw_super] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
 			{
 				if ((netgame || multiplayer) && players[i].spectator)// Ignore spectators
 					continue;
@@ -5780,7 +5780,7 @@ void A_MixUp(mobj_t *actor)
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			if (playeringame[i] && players[i].playerstate == PST_LIVE
-				&& players[i].mo && players[i].mo->health > 0 && !players[i].exiting && !players[i].powers[pw_super])
+				&& players[i].mo && players[i].mo->health > 0 && !players[i].exiting && !players[i].powers[pw_super] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
 			{
 				if ((netgame || multiplayer) && players[i].spectator)// Ignore spectators
 					continue;
@@ -5810,7 +5810,7 @@ void A_MixUp(mobj_t *actor)
 		if (teleported[i])
 		{
 			if (playeringame[i] && players[i].playerstate == PST_LIVE
-				&& players[i].mo && players[i].mo->health > 0 && !players[i].exiting && !players[i].powers[pw_super])
+				&& players[i].mo && players[i].mo->health > 0 && !players[i].exiting && !players[i].powers[pw_super] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
 			{
 				if ((netgame || multiplayer) && players[i].spectator)// Ignore spectators
 					continue;
@@ -8238,7 +8238,7 @@ void A_OrbitNights(mobj_t* actor)
 #endif
 
 	if (!actor->target || !actor->target->player ||
-	    !(actor->target->player->pflags & PF_NIGHTSMODE) || !actor->target->player->nightstime
+	    !(actor->target->player->powers[pw_carry] == CR_NIGHTSMODE) || !actor->target->player->nightstime
 	    // Also remove this object if they no longer have a NiGHTS helper
 		|| (ishelper && !actor->target->player->powers[pw_nights_helper]))
 	{
diff --git a/src/p_inter.c b/src/p_inter.c
index 00c1cccaa..661a3e35d 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -367,7 +367,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			return;
 		}
 
-		if (((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
+		if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 		|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
@@ -408,7 +408,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		////////////////////////////////////////////////////////
 		/////ENEMIES!!//////////////////////////////////////////
 		////////////////////////////////////////////////////////
-		if (special->type == MT_GSNAPPER && !(((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
+		if (special->type == MT_GSNAPPER && !(((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 		|| player->powers[pw_invulnerability] || player->powers[pw_super] || elementalpierce)
 		&& toucher->z < special->z + special->height && toucher->z + toucher->height > special->z)
 		{
@@ -426,7 +426,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			else // Cannot hit sharp from above or when red and angry
 				P_DamageMobj(toucher, special, special, 1, 0);
 		}
-		else if (((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
+		else if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 		|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
@@ -765,12 +765,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			else //Initial transformation. Don't allow second chances in special stages!
 			{
-				if (player->pflags & PF_NIGHTSMODE)
+				if (player->powers[pw_carry] == CR_NIGHTSMODE)
 					return;
 
 				S_StartSound(toucher, sfx_supert);
 			}
-			if (!(netgame || multiplayer) && !(player->pflags & PF_NIGHTSMODE))
+			if (!(netgame || multiplayer) && !(player->powers[pw_carry] == CR_NIGHTSMODE))
 				P_SetTarget(&special->tracer, toucher);
 			P_NightserizePlayer(player, special->health); // Transform!
 			return;
@@ -894,7 +894,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				return;
 
 			// make sure everything is as it should be, THEN take rings from players in special stages
-			if (player->pflags & PF_NIGHTSMODE && !toucher->target)
+			if (player->powers[pw_carry] == CR_NIGHTSMODE && !toucher->target)
 				return;
 
 			if (player->mare != special->threshold) // wrong mare
@@ -932,7 +932,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->bumpertime < TICRATE/4)
 			{
 				S_StartSound(toucher, special->info->seesound);
-				if (player->pflags & PF_NIGHTSMODE)
+				if (player->powers[pw_carry] == CR_NIGHTSMODE)
 				{
 					player->bumpertime = TICRATE/2;
 					if (special->threshold > 0)
@@ -988,14 +988,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			return;
 		case MT_NIGHTSSUPERLOOP:
-			if (player->bot || !(player->pflags & PF_NIGHTSMODE))
+			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
 				return;
 			if (!G_IsSpecialStage(gamemap))
 				player->powers[pw_nights_superloop] = (UINT16)special->info->speed;
 			else
 			{
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 						players[i].powers[pw_nights_superloop] = (UINT16)special->info->speed;
 				if (special->info->deathsound != sfx_None)
 					S_StartSound(NULL, special->info->deathsound);
@@ -1010,14 +1010,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			break;
 		case MT_NIGHTSDRILLREFILL:
-			if (player->bot || !(player->pflags & PF_NIGHTSMODE))
+			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
 				return;
 			if (!G_IsSpecialStage(gamemap))
 				player->drillmeter = special->info->speed;
 			else
 			{
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 						players[i].drillmeter = special->info->speed;
 				if (special->info->deathsound != sfx_None)
 					S_StartSound(NULL, special->info->deathsound);
@@ -1032,7 +1032,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			break;
 		case MT_NIGHTSHELPER:
-			if (player->bot || !(player->pflags & PF_NIGHTSMODE))
+			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
 				return;
 			if (!G_IsSpecialStage(gamemap))
 			{
@@ -1046,7 +1046,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				mobj_t *flickyobj;
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].mo && players[i].pflags & PF_NIGHTSMODE) {
+					if (playeringame[i] && players[i].mo && players[i].powers[pw_carry] == CR_NIGHTSMODE) {
 						players[i].powers[pw_nights_helper] = (UINT16)special->info->speed;
 						flickyobj = P_SpawnMobj(players[i].mo->x, players[i].mo->y, players[i].mo->z + players[i].mo->info->height, MT_NIGHTOPIANHELPER);
 						P_SetTarget(&flickyobj->target, players[i].mo);
@@ -1064,7 +1064,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			break;
 		case MT_NIGHTSEXTRATIME:
-			if (player->bot || !(player->pflags & PF_NIGHTSMODE))
+			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
 				return;
 			if (!G_IsSpecialStage(gamemap))
 			{
@@ -1075,7 +1075,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			else
 			{
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 					{
 						players[i].nightstime += special->info->speed;
 						players[i].startedtime += special->info->speed;
@@ -1094,7 +1094,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			break;
 		case MT_NIGHTSLINKFREEZE:
-			if (player->bot || !(player->pflags & PF_NIGHTSMODE))
+			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
 				return;
 			if (!G_IsSpecialStage(gamemap))
 			{
@@ -1104,7 +1104,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			else
 			{
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 					{
 						players[i].powers[pw_nights_linkfreeze] += (UINT16)special->info->speed;
 						players[i].linktimer = 2*TICRATE;
@@ -1162,7 +1162,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (G_IsSpecialStage(gamemap))
 			{
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 						players[i].drillmeter += TICRATE/2;
 			}
 			else if (player->bot)
@@ -1420,7 +1420,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					S_StartSound(toucher, special->info->painsound);
 					return;
 				}
-				else if (((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
+				else if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 						|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 						|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
 						|| (player->pflags & (PF_SPINNING|PF_GLIDING))
@@ -2733,7 +2733,7 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 
 static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
 {
-	player->pflags &= ~(PF_SLIDING|PF_NIGHTSMODE);
+	player->pflags &= ~PF_SLIDING;
 
 	player->powers[pw_carry] = CR_NONE;
 
@@ -3075,7 +3075,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			if (player->pflags & PF_GODMODE)
 				return false;
 
-			if (!(target->player->pflags & (PF_NIGHTSMODE|PF_NIGHTSFALL)) && (maptol & TOL_NIGHTS))
+			if (!(target->player->powers[pw_carry] == CR_NIGHTSMODE || target->player->pflags & PF_NIGHTSFALL) && (maptol & TOL_NIGHTS))
 				return false;
 
 			switch (damagetype)
@@ -3097,7 +3097,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			}
 		}
 
-		if (player->pflags & PF_NIGHTSMODE) // NiGHTS damage handling
+		if (player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS damage handling
 		{
 			if (!force)
 			{
diff --git a/src/p_map.c b/src/p_map.c
index 7d34d73e1..5cf9612e2 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -124,7 +124,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	if (object->player && object->player->spectator)
 		return false;
 
-	if (object->player && (object->player->pflags & PF_NIGHTSMODE))
+	if (object->player && (object->player->powers[pw_carry] == CR_NIGHTSMODE))
 	{
 		/*Someone want to make these work like bumpers?*/
 		return false;
@@ -325,9 +325,6 @@ static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 	if (tails->bot == 1)
 		return;
 
-	if (sonic->pflags & PF_NIGHTSMODE)
-		return;
-
 	if ((sonic->mo->eflags & MFE_VERTICALFLIP) != (tails->mo->eflags & MFE_VERTICALFLIP))
 		return; // Both should be in same gravity
 
@@ -378,6 +375,7 @@ static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 	else {
 		if (sonic-players == consoleplayer && botingame)
 			CV_SetValue(&cv_analog2, true);
+		P_SetTarget(&sonic->mo->tracer, NULL);
 		sonic->powers[pw_carry] = CR_NONE;
 	}
 }
@@ -500,7 +498,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	// Don't collide with your buddies while NiGHTS-flying.
 	if (tmthing->player && thing->player && (maptol & TOL_NIGHTS)
-		&& ((tmthing->player->pflags & PF_NIGHTSMODE) || (thing->player->pflags & PF_NIGHTSMODE)))
+		&& ((tmthing->player->powers[pw_carry] == CR_NIGHTSMODE) || (thing->player->powers[pw_carry] == CR_NIGHTSMODE)))
 		return true;
 
 	blockdist = thing->radius + tmthing->radius;
@@ -897,7 +895,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		// must be flying in the SAME DIRECTION as the last time you came through.
 		// not (your direction) xor (stored direction)
 		// In other words, you can't u-turn and respawn rings near the drone.
-		if (pl->bonustime && (pl->pflags & PF_NIGHTSMODE) && (INT32)leveltime > droneobj->extravalue2 && (
+		if (pl->bonustime && (pl->powers[pw_carry] == CR_NIGHTSMODE) && (INT32)leveltime > droneobj->extravalue2 && (
 		   !(pl->anotherflyangle >= 90 &&   pl->anotherflyangle <= 270)
 		^ (droneobj->extravalue1 >= 90 && droneobj->extravalue1 <= 270)
 		))
@@ -1006,7 +1004,10 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		if (thing->player-players == consoleplayer && botingame)
 			CV_SetValue(&cv_analog2, true);
 		if (thing->player->powers[pw_carry] == CR_PLAYER)
+		{
+			P_SetTarget(&thing->tracer, NULL);
 			thing->player->powers[pw_carry] = CR_NONE;
+		}
 	}
 
 	if (thing->player)
diff --git a/src/p_mobj.c b/src/p_mobj.c
index bbecf84ad..4e196519e 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1581,11 +1581,8 @@ static void P_PlayerFlip(mobj_t *mo)
 	G_GhostAddFlip();
 	// Flip aiming to match!
 
-	if (mo->player->pflags & PF_NIGHTSMODE) // NiGHTS doesn't use flipcam
-	{
-		if (mo->tracer)
-			mo->tracer->eflags ^= MFE_VERTICALFLIP;
-	}
+	if (mo->player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS doesn't use flipcam
+		;
 	else if (mo->player->pflags & PF_FLIPCAM)
 	{
 		mo->player->aiming = InvAngle(mo->player->aiming);
@@ -1681,7 +1678,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 		|| (mo->player->charability == CA_FLY && (mo->player->powers[pw_tailsfly]
 			|| mo->state-states == S_PLAY_FLY_TIRED)))
 			gravityadd = gravityadd/3; // less gravity while flying/gliding
-		if (mo->player->climbing || (mo->player->pflags & PF_NIGHTSMODE))
+		if (mo->player->climbing || (mo->player->powers[pw_carry] == CR_NIGHTSMODE))
 			gravityadd = 0;
 
 		if (!(mo->flags2 & MF2_OBJECTFLIP) != !(mo->player->powers[pw_gravityboots])) // negated to turn numeric into bool - would be double negated, but not needed if both would be
@@ -2253,7 +2250,7 @@ void P_XYMovement(mobj_t *mo)
 	// Check the gravity status.
 	P_CheckGravity(mo, false);
 
-	if (player && !moved && player->pflags & PF_NIGHTSMODE && mo->target)
+	if (player && !moved && player->powers[pw_carry] == CR_NIGHTSMODE && mo->target)
 	{
 		angle_t fa;
 
@@ -2296,7 +2293,7 @@ void P_XYMovement(mobj_t *mo)
 	if (player && player->homing) // no friction for homing
 		return;
 
-	if (player && player->pflags & PF_NIGHTSMODE)
+	if (player && player->powers[pw_carry] == CR_NIGHTSMODE)
 		return; // no friction for NiGHTS players
 
 #ifdef ESLOPE
@@ -3078,7 +3075,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 		else
 			mo->z = mo->floorz;
 
-		if (mo->player->pflags & PF_NIGHTSMODE)
+		if (mo->player->powers[pw_carry] == CR_NIGHTSMODE)
 		{
 			// bounce off floor if you were flying towards it
 			if ((mo->eflags & MFE_VERTICALFLIP && mo->player->flyangle > 0 && mo->player->flyangle < 180)
@@ -3338,7 +3335,7 @@ nightsdone:
 		else
 			mo->z = mo->ceilingz - mo->height;
 
-		if (mo->player->pflags & PF_NIGHTSMODE)
+		if (mo->player->powers[pw_carry] == CR_NIGHTSMODE)
 		{
 			// bounce off ceiling if you were flying towards it
 			if ((mo->eflags & MFE_VERTICALFLIP && mo->player->flyangle > 180 && mo->player->flyangle <= 359)
@@ -3551,7 +3548,7 @@ static boolean P_SceneryZMovement(mobj_t *mo)
 //
 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
 {
-	if (!(player->pflags & PF_NIGHTSMODE) && !player->homing
+	if (!player->powers[pw_carry] && !player->homing
 		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= 3*TICRATE) && player->mo->ceilingz-*rover->topheight >= player->mo->height)
 		&& (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
 		&& !(player->pflags & PF_SLIDING)
@@ -3980,7 +3977,7 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled
 		|| (thiscam == &camera2 && players[secondarydisplayplayer].mo && (players[secondarydisplayplayer].mo->flags2 & MF2_TWOD)))
 		itsatwodlevel = true;
 
-	if (player->pflags & PF_FLIPCAM && !(player->pflags & PF_NIGHTSMODE) && player->mo->eflags & MFE_VERTICALFLIP)
+	if (player->pflags & PF_FLIPCAM && !(player->powers[pw_carry] == CR_NIGHTSMODE) && player->mo->eflags & MFE_VERTICALFLIP)
 		postimg = postimg_flip;
 	else if (player->awayviewtics)
 	{
@@ -4236,7 +4233,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	}
 	else
 	{
-		if (!(mobj->player->pflags & PF_NIGHTSMODE)) // "jumping" is used for drilling
+		if (!(mobj->player->powers[pw_carry] == CR_NIGHTSMODE)) // "jumping" is used for drilling
 			mobj->player->jumping = 0;
 		mobj->player->pflags &= ~PF_JUMPED;
 		if (mobj->player->secondjump || mobj->player->powers[pw_tailsfly])
@@ -7558,7 +7555,7 @@ void P_MobjThinker(mobj_t *mobj)
 				}
 				else if (mobj->tracer && mobj->tracer->player)
 				{
-					if (!(mobj->tracer->player->pflags & PF_NIGHTSMODE))
+					if (!(mobj->tracer->player->powers[pw_carry] == CR_NIGHTSMODE))
 					{
 						mobj->flags &= ~MF_NOGRAVITY;
 						mobj->flags2 &= ~MF2_DONTDRAW;
@@ -7610,7 +7607,7 @@ void P_MobjThinker(mobj_t *mobj)
 						P_SetTarget(&mobj->target, NULL);
 					}
 
-					if (mobj->tracer->player->pflags & PF_NIGHTSMODE)
+					if (mobj->tracer->player->powers[pw_carry] == CR_NIGHTSMODE)
 					{
 						if (mobj->tracer->player->bonustime)
 						{
diff --git a/src/p_setup.c b/src/p_setup.c
index 6df103255..012caba78 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2774,8 +2774,6 @@ boolean P_SetupLevel(boolean skipprecip)
 	for (i = 0; i < MAXPLAYERS; i++)
 		if (playeringame[i])
 		{
-			players[i].pflags &= ~PF_NIGHTSMODE;
-
 			// Start players with pity shields if possible
 			players[i].pity = -1;
 
diff --git a/src/p_spec.c b/src/p_spec.c
index 577940094..be99a308e 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3530,7 +3530,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 				P_DamageMobj(player->mo, NULL, NULL, 1, 0);
 			break;
 		case 2: // Damage (Water)
-			if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_underwater] || player->pflags & PF_NIGHTSMODE))
+			if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_underwater] || player->powers[pw_carry] == CR_NIGHTSMODE))
 				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_WATER);
 			break;
 		case 3: // Damage (Fire)
@@ -4083,7 +4083,7 @@ DoneSection2:
 				{
 					player->laps++;
 
-					if (player->pflags & PF_NIGHTSMODE)
+					if (player->powers[pw_carry] == CR_NIGHTSMODE)
 						player->drillmeter += 48*20;
 
 					if (player->laps >= (UINT8)cv_numlaps.value)
@@ -7256,7 +7256,7 @@ static inline boolean PIT_PushThing(mobj_t *thing)
 		return false;
 
 	// Allow this to affect pushable objects at some point?
-	if (thing->player && (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) || thing->player->pflags & PF_NIGHTSMODE))
+	if (thing->player && (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) || thing->player->powers[pw_carry] == CR_NIGHTSMODE))
 	{
 		INT32 dist;
 		INT32 speed;
@@ -7287,7 +7287,7 @@ static inline boolean PIT_PushThing(mobj_t *thing)
 		// Written with bits and pieces of P_HomingAttack
 		if ((speed > 0) && (P_CheckSight(thing, tmpusher->source)))
 		{
-			if (!(thing->player->pflags & PF_NIGHTSMODE))
+			if (thing->player->powers[pw_carry] != CR_NIGHTSMODE)
 			{
 				// only push wrt Z if health & 1 (mapthing has ambush flag)
 				if (tmpusher->source->health & 1)
diff --git a/src/p_telept.c b/src/p_telept.c
index 671accb0f..f70c4664c 100644
--- a/src/p_telept.c
+++ b/src/p_telept.c
@@ -163,6 +163,7 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle
 				if (playeringame[p] && players[p].mo && players[p].powers[pw_carry] == CR_PLAYER && players[p].mo->tracer == thing)
 				{
 					players[p].powers[pw_carry] = CR_NONE;
+					P_SetTarget(&players[p].mo->tracer, NULL);
 					break;
 				}
 			thing->player->cmomx = thing->player->cmomy = 0;
diff --git a/src/p_user.c b/src/p_user.c
index 62ad6d4e4..b6d03aec9 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -574,7 +574,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	thinker_t *th;
 	mobj_t *mo2;
 
-	player->pflags &= ~PF_NIGHTSMODE;
+	player->powers[pw_carry] = CR_NONE;
 
 	player->powers[pw_underwater] = 0;
 	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
@@ -609,7 +609,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	{
 		INT32 i;
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+			if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				players[i].nightstime = 1; // force everyone else to fall too.
 		player->exiting = 3*TICRATE;
 		stagefailed = true; // NIGHT OVER
@@ -647,7 +647,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	if (player->bot)
 		return;
 
-	if (!(player->pflags & PF_NIGHTSMODE))
+	if (player->powers[pw_carry] != CR_NIGHTSMODE)
 		player->mo->height = P_GetPlayerHeight(player); // Just to make sure jumping into the drone doesn't result in a squashed hitbox.
 
 	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING|PF_DRILLING);
@@ -697,7 +697,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		if (G_IsSpecialStage(gamemap))
 		{
 			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i]/* && players[i].pflags & PF_NIGHTSMODE*/)
+				if (playeringame[i]/* && players[i].powers[pw_carry] == CR_NIGHTSMODE*/)
 					total_rings += players[i].rings;
 		}
 
@@ -765,7 +765,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			player->texttimer = (UINT8)(110 - timeinmap);
 	}
 
-	player->pflags |= PF_NIGHTSMODE;
+	player->powers[pw_carry] = CR_NIGHTSMODE;
 }
 
 //
@@ -872,7 +872,10 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 void P_ResetPlayer(player_t *player)
 {
 	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_FORCEJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
-	player->powers[pw_carry] = CR_NONE;
+
+	if (player->powers[pw_carry] != CR_NIGHTSMODE)
+		player->powers[pw_carry] = CR_NONE;
+
 	player->jumping = 0;
 	player->secondjump = 0;
 	player->glidetime = 0;
@@ -1001,7 +1004,7 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 		{ // Pseudo-shared score for multiplayer special stages.
 			INT32 i;
 			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+				if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				{
 					oldscore = players[i].marescore;
 
@@ -2244,7 +2247,7 @@ static void P_DoBubbleBreath(player_t *player)
 	fixed_t z = player->mo->z;
 	mobj_t *bubble = NULL;
 
-	if (!(player->mo->eflags & MFE_UNDERWATER) || ((player->powers[pw_shield] & SH_PROTECTWATER) && !(player->pflags & PF_NIGHTSMODE)) || player->spectator)
+	if (!(player->mo->eflags & MFE_UNDERWATER) || ((player->powers[pw_shield] & SH_PROTECTWATER) && !(player->powers[pw_carry] == CR_NIGHTSMODE)) || player->spectator)
 		return;
 
 	if (player->charflags & SF_MACHINE)
@@ -2279,7 +2282,7 @@ static void P_DoBubbleBreath(player_t *player)
 		P_SetScale(bubble, bubble->destscale);
 	}
 
-	if (player->pflags & PF_NIGHTSMODE) // NiGHTS Super doesn't spawn flight bubbles
+	if (player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS Super doesn't spawn flight bubbles
 		return;
 
 	// Tails stirs up the water while flying in it
@@ -3428,15 +3431,9 @@ static void P_DoSuperStuff(player_t *player)
 	|| (player->mo->state == &states[S_PLAY_SUPER_TRANS9] && player->mo->tics > 1))) // needed to prevent one-frame old colour...
 		return; // don't do anything right now, we're in the middle of transforming!
 
-	if (player->pflags & PF_NIGHTSMODE)
+	if (player->powers[pw_carry] == CR_NIGHTSMODE)
 		return; // NiGHTS Super doesn't mix with normal super
 
-	// Does player have all emeralds? If so, flag the "Ready For Super!"
-	if (ALL7EMERALDS(emeralds) && player->rings >= 50)
-		player->pflags |= PF_SUPERREADY;
-	else
-		player->pflags &= ~PF_SUPERREADY;
-
 	if (player->powers[pw_super])
 	{
 		// If you're super and not Sonic, de-superize!
@@ -3537,10 +3534,10 @@ static void P_DoSuperStuff(player_t *player)
 //
 boolean P_SuperReady(player_t *player)
 {
-	if (player->pflags & PF_SUPERREADY && !player->powers[pw_super] && !player->powers[pw_tailsfly]
+	if ((ALL7EMERALDS(emeralds) && player->rings >= 50) && !player->powers[pw_super] && !player->powers[pw_tailsfly]
 	&& !(player->powers[pw_shield] & SH_NOSTACK)
 	&& !player->powers[pw_invulnerability]
-	&& !(maptol & TOL_NIGHTS) // don't turn 'regular super' in nights levels
+	&& !(maptol & TOL_NIGHTS || (player->powers[pw_carry] == CR_NIGHTSMODE)) // don't turn 'regular super' in nights levels
 	&& player->pflags & PF_JUMPED
 	&& player->charflags & SF_SUPER)
 		return true;
@@ -3608,6 +3605,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		{
 			player->mo->momz = 9*FRACUNIT;
 			player->powers[pw_carry] = CR_NONE;
+			P_SetTarget(&player->mo->tracer, NULL);
 			if (player-players == consoleplayer && botingame)
 				CV_SetValue(&cv_analog2, true);
 		}
@@ -3615,6 +3613,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		{
 			player->mo->momz = 9*FRACUNIT;
 			player->powers[pw_carry] = CR_NONE;
+			P_SetTarget(&player->mo->tracer, NULL);
 		}
 		else if (player->powers[pw_carry] == CR_ROPEHANG)
 		{
@@ -4127,6 +4126,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		else if (player->powers[pw_carry] == CR_MACESPIN && player->mo->tracer)
 		{
 			player->powers[pw_carry] = CR_NONE;
+			P_SetTarget(&player->mo->tracer, NULL);
 			player->powers[pw_flashing] = TICRATE/4;
 		}
 		else
@@ -5597,7 +5597,7 @@ static void P_DoNiGHTSCapsule(player_t *player)
 	else if (player->mo->z < player->capsule->z+(player->capsule->height/3))
 		player->mo->momz = 2*FRACUNIT;
 
-	if (player->pflags & PF_NIGHTSMODE)
+	if (player->powers[pw_carry] == CR_NIGHTSMODE)
 	{
 		if (player->mo->momx || player->mo->momy || player->mo->momz)
 		{
@@ -5675,7 +5675,7 @@ static void P_DoNiGHTSCapsule(player_t *player)
 						P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
 					}*/
 
-					if (player->pflags & PF_NIGHTSMODE)
+					if (player->powers[pw_carry] == CR_NIGHTSMODE)
 					{
 						// Only give it to ONE person, and THAT player has to get to the goal!
 						emmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->info->height, MT_GOTEMERALD);
@@ -5689,7 +5689,7 @@ static void P_DoNiGHTSCapsule(player_t *player)
 					// Find the player with the lowest time remaining and award points based on that time instead.
 					lowest_time = player->finishedtime;
 					for (i = 0; i < MAXPLAYERS; i++)
-						if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE)
+						if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 							if (players[i].finishedtime < lowest_time)
 								lowest_time = players[i].finishedtime;
 					P_AddPlayerScore(player, (lowest_time/TICRATE) * 100);
@@ -5762,7 +5762,7 @@ static void P_NiGHTSMovement(player_t *player)
 			player->drillmeter = TICRATE/10;
 	}
 
-	if (!(player->pflags & PF_NIGHTSMODE))
+	if (!(player->powers[pw_carry] == CR_NIGHTSMODE))
 	{
 		P_DeNightserizePlayer(player);
 		return;
@@ -5773,7 +5773,7 @@ static void P_NiGHTSMovement(player_t *player)
 		boolean capsule = false;
 		// NiGHTS special stages have a pseudo-shared timer, so check if ANYONE is feeding the capsule.
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] /*&& players[i].pflags & PF_NIGHTSMODE*/
+			if (playeringame[i] /*&& players[i].powers[pw_carry] == CR_NIGHTSMODE*/
 			&& (players[i].capsule && players[i].capsule->reactiontime))
 				capsule = true;
 		if (!capsule
@@ -6597,7 +6597,7 @@ static void P_MovePlayer(player_t *player)
 	// Locate the capsule for this mare.
 	else if (maptol & TOL_NIGHTS)
 	{
-		if ((player->pflags & PF_NIGHTSMODE)
+		if ((player->powers[pw_carry] == CR_NIGHTSMODE)
 		&& !(player->mo->state >= &states[S_PLAY_NIGHTS_TRANS]
 		&& player->mo->state <= &states[S_PLAY_NIGHTS_TRANS6] // NOT 9 - it's 6 when the player turns their supercolor.
 		&& !(player->exiting)))
@@ -6630,7 +6630,7 @@ static void P_MovePlayer(player_t *player)
 		}
 
 		// Test revamped NiGHTS movement.
-		if (player->pflags & PF_NIGHTSMODE)
+		if (player->powers[pw_carry] == CR_NIGHTSMODE)
 		{
 			P_NiGHTSMovement(player);
 			// No more goto blockchecking, let's just do all that here =D
@@ -8193,9 +8193,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	subsector_t *newsubsec;
 	fixed_t f1, f2;
 
-	cameranoclip = (player->pflags & (PF_NOCLIP|PF_NIGHTSMODE)) || (player->mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!!
+	cameranoclip = (player->powers[pw_carry] == CR_NIGHTSMODE || player->pflags & PF_NOCLIP) || (player->mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!!
 
-	if (!(player->climbing || (player->pflags & PF_NIGHTSMODE) || player->playerstate == PST_DEAD))
+	if (!(player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD))
 	{
 		if (player->spectator) // force cam off for spectators
 			return true;
@@ -8242,7 +8242,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 //	if (leveltime > 0 && timeinmap <= 0)
 //		return true;
 
-	if (player->pflags & PF_NIGHTSMODE)
+	if (player->powers[pw_carry] == CR_NIGHTSMODE)
 	{
 		focusangle = player->mo->angle;
 		focusaiming = 0;
@@ -8299,7 +8299,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		angle = ANGLE_90;
 	else if (camstill || resetcalled || player->playerstate == PST_DEAD)
 		angle = thiscam->angle;
-	else if (player->pflags & PF_NIGHTSMODE) // NiGHTS Level
+	else if (player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS Level
 	{
 		if ((player->pflags & PF_TRANSFERTOCLOSEST) && player->axis1 && player->axis2)
 		{
@@ -8338,7 +8338,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		thiscam->angle = angle;
 	}
 
-	if (!objectplacing && !(twodlevel || (mo->flags2 & MF2_TWOD)) && !(player->pflags & PF_NIGHTSMODE) && displayplayer == consoleplayer)
+	if (!objectplacing && !(twodlevel || (mo->flags2 & MF2_TWOD)) && (player->powers[pw_carry] != CR_NIGHTSMODE) && displayplayer == consoleplayer)
 	{
 #ifdef REDSANALOG
 		if ((player->cmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)); else
@@ -8384,13 +8384,13 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	// sets ideal cam pos
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
 		dist = 480<<FRACBITS;
-	else if (player->pflags & PF_NIGHTSMODE)
+	else if (player->powers[pw_carry] == CR_NIGHTSMODE)
 		dist = 320<<FRACBITS;
 	else
 	{
 		dist = camdist;
 
-		if (player->climbing || player->exiting || player->playerstate == PST_DEAD || (player->powers[pw_carry] && player->powers[pw_carry] != CR_PLAYER))
+		if (player->climbing || player->exiting || player->playerstate == PST_DEAD || (player->powers[pw_carry] == CR_ROPEHANG || player->powers[pw_carry] == CR_GENERIC || player->powers[pw_carry] == CR_MACESPIN))
 			dist <<= 1;
 	}
 
@@ -8732,7 +8732,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	else
 		player->mo->flags2 &= ~MF2_SHADOW;
 
-/*	if (!resetcalled && (player->pflags & PF_NIGHTSMODE && player->exiting))
+/*	if (!resetcalled && (player->powers[pw_carry] == CR_NIGHTSMODE && player->exiting))
 	{
 		// Don't let the camera match your movement.
 		thiscam->momz = 0;
@@ -9099,7 +9099,7 @@ void P_PlayerThink(player_t *player)
 
 			player->pflags |= PF_TIMEOVER;
 
-			if (player->pflags & PF_NIGHTSMODE)
+			if (player->powers[pw_carry] == CR_NIGHTSMODE)
 			{
 				P_DeNightserizePlayer(player);
 				S_StartScreamSound(player->mo, sfx_s3k66);
@@ -9225,7 +9225,7 @@ void P_PlayerThink(player_t *player)
 
 	// Even if not NiGHTS, pull in nearby objects when walking around as John Q. Elliot.
 	if (!objectplacing && !((netgame || multiplayer) && player->spectator)
-	&& maptol & TOL_NIGHTS && (!(player->pflags & PF_NIGHTSMODE) || player->powers[pw_nights_helper]))
+	&& maptol & TOL_NIGHTS && (player->powers[pw_carry] != CR_NIGHTSMODE || player->powers[pw_nights_helper]))
 	{
 		thinker_t *th;
 		mobj_t *mo2;
@@ -9335,7 +9335,7 @@ void P_PlayerThink(player_t *player)
 #endif
 
 	// check for use
-	if (!(player->pflags & PF_NIGHTSMODE))
+	if (player->powers[pw_carry] != CR_NIGHTSMODE)
 	{
 		if (cmd->buttons & BT_USE)
 			player->pflags |= PF_USEDOWN;
@@ -9353,7 +9353,7 @@ void P_PlayerThink(player_t *player)
 	if (player->powers[pw_invulnerability] && player->powers[pw_invulnerability] < UINT16_MAX)
 		player->powers[pw_invulnerability]--;
 
-	if (player->powers[pw_flashing] && player->powers[pw_flashing] < UINT16_MAX && ((player->pflags & PF_NIGHTSMODE) || player->powers[pw_flashing] < flashingtics))
+	if (player->powers[pw_flashing] && player->powers[pw_flashing] < UINT16_MAX && ((player->powers[pw_carry] == CR_NIGHTSMODE) || player->powers[pw_flashing] < flashingtics))
 		player->powers[pw_flashing]--;
 
 	if (player->powers[pw_tailsfly] && player->powers[pw_tailsfly] < UINT16_MAX && player->charability != CA_SWIM) // tails fly counter
@@ -9571,7 +9571,7 @@ void P_PlayerAfterThink(player_t *player)
 		return;
 	}
 
-	if (player->pflags & PF_NIGHTSMODE)
+	if (player->powers[pw_carry] == CR_NIGHTSMODE)
 	{
 		player->powers[pw_gravityboots] = 0;
 		//player->mo->eflags &= ~MFE_VERTICALFLIP;
@@ -9702,7 +9702,10 @@ void P_PlayerAfterThink(player_t *player)
 		player->mo->height = FixedDiv(P_GetPlayerHeight(player), FixedDiv(14*FRACUNIT,10*FRACUNIT));
 
 		if (player->mo->tracer->player && !(player->mo->tracer->player->pflags & PF_CANCARRY))
-				player->powers[pw_carry] = CR_NONE;
+		{
+			player->powers[pw_carry] = CR_NONE;
+			P_SetTarget(&player->mo->tracer, NULL);
+		}
 
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 		{
@@ -9710,7 +9713,10 @@ void P_PlayerAfterThink(player_t *player)
 				&& (player->mo->tracer->eflags & MFE_VERTICALFLIP)) // Reverse gravity check for the carrier - Flame
 				player->mo->z = player->mo->tracer->z + player->mo->tracer->height + FixedMul(FRACUNIT, player->mo->scale);
 			else
+			{
 				player->powers[pw_carry] = CR_NONE;
+				P_SetTarget(&player->mo->tracer, NULL);
+			}
 		}
 		else
 		{
@@ -9718,7 +9724,10 @@ void P_PlayerAfterThink(player_t *player)
 				&& !(player->mo->tracer->eflags & MFE_VERTICALFLIP)) // Correct gravity check for the carrier - Flame
 				player->mo->z = player->mo->tracer->z - player->mo->height - FixedMul(FRACUNIT, player->mo->scale);
 			else
+			{
 				player->powers[pw_carry] = CR_NONE;
+				P_SetTarget(&player->mo->tracer, NULL);
+			}
 		}
 
 		if (player->mo->tracer->health <= 0)
@@ -9745,7 +9754,10 @@ void P_PlayerAfterThink(player_t *player)
 		}
 
 		if (P_AproxDistance(player->mo->x - player->mo->tracer->x, player->mo->y - player->mo->tracer->y) > player->mo->radius)
+		{
 			player->powers[pw_carry] = CR_NONE;
+			P_SetTarget(&player->mo->tracer, NULL);
+		}
 
 		P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
 
diff --git a/src/r_main.c b/src/r_main.c
index 4cff0ff83..f970762f4 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1000,7 +1000,7 @@ void R_SetupFrame(player_t *player, boolean skybox)
 		chasecam = (cv_chasecam.value != 0);
 	}
 
-	if (player->climbing || (player->pflags & PF_NIGHTSMODE) || player->playerstate == PST_DEAD)
+	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD)
 		chasecam = true; // force chasecam on
 	else if (player->spectator) // no spectator chasecam
 		chasecam = false; // force chasecam off
diff --git a/src/r_things.c b/src/r_things.c
index 1a7dc2ecb..a4898170d 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2647,7 +2647,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 
 		if (player->mo)
 		{
-			if ((player->pflags & PF_NIGHTSMODE) && (skin->sprites[SPR2_NGT0].numframes == 0)) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin.
+			if ((player->powers[pw_carry] == CR_NIGHTSMODE) && (skin->sprites[SPR2_NGT0].numframes == 0)) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin.
 			{
 				skin = &skins[DEFAULTNIGHTSSKIN];
 				newcolor = ((skin->flags & SF_SUPER) ? skin->supercolor : skin->prefcolor);
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 49f5ea2a5..12891f697 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -714,7 +714,7 @@ static void ST_drawLives(void)
 		// skincolor face/super
 		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->mo->color, GTC_CACHE);
 		patch_t *face = faceprefix[stplyr->skin];
-		if ((stplyr->powers[pw_super] && (stplyr->mo->state < &states[S_PLAY_SUPER_TRANS] || stplyr->mo->state > &states[S_PLAY_SUPER_TRANS9])) || stplyr->pflags & PF_NIGHTSMODE)
+		if ((stplyr->powers[pw_super] && (stplyr->mo->state < &states[S_PLAY_SUPER_TRANS] || stplyr->mo->state > &states[S_PLAY_SUPER_TRANS9])) || (stplyr->powers[pw_carry] == CR_NIGHTSMODE && skins[stplyr->skin].flags & SF_SUPER))
 			face = superprefix[stplyr->skin];
 		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
 			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,face, colormap);
@@ -1077,7 +1077,7 @@ static void ST_drawNiGHTSHUD(void)
 #ifdef HAVE_BLUA
 	LUA_HudEnabled(hud_nightsdrill) &&
 #endif
-	stplyr->pflags & PF_NIGHTSMODE)
+	stplyr->powers[pw_carry] == CR_NIGHTSMODE)
 	{
 		INT32 locx, locy;
 		INT32 dfill;
@@ -1163,7 +1163,7 @@ static void ST_drawNiGHTSHUD(void)
 		INT32 i;
 		total_ringcount = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] /*&& players[i].pflags & PF_NIGHTSMODE*/ && players[i].rings)
+			if (playeringame[i] /*&& players[i].powers[pw_carry] == CR_NIGHTSMODE*/ && players[i].rings)
 				total_ringcount += players[i].rings;
 	}
 	else
@@ -1296,7 +1296,7 @@ static void ST_drawNiGHTSHUD(void)
 			tic_t lowest_time = stplyr->nightstime;
 			INT32 i;
 			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE && players[i].nightstime < lowest_time)
+				if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE && players[i].nightstime < lowest_time)
 					lowest_time = players[i].nightstime;
 			realnightstime = lowest_time/TICRATE;
 		}

From b53e69b949c0d5e2be6038906ccee07dd2a011ae Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 15 Mar 2017 20:42:01 +0000
Subject: [PATCH 055/119] Corrected a silly oversight. No, you can't swap your
 carry stuff when recycling!

---
 src/p_enemy.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_enemy.c b/src/p_enemy.c
index e11c67686..bbb89338f 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -5952,7 +5952,7 @@ void A_RecyclePowers(mobj_t *actor)
 
 		for (j = 0; j < NUMPOWERS; j++)
 		{
-			if (j == pw_flashing || j == pw_underwater || j == pw_spacetime
+			if (j == pw_flashing || j == pw_underwater || j == pw_spacetime || j == pw_carry
 			    || j == pw_tailsfly || j == pw_extralife || j == pw_nocontrol || j == pw_super)
 				continue;
 			players[recv_pl].powers[j] = powers[send_pl][j];

From 95e4a23a6963f8f782be303951ab540b3755c833 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 15 Mar 2017 21:51:35 +0000
Subject: [PATCH 056/119] pw_ingoop is now CR_BRAKGOOP because i can't stop
 refactoring CAN'T STOP WON'T STOP

---
 src/d_player.h |  6 +++---
 src/dehacked.c |  4 ++--
 src/p_inter.c  | 16 +++++++---------
 src/p_mobj.c   |  4 ++--
 src/p_user.c   | 25 ++++++++++++++++++-------
 src/st_stuff.c |  2 +-
 6 files changed, 33 insertions(+), 24 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index c35c29903..f316ec251 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -225,6 +225,8 @@ typedef enum
 	CR_PLAYER,
 	// NiGHTS mode. Not technically a CARRYING, but doesn't stack with any of the others, so might as well go here.
 	CR_NIGHTSMODE,
+	// Old Brak sucks hard, but this gimmick could be used for something better, so we might as well continue supporting it.
+	CR_BRAKGOOP,
 	// Specific level gimmicks.
 	CR_ZOOMTUBE,
 	CR_ROPEHANG,
@@ -264,9 +266,7 @@ typedef enum
 	pw_nights_helper,
 	pw_nights_linkfreeze,
 
-	//for linedef exec 427
-	pw_nocontrol,
-	pw_ingoop, // In goop
+	pw_nocontrol, //for linedef exec 427
 
 	NUMPOWERS
 } powertype_t;
diff --git a/src/dehacked.c b/src/dehacked.c
index 38aaa72d9..f8d9d5c66 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -7129,8 +7129,7 @@ static const char *const POWERS_LIST[] = {
 	"NIGHTS_LINKFREEZE",
 
 	//for linedef exec 427
-	"NOCONTROL",
-	"INGOOP" // In goop
+	"NOCONTROL"
 };
 
 static const char *const HUDITEMS_LIST[] = {
@@ -7355,6 +7354,7 @@ struct {
 	{"CR_GENERIC",CR_GENERIC},
 	{"CR_PLAYER",CR_PLAYER},
 	{"CR_NIGHTSMODE",CR_NIGHTSMODE},
+	{"CR_BRAKGOOP",CR_BRAKGOOP},
 	{"CR_ZOOMTUBE",CR_ZOOMTUBE},
 	{"CR_ROPEHANG",CR_ROPEHANG},
 	{"CR_MACESPIN",CR_MACESPIN},
diff --git a/src/p_inter.c b/src/p_inter.c
index 661a3e35d..4d07c21f1 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1355,7 +1355,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			return;
 
 		case MT_BLACKEGGMAN_GOOPFIRE:
-			if (toucher->state != &states[S_PLAY_PAIN] && !player->powers[pw_flashing])
+			if (!player->powers[pw_flashing])
 			{
 				toucher->momx = 0;
 				toucher->momy = 0;
@@ -1363,13 +1363,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (toucher->momz != 0)
 					special->momz = toucher->momz;
 
-				player->powers[pw_ingoop] = 2;
-
-				if (player->powers[pw_carry] == CR_GENERIC)
-				{
-					P_SetTarget(&toucher->tracer, NULL);
-					player->powers[pw_carry] = CR_NONE;
-				}
+				player->powers[pw_carry] = CR_BRAKGOOP;
+				P_SetTarget(&toucher->tracer, special);
 
 				P_ResetPlayer(player);
 
@@ -1382,7 +1377,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				}
 			}
 			else
-				player->powers[pw_ingoop] = 0;
+			{
+				player->powers[pw_carry] = CR_NONE;
+				P_SetTarget(&toucher->tracer, NULL);
+			}
 			return;
 		case MT_EGGSHIELD:
 			{
diff --git a/src/p_mobj.c b/src/p_mobj.c
index b40ff5930..de6a604a1 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -5446,10 +5446,10 @@ static void P_Boss7Thinker(mobj_t *mobj)
 			if (players[i].mo->health <= 0)
 				continue;
 
-			if (players[i].powers[pw_ingoop])
+			if (players[i].powers[pw_carry] == CR_BRAKGOOP)
 			{
 				closestNum = -1;
-				closestdist = 16384*FRACUNIT; // Just in case...
+				closestdist = INT32_MAX; // Just in case...
 
 				// Find waypoint he is closest to
 				for (th = thinkercap.next; th != &thinkercap; th = th->next)
diff --git a/src/p_user.c b/src/p_user.c
index b6d03aec9..1f64238d6 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -873,7 +873,7 @@ void P_ResetPlayer(player_t *player)
 {
 	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_FORCEJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
 
-	if (player->powers[pw_carry] != CR_NIGHTSMODE)
+	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_BRAKGOOP))
 		player->powers[pw_carry] = CR_NONE;
 
 	player->jumping = 0;
@@ -3857,7 +3857,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 	if (onground && !(player->pflags & PF_USEDOWN) && (player->pflags & PF_STARTDASH) && (player->pflags & PF_SPINNING))
 	{
 		player->pflags &= ~PF_STARTDASH;
-		if (player->powers[pw_ingoop])
+		if (player->powers[pw_carry] == CR_BRAKGOOP)
 			player->dashspeed = 0;
 
 		if (!((gametype == GT_RACE || gametype == GT_COMPETITION) && leveltime < 4*TICRATE))
@@ -6544,7 +6544,7 @@ static void P_MovePlayer(player_t *player)
 	runspd = FixedMul(runspd, player->mo->movefactor);
 
 	// Control relinquishing stuff!
-	if (player->powers[pw_ingoop])
+	if (player->powers[pw_carry] == CR_BRAKGOOP)
 		player->pflags |= PF_FULLSTASIS;
 	else if (player->pflags & PF_GLIDING && player->skidtime)
 		player->pflags |= PF_FULLSTASIS;
@@ -9398,12 +9398,20 @@ void P_PlayerThink(player_t *player)
 	if (player->powers[pw_super])
 		player->powers[pw_super]++;
 
-	if (player->powers[pw_ingoop])
+	if (player->powers[pw_carry] == CR_BRAKGOOP)
 	{
-		if (player->mo->state == &states[S_PLAY_STND])
-			player->mo->tics = 2;
+		if (!player->powers[pw_flashing])
+		{
+			if (player->mo->state != &states[S_PLAY_STND])
+				P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			else
+				player->mo->tics = 2;
+		}
+		else
+			P_SetTarget(&player->mo->tracer, NULL);
 
-		player->powers[pw_ingoop]--;
+		if (!player->mo->tracer)
+			player->powers[pw_carry] = CR_NONE;
 	}
 
 	if (player->bumpertime)
@@ -9697,6 +9705,9 @@ void P_PlayerAfterThink(player_t *player)
 	&& !(player->charflags & SF_NOJUMPSPIN))
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 
+	/* if (player->powers[pw_carry] == CR_NONE && player->mo->tracer && !player->homing)
+		P_SetTarget(&player->mo->tracer, NULL);
+	else */
 	if (player->powers[pw_carry] == CR_PLAYER && player->mo->tracer)
 	{
 		player->mo->height = FixedDiv(P_GetPlayerHeight(player), FixedDiv(14*FRACUNIT,10*FRACUNIT));
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 12891f697..72ee20d99 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -587,7 +587,7 @@ static void ST_drawDebugInfo(void)
 		V_DrawString(304-72,             height - 72, (stplyr->pflags & PF_JUMPED) ? V_GREENMAP : V_REDMAP, "JD");
 		V_DrawString(304-54,             height - 72, (stplyr->pflags & PF_SPINNING) ? V_GREENMAP : V_REDMAP, "SP");
 		V_DrawString(304-36,             height - 72, (stplyr->pflags & PF_STARTDASH) ? V_GREENMAP : V_REDMAP, "ST");
-		V_DrawString(304-18,                height - 72, (stplyr->pflags & PF_THOKKED) ? V_GREENMAP : V_REDMAP, "TH");
+		V_DrawString(304-18,             height - 72, (stplyr->pflags & PF_THOKKED) ? V_GREENMAP : V_REDMAP, "TH");
 		V_DrawString(304,                height - 72, (stplyr->pflags & PF_SHIELDABILITY) ? V_GREENMAP : V_REDMAP, "SH");
 
 		V_DrawRightAlignedString(320, height - 64, V_MONOSPACE, va("CEILZ: %6d", stplyr->mo->ceilingz>>FRACBITS));

From a65ffc6b24ee2c5db20db5f5990fa9385a10dccf Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 16 Mar 2017 11:23:23 +0000
Subject: [PATCH 057/119] * Cleaned up a bit of irrelevant oldbrak goop. * Made
 it so the goop always appears on-top of the player.

---
 src/info.c    | 2 +-
 src/p_inter.c | 5 -----
 2 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/src/info.c b/src/info.c
index 2374f65fc..932ef4a58 100644
--- a/src/info.c
+++ b/src/info.c
@@ -4562,7 +4562,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		30*FRACUNIT,    // speed
 		11*FRACUNIT,    // radius
 		8*FRACUNIT,     // height
-		0,              // display offset
+		100,            // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
diff --git a/src/p_inter.c b/src/p_inter.c
index 4d07c21f1..ae56679a8 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1376,11 +1376,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						P_SetMobjState(special->target, special->target->info->raisestate);
 				}
 			}
-			else
-			{
-				player->powers[pw_carry] = CR_NONE;
-				P_SetTarget(&toucher->tracer, NULL);
-			}
 			return;
 		case MT_EGGSHIELD:
 			{

From 60a2e26569dff28dd781e0381123fa6eb3e25bb2 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 16 Mar 2017 18:57:42 +0000
Subject: [PATCH 058/119] First attempt at making skin availabilities
 netgame-synchronised. However, I am completely unable to test this right now,
 since I'm on uni internet which is super locked down; in order to have it not
 get tangled up with anything else, I'm committing it now in a potentially
 broken state so that it can be reverted at a later date if necessary.

---
 src/command.c     |  4 ++--
 src/d_clisrv.c    |  2 ++
 src/d_clisrv.h    |  1 +
 src/d_netcmd.c    | 14 ++++++++++----
 src/d_player.h    |  1 +
 src/doomdef.h     |  2 +-
 src/g_game.c      |  3 +++
 src/lua_mobjlib.c |  2 ++
 src/m_menu.c      |  8 ++++----
 src/r_things.c    | 22 +++++++++++++++++-----
 src/r_things.h    |  6 +++---
 11 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/src/command.c b/src/command.c
index 6534fed3d..00749ed8d 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1165,7 +1165,7 @@ found:
 		if (var == &cv_forceskin)
 		{
 			var->value = R_SkinAvailable(var->string);
-			if (!R_SkinUnlock(var->value))
+			if (!R_SkinUnlock(-1, var->value))
 				var->value = -1;
 		}
 		else
@@ -1478,7 +1478,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 			else if (newvalue >= numskins)
 				newvalue = -1;
 		} while ((oldvalue != newvalue)
-				&& !(R_SkinUnlock(newvalue)));
+				&& !(R_SkinUnlock(-1, newvalue)));
 	}
 	else
 		newvalue = var->value + increment;
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index f80011efb..e2bee7e0f 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -528,6 +528,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 
 	rsp->skincolor = players[i].skincolor;
 	rsp->skin = LONG(players[i].skin);
+	rsp->availabilities = players[i].availabilities;
 	// Just in case Lua does something like
 	// modify these at runtime
 	rsp->camerascale = (fixed_t)LONG(players[i].camerascale);
@@ -657,6 +658,7 @@ static void resynch_read_player(resynch_pak *rsp)
 
 	players[i].skincolor = rsp->skincolor;
 	players[i].skin = LONG(rsp->skin);
+	players[i].availabilities = rsp->availabilities;
 	// Just in case Lua does something like
 	// modify these at runtime
 	players[i].camerascale = (fixed_t)LONG(rsp->camerascale);
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 382329539..8f0aedbf3 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -172,6 +172,7 @@ typedef struct
 
 	UINT8 skincolor;
 	INT32 skin;
+	UINT32 availabilities;
 	// Just in case Lua does something like
 	// modify these at runtime
 	fixed_t camerascale;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 55a3b30f9..7fcbac092 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1109,6 +1109,8 @@ static void SendNameAndColor(void)
 	if (!Playing())
 		return;
 
+	players[consoleplayer].availabilities = R_GetSkinAvailabilities();
+
 	// If you're not in a netgame, merely update the skin, color, and name.
 	if (!netgame)
 	{
@@ -1127,7 +1129,7 @@ static void SendNameAndColor(void)
 			SetPlayerSkinByNum(consoleplayer, 0);
 			CV_StealthSet(&cv_skin, skins[0].name);
 		}
-		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUnlock(foundskin))
+		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUnlock(consoleplayer, foundskin))
 		{
 			boolean notsame;
 
@@ -1174,7 +1176,7 @@ static void SendNameAndColor(void)
 	// check if player has the skin loaded (cv_skin may have
 	// the name of a skin that was available in the previous game)
 	cv_skin.value = R_SkinAvailable(cv_skin.string);
-	if ((cv_skin.value < 0) || !R_SkinUnlock(cv_skin.value))
+	if ((cv_skin.value < 0) || !R_SkinUnlock(consoleplayer, cv_skin.value))
 	{
 		CV_StealthSet(&cv_skin, DEFAULTSKIN);
 		cv_skin.value = 0;
@@ -1182,6 +1184,7 @@ static void SendNameAndColor(void)
 
 	// Finally write out the complete packet and send it off.
 	WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME);
+	WRITEUINT32(p, (UINT32)players[consoleplayer].availabilities);
 	WRITEUINT8(p, (UINT8)cv_playercolor.value);
 	WRITEUINT8(p, (UINT8)cv_skin.value);
 	SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf);
@@ -1224,6 +1227,8 @@ static void SendNameAndColor2(void)
 	if (!Playing())
 		return;
 
+	players[secondplaya].availabilities = R_GetSkinAvailabilities();
+
 	// If you're not in a netgame, merely update the skin, color, and name.
 	if (botingame)
 	{
@@ -1252,7 +1257,7 @@ static void SendNameAndColor2(void)
 			SetPlayerSkinByNum(secondplaya, forcedskin);
 			CV_StealthSet(&cv_skin2, skins[forcedskin].name);
 		}
-		else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUnlock(foundskin))
+		else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUnlock(secondplaya, foundskin))
 		{
 			boolean notsame;
 
@@ -1307,6 +1312,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 #endif
 
 	READSTRINGN(*cp, name, MAXPLAYERNAME);
+	p->availabilities = READUINT32(*cp);
 	color = READUINT8(*cp);
 	skin = READUINT8(*cp);
 
@@ -4042,7 +4048,7 @@ static void Command_Archivetest_f(void)
   */
 static void ForceSkin_OnChange(void)
 {
-	if ((server || adminplayer == consoleplayer) && ((cv_forceskin.value == -1 && stricmp(cv_forceskin.string, "None")) || !(R_SkinUnlock(cv_forceskin.value))))
+	if ((server || adminplayer == consoleplayer) && ((cv_forceskin.value == -1 && stricmp(cv_forceskin.string, "None")) || !(R_SkinUnlock(-1, cv_forceskin.value))))
 	{
 		CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
 		CV_SetValue(&cv_forceskin, -1);
diff --git a/src/d_player.h b/src/d_player.h
index f316ec251..8ff591a96 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -342,6 +342,7 @@ typedef struct player_s
 	UINT8 skincolor;
 
 	INT32 skin;
+	UINT32 availabilities;
 
 	UINT32 score; // player score
 	fixed_t dashspeed; // dashing speed
diff --git a/src/doomdef.h b/src/doomdef.h
index a35c17ba6..ed197e20c 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -222,7 +222,7 @@ extern FILE *logstream;
 // NOTE: it needs more than this to increase the number of players...
 
 #define MAXPLAYERS 32
-#define MAXSKINS MAXPLAYERS
+#define MAXSKINS 32
 #define PLAYERSMASK (MAXPLAYERS-1)
 #define MAXPLAYERNAME 21
 
diff --git a/src/g_game.c b/src/g_game.c
index b20d91f9f..3839e0448 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2080,6 +2080,7 @@ void G_PlayerReborn(INT32 player)
 	UINT8 mare;
 	UINT8 skincolor;
 	INT32 skin;
+	UINT32 availabilities;
 	tic_t jointime;
 	boolean spectator;
 	INT16 bot;
@@ -2104,6 +2105,7 @@ void G_PlayerReborn(INT32 player)
 
 	skincolor = players[player].skincolor;
 	skin = players[player].skin;
+	availabilities = players[player].availabilities;
 	camerascale = players[player].camerascale;
 	shieldscale = players[player].shieldscale;
 	charability = players[player].charability;
@@ -2149,6 +2151,7 @@ void G_PlayerReborn(INT32 player)
 	// save player config truth reborn
 	p->skincolor = skincolor;
 	p->skin = skin;
+	p->availabilities = availabilities;
 	p->camerascale = camerascale;
 	p->shieldscale = shieldscale;
 	p->charability = charability;
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 93febf209..0935f56de 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -511,6 +511,8 @@ static int mobj_set(lua_State *L)
 		for (i = 0; i < numskins; i++)
 			if (fastcmp(skins[i].name, skin))
 			{
+				if (mo->player && !R_SkinUnlock(mo->player-players, i))
+					return luaL_error(L, "mobj.skin '%s' not found!", skin);
 				mo->skin = &skins[i];
 				return 0;
 			}
diff --git a/src/m_menu.c b/src/m_menu.c
index f682cd1b5..f7025693f 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3417,7 +3417,7 @@ static void M_PatchSkinNameTable(void)
 
 	for (j = 0; j < MAXSKINS; j++)
 	{
-		if (skins[j].name[0] != '\0' && R_SkinUnlock(j))
+		if (skins[j].name[0] != '\0' && R_SkinUnlock(-1, j))
 		{
 			skins_cons_t[j].strvalue = skins[j].realname;
 			skins_cons_t[j].value = j+1;
@@ -4780,7 +4780,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 		{
 			name = strtok(Z_StrDup(description[i].skinname), "&");
 			skinnum = R_SkinAvailable(name);
-			if ((skinnum != -1) && (R_SkinUnlock(skinnum)))
+			if ((skinnum != -1) && (R_SkinUnlock(-1, skinnum)))
 			{
 				// Handling order.
 				if (firstvalid == 255)
@@ -6525,7 +6525,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 					if (setupm_fakeskin < 0)
 						setupm_fakeskin = numskins-1;
 				}
-				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(setupm_fakeskin)));
+				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(-1, setupm_fakeskin)));
 			}
 			else if (itemOn == 1) // player color
 			{
@@ -6545,7 +6545,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 					if (setupm_fakeskin > numskins-1)
 						setupm_fakeskin = 0;
 				}
-				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(setupm_fakeskin)));
+				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(-1, setupm_fakeskin)));
 			}
 			else if (itemOn == 1) // player color
 			{
diff --git a/src/r_things.c b/src/r_things.c
index 089eddba8..03125f921 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2554,13 +2554,26 @@ void R_InitSkins(void)
 	numskins = 0;
 }
 
+UINT32 R_GetSkinAvailabilities(void)
+{
+	INT32 s;
+	UINT32 response = 0;
+
+	for (s = 0; s < MAXSKINS; s++)
+	{
+		if (!skins[s].availability || unlockables[skins[s].availability - 1].unlocked)
+			response |= (1 << s);
+	}
+	return response;
+}
+
 // returns true if available in circumstances, otherwise nope
 // warning don't use with an invalid skinnum other than -1 which always returns true
-boolean R_SkinUnlock(INT32 skinnum)
+boolean R_SkinUnlock(INT32 playernum, INT32 skinnum)
 {
 	return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
 		|| (!skins[skinnum].availability)
-		|| (unlockables[skins[skinnum].availability - 1].unlocked)
+		|| ((playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
 		|| (modeattacking) // If you have someone else's run you might as well take a look
 		|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1.
 		|| (netgame && !(server || adminplayer == consoleplayer) && (cv_forceskin.value == skinnum)) // Force 2.
@@ -2588,7 +2601,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
 	INT32 i = R_SkinAvailable(skinname);
 	player_t *player = &players[playernum];
 
-	if ((i != -1) && (!P_IsLocalPlayer(player) || R_SkinUnlock(i)))
+	if ((i != -1) && R_SkinUnlock(playernum, i))
 	{
 		SetPlayerSkinByNum(playernum, i);
 		return;
@@ -2610,8 +2623,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 	skin_t *skin = &skins[skinnum];
 	UINT8 newcolor = 0;
 
-	if ((skinnum >= 0 && skinnum < numskins) // Make sure it exists!
-	&& (!P_IsLocalPlayer(player) || R_SkinUnlock(skinnum))) // ...but is it allowed? We must always allow external players to change skin. The server should vet that...
+	if (skinnum >= 0 && skinnum < numskins && R_SkinUnlock(playernum, skinnum)) // Make sure it exists!
 	{
 		player->skin = skinnum;
 
diff --git a/src/r_things.h b/src/r_things.h
index 5684f8a89..123ab2280 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -29,8 +29,6 @@
 #define VISSPRITESPERCHUNK (1 << VISSPRITECHUNKBITS)
 #define VISSPRITEINDEXMASK (VISSPRITESPERCHUNK - 1)
 
-#define DEFAULTNIGHTSSKIN 0
-
 // Constant arrays used for psprite clipping
 //  and initializing clipping.
 extern INT16 negonearray[MAXVIDWIDTH];
@@ -71,6 +69,7 @@ void R_DrawMasked(void);
 // should be all lowercase!! S_SKIN processing does a strlwr
 #define DEFAULTSKIN "sonic"
 #define DEFAULTSKIN2 "tails" // secondary player
+#define DEFAULTNIGHTSSKIN 0
 
 typedef struct
 {
@@ -204,7 +203,8 @@ extern skin_t skins[MAXSKINS + 1];
 
 void SetPlayerSkin(INT32 playernum,const char *skinname);
 void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
-boolean R_SkinUnlock(INT32 skinnum);
+boolean R_SkinUnlock(INT32 playernum, INT32 skinnum);
+UINT32 R_GetSkinAvailabilities(void);
 INT32 R_SkinAvailable(const char *name);
 void R_AddSkins(UINT16 wadnum);
 

From 3fdb8a31811fe003391a3f428d716968f620bde3 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 16 Mar 2017 20:55:41 +0000
Subject: [PATCH 059/119] this video game is haunted because it was built on an
 abandoned endian preservation

---
 src/d_clisrv.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index e2bee7e0f..000816450 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -528,7 +528,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 
 	rsp->skincolor = players[i].skincolor;
 	rsp->skin = LONG(players[i].skin);
-	rsp->availabilities = players[i].availabilities;
+	rsp->availabilities = LONG(players[i].availabilities);
 	// Just in case Lua does something like
 	// modify these at runtime
 	rsp->camerascale = (fixed_t)LONG(players[i].camerascale);
@@ -658,7 +658,7 @@ static void resynch_read_player(resynch_pak *rsp)
 
 	players[i].skincolor = rsp->skincolor;
 	players[i].skin = LONG(rsp->skin);
-	players[i].availabilities = rsp->availabilities;
+	players[i].availabilities = LONG(rsp->availabilities);
 	// Just in case Lua does something like
 	// modify these at runtime
 	players[i].camerascale = (fixed_t)LONG(rsp->camerascale);

From 6fa319041a0bd1671e8ca53393b1067fc839a821 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 17 Mar 2017 14:27:17 +0000
Subject: [PATCH 060/119] A serious defuckening of my fancy character select
 code.

---
 src/dehacked.c |  26 ++---
 src/m_menu.c   | 293 ++++++++++++++++++++++++-------------------------
 src/m_menu.h   |   4 +-
 3 files changed, 156 insertions(+), 167 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index f8d9d5c66..e2a833d68 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -432,22 +432,20 @@ static void readAnimTex(MYFILE *f, INT32 num)
 }
 */
 
-static boolean findFreeSlot(INT32 *num, UINT16 wadnum)
+static boolean findFreeSlot(INT32 *num)
 {
 	// Send the character select entry to a free slot.
-	while (*num < 32 && (!(PlayerMenu[*num].status & IT_DISABLED) || description[*num].wadnum == wadnum)) // Will kill hidden characters from other files, but that's okay.
+	while (*num < 32 && (description[*num].used))
 		*num = *num+1;
 
 	// No more free slots. :(
 	if (*num >= 32)
 		return false;
 
-	PlayerMenu[*num].status = IT_CALL;
-	description[*num].wadnum = wadnum;
 	description[*num].picname[0] = '\0'; // Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
 
 	// Found one! ^_^
-	return true;
+	return (description[*num].used = true);
 }
 
 // Reads a player.
@@ -479,7 +477,7 @@ static void readPlayer(MYFILE *f, INT32 num)
 			{
 				char *playertext = NULL;
 
-				if (!slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
 					goto done;
 
 				for (i = 0; i < MAXLINELEN-3; i++)
@@ -528,7 +526,7 @@ static void readPlayer(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "PICNAME"))
 			{
-				if (!slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
 					goto done;
 				DEH_WriteUndoline(word, &description[num].picname[0], UNDO_NONE);
 
@@ -536,12 +534,6 @@ static void readPlayer(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "STATUS"))
 			{
-				// Limit the status to only IT_DISABLED and IT_CALL
-				if (i)
-					i = IT_CALL;
-				else
-					i = IT_DISABLED;
-
 				/*
 					You MAY disable previous entries if you so desire...
 					But try to enable something that's already enabled and you will be sent to a free slot.
@@ -549,15 +541,15 @@ static void readPlayer(MYFILE *f, INT32 num)
 					Because of this, you are allowed to edit any previous entries you like, but only if you
 					signal that you are purposely doing so by disabling and then reenabling the slot.
 				*/
-				if (i != IT_DISABLED && !slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+				if (i && !slotfound && (slotfound = findFreeSlot(&num)) == false)
 					goto done;
-				DEH_WriteUndoline(word, va("%d", PlayerMenu[num].status), UNDO_NONE);
-				PlayerMenu[num].status = (INT16)i;
+				DEH_WriteUndoline(word, va("%d", description[num].used), UNDO_NONE);
+				description[num].used = (!!i);
 			}
 			else if (fastcmp(word, "SKINNAME"))
 			{
 				// Send to free slot.
-				if (!slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
 					goto done;
 				DEH_WriteUndoline(word, description[num].skinname, UNDO_NONE);
 
diff --git a/src/m_menu.c b/src/m_menu.c
index f7025693f..28ea324c1 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -108,41 +108,45 @@ typedef enum
 const char *quitmsg[NUM_QUITMESSAGES];
 
 // Stuff for customizing the player select screen Tails 09-22-2003
+// A rare case.
+// External files modify this menu, so we can't call it static.
+// And I'm too lazy to go through and rename it everywhere. ARRGH!
 description_t description[32] =
 {
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0},
-	{"???", "", "", 0, 0, 0}
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0},
+	{false, "???", "", "", 0, 0}
 };
+static INT16 char_on = 0;
 static char *char_notes = NULL;
 static fixed_t char_scroll = 0;
 
@@ -170,7 +174,6 @@ static saveinfo_t savegameinfo[MAXSAVEGAMES]; // Extra info about the save games
 INT16 startmap; // Mario, NiGHTS, or just a plain old normal game?
 
 static INT16 itemOn = 1; // menu item skull is on, Hack by Tails 09-18-2002
-static boolean lastdirection = true; // toaster - Only You Can Prevent Hacks - true is for forward, false is for backwards
 static INT16 skullAnimCounter = 10; // skull animation counter
 
 static  boolean setupcontrols_secondaryplayer;
@@ -246,6 +249,7 @@ static void M_ChooseNightsAttack(INT32 choice);
 static void M_ModeAttackRetry(INT32 choice);
 static void M_ModeAttackEndGame(INT32 choice);
 static void M_SetGuestReplay(INT32 choice);
+static void M_HandleChoosePlayerMenu(INT32 choice);
 static void M_ChoosePlayer(INT32 choice);
 menu_t SP_GameStatsDef, SP_LevelStatsDef;
 static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
@@ -820,43 +824,10 @@ static menuitem_t SP_LevelStatsMenu[] =
 	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelStats, '\0'},     // dummy menuitem for the control func
 };
 
-// A rare case.
-// External files modify this menu, so we can't call it static.
-// And I'm too lazy to go through and rename it everywhere. ARRGH!
-menuitem_t PlayerMenu[32] =
+// Player menu dummy
+static menuitem_t SP_PlayerMenu[32] =
 {
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0}
+	{IT_NOTHING | IT_KEYHANDLER, NULL, "", M_HandleChoosePlayerMenu, 0},     // dummy menuitem for the control func
 };
 
 // -----------------------------------
@@ -1594,9 +1565,9 @@ static menu_t SP_NightsGhostDef =
 menu_t SP_PlayerDef =
 {
 	"M_PICKP",
-	sizeof (PlayerMenu)/sizeof (menuitem_t),//player_end,
+	sizeof (SP_PlayerMenu)/sizeof (menuitem_t),
 	&SP_MainDef,
-	PlayerMenu,
+	SP_PlayerMenu,
 	M_DrawSetupChoosePlayerMenu,
 	24, 32,
 	0,
@@ -2034,8 +2005,6 @@ static boolean M_ChangeStringCvar(INT32 choice)
 static void M_NextOpt(void)
 {
 	INT16 oldItemOn = itemOn; // prevent infinite loop
-	lastdirection = true;
-
 	do
 	{
 		if (itemOn + 1 > currentMenu->numitems - 1)
@@ -2048,8 +2017,6 @@ static void M_NextOpt(void)
 static void M_PrevOpt(void)
 {
 	INT16 oldItemOn = itemOn; // prevent infinite loop
-	lastdirection = false;
-
 	do
 	{
 		if (!itemOn)
@@ -2320,21 +2287,11 @@ boolean M_Responder(event_t *ev)
 		case KEY_DOWNARROW:
 			M_NextOpt();
 			S_StartSound(NULL, sfx_menu1);
-			if (currentMenu == &SP_PlayerDef)
-			{
-				Z_Free(char_notes);
-				char_notes = NULL;
-			}
 			return true;
 
 		case KEY_UPARROW:
 			M_PrevOpt();
 			S_StartSound(NULL, sfx_menu1);
-			if (currentMenu == &SP_PlayerDef)
-			{
-				Z_Free(char_notes);
-				char_notes = NULL;
-			}
 			return true;
 
 		case KEY_LEFTARROW:
@@ -4768,15 +4725,15 @@ static void M_SetupChoosePlayer(INT32 choice)
 	UINT8 i;
 	UINT8 firstvalid = 255;
 	UINT8 lastvalid = 0;
+	boolean allowed = false;
 	char *name;
 	(void)choice;
 
-	if (PlayerMenu[0].status & (IT_DYBIGSPACE)) // Correcting a hack that may be made below.
-		PlayerMenu[0].status = (IT_DISABLED|(PlayerMenu[0].status & IT_CENTER));
+	SP_PlayerMenu[0].status &= ~IT_DYBIGSPACE; // Correcting a hack that may be made below.
 
 	for (i = 0; i < 32; i++) // Handle charsels, availability, and unlocks.
 	{
-		if (PlayerMenu[i].status != IT_DISABLED) // If the character's disabled through SOC, there's nothing we can do for it.
+		if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
 		{
 			name = strtok(Z_StrDup(description[i].skinname), "&");
 			skinnum = R_SkinAvailable(name);
@@ -4792,14 +4749,13 @@ static void M_SetupChoosePlayer(INT32 choice)
 				}
 				lastvalid = i;
 
-				// Handling visibility.
-				if (PlayerMenu[i].status & (IT_DISABLED|IT_CENTER))
-					PlayerMenu[i].status = IT_CALL;
+				if (i == char_on)
+					allowed = true;
+
 				if (description[i].picname[0] == '\0')
 					strncpy(description[i].picname, skins[skinnum].charsel, 8);
 			}
-			else // Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
-				PlayerMenu[i].status = (IT_DISABLED|IT_CENTER);
+			// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
 			Z_Free(name);
 		}
 	}
@@ -4815,7 +4771,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 	}
 	else // We're being forced into a specific character, so might as well.
 	{
-		PlayerMenu[0].status = (IT_CALL|IT_DYBIGSPACE|(PlayerMenu[0].status & IT_CENTER)); // This is a hack to make a non-IT_CALL character in slot 0 not softlock the game. IT_DYBIGSPACE is a dummy flag, whilst IT_CENTER is preserved.
+		SP_PlayerMenu[0].status |= IT_DYBIGSPACE; // This is a dummy flag hack to make a non-IT_CALL character in slot 0 not softlock the game.
 		M_ChoosePlayer(0);
 		return;
 	}
@@ -4829,9 +4785,77 @@ static void M_SetupChoosePlayer(INT32 choice)
 
 	SP_PlayerDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&SP_PlayerDef);
-	char_scroll = itemOn*128*FRACUNIT; // finish scrolling the menu
+	if (!allowed)
+		char_on = firstvalid;
+	char_scroll = 0; // finish scrolling the menu
 	Z_Free(char_notes);
-	char_notes = NULL;
+	char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
+}
+
+//
+// M_HandleChoosePlayerMenu
+//
+// Reacts to your key inputs. Basically a mini menu thinker.
+//
+static void M_HandleChoosePlayerMenu(INT32 choice)
+{
+	boolean exitmenu = false;  // exit to previous menu
+	INT32 selectval;
+
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			if ((selectval = description[char_on].next) != char_on)
+			{
+				S_StartSound(NULL,sfx_s3kb7);
+				char_on = selectval;
+				char_scroll = -128*FRACUNIT;
+				Z_Free(char_notes);
+				char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
+			}
+			else if (!char_scroll)
+			{
+				S_StartSound(NULL,sfx_s3kb7);
+				char_scroll = 16*FRACUNIT;
+			}
+			break;
+
+		case KEY_UPARROW:
+			if ((selectval = description[char_on].prev) != char_on)
+			{
+				S_StartSound(NULL,sfx_s3kb7);
+				char_on = selectval;
+				char_scroll = 128*FRACUNIT;
+				Z_Free(char_notes);
+				char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
+			}
+			else if (!char_scroll)
+			{
+				S_StartSound(NULL,sfx_s3kb7);
+				char_scroll = -16*FRACUNIT;
+			}
+			break;
+
+		case KEY_ENTER:
+			S_StartSound(NULL, sfx_menu1);
+			M_ChoosePlayer(char_on);
+			break;
+
+		case KEY_ESCAPE:
+			exitmenu = true;
+			break;
+
+		default:
+			break;
+	}
+
+	if (exitmenu)
+	{
+		if (currentMenu->prevMenu)
+			M_SetupNextMenu(currentMenu->prevMenu);
+		else
+			M_ClearMenus(true);
+	}
 }
 
 // Draw the choose player setup menu, had some fun with player anim
@@ -4841,7 +4865,6 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	patch_t *patch;
 	INT32 i, o;
 	UINT8 prev, next;
-	boolean loophack = false;
 
 	// Black BG
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
@@ -4850,37 +4873,20 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	// Character select profile images!1
 	M_DrawTextBox(0, my, 16, 20);
 
-	i = (itemOn*128 - (char_scroll / FRACUNIT));
-
-	if (!char_notes)
-	{
-		if (i) // turns out this and the preceding check is better then (abs(i) > 128)
-		{
-			o = (lastdirection) ? -1 : 1;
-			char_scroll = (itemOn + o)*128*FRACUNIT;
-			i = -o*128;
-		}
-		char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[itemOn].notes);
-	}
-
-	if (abs(i) > 1)
-		char_scroll += i*FRACUNIT>>2;
+	if (abs(char_scroll) > FRACUNIT)
+		char_scroll -= (char_scroll>>2);
 	else // close enough.
-		char_scroll = itemOn*128*FRACUNIT; // just be exact now.
+		char_scroll = 0; // just be exact now.
 
-	o = ((char_scroll / FRACUNIT) + 16);
+	o = (char_scroll >> FRACBITS) + 16;
 
-	if (o < 0) // This hack is to prevent visual glitches when looping from the last character to the 1st character.
-		loophack = true;
-
-	if (loophack)
+	if (o < 0) // A little hacky...
+	{
+		i = description[char_on].prev;
 		o += 128;
-
-	i = (o / 128);
-	o = (o % 128);
-
-	if (loophack)
-		i = description[i].prev;
+	}
+	else
+		i = char_on;
 
 	// Get prev character...
 	prev = description[i].prev;
@@ -4891,7 +4897,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		next = description[i].next;
 
 		// Draw prev character if it's visible and its number isn't greater than the current one or there's more than two
-		if (o < 32) // (prev != i) was previously a part of this, but we don't need to check again after above.
+		if (o < 32)
 		{
 			patch = W_CachePatchName(description[prev].picname, PU_CACHE);
 			if (SHORT(patch->width) >= 256)
@@ -4911,31 +4917,24 @@ static void M_DrawSetupChoosePlayerMenu(void)
 				V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT, 0, patch, 0, 0, SHORT(patch->width), o);
 			W_UnlockCachedPatch(patch);
 		}
-
-		// current character
-		if (PlayerMenu[i].status & IT_DISABLED) // Prevent flickering.
-			i = (lastdirection) ? prev : next; // This actually causes duplication at slow scroll speeds (<16FU per tic), but thankfully we always go quickly.
 	}
 
-	if (!(PlayerMenu[i].status & IT_DISABLED))
+	patch = W_CachePatchName(description[i].picname, PU_CACHE);
+	if (o >= 0 && o <= 32)
 	{
-		patch = W_CachePatchName(description[i].picname, PU_CACHE);
-		if (o >= 0 && o <= 32)
-		{
-			if (SHORT(patch->width) >= 256)
-				V_DrawSmallScaledPatch(8, my + 40 - o, 0, patch);
-			else
-				V_DrawScaledPatch(8, my + 40 - o, 0, patch);
-		}
+		if (SHORT(patch->width) >= 256)
+			V_DrawSmallScaledPatch(8, my + 40 - o, 0, patch);
 		else
-		{
-			if (SHORT(patch->width) >= 256)
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, (o - 32)*2, SHORT(patch->width), SHORT(patch->height));
-			else
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT, 0, patch, 0, o - 32, SHORT(patch->width), SHORT(patch->height));
-		}
-		W_UnlockCachedPatch(patch);
+			V_DrawScaledPatch(8, my + 40 - o, 0, patch);
 	}
+	else
+	{
+		if (SHORT(patch->width) >= 256)
+			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, (o-32)*2, SHORT(patch->width), SHORT(patch->height));
+		else
+			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT, 0, patch, 0, (o-32), SHORT(patch->width), SHORT(patch->height));
+	}
+	W_UnlockCachedPatch(patch);
 
 	// draw title (or big pic)
 	M_DrawMenuTitle();
@@ -4953,10 +4952,10 @@ static void M_ChoosePlayer(INT32 choice)
 	boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
 
 	// skip this if forcecharacter or no characters available
-	if (!(PlayerMenu[choice].status & IT_DYBIGSPACE))
+	if (!(SP_PlayerMenu[choice].status & IT_DYBIGSPACE))
 	{
 		// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
-		char_scroll = itemOn*128*FRACUNIT; // finish scrolling the menu
+		char_scroll = 0; // finish scrolling the menu
 		M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
 	}
 	M_ClearMenus(true);
diff --git a/src/m_menu.h b/src/m_menu.h
index 9cac52ed5..1c3b226cf 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -149,8 +149,6 @@ typedef struct menuitem_s
 	UINT8 alphaKey;
 } menuitem_t;
 
-extern menuitem_t PlayerMenu[32];
-
 typedef struct menu_s
 {
 	const char    *menutitlepic;
@@ -174,10 +172,10 @@ extern menu_t SP_LoadDef;
 // Stuff for customizing the player select screen
 typedef struct
 {
+	boolean used;
 	char notes[441];
 	char picname[8];
 	char skinname[SKINNAMESIZE*2+2]; // skin&skin\0
-	UINT16 wadnum; // for duplicate characters
 	UINT8 prev;
 	UINT8 next;
 } description_t;

From ed3569feaeaae3a58e3efb90322969e570cfd339 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 17 Mar 2017 16:47:37 +0000
Subject: [PATCH 061/119] Woops.

---
 src/m_menu.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 28ea324c1..bdd0a2499 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -825,7 +825,7 @@ static menuitem_t SP_LevelStatsMenu[] =
 };
 
 // Player menu dummy
-static menuitem_t SP_PlayerMenu[32] =
+static menuitem_t SP_PlayerMenu[] =
 {
 	{IT_NOTHING | IT_KEYHANDLER, NULL, "", M_HandleChoosePlayerMenu, 0},     // dummy menuitem for the control func
 };
@@ -4952,7 +4952,7 @@ static void M_ChoosePlayer(INT32 choice)
 	boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
 
 	// skip this if forcecharacter or no characters available
-	if (!(SP_PlayerMenu[choice].status & IT_DYBIGSPACE))
+	if (!(SP_PlayerMenu[0].status & IT_DYBIGSPACE))
 	{
 		// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
 		char_scroll = 0; // finish scrolling the menu

From 8903a0e185cca8cc026c5ddc21e2fedcaf49b106 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 17 Mar 2017 17:18:14 +0000
Subject: [PATCH 062/119] Yeah, this wasn't network safe.

---
 src/lua_mobjlib.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 0935f56de..8ec277189 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -511,9 +511,8 @@ static int mobj_set(lua_State *L)
 		for (i = 0; i < numskins; i++)
 			if (fastcmp(skins[i].name, skin))
 			{
-				if (mo->player && !R_SkinUnlock(mo->player-players, i))
-					return luaL_error(L, "mobj.skin '%s' not found!", skin);
-				mo->skin = &skins[i];
+				if (!mo->player || R_SkinUnlock(mo->player-players, i))
+					mo->skin = &skins[i];
 				return 0;
 			}
 		return luaL_error(L, "mobj.skin '%s' not found!", skin);

From b66ded844e648ea3f6b3626948df909e8fdabb6b Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 17 Mar 2017 23:09:15 +0000
Subject: [PATCH 063/119] compile fix

---
 src/p_mobj.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index de6a604a1..4f6c07575 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3965,7 +3965,7 @@ void P_DestroyRobots(void)
 			continue; // not a mobj thinker
 
 		mo = (mobj_t *)think;
-		if (mo->health <= 0 || !(mo->flags & MF_ENEMY || mo->flags & MF_BOSS))
+		if (mo->health <= 0 || !(mo->flags & (MF_ENEMY|MF_BOSS)))
 			continue; // not a valid enemy
 
 		if (mo->type == MT_PLAYER) // Don't chase after other players!

From 0060caaf1afd561b30a31b9c539d74ff6810f090 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 17 Mar 2017 23:11:32 +0000
Subject: [PATCH 064/119] Fixed synchronisation of skin changes and forceskin.

There's an outstanding issue - you can set forceskin to whatever you want to as the host. This needs to be fixed, but I'm commiting my successes first.
---
 src/command.c  | 6 +++++-
 src/d_clisrv.c | 7 ++++++-
 src/d_clisrv.h | 1 +
 src/d_netcmd.c | 7 -------
 src/r_things.c | 3 ++-
 5 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/src/command.c b/src/command.c
index 00749ed8d..ad576b54c 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1407,8 +1407,12 @@ static void CV_SetValueMaybeStealth(consvar_t *var, INT32 value, boolean stealth
 
 	if (var == &cv_forceskin) // Special handling.
 	{
-		if ((value < 0) || (value >= numskins))
+		if ((server || adminplayer == consoleplayer) && ((value < 0) || (value >= numskins) || !(R_SkinUnlock(-1, cv_forceskin.value))))
+		{
+			CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
 			sprintf(val, "None");
+			value = -1;
+		}
 		else
 			sprintf(val, "%s", skins[value].name);
 	}
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 000816450..8d5b91b38 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1366,6 +1366,8 @@ static boolean SV_SendServerConfig(INT32 node)
 	// which is nice and easy for us to detect
 	memset(netbuffer->u.servercfg.playerskins, 0xFF, sizeof(netbuffer->u.servercfg.playerskins));
 	memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor));
+	// ...except for availabilities, where 00 is nonexistent
+	memset(netbuffer->u.servercfg.playeravailabilities, 0x00, sizeof(netbuffer->u.servercfg.playeravailabilities));
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -1373,6 +1375,7 @@ static boolean SV_SendServerConfig(INT32 node)
 			continue;
 		netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin;
 		netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor;
+		netbuffer->u.servercfg.playeravailabilities[i] = (UINT32)players[i].availabilities;
 	}
 
 	memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
@@ -3494,10 +3497,12 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			for (j = 0; j < MAXPLAYERS; j++)
 			{
 				if (netbuffer->u.servercfg.playerskins[j] == 0xFF
-				 && netbuffer->u.servercfg.playercolor[j] == 0xFF)
+				 && netbuffer->u.servercfg.playercolor[j] == 0xFF
+				 && netbuffer->u.servercfg.playeravailabilities[j] == 0x00)
 					continue; // not in game
 
 				playeringame[j] = true;
+				players[j].availabilities = netbuffer->u.servercfg.playeravailabilities[j];
 				SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]);
 				players[j].skincolor = netbuffer->u.servercfg.playercolor[j];
 			}
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 8f0aedbf3..c2ec1ad58 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -285,6 +285,7 @@ typedef struct
 	// 0xFF == not in game; else player skin num
 	UINT8 playerskins[MAXPLAYERS];
 	UINT8 playercolor[MAXPLAYERS];
+	UINT32 playeravailabilities[MAXPLAYERS];
 
 	UINT8 gametype;
 	UINT8 modifiedgame;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 7fcbac092..1220b77af 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -4048,13 +4048,6 @@ static void Command_Archivetest_f(void)
   */
 static void ForceSkin_OnChange(void)
 {
-	if ((server || adminplayer == consoleplayer) && ((cv_forceskin.value == -1 && stricmp(cv_forceskin.string, "None")) || !(R_SkinUnlock(-1, cv_forceskin.value))))
-	{
-		CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
-		CV_SetValue(&cv_forceskin, -1);
-		return;
-	}
-
 	// NOT in SP, silly!
 	if (!(netgame || multiplayer))
 		return;
diff --git a/src/r_things.c b/src/r_things.c
index 03125f921..520dae30e 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2576,7 +2576,7 @@ boolean R_SkinUnlock(INT32 playernum, INT32 skinnum)
 		|| ((playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
 		|| (modeattacking) // If you have someone else's run you might as well take a look
 		|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1.
-		|| (netgame && !(server || adminplayer == consoleplayer) && (cv_forceskin.value == skinnum)) // Force 2.
+		|| (netgame && (cv_forceskin.value == skinnum)) // Force 2.
 		);
 }
 
@@ -2622,6 +2622,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 	player_t *player = &players[playernum];
 	skin_t *skin = &skins[skinnum];
 	UINT8 newcolor = 0;
+	CONS_Printf("%d - %d\n", playernum, player->availabilities);
 
 	if (skinnum >= 0 && skinnum < numskins && R_SkinUnlock(playernum, skinnum)) // Make sure it exists!
 	{

From 87ba3411a43125a26502f96287691052dfa35122 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Mar 2017 00:41:40 +0000
Subject: [PATCH 065/119] * Fixes forceskin causing desynchronisations, baby!
 Checks only when attempting to send.

* Makes the forceskin print more consistent with the skin print.

* Disabled the printing of availabilities.
---
 src/command.c  | 16 +++++++++++-----
 src/d_netcmd.c |  2 +-
 src/r_things.c |  1 -
 3 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/command.c b/src/command.c
index ad576b54c..412b75e32 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1361,6 +1361,16 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
 			return;
 		}
 
+		if (var == &cv_forceskin)
+		{
+			INT32 skin = R_SkinAvailable(value);
+			if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUnlock(-1, skin)))
+			{
+				CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
+				return;
+			}
+		}
+
 		// Only add to netcmd buffer if in a netgame, otherwise, just change it.
 		if (netgame || multiplayer)
 		{
@@ -1407,12 +1417,8 @@ static void CV_SetValueMaybeStealth(consvar_t *var, INT32 value, boolean stealth
 
 	if (var == &cv_forceskin) // Special handling.
 	{
-		if ((server || adminplayer == consoleplayer) && ((value < 0) || (value >= numskins) || !(R_SkinUnlock(-1, cv_forceskin.value))))
-		{
-			CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
+		if ((value < 0) || (value >= numskins))
 			sprintf(val, "None");
-			value = -1;
-		}
 		else
 			sprintf(val, "%s", skins[value].name);
 	}
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1220b77af..51b9e28ca 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -4056,7 +4056,7 @@ static void ForceSkin_OnChange(void)
 		CONS_Printf("The server has lifted the forced skin restrictions.\n");
 	else
 	{
-		CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].realname);
+		CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].name);
 		ForceAllSkins(cv_forceskin.value);
 	}
 }
diff --git a/src/r_things.c b/src/r_things.c
index 520dae30e..5280db73a 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2622,7 +2622,6 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 	player_t *player = &players[playernum];
 	skin_t *skin = &skins[skinnum];
 	UINT8 newcolor = 0;
-	CONS_Printf("%d - %d\n", playernum, player->availabilities);
 
 	if (skinnum >= 0 && skinnum < numskins && R_SkinUnlock(playernum, skinnum)) // Make sure it exists!
 	{

From ced6cd4349fefb98e222d4d6387a45dbfb139f23 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Mar 2017 10:12:14 +0000
Subject: [PATCH 066/119] Why do we want to end Ian, anyways? Action Man is a
 good boy. #deepcut

---
 src/d_clisrv.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 8d5b91b38..492a8a645 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1375,7 +1375,7 @@ static boolean SV_SendServerConfig(INT32 node)
 			continue;
 		netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin;
 		netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor;
-		netbuffer->u.servercfg.playeravailabilities[i] = (UINT32)players[i].availabilities;
+		netbuffer->u.servercfg.playeravailabilities[i] = (UINT32)LONG(players[i].availabilities);
 	}
 
 	memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
@@ -3502,7 +3502,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 					continue; // not in game
 
 				playeringame[j] = true;
-				players[j].availabilities = netbuffer->u.servercfg.playeravailabilities[j];
+				players[j].availabilities = (UINT32)LONG(netbuffer->u.servercfg.playeravailabilities[j]);
 				SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]);
 				players[j].skincolor = netbuffer->u.servercfg.playercolor[j];
 			}

From 096921cbbf112de9d5416e6421b225b8274b3f7d Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Mar 2017 13:03:29 +0000
Subject: [PATCH 067/119] Added a bit of a fun limitation.

* Made it so (player->availabilities & 1 << skinnum) is only true if skins[skinnum] DOES have an availability method, and always false otherwise.
* This means that setting your availabilities to INT32_MAX is no longer the equivalent of having gotten all skins.
* This means we can detect and kick cheat engine script kiddies who try to fool the server that they can use everything.
* This means availabilities of 0x00 is now valid, so make it all F's for an invalid not-in-game.
---
 src/d_clisrv.c |  5 ++---
 src/d_netcmd.c | 11 +++++++++++
 src/r_things.c |  2 +-
 3 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 492a8a645..847c22169 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1366,8 +1366,7 @@ static boolean SV_SendServerConfig(INT32 node)
 	// which is nice and easy for us to detect
 	memset(netbuffer->u.servercfg.playerskins, 0xFF, sizeof(netbuffer->u.servercfg.playerskins));
 	memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor));
-	// ...except for availabilities, where 00 is nonexistent
-	memset(netbuffer->u.servercfg.playeravailabilities, 0x00, sizeof(netbuffer->u.servercfg.playeravailabilities));
+	memset(netbuffer->u.servercfg.playeravailabilities, 0xFF, sizeof(netbuffer->u.servercfg.playeravailabilities));
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -3498,7 +3497,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			{
 				if (netbuffer->u.servercfg.playerskins[j] == 0xFF
 				 && netbuffer->u.servercfg.playercolor[j] == 0xFF
-				 && netbuffer->u.servercfg.playeravailabilities[j] == 0x00)
+				 && netbuffer->u.servercfg.playeravailabilities[j] == 0xFFFFFFFF)
 					continue; // not in game
 
 				playeringame[j] = true;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 51b9e28ca..ce1a631b6 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1329,6 +1329,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 	if (server && (p != &players[consoleplayer] && p != &players[secondarydisplayplayer]))
 	{
 		boolean kick = false;
+		INT32 s;
 
 		// team colors
 		if (G_GametypeHasTeams())
@@ -1343,6 +1344,16 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 		if (!p->skincolor)
 			kick = true;
 
+		// availabilities
+		for (s = 0; s < MAXSKINS; s++)
+		{
+			if (!skins[s].availability && (p->availabilities & (1 << s)))
+			{
+				kick = true;
+				break;
+			}
+		}
+
 		if (kick)
 		{
 			XBOXSTATIC UINT8 buf[2];
diff --git a/src/r_things.c b/src/r_things.c
index 5280db73a..0a6230c8f 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2561,7 +2561,7 @@ UINT32 R_GetSkinAvailabilities(void)
 
 	for (s = 0; s < MAXSKINS; s++)
 	{
-		if (!skins[s].availability || unlockables[skins[s].availability - 1].unlocked)
+		if (skins[s].availability && unlockables[skins[s].availability - 1].unlocked)
 			response |= (1 << s);
 	}
 	return response;

From 842c27e78b8e2c20e5d01ec087665e8482da8e6d Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Mar 2017 14:20:42 +0000
Subject: [PATCH 068/119] Let's not use terms like this so plainly, right?

---
 src/command.c     | 6 +++---
 src/d_netcmd.c    | 6 +++---
 src/lua_mobjlib.c | 2 +-
 src/m_menu.c      | 8 ++++----
 src/r_things.c    | 6 +++---
 src/r_things.h    | 2 +-
 6 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/command.c b/src/command.c
index 412b75e32..e525f5e97 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1165,7 +1165,7 @@ found:
 		if (var == &cv_forceskin)
 		{
 			var->value = R_SkinAvailable(var->string);
-			if (!R_SkinUnlock(-1, var->value))
+			if (!R_SkinUsable(-1, var->value))
 				var->value = -1;
 		}
 		else
@@ -1364,7 +1364,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
 		if (var == &cv_forceskin)
 		{
 			INT32 skin = R_SkinAvailable(value);
-			if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUnlock(-1, skin)))
+			if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin)))
 			{
 				CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
 				return;
@@ -1488,7 +1488,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 			else if (newvalue >= numskins)
 				newvalue = -1;
 		} while ((oldvalue != newvalue)
-				&& !(R_SkinUnlock(-1, newvalue)));
+				&& !(R_SkinUsable(-1, newvalue)));
 	}
 	else
 		newvalue = var->value + increment;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index ce1a631b6..1c04601a8 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1129,7 +1129,7 @@ static void SendNameAndColor(void)
 			SetPlayerSkinByNum(consoleplayer, 0);
 			CV_StealthSet(&cv_skin, skins[0].name);
 		}
-		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUnlock(consoleplayer, foundskin))
+		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUsable(consoleplayer, foundskin))
 		{
 			boolean notsame;
 
@@ -1176,7 +1176,7 @@ static void SendNameAndColor(void)
 	// check if player has the skin loaded (cv_skin may have
 	// the name of a skin that was available in the previous game)
 	cv_skin.value = R_SkinAvailable(cv_skin.string);
-	if ((cv_skin.value < 0) || !R_SkinUnlock(consoleplayer, cv_skin.value))
+	if ((cv_skin.value < 0) || !R_SkinUsable(consoleplayer, cv_skin.value))
 	{
 		CV_StealthSet(&cv_skin, DEFAULTSKIN);
 		cv_skin.value = 0;
@@ -1257,7 +1257,7 @@ static void SendNameAndColor2(void)
 			SetPlayerSkinByNum(secondplaya, forcedskin);
 			CV_StealthSet(&cv_skin2, skins[forcedskin].name);
 		}
-		else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUnlock(secondplaya, foundskin))
+		else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUsable(secondplaya, foundskin))
 		{
 			boolean notsame;
 
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 8ec277189..2fcccab66 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -511,7 +511,7 @@ static int mobj_set(lua_State *L)
 		for (i = 0; i < numskins; i++)
 			if (fastcmp(skins[i].name, skin))
 			{
-				if (!mo->player || R_SkinUnlock(mo->player-players, i))
+				if (!mo->player || R_SkinUsable(mo->player-players, i))
 					mo->skin = &skins[i];
 				return 0;
 			}
diff --git a/src/m_menu.c b/src/m_menu.c
index bdd0a2499..026349dfa 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3374,7 +3374,7 @@ static void M_PatchSkinNameTable(void)
 
 	for (j = 0; j < MAXSKINS; j++)
 	{
-		if (skins[j].name[0] != '\0' && R_SkinUnlock(-1, j))
+		if (skins[j].name[0] != '\0' && R_SkinUsable(-1, j))
 		{
 			skins_cons_t[j].strvalue = skins[j].realname;
 			skins_cons_t[j].value = j+1;
@@ -4737,7 +4737,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 		{
 			name = strtok(Z_StrDup(description[i].skinname), "&");
 			skinnum = R_SkinAvailable(name);
-			if ((skinnum != -1) && (R_SkinUnlock(-1, skinnum)))
+			if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
 			{
 				// Handling order.
 				if (firstvalid == 255)
@@ -6524,7 +6524,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 					if (setupm_fakeskin < 0)
 						setupm_fakeskin = numskins-1;
 				}
-				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(-1, setupm_fakeskin)));
+				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin)));
 			}
 			else if (itemOn == 1) // player color
 			{
@@ -6544,7 +6544,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 					if (setupm_fakeskin > numskins-1)
 						setupm_fakeskin = 0;
 				}
-				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(-1, setupm_fakeskin)));
+				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin)));
 			}
 			else if (itemOn == 1) // player color
 			{
diff --git a/src/r_things.c b/src/r_things.c
index 0a6230c8f..cc43e3cfc 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2569,7 +2569,7 @@ UINT32 R_GetSkinAvailabilities(void)
 
 // returns true if available in circumstances, otherwise nope
 // warning don't use with an invalid skinnum other than -1 which always returns true
-boolean R_SkinUnlock(INT32 playernum, INT32 skinnum)
+boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 {
 	return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
 		|| (!skins[skinnum].availability)
@@ -2601,7 +2601,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
 	INT32 i = R_SkinAvailable(skinname);
 	player_t *player = &players[playernum];
 
-	if ((i != -1) && R_SkinUnlock(playernum, i))
+	if ((i != -1) && R_SkinUsable(playernum, i))
 	{
 		SetPlayerSkinByNum(playernum, i);
 		return;
@@ -2623,7 +2623,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 	skin_t *skin = &skins[skinnum];
 	UINT8 newcolor = 0;
 
-	if (skinnum >= 0 && skinnum < numskins && R_SkinUnlock(playernum, skinnum)) // Make sure it exists!
+	if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum)) // Make sure it exists!
 	{
 		player->skin = skinnum;
 
diff --git a/src/r_things.h b/src/r_things.h
index 123ab2280..3907fd2ae 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -203,7 +203,7 @@ extern skin_t skins[MAXSKINS + 1];
 
 void SetPlayerSkin(INT32 playernum,const char *skinname);
 void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
-boolean R_SkinUnlock(INT32 playernum, INT32 skinnum);
+boolean R_SkinUsable(INT32 playernum, INT32 skinnum);
 UINT32 R_GetSkinAvailabilities(void);
 INT32 R_SkinAvailable(const char *name);
 void R_AddSkins(UINT16 wadnum);

From 98095292d615624b80de009720e4e678f0f168a1 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Mar 2017 17:02:34 +0000
Subject: [PATCH 069/119] Rob wanted hudnames that were less than 5 padded at
 the front. Whilst I tested it and didn't think it was a good idea, I'm
 committing the code anyways lest some fool wish to try it in future.

---
 src/r_things.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/r_things.c b/src/r_things.c
index cc43e3cfc..09a2d2a45 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -3014,6 +3014,17 @@ next_token:
 		skin_cons_t[numskins].strvalue = skin->name;
 #endif
 
+#ifdef SKINNAMEPADDING
+		if ((size = strlen(skin->hudname)) < 5)
+		{
+			size_t offset = 5 - size;
+			for (size++; size--;)
+				skin->hudname[size+offset] = skin->hudname[size];
+			while (offset--)
+				skin->hudname[offset] = ' ';
+		}
+#endif
+
 		// add face graphics
 		ST_LoadFaceGraphics(skin->face, skin->superface, numskins);
 

From 99f13c88aece2fa9f6a8b9a36fce1a15e3724f87 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 18 Mar 2017 21:06:06 +0000
Subject: [PATCH 070/119] Made jump damage flag application less hacky.

---
 src/d_player.h |   4 +-
 src/dehacked.c |   2 +-
 src/p_inter.c  |  10 ++---
 src/p_map.c    |  14 +++---
 src/p_mobj.c   |   4 +-
 src/p_spec.c   |  10 ++---
 src/p_user.c   | 116 +++++++++++++++++++++++++------------------------
 7 files changed, 80 insertions(+), 80 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index 8ff591a96..d7901434f 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -154,8 +154,8 @@ typedef enum
 	// Used shield ability
 	PF_SHIELDABILITY     = 1<<28,
 
-	// Force jump damage?
-	PF_FORCEJUMPDAMAGE   = 1<<29,
+	// Jump damage?
+	PF_NOJUMPDAMAGE   = 1<<29,
 
 	// Bouncing
 	PF_BOUNCING          = 1<<30
diff --git a/src/dehacked.c b/src/dehacked.c
index e2a833d68..2fdd4ff46 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6971,7 +6971,7 @@ static const char *const PLAYERFLAG_LIST[] = {
 	"ANALOGMODE", // Analog mode?
 	"CANCARRY", // Can carry?
 	"SHIELDABILITY", // Thokked with shield ability
-	"FORCEJUMPDAMAGE", // Force jump damage
+	"NOJUMPDAMAGE", // No jump damage
 	"BOUNCING",
 
 	NULL // stop loop here.
diff --git a/src/p_inter.c b/src/p_inter.c
index ae56679a8..bf002e62e 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -368,7 +368,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		}
 
 		if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
+		|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
 		|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
@@ -427,7 +427,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_DamageMobj(toucher, special, special, 1, 0);
 		}
 		else if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
+		|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
 		|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
@@ -1345,7 +1345,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				toucher->momz = -toucher->momz;
 				if (player->pflags & PF_GLIDING)
 				{
-					player->pflags &= ~(PF_GLIDING|PF_JUMPED);
+					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
 				}
 
@@ -1405,7 +1405,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					toucher->momz = -toucher->momz;
 					if (player->pflags & PF_GLIDING)
 					{
-						player->pflags &= ~(PF_GLIDING|PF_JUMPED);
+						player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 						P_SetPlayerMobjState(toucher, S_PLAY_FALL);
 					}
 
@@ -1414,7 +1414,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					return;
 				}
 				else if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-						|| ((player->pflags & PF_JUMPED) && (player->pflags & PF_FORCEJUMPDAMAGE || !(player->charflags & SF_NOJUMPSPIN) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
+						|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 						|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
 						|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 						|| player->powers[pw_invulnerability] || player->powers[pw_super]) // Do you possess the ability to subdue the object?
diff --git a/src/p_map.c b/src/p_map.c
index 5cf9612e2..10202b8c4 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -203,14 +203,14 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			}
 		}
 
-		pflags = object->player->pflags & (PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_SHIELDABILITY|PF_BOUNCING); // I still need these.
+		pflags = object->player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_SHIELDABILITY|PF_BOUNCING); // I still need these.
 		jumping = object->player->jumping;
 		secondjump = object->player->secondjump;
 		P_ResetPlayer(object->player);
 
 		if (spring->info->painchance)
 		{
-			object->player->pflags |= PF_JUMPED;
+			object->player->pflags |= (PF_JUMPED|((object->player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 			P_SetPlayerMobjState(object, S_PLAY_JUMP);
 		}
 		else if (!vertispeed || (pflags & PF_BOUNCING)) // horizontal spring or bouncing
@@ -784,7 +784,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		{
 			// Hop on the missile for a ride!
 			thing->player->powers[pw_carry] = CR_GENERIC;
-			thing->player->pflags &= ~PF_JUMPED;
+			thing->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 			P_SetTarget(&thing->tracer, tmthing);
 			P_SetTarget(&tmthing->target, thing); // Set owner to the player
 			P_SetTarget(&tmthing->tracer, NULL); // Disable homing-ness
@@ -1065,8 +1065,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			if (thing->flags & MF_MONITOR
 				&& (tmthing->player->pflags & (PF_SPINNING|PF_GLIDING)
 				|| ((tmthing->player->pflags & PF_JUMPED)
-					&& (tmthing->player->pflags & PF_FORCEJUMPDAMAGE
-					|| !(tmthing->player->charflags & SF_NOJUMPSPIN)
+					&& (!(tmthing->player->pflags & PF_NOJUMPDAMAGE)
 					|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
 				|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
 				|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
@@ -1111,8 +1110,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	else if (thing->flags & MF_MONITOR && tmthing->player
 	&& (tmthing->player->pflags & (PF_SPINNING|PF_GLIDING)
 		|| ((tmthing->player->pflags & PF_JUMPED)
-			&& (tmthing->player->pflags & PF_FORCEJUMPDAMAGE
-			|| !(tmthing->player->charflags & SF_NOJUMPSPIN)
+			&& (!(tmthing->player->pflags & PF_NOJUMPDAMAGE)
 			|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
 		|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
 		|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
@@ -2896,7 +2894,7 @@ isblocking:
 					slidemo->player->climbing = 5;
 				}
 
-				slidemo->player->pflags &= ~(PF_GLIDING|PF_SPINNING|PF_JUMPED|PF_THOKKED);
+				slidemo->player->pflags &= ~(PF_GLIDING|PF_SPINNING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED);
 				slidemo->player->glidetime = 0;
 				slidemo->player->secondjump = 0;
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 4f6c07575..3e099eab6 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3252,7 +3252,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 						mo->player->pflags &= ~PF_SPINNING;
 
 					if (!(mo->player->pflags & PF_GLIDING))
-						mo->player->pflags &= ~(PF_JUMPED|PF_FORCEJUMPDAMAGE);
+						mo->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 
 					mo->player->pflags &= ~(PF_THOKKED|PF_CANCARRY/*|PF_GLIDING*/);
 					mo->player->jumping = 0;
@@ -4246,7 +4246,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	{
 		if (!(mobj->player->powers[pw_carry] == CR_NIGHTSMODE)) // "jumping" is used for drilling
 			mobj->player->jumping = 0;
-		mobj->player->pflags &= ~PF_JUMPED;
+		mobj->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 		if (mobj->player->secondjump || mobj->player->powers[pw_tailsfly])
 		{
 			mobj->player->secondjump = 0;
diff --git a/src/p_spec.c b/src/p_spec.c
index 5fffe815b..d682c6a30 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4030,7 +4030,7 @@ DoneSection2:
 				player->powers[pw_carry] = CR_ZOOMTUBE;
 				player->speed = speed;
 				player->pflags |= PF_SPINNING;
-				player->pflags &= ~(PF_JUMPED|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
+				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 
 				if (player->mo->state-states != S_PLAY_ROLL)
@@ -4110,7 +4110,7 @@ DoneSection2:
 				player->powers[pw_carry] = CR_ZOOMTUBE;
 				player->speed = speed;
 				player->pflags |= PF_SPINNING;
-				player->pflags &= ~(PF_JUMPED|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
+				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 
 				if (player->mo->state-states != S_PLAY_ROLL)
@@ -4418,7 +4418,7 @@ DoneSection2:
 
 				S_StartSound(player->mo, sfx_s3k4a);
 
-				player->pflags &= ~(PF_JUMPED|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
+				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 				P_SetThingPosition(player->mo);
 				P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
@@ -7661,11 +7661,11 @@ void T_Pusher(pusher_t *p)
 		{
 			if (p->slider && thing->player)
 			{
-				boolean jumped = (thing->player->pflags & PF_JUMPED);
+				pflags_t jumped = (thing->player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE));
 				P_ResetPlayer (thing->player);
 
 				if (jumped)
-					thing->player->pflags |= PF_JUMPED;
+					thing->player->pflags |= jumped;
 
 				thing->player->pflags |= PF_SLIDING;
 				P_SetPlayerMobjState (thing, thing->info->painstate); // Whee!
diff --git a/src/p_user.c b/src/p_user.c
index 1f64238d6..c1e483a87 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -577,7 +577,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->powers[pw_carry] = CR_NONE;
 
 	player->powers[pw_underwater] = 0;
-	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
+	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
 	player->secondjump = 0;
 	player->jumping = 0;
 	player->homing = 0;
@@ -650,7 +650,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	if (player->powers[pw_carry] != CR_NIGHTSMODE)
 		player->mo->height = P_GetPlayerHeight(player); // Just to make sure jumping into the drone doesn't result in a squashed hitbox.
 
-	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING|PF_DRILLING);
+	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|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;
@@ -871,7 +871,7 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 // Useful when you want to kill everything the player is doing.
 void P_ResetPlayer(player_t *player)
 {
-	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_FORCEJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
+	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
 
 	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_BRAKGOOP))
 		player->powers[pw_carry] = CR_NONE;
@@ -1670,7 +1670,7 @@ void P_DoPlayerExit(player_t *player)
 	if (player->climbing)
 	{
 		player->climbing = 0;
-		player->pflags |= PF_JUMPED;
+		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 	}
 	player->powers[pw_underwater] = 0;
@@ -2005,7 +2005,7 @@ static void P_CheckBouncySectors(player_t *player)
 						if (player->pflags & PF_SPINNING)
 						{
 							player->pflags &= ~PF_SPINNING;
-							player->pflags |= PF_JUMPED;
+							player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 							player->pflags |= PF_THOKKED;
 						}
 					}
@@ -2017,7 +2017,7 @@ static void P_CheckBouncySectors(player_t *player)
 						if (player->pflags & PF_SPINNING)
 						{
 							player->pflags &= ~PF_SPINNING;
-							player->pflags |= PF_JUMPED;
+							player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 							player->pflags |= PF_THOKKED;
 						}
 					}
@@ -2025,7 +2025,7 @@ static void P_CheckBouncySectors(player_t *player)
 					if ((player->pflags & PF_SPINNING) && player->speed < FixedMul(1<<FRACBITS, player->mo->scale) && player->mo->momz)
 					{
 						player->pflags &= ~PF_SPINNING;
-						player->pflags |= PF_JUMPED;
+						player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 					}
 
 					goto bouncydone;
@@ -2767,21 +2767,21 @@ static void P_DoClimbing(player_t *player)
 				P_InstaThrust(player->mo, player->mo->angle, FixedMul(4*FRACUNIT, player->mo->scale)); // Lil' boost up.
 
 			player->climbing = 0;
-			player->pflags |= PF_JUMPED;
+			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 
 		if (skyclimber)
 		{
 			player->climbing = 0;
-			player->pflags |= PF_JUMPED;
+			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 	}
 	else
 	{
 		player->climbing = 0;
-		player->pflags |= PF_JUMPED;
+		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 	}
 
@@ -2799,7 +2799,7 @@ static void P_DoClimbing(player_t *player)
 	if (cmd->buttons & BT_USE && !(player->pflags & PF_JUMPSTASIS))
 	{
 		player->climbing = 0;
-		player->pflags |= PF_JUMPED;
+		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		P_SetObjectMomZ(player->mo, 4*FRACUNIT, false);
 		P_InstaThrust(player->mo, player->mo->angle, FixedMul(-4*FRACUNIT, player->mo->scale));
@@ -3595,7 +3595,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		else if (player->mo->momz < 0) // still descending?
 			player->mo->momz = (39*(FRACUNIT/4))>>1; // just default to the jump height.
 	}
-	else if (!(player->pflags & PF_JUMPED)) // Spin Attack
+	else if (!(player->pflags & PF_JUMPED)) // Jump
 	{
 		if (player->mo->ceilingz-player->mo->floorz <= player->mo->height-1)
 			return;
@@ -3695,7 +3695,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 	}
 	player->mo->eflags &= ~MFE_APPLYPMOMZ;
 
-	player->pflags |= PF_JUMPED;
+	player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));;
 
 	if (soundandstate)
 	{
@@ -3930,7 +3930,7 @@ void P_DoJumpShield(player_t *player)
 	}
 	else
 	{
-		player->pflags &= ~PF_JUMPED;
+		player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 		S_StartSound(player->mo, sfx_wdjump);
 	}
@@ -3943,7 +3943,7 @@ void P_DoJumpShield(player_t *player)
 //
 void P_DoBubbleBounce(player_t *player)
 {
-	player->pflags &= ~(PF_JUMPED|PF_SHIELDABILITY);
+	player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SHIELDABILITY);
 	S_StartSound(player->mo, sfx_s3k44);
 	P_DoJump(player, false);
 	if (player->charflags & SF_NOJUMPSPIN)
@@ -4218,7 +4218,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						player->powers[pw_tailsfly] = tailsflytics + 1; // Set the fly timer
 
-						player->pflags &= ~(PF_JUMPED|PF_SPINNING|PF_STARTDASH);
+						player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_STARTDASH);
 						player->pflags |= (PF_THOKKED|PF_CANCARRY);
 					}
 					break;
@@ -4256,7 +4256,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						else
 							P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
 						player->pflags |= PF_THOKKED;
-						player->pflags &= ~(PF_JUMPED|PF_SPINNING);
+						player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING);
 						player->secondjump = 1;
 					}
 					break;
@@ -4288,7 +4288,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
 					{
 						P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE);
-						player->pflags &= ~PF_JUMPED;
+						player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 						player->pflags |= PF_THOKKED|PF_BOUNCING;
 						player->mo->momx >>= 1;
 						player->mo->momy >>= 1;
@@ -4372,7 +4372,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		{
 			if (player->charability2 == CA2_MULTIABILITY)
 			{
-				player->pflags |= PF_JUMPED;
+				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 				player->secondjump = 0;
 			}
@@ -6439,7 +6439,7 @@ static void P_SkidStuff(player_t *player)
 		if (!onground)
 		{
 			player->skidtime = 0;
-			player->pflags &= ~(PF_GLIDING|PF_JUMPED);
+			player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 			P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 		}
 		// Get up and brush yourself off, idiot.
@@ -6758,10 +6758,9 @@ static void P_MovePlayer(player_t *player)
 	if (onground && player->pflags & PF_JUMPED && !(player->pflags & PF_GLIDING)
 	&& P_MobjFlip(player->mo)*player->mo->momz < 0)
 	{
-		player->pflags &= ~PF_JUMPED;
+		player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY);
 		player->jumping = 0;
 		player->secondjump = 0;
-		player->pflags &= ~PF_THOKKED;
 		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
 	}
 
@@ -6772,7 +6771,7 @@ static void P_MovePlayer(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 		else
 		{
-			player->pflags |= PF_JUMPED;
+			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 		player->pflags &= ~PF_GLIDING;
@@ -6787,38 +6786,15 @@ static void P_MovePlayer(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 		else
 		{
-			player->pflags |= PF_JUMPED;
+			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 		player->pflags &= ~PF_BOUNCING;
 	}
 
-	// Bouncing...
-	if (player->pflags & PF_BOUNCING)
-	{
-		if (!(player->pflags & PF_JUMPDOWN) || (onground && P_MobjFlip(player->mo)*player->mo->momz <= 0)) // If not holding the jump button OR on flat ground
-		{
-			P_ResetPlayer(player); // down, stop bouncing.
-			player->pflags |= PF_THOKKED;
-			if (onground)
-				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-			else if (player->charability2 == CA2_MULTIABILITY)
-			{
-				player->pflags |= PF_JUMPED;
-				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
-			}
-			else
-			{
-				player->mo->momx >>= 1;
-				player->mo->momy >>= 1;
-				player->mo->momz >>= 1;
-				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
-			}
-		}
-	}
 	// Glide MOMZ
 	// AKA my own gravity. =)
-	else if (player->pflags & PF_GLIDING)
+	if (player->pflags & PF_GLIDING)
 	{
 		fixed_t leeway;
 		fixed_t glidespeed = player->actionspd;
@@ -6864,7 +6840,7 @@ static void P_MovePlayer(player_t *player)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 			else if (player->charability2 == CA2_MULTIABILITY)
 			{
-				player->pflags |= PF_JUMPED;
+				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 			}
 			else
@@ -6891,6 +6867,30 @@ static void P_MovePlayer(player_t *player)
 				player->mo->momz = 0;
 		}
 	}
+	else if (player->pflags & PF_BOUNCING)
+	{
+		if (!(player->pflags & PF_JUMPDOWN) || (onground && P_MobjFlip(player->mo)*player->mo->momz <= 0)) // If not holding the jump button OR on flat ground
+		{
+			P_ResetPlayer(player); // down, stop bouncing.
+			player->pflags |= PF_THOKKED;
+			if (onground)
+				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+			else if (player->charability2 == CA2_MULTIABILITY)
+			{
+				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+			}
+			else
+			{
+				player->mo->momx >>= 1;
+				player->mo->momy >>= 1;
+				player->mo->momz >>= 1;
+				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+			}
+		}
+	}
+	else if (player->mo->state-states == S_PLAY_BOUNCE)
+		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 
 	// If you're running fast enough, you can create splashes as you run in shallow water.
 	if (!player->climbing
@@ -6934,7 +6934,7 @@ static void P_MovePlayer(player_t *player)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 			else
 			{
-				player->pflags |= PF_JUMPED;
+				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 			}
 		}
@@ -7158,7 +7158,7 @@ static void P_MovePlayer(player_t *player)
 								player->homing = 2;
 								if (P_LookForEnemies(player, false) && player->mo->tracer)
 								{
-									player->pflags |= PF_FORCEJUMPDAMAGE;
+									player->pflags &= ~PF_NOJUMPDAMAGE;
 									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 									S_StartSound(player->mo, sfx_s3k40);
 									player->homing = 3*TICRATE;
@@ -7169,7 +7169,8 @@ static void P_MovePlayer(player_t *player)
 							// Elemental/Bubblewrap shield activation
 							case SH_ELEMENTAL:
 							case SH_BUBBLEWRAP:
-								player->pflags |= PF_FORCEJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY;
+								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+								player->pflags &= ~PF_NOJUMPDAMAGE;
 								P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 								player->secondjump = 0;
 								player->mo->momx = player->mo->momy = 0;
@@ -7590,7 +7591,7 @@ static void P_DoRopeHang(player_t *player)
 	{
 		P_SetTarget(&player->mo->tracer, NULL);
 
-		player->pflags |= PF_JUMPED;
+		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 		player->powers[pw_carry] = CR_NONE;
 
 		if (!(player->pflags & PF_SLIDING) && (player->pflags & PF_JUMPED)
@@ -7688,7 +7689,7 @@ static void P_DoRopeHang(player_t *player)
 		{
 			if (player->mo->tracer->flags & MF_SLIDEME)
 			{
-				player->pflags |= PF_JUMPED;
+				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 
 				if (!(player->pflags & PF_SLIDING) && (player->pflags & PF_JUMPED)
 				&& !(player->panim == PA_JUMP))
@@ -9034,11 +9035,11 @@ void P_PlayerThink(player_t *player)
 		if (player->panim != PA_ABILITY)
 			P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
 	}
-	else if ((player->pflags & PF_JUMPED) && !player->powers[pw_super] && ((player->charflags & SF_NOJUMPSPIN && player->pflags & PF_FORCEJUMPDAMAGE && player->panim != PA_ROLL) || (!(player->charflags & SF_NOJUMPSPIN) && player->panim != PA_JUMP)))
+	else if ((player->pflags & PF_JUMPED) && !player->powers[pw_super] && ((player->charflags & SF_NOJUMPSPIN && player->pflags & PF_NOJUMPDAMAGE && player->panim != PA_ROLL) || (!(player->charflags & SF_NOJUMPSPIN) && player->panim != PA_JUMP)))
 	{
 		if (!(player->charflags & SF_NOJUMPSPIN))
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
-		else if (player->pflags & PF_FORCEJUMPDAMAGE)
+		else if (!(player->pflags & PF_NOJUMPDAMAGE))
 			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 	}
 
@@ -9816,7 +9817,8 @@ void P_PlayerAfterThink(player_t *player)
 		player->mo->momy = (player->mo->tracer->y - player->mo->y)*2;
 		player->mo->momz = (player->mo->tracer->z - (player->mo->height-player->mo->tracer->height/2) - player->mo->z)*2;
 		P_TeleportMove(player->mo, player->mo->tracer->x, player->mo->tracer->y, player->mo->tracer->z - (player->mo->height-player->mo->tracer->height/2));
-		player->pflags |= PF_JUMPED|PF_FORCEJUMPDAMAGE;
+		player->pflags |= PF_JUMPED;
+		player->pflags &= ~PF_NOJUMPDAMAGE;
 		player->secondjump = 0;
 		player->pflags &= ~PF_THOKKED;
 

From 7c852b575a55e1656afdacdafaeb75656d580745 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 19 Mar 2017 12:45:01 +0000
Subject: [PATCH 071/119] Inu made some suggestions about the skinnamepadding
 stuff, so here's the fix.

---
 src/r_things.c | 29 +++++++++++++++--------------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/src/r_things.c b/src/r_things.c
index 09a2d2a45..e993e696a 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2492,7 +2492,11 @@ static void Sk_SetDefaultValue(skin_t *skin)
 	skin->flags = 0;
 
 	strcpy(skin->realname, "Someone");
+#ifdef SKINNAMEPADDING
+	strcpy(skin->hudname, "  ???");
+#else
 	strcpy(skin->hudname, "???");
+#endif
 	strncpy(skin->charsel, "CHRSONIC", 8);
 	strncpy(skin->face, "MISSING", 8);
 	strncpy(skin->superface, "MISSING", 8);
@@ -2713,6 +2717,12 @@ static UINT16 W_CheckForSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
 	return INT16_MAX; // not found
 }
 
+#ifdef SKINNAMEPADDING
+#define HUDNAMEWRITE(value) snprintf(skin->hudname, sizeof(skin->hudname), "%5s", value)
+#else
+#define HUDNAMEWRITE(value) STRBUFCPY(skin->hudname, skin->name)
+#endif
+
 //
 // Find skin sprites, sounds & optional status bar face, & add them
 //
@@ -2805,7 +2815,7 @@ void R_AddSkins(UINT16 wadnum)
 				}
 				if (!hudname)
 				{
-					STRBUFCPY(skin->hudname, skin->name);
+					HUDNAMEWRITE(skin->name);
 					strupr(skin->hudname);
 					for (value = skin->hudname; *value; value++)
 						if (*value == '_') *value = ' '; // turn _ into spaces.
@@ -2818,12 +2828,12 @@ void R_AddSkins(UINT16 wadnum)
 				for (value = skin->realname; *value; value++)
 					if (*value == '_') *value = ' '; // turn _ into spaces.
 				if (!hudname)
-					STRBUFCPY(skin->hudname, skin->realname);
+					HUDNAMEWRITE(skin->realname);
 			}
 			else if (!stricmp(stoken, "hudname"))
 			{ // Life icon name (eg. "K.T.E")
 				hudname = true;
-				STRBUFCPY(skin->hudname, value);
+				HUDNAMEWRITE(value);
 				for (value = skin->hudname; *value; value++)
 					if (*value == '_') *value = ' '; // turn _ into spaces.
 				if (!realname)
@@ -3014,17 +3024,6 @@ next_token:
 		skin_cons_t[numskins].strvalue = skin->name;
 #endif
 
-#ifdef SKINNAMEPADDING
-		if ((size = strlen(skin->hudname)) < 5)
-		{
-			size_t offset = 5 - size;
-			for (size++; size--;)
-				skin->hudname[size+offset] = skin->hudname[size];
-			while (offset--)
-				skin->hudname[offset] = ' ';
-		}
-#endif
-
 		// add face graphics
 		ST_LoadFaceGraphics(skin->face, skin->superface, numskins);
 
@@ -3038,6 +3037,8 @@ next_token:
 	return;
 }
 
+#undef HUDNAMEWRITE
+
 #ifdef DELFILE
 void R_DelSkins(UINT16 wadnum)
 {

From 8aaf3ac851c6dab4595285202da22fa184167f02 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 19 Mar 2017 12:49:29 +0000
Subject: [PATCH 072/119] ...copypaste failure

---
 src/r_things.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/r_things.c b/src/r_things.c
index e993e696a..d3faa3644 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2720,7 +2720,7 @@ static UINT16 W_CheckForSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
 #ifdef SKINNAMEPADDING
 #define HUDNAMEWRITE(value) snprintf(skin->hudname, sizeof(skin->hudname), "%5s", value)
 #else
-#define HUDNAMEWRITE(value) STRBUFCPY(skin->hudname, skin->name)
+#define HUDNAMEWRITE(value) STRBUFCPY(skin->hudname, value)
 #endif
 
 //

From 1582aee909674f9eb989c7295d72531944b180b4 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 19 Mar 2017 13:48:54 +0000
Subject: [PATCH 073/119] No more switching characters in forcecharacter maps!

---
 src/d_netcmd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1c04601a8..760d930dc 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1011,7 +1011,7 @@ UINT8 CanChangeSkin(INT32 playernum)
 		return true;
 
 	// Force skin in effect.
-	if (client && (cv_forceskin.value != -1) && !(adminplayer == playernum && serverplayer == -1))
+	if (client && ((cv_forceskin.value != -1) || (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0')) && !(adminplayer == playernum && serverplayer == -1))
 		return false;
 
 	// Can change skin in intermission and whatnot.

From 3a6f659b930fd8b0cf6f9e2f431fbfc1884f7a62 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 19 Mar 2017 16:48:35 +0000
Subject: [PATCH 074/119] She's a lot more complete.

* Melee now has a proper weightiness to it with a landing frame.
* If you have twinspin too, do a somersalt when running and hitting the melee button.
* If you have melee, go into the melee landing frame when twinspinning into the ground.
---
 src/dehacked.c |  1 +
 src/doomdef.h  |  3 +++
 src/info.c     |  4 +++-
 src/info.h     |  2 ++
 src/p_inter.c  |  4 ++--
 src/p_map.c    |  2 +-
 src/p_mobj.c   | 12 +++++++-----
 src/p_user.c   | 35 ++++++++++++++++++++++++-----------
 8 files changed, 43 insertions(+), 20 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 2fdd4ff46..a39cf9136 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3940,6 +3940,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	// CA2_MELEE
 	"S_PLAY_MELEE",
 	"S_PLAY_MELEE_FINISH",
+	"S_PLAY_MELEE_LANDING",
 
 	// SF_SUPER
 	"S_PLAY_SUPERTRANS1",
diff --git a/src/doomdef.h b/src/doomdef.h
index ed197e20c..73ecf7dc9 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -543,4 +543,7 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// Experimental attempts at preventing MF_PAPERCOLLISION objects from getting stuck in walls.
 //#define PAPER_COLLISIONCORRECTION
 
+/// Hudname padding.
+#define SKINNAMEPADDING
+
 #endif // __DOOMDEF__
diff --git a/src/info.c b/src/info.c
index 932ef4a58..e62b66ccd 100644
--- a/src/info.c
+++ b/src/info.c
@@ -421,6 +421,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"TWIN",
 
 	"MLEE",
+	"MLEL",
 
 	"TRNS",
 
@@ -530,7 +531,8 @@ state_t states[NUMSTATES] =
 
 	// CA2_MELEE
 	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
-	{SPR_PLAY, SPR2_MLEE,                20, {NULL},                   0, 0, S_PLAY_FALL}, // S_PLAY_MELEE_FINISH
+	{SPR_PLAY, SPR2_MLEE,                20, {NULL},                   0, 0, S_PLAY_FALL},  // S_PLAY_MELEE_FINISH
+	{SPR_PLAY, SPR2_MLEL,                35, {NULL},                   0, 0, S_PLAY_WALK},  // S_PLAY_MELEE_LANDING
 
 	// SF_SUPER
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER,                4, {NULL}, 0, 0, S_PLAY_SUPER_TRANS2}, // S_PLAY_SUPER_TRANS
diff --git a/src/info.h b/src/info.h
index d5508d501..a981a39fa 100644
--- a/src/info.h
+++ b/src/info.h
@@ -634,6 +634,7 @@ enum playersprite
 	SPR2_TWIN, // twinspin
 
 	SPR2_MLEE, // melee
+	SPR2_MLEL, // melee land
 
 	SPR2_TRNS, // transformation
 
@@ -741,6 +742,7 @@ typedef enum state
 	// CA2_MELEE
 	S_PLAY_MELEE,
 	S_PLAY_MELEE_FINISH,
+	S_PLAY_MELEE_LANDING,
 
 	// SF_SUPER
 	S_PLAY_SUPER_TRANS,
diff --git a/src/p_inter.c b/src/p_inter.c
index bf002e62e..36e800bf0 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -379,7 +379,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				if (elementalpierce == 2)
 					P_DoBubbleBounce(player);
-				else
+				else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 					toucher->momz = -toucher->momz;
 			}
 			if (player->pflags & PF_BOUNCING)
@@ -437,7 +437,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				if (elementalpierce == 2)
 					P_DoBubbleBounce(player);
-				else
+				else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 					toucher->momz = -toucher->momz;
 			}
 			if (player->pflags & PF_BOUNCING)
diff --git a/src/p_map.c b/src/p_map.c
index 10202b8c4..b91358140 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1085,7 +1085,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				{
 					if (elementalpierce == 2)
 						P_DoBubbleBounce(player);
-					else
+					else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 						*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
 				}
 				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 3e099eab6..e867175ae 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -468,6 +468,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 	case S_PLAY_SPINDASH: // ...but the act of SPINDASHING is charability2 specific.
 	case S_PLAY_MELEE:
 	case S_PLAY_MELEE_FINISH:
+	case S_PLAY_MELEE_LANDING:
 		player->panim = PA_ABILITY2;
 		break;
 	case S_PLAY_RIDE:
@@ -3194,8 +3195,8 @@ static void P_PlayerZMovement(mobj_t *mo)
 					// aren't pressing any controls.
 					if (!(mo->player->cmd.forwardmove || mo->player->cmd.sidemove) && !mo->player->cmomx && !mo->player->cmomy && !(mo->player->pflags & PF_SPINNING))
 					{
-						mo->momx = mo->momx/2;
-						mo->momy = mo->momy/2;
+						mo->momx >>= 1;
+						mo->momy >>= 1;
 					}
 				}
 
@@ -3206,10 +3207,11 @@ static void P_PlayerZMovement(mobj_t *mo)
 						mo->player->skidtime = TICRATE;
 						mo->tics = -1;
 					}
-					else if (mo->player->charability2 == CA2_MELEE && mo->player->panim == PA_ABILITY2)
+					else if (mo->player->charability2 == CA2_MELEE && ((mo->player->charability == CA_TWINSPIN && mo->player->panim == PA_ABILITY) || (mo->player->panim == PA_ABILITY2)))
 					{
-						P_InstaThrust(mo, mo->angle, 0);
-						P_SetPlayerMobjState(mo, S_PLAY_STND);
+						P_SetPlayerMobjState(mo, S_PLAY_MELEE_LANDING);
+						mo->tics = mo->player->powers[pw_nocontrol] = (mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(mo->movefactor)))>>FRACBITS;
+						S_StartSound(mo, sfx_s3k8b);
 					}
 					else if (mo->player->pflags & PF_JUMPED || (mo->player->pflags & (PF_SPINNING|PF_USEDOWN)) != (PF_SPINNING|PF_USEDOWN)
 					|| mo->player->powers[pw_tailsfly] || mo->state-states == S_PLAY_FLY_TIRED)
diff --git a/src/p_user.c b/src/p_user.c
index c1e483a87..c8d0c59e3 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3815,19 +3815,33 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 				}
 				break;
 			case CA2_MELEE: // Melee attack
-				if (!(player->panim == PA_ABILITY2) && (cmd->buttons & BT_USE) && player->speed < FixedMul(10<<FRACBITS, player->mo->scale)
+				if (!(player->panim == PA_ABILITY2) && (cmd->buttons & BT_USE)
 				&& !player->mo->momz && onground && !(player->pflags & PF_USEDOWN)
 				&& canstand)
 				{
 					P_ResetPlayer(player);
-					player->mo->z += P_MobjFlip(player->mo);
-					player->mo->momx = player->cmomx = 0;
-					player->mo->momy = player->cmomy = 0;
-					P_SetObjectMomZ(player->mo, player->mindash, false);
-					P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->maxdash, player->mo->scale));
-					P_SetPlayerMobjState(player->mo, S_PLAY_MELEE);
+					if ((player->charability == CA_TWINSPIN) && (player->speed > FixedMul(player->runspeed, player->mo->scale)))
+					{
+						P_DoJump(player, false);
+						player->jumping = 0;
+						player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2);
+						player->pflags |= PF_THOKKED;
+						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
+						S_StartSound(player->mo, sfx_s3k8b);
+					}
+					else
+					{
+						player->powers[pw_nocontrol] = TICRATE;
+						player->mo->z += P_MobjFlip(player->mo);
+						P_SetObjectMomZ(player->mo, player->mindash, false);
+						if (FixedMul(player->speed, FINECOSINE(((player->mo->angle - R_PointToAngle2(0, 0, player->rmomx, player->rmomy)) >> ANGLETOFINESHIFT) & FINEMASK)) < FixedMul(player->maxdash, player->mo->scale))
+							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->maxdash, player->mo->scale));
+						player->mo->momx += player->cmomx;
+						player->mo->momy += player->cmomy;
+						P_SetPlayerMobjState(player->mo, S_PLAY_MELEE);
+						S_StartSound(player->mo, sfx_s3k42);
+					}
 					player->pflags |= PF_USEDOWN;
-					S_StartSound(player->mo, sfx_s3k8b);
 				}
 				break;
 		}
@@ -6503,11 +6517,10 @@ static void P_SkidStuff(player_t *player)
 			// If your push angle is more than this close to a full 180 degrees, trigger a skid.
 			if (dang > ANGLE_157h)
 			{
-				player->skidtime = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
-				S_StartSound(player->mo, sfx_skid);
 				if (player->panim != PA_WALK)
 					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-				player->mo->tics = player->skidtime;
+				player->mo->tics = player->skidtime = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
+				S_StartSound(player->mo, sfx_skid);
 			}
 		}
 	}

From 9fd09c07f34f2b0ce6a75b7c133e123a2028baed Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 19 Mar 2017 17:18:41 +0000
Subject: [PATCH 075/119] <Rob> The host should not be able to switch their
 skin if skin switching is locked <Rob> That is completely unfair

---
 src/d_netcmd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 760d930dc..3013fef82 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1011,7 +1011,7 @@ UINT8 CanChangeSkin(INT32 playernum)
 		return true;
 
 	// Force skin in effect.
-	if (client && ((cv_forceskin.value != -1) || (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0')) && !(adminplayer == playernum && serverplayer == -1))
+	if ((cv_forceskin.value != -1) || (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0'))
 		return false;
 
 	// Can change skin in intermission and whatnot.

From f0c364c71ceae8450a5b508b27a7d8e6c22967e9 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sun, 19 Mar 2017 22:22:39 +0000
Subject: [PATCH 076/119] For melee: * Fixed underwater hop going too high. *
 Added helpful comment. * Changed animation speed.

---
 src/info.c   | 4 ++--
 src/p_user.c | 4 +++-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/info.c b/src/info.c
index e62b66ccd..60acd2d4b 100644
--- a/src/info.c
+++ b/src/info.c
@@ -530,8 +530,8 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
 
 	// CA2_MELEE
-	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
-	{SPR_PLAY, SPR2_MLEE,                20, {NULL},                   0, 0, S_PLAY_FALL},  // S_PLAY_MELEE_FINISH
+	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
+	{SPR_PLAY, SPR2_MLEE,                70, {NULL},                   0, 0, S_PLAY_FALL},  // S_PLAY_MELEE_FINISH
 	{SPR_PLAY, SPR2_MLEL,                35, {NULL},                   0, 0, S_PLAY_WALK},  // S_PLAY_MELEE_LANDING
 
 	// SF_SUPER
diff --git a/src/p_user.c b/src/p_user.c
index c8d0c59e3..ec197765b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3824,7 +3824,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					{
 						P_DoJump(player, false);
 						player->jumping = 0;
-						player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2);
+						player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2); // NOT 1.5 times the jump height, but 2.25 times.
 						player->pflags |= PF_THOKKED;
 						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
 						S_StartSound(player->mo, sfx_s3k8b);
@@ -3834,6 +3834,8 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						player->powers[pw_nocontrol] = TICRATE;
 						player->mo->z += P_MobjFlip(player->mo);
 						P_SetObjectMomZ(player->mo, player->mindash, false);
+						if (player->mo->eflags & MFE_UNDERWATER)
+							player->mo->momz >>= 1;
 						if (FixedMul(player->speed, FINECOSINE(((player->mo->angle - R_PointToAngle2(0, 0, player->rmomx, player->rmomy)) >> ANGLETOFINESHIFT) & FINEMASK)) < FixedMul(player->maxdash, player->mo->scale))
 							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->maxdash, player->mo->scale));
 						player->mo->momx += player->cmomx;

From a4c1c3195f958dff8751ef61d041925683e241c5 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 20 Mar 2017 16:57:24 +0000
Subject: [PATCH 077/119] Cleanup.

* Fixed a major hack from when I SPR2ised NiGHTS by spawning in a null object.
* Made P_LookForEnemies better in preperation for the future.
* Changed some defaulting characteristics of P_GetMobjSprite2.
* Fixed CA_DOUBLEJUMP potentially counting as a float.
* Killed some extremely small boolean functions and replaced them with #defines.
---
 src/p_local.h |  8 ++++++++
 src/p_mobj.c  | 16 ++++++++++------
 src/p_user.c  | 40 ++++++++++++++++++----------------------
 3 files changed, 36 insertions(+), 28 deletions(-)

diff --git a/src/p_local.h b/src/p_local.h
index 2532a8e04..f50d15870 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -150,7 +150,11 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings);
 void P_GivePlayerLives(player_t *player, INT32 numlives);
 UINT8 P_GetNextEmerald(void);
 void P_GiveEmerald(boolean spawnObj);
+#if 0
 void P_ResetScore(player_t *player);
+#else
+#define P_ResetScore(player) player->scoreadd = 0
+#endif
 boolean P_AutoPause(void);
 
 void P_DoJumpShield(player_t *player);
@@ -175,7 +179,11 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
 void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
 boolean P_SuperReady(player_t *player);
 void P_DoJump(player_t *player, boolean soundandstate);
+#if 0
 boolean P_AnalogMove(player_t *player);
+#else
+#define P_AnalogMove(player) (player->pflags & PF_ANALOGMODE)
+#endif
 boolean P_TransferToNextMare(player_t *player);
 UINT8 P_FindLowestMare(void);
 void P_FindEmerald(void);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index e867175ae..111777ccf 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -211,9 +211,6 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 
 		switch(spr2)
 		{
-		case SPR2_DASH:
-			spr2 = SPR2_RUN ;
-			break;
 		case SPR2_RUN:
 			spr2 = SPR2_WALK;
 			break;
@@ -272,6 +269,10 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_RUN ;
 			break;
 
+		case SPR2_DASH:
+			spr2 = SPR2_FRUN;
+			break;
+
 		case SPR2_BNCE:
 			spr2 = SPR2_FALL;
 			break;
@@ -289,10 +290,12 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 
 		// NiGHTS sprites.
 		case SPR2_NSTD:
-			spr2 = FF_SPR2SUPER|SPR2_STND;
+			spr2 = SPR2_STND;
+			super = FF_SPR2SUPER;
 			break;
 		case SPR2_NFLT:
-			spr2 = FF_SPR2SUPER|SPR2_FLT ;
+			spr2 = SPR2_FLT ;
+			super = FF_SPR2SUPER;
 			break;
 		case SPR2_NSTN:
 			spr2 = SPR2_STUN;
@@ -301,7 +304,8 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_NSTN;
 			break;
 		case SPR2_NATK:
-			spr2 = FF_SPR2SUPER|SPR2_ROLL;
+			spr2 = SPR2_ROLL;
+			super = FF_SPR2SUPER;
 			break;
 		/*case SPR2_NGT0:
 			spr2 = SPR2_NFLT;
diff --git a/src/p_user.c b/src/p_user.c
index ec197765b..38ff9da9a 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -325,6 +325,7 @@ void P_GiveEmerald(boolean spawnObj)
 	}
 }
 
+#if 0
 //
 // P_ResetScore
 //
@@ -335,6 +336,7 @@ void P_ResetScore(player_t *player)
 
 	player->scoreadd = 0;
 }
+#endif
 
 //
 // P_FindLowestMare
@@ -3932,7 +3934,7 @@ void P_DoJumpShield(player_t *player)
 		INT32 i;
 #define numangles 6
 #define limitangle (360/numangles)
-		angle_t travelangle = player->mo->angle + P_RandomRange(-limitangle, limitangle)*ANG1;
+		const angle_t travelangle = player->mo->angle + P_RandomRange(-limitangle, limitangle)*ANG1;
 		for (i = 0; i < numangles; i++)
 		{
 			spark = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_THUNDERCOIN_SPARK);
@@ -4364,7 +4366,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 		if ((gametype != GT_CTF || !player->gotflag) && !player->exiting)
 		{
-			if (player->secondjump == 1)
+			if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP)
 			{
 				fixed_t potentialmomz;
 				if (player->charability == CA_SLOWFALL)
@@ -4408,10 +4410,12 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	}
 }
 
+#if 0
 boolean P_AnalogMove(player_t *player)
 {
 	return player->pflags & PF_ANALOGMODE;
 }
+#endif
 
 //
 // P_GetPlayerControlDirection
@@ -5164,28 +5168,20 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 	if (player->exiting)
 		return;
 
-	/*
-	In some ways worse, in some ways better.
-	I did the following this way because the player object has to deal with touchspecials too, not just solids.
-	There were all sorts of fun bugs when the player got to touch the goal a frame earlier than it should've.
-	Technically, we lose out on being blocked by MF_SOLID objects, but official stages don't use them on the track.
-	I know we probably could've kept around MT_NIGHTSCHAR in some fashion, having an invisible hitbox following the
-	player around... but I'd already removed all its references, restructured the way the chaos emerald follows
-	the player around to fill the player->mo->tracer gap left behind, and NiGHTS is a lag magnet (lagnet?)
-	enough as it is... so whatever.
-	~toast
-	*/
 	{
-		fixed_t prevx = player->mo->x;
-		fixed_t prevy = player->mo->y;
 		boolean notallowed;
-		player->mo->flags |= MF_NOCLIPTHING; // player = NULL; // YIKES
-		notallowed = (!(P_TryMove(player->mo, player->mo->x+player->mo->momx, player->mo->y+player->mo->momy, true)));
-		P_TeleportMove(player->mo, prevx, prevy, player->mo->z);
-		player->mo->flags &= ~MF_NOCLIPTHING; // player = player; // unyikes
+		mobj_t *hack = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_NULL);
+		hack->flags = MF_NOGRAVITY;
+		hack->radius = player->mo->radius;
+		hack->height = player->mo->height;
+		hack->z = player->mo->z;
+		P_SetThingPosition(hack);
+		notallowed = (!(P_TryMove(hack, player->mo->x+player->mo->momx, player->mo->y+player->mo->momy, true)));
+		P_RemoveMobj(hack);
 		if (notallowed)
 			return;
 	}
+#endif
 
 	{
 		const INT32 sequence = player->mo->target->threshold;
@@ -7825,6 +7821,7 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies)
 	thinker_t *think;
 	mobj_t *closestmo = NULL;
 	angle_t an;
+	const UINT32 targetmask = (MF_ENEMY|MF_BOSS|(nonenemies ? (MF_MONITOR|MF_SPRING) : 0));
 
 	for (think = thinkercap.next; think != &thinkercap; think = think->next)
 	{
@@ -7832,9 +7829,8 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies)
 			continue; // not a mobj thinker
 
 		mo = (mobj_t *)think;
-		if ((nonenemies && !(mo->flags & (MF_ENEMY|MF_BOSS|MF_MONITOR|MF_SPRING)))
-		|| (!nonenemies && !(mo->flags & (MF_ENEMY|MF_BOSS))))
-			continue; // not a valid enemy
+		if (!(mo->flags & targetmask))
+			continue; // not a valid target
 
 		if (mo->health <= 0) // dead
 			continue;

From ee74fa4042a43606a576a734061dbc59e5256eab Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 20 Mar 2017 19:04:33 +0000
Subject: [PATCH 078/119] * Changed how CA2_MELEE handles nocontrol to not
 tweak pw_nocontrol. * Added CA_TWINSPIN/CA2_MELEE spring boosts. * Made it so
 you can't land with CA2_MELEE twice. * Fixed stray #endif from NiGHTS fixes.

---
 src/p_map.c  | 11 +++++++++++
 src/p_mobj.c |  4 ++--
 src/p_user.c |  8 +++-----
 3 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index b91358140..e2d296fb4 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -134,6 +134,17 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	object->standingslope = NULL; // Okay, now we can't return - no launching off at silly angles for you.
 #endif
 
+	if (object->player
+	&& ((object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
+	|| (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)))
+	{
+		S_StartSound(object, sfx_s3k8b);
+#define scalefactor ((4*FRACUNIT)/3)
+		horizspeed = FixedMul(horizspeed, scalefactor);
+		vertispeed = FixedMul(vertispeed, FixedSqrt(scalefactor));
+#undef scalefactor
+	}
+
 	object->eflags |= MFE_SPRUNG; // apply this flag asap!
 	spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 111777ccf..4de36ff07 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3211,10 +3211,10 @@ static void P_PlayerZMovement(mobj_t *mo)
 						mo->player->skidtime = TICRATE;
 						mo->tics = -1;
 					}
-					else if (mo->player->charability2 == CA2_MELEE && ((mo->player->charability == CA_TWINSPIN && mo->player->panim == PA_ABILITY) || (mo->player->panim == PA_ABILITY2)))
+					else if (mo->player->charability2 == CA2_MELEE && ((mo->player->charability == CA_TWINSPIN && mo->player->panim == PA_ABILITY) || (mo->player->panim == PA_ABILITY2 && mo->state-states != S_PLAY_MELEE_LANDING)))
 					{
 						P_SetPlayerMobjState(mo, S_PLAY_MELEE_LANDING);
-						mo->tics = mo->player->powers[pw_nocontrol] = (mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(mo->movefactor)))>>FRACBITS;
+						mo->tics = (mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(mo->movefactor)))>>FRACBITS;
 						S_StartSound(mo, sfx_s3k8b);
 					}
 					else if (mo->player->pflags & PF_JUMPED || (mo->player->pflags & (PF_SPINNING|PF_USEDOWN)) != (PF_SPINNING|PF_USEDOWN)
diff --git a/src/p_user.c b/src/p_user.c
index 38ff9da9a..59142edd0 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3833,7 +3833,6 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					}
 					else
 					{
-						player->powers[pw_nocontrol] = TICRATE;
 						player->mo->z += P_MobjFlip(player->mo);
 						P_SetObjectMomZ(player->mo, player->mindash, false);
 						if (player->mo->eflags & MFE_UNDERWATER)
@@ -5181,7 +5180,6 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		if (notallowed)
 			return;
 	}
-#endif
 
 	{
 		const INT32 sequence = player->mo->target->threshold;
@@ -6555,9 +6553,9 @@ static void P_MovePlayer(player_t *player)
 	runspd = FixedMul(runspd, player->mo->movefactor);
 
 	// Control relinquishing stuff!
-	if (player->powers[pw_carry] == CR_BRAKGOOP)
-		player->pflags |= PF_FULLSTASIS;
-	else if (player->pflags & PF_GLIDING && player->skidtime)
+	if ((player->powers[pw_carry] == CR_BRAKGOOP)
+	|| (player->pflags & PF_GLIDING && player->skidtime)
+	|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 		player->pflags |= PF_FULLSTASIS;
 	else if (player->powers[pw_nocontrol])
 	{

From 59bc0da0222d6549697e5cd4d5ec4aa1fc022976 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 21 Mar 2017 00:54:47 +0000
Subject: [PATCH 079/119] Introducing CA2_GUNSLINGER. * Mildly hacky proof of
 concept! Will de-hack later. * Charge up your ability! * You can only damage
 others if it's at full power. * Fires directly at enemies/bosses when at full
 power. Does NOT aim at players.

---
 src/d_player.h    |   1 +
 src/dehacked.c    |   9 ++-
 src/info.c        |  16 +++-
 src/info.h        |  14 +++-
 src/lua_baselib.c |   3 +-
 src/p_local.h     |   2 +-
 src/p_map.c       |  12 ++-
 src/p_mobj.c      |   8 +-
 src/p_user.c      | 181 +++++++++++++++++++++++++++++++++-------------
 9 files changed, 181 insertions(+), 65 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index d7901434f..8db170089 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -75,6 +75,7 @@ typedef enum
 	CA2_NONE=0,
 	CA2_SPINDASH,
 	CA2_MULTIABILITY,
+	CA2_GUNSLINGER,
 	CA2_MELEE
 } charability2_t;
 
diff --git a/src/dehacked.c b/src/dehacked.c
index a39cf9136..af982662d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3908,7 +3908,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_DEAD",
 	"S_PLAY_DRWN",
 	"S_PLAY_ROLL",
-	"S_PLAY_SPINDASH",
 	"S_PLAY_GASP",
 	"S_PLAY_JUMP",
 	"S_PLAY_SPRING",
@@ -3916,6 +3915,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_EDGE",
 	"S_PLAY_RIDE",
 
+	// CA2_SPINDASH
+	"S_PLAY_SPINDASH",
+
 	// CA_FLY/SWIM
 	"S_PLAY_FLY",
 	"S_PLAY_SWIM",
@@ -3934,6 +3936,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_BOUNCE",
 	"S_PLAY_BOUNCE_LANDING",
 
+	// CA2_GUNSLINGER
+	"S_PLAY_CHARGE",
+	"S_PLAY_FIRE",
+
 	// CA_TWINSPIN
 	"S_PLAY_TWINSPIN",
 
@@ -7398,6 +7404,7 @@ struct {
 	{"CA2_NONE",CA2_NONE}, // now slot 0!
 	{"CA2_SPINDASH",CA2_SPINDASH},
 	{"CA2_MULTIABILITY",CA2_MULTIABILITY},
+	{"CA2_GUNSLINGER",CA2_GUNSLINGER},
 	{"CA2_MELEE",CA2_MELEE},
 
 	// Sound flags
diff --git a/src/info.c b/src/info.c
index 60acd2d4b..4434a6908 100644
--- a/src/info.c
+++ b/src/info.c
@@ -396,7 +396,6 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"DEAD",
 	"DRWN",
 	"ROLL",
-	"SPIN",
 	"GASP",
 	"JUMP",
 	"SPNG",
@@ -404,6 +403,8 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"EDGE",
 	"RIDE",
 
+	"SPIN",
+
 	"FLY_",
 	"SWIM",
 	"TIRE",
@@ -418,6 +419,9 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"BNCE",
 	"BLND",
 
+	"CHRG",
+	"FIRE",
+
 	"TWIN",
 
 	"MLEE",
@@ -500,7 +504,6 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_DEAD|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},      // S_PLAY_DEAD
 	{SPR_PLAY, SPR2_DRWN|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},      // S_PLAY_DRWN
 	{SPR_PLAY, SPR2_ROLL,                 1, {NULL}, 0,  0, S_PLAY_ROLL}, // S_PLAY_ROLL
-	{SPR_PLAY, SPR2_SPIN,                 2, {NULL}, 0,  0, S_PLAY_SPINDASH}, // S_PLAY_SPINDASH
 	{SPR_PLAY, SPR2_GASP|FF_ANIMATE,     14, {NULL}, 0,  4, S_PLAY_WALK}, // S_PLAY_GASP
 	{SPR_PLAY, SPR2_JUMP,                 1, {NULL}, 0,  0, S_PLAY_JUMP}, // S_PLAY_JUMP
 	{SPR_PLAY, SPR2_SPNG,                 2, {NULL}, 0,  0, S_PLAY_SPRING}, // S_PLAY_SPRING
@@ -508,6 +511,9 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_EDGE|FF_ANIMATE,     -1, {NULL}, 0, 12, S_NULL},      // S_PLAY_EDGE
 	{SPR_PLAY, SPR2_RIDE,                 4, {NULL}, 0,  0, S_PLAY_RIDE}, // S_PLAY_RIDE
 
+	// CA2_SPINDASH
+	{SPR_PLAY, SPR2_SPIN,                 2, {NULL}, 0,  0, S_PLAY_SPINDASH}, // S_PLAY_SPINDASH
+
 	// CA_FLY/CA_SWIM
 	{SPR_PLAY, SPR2_FLY ,                 2, {NULL}, 0,  0, S_PLAY_FLY},  // S_PLAY_FLY
 	{SPR_PLAY, SPR2_SWIM,                 2, {NULL}, 0,  0, S_PLAY_SWIM}, // S_PLAY_SWIM
@@ -524,7 +530,11 @@ state_t states[NUMSTATES] =
 
 	// CA_BOUNCE
 	{SPR_PLAY, SPR2_BNCE|FF_ANIMATE,     -1, {NULL},             0,  0, S_NULL},                // S_PLAY_BOUNCE
-	{SPR_PLAY, SPR2_BLND|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_BOUNCE,  4, S_PLAY_BOUNCE_LANDING}, // S_PLAY_BOUNCE_LANDING
+	{SPR_PLAY, SPR2_BLND|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_BOUNCE,  0, S_PLAY_BOUNCE_LANDING}, // S_PLAY_BOUNCE_LANDING
+
+	// CA2_GUNSLINGER
+	{SPR_PLAY, SPR2_CHRG,                 2, {NULL},           0,  0, S_PLAY_CHARGE}, // S_PLAY_CHARGE
+	{SPR_PLAY, SPR2_FIRE|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_STND,  0, S_PLAY_FIRE},   // S_PLAY_FIRE
 
 	// CA_TWINSPIN
 	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
diff --git a/src/info.h b/src/info.h
index a981a39fa..15f25b493 100644
--- a/src/info.h
+++ b/src/info.h
@@ -609,7 +609,6 @@ enum playersprite
 	SPR2_DEAD,
 	SPR2_DRWN, // drown
 	SPR2_ROLL,
-	SPR2_SPIN, // spindash charge
 	SPR2_GASP,
 	SPR2_JUMP,
 	SPR2_SPNG, // spring
@@ -617,6 +616,8 @@ enum playersprite
 	SPR2_EDGE,
 	SPR2_RIDE,
 
+	SPR2_SPIN, // spindash
+
 	SPR2_FLY ,
 	SPR2_SWIM,
 	SPR2_TIRE, // tired
@@ -631,6 +632,9 @@ enum playersprite
 	SPR2_BNCE, // bounce
 	SPR2_BLND, // bounce landing
 
+	SPR2_CHRG, // charge
+	SPR2_FIRE, // fire
+
 	SPR2_TWIN, // twinspin
 
 	SPR2_MLEE, // melee
@@ -710,7 +714,6 @@ typedef enum state
 	S_PLAY_DEAD,
 	S_PLAY_DRWN,
 	S_PLAY_ROLL,
-	S_PLAY_SPINDASH,
 	S_PLAY_GASP,
 	S_PLAY_JUMP,
 	S_PLAY_SPRING,
@@ -718,6 +721,9 @@ typedef enum state
 	S_PLAY_EDGE,
 	S_PLAY_RIDE,
 
+	// CA2_SPINDASH
+	S_PLAY_SPINDASH,
+
 	// CA_FLY/SWIM
 	S_PLAY_FLY,
 	S_PLAY_SWIM,
@@ -736,6 +742,10 @@ typedef enum state
 	S_PLAY_BOUNCE,
 	S_PLAY_BOUNCE_LANDING,
 
+	// CA2_GUNSLINGER
+	S_PLAY_CHARGE,
+	S_PLAY_FIRE,
+
 	// CA_TWINSPIN
 	S_PLAY_TWINSPIN,
 
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index daea8a5c3..ffafb7f34 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1055,11 +1055,12 @@ static int lib_pLookForEnemies(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
 	boolean nonenemies = lua_opttrueboolean(L, 2);
+	boolean abovehorizontal = lua_opttrueboolean(L, 3);
 	NOHUD
 	INLEVEL
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
-	lua_pushboolean(L, P_LookForEnemies(player, nonenemies));
+	lua_pushboolean(L, P_LookForEnemies(player, nonenemies, abovehorizontal));
 	return 1;
 }
 
diff --git a/src/p_local.h b/src/p_local.h
index f50d15870..b7738ed98 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -174,7 +174,7 @@ fixed_t P_ReturnThrustX(mobj_t *mo, angle_t angle, fixed_t move);
 fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move);
 void P_InstaThrustEvenIn2D(mobj_t *mo, angle_t angle, fixed_t move);
 
-boolean P_LookForEnemies(player_t *player, boolean nonenemies);
+boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehorizontal);
 void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
 void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
 boolean P_SuperReady(player_t *player);
diff --git a/src/p_map.c b/src/p_map.c
index e2d296fb4..7ff6301e2 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -938,11 +938,13 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		if (tmthing->eflags & MFE_VERTICALFLIP)
 		{
 			if (thing->z + thing->height <= tmthing->z + FixedMul(FRACUNIT, tmthing->scale)
-			&& thing->z + thing->height + thing->momz  >= tmthing->z + FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz)
+			&& thing->z + thing->height + thing->momz  >= tmthing->z + FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
+			&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && thing->eflags & MFE_VERTICALFLIP))
 				P_DamageMobj(thing, tmthing, tmthing, 1, 0);
 		}
 		else if (thing->z >= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale)
-		&& thing->z + thing->momz <= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz)
+		&& thing->z + thing->momz <= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
+		&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && !(thing->eflags & MFE_VERTICALFLIP)))
 			P_DamageMobj(thing, tmthing, tmthing, 1, 0);
 	}
 	else if (thing->type == MT_SPIKE && thing->flags & MF_SOLID && tmthing->player) // unfortunate player falls into spike?!
@@ -950,11 +952,13 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		if (thing->eflags & MFE_VERTICALFLIP)
 		{
 			if (tmthing->z + tmthing->height <= thing->z - FixedMul(FRACUNIT, thing->scale)
-			&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale))
+			&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale)
+			&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && tmthing->eflags & MFE_VERTICALFLIP))
 				P_DamageMobj(tmthing, thing, thing, 1, 0);
 		}
 		else if (tmthing->z >= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
-		&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale))
+		&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
+		&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && !(tmthing->eflags & MFE_VERTICALFLIP)))
 			P_DamageMobj(tmthing, thing, thing, 1, 0);
 	}
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 4de36ff07..5a3651a54 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -280,6 +280,10 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_ROLL;
 			break;
 
+		case SPR2_FIRE:
+			spr2 = SPR2_CHRG;
+			break;
+
 		case SPR2_TWIN:
 			spr2 = SPR2_ROLL;
 			break;
@@ -470,6 +474,8 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		player->panim = PA_ABILITY;
 		break;
 	case S_PLAY_SPINDASH: // ...but the act of SPINDASHING is charability2 specific.
+	case S_PLAY_CHARGE:
+	case S_PLAY_FIRE:
 	case S_PLAY_MELEE:
 	case S_PLAY_MELEE_FINISH:
 	case S_PLAY_MELEE_LANDING:
@@ -517,7 +523,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 				else
 					mobj->tics = 1;
 			}
-			else if (player->panim == PA_ABILITY2 && player->charability2 == CA2_SPINDASH)
+			else if (player->panim == PA_ABILITY2 && (player->charability2 == CA2_SPINDASH || state == S_PLAY_CHARGE))
 			{
 				fixed_t step = (player->maxdash - player->mindash)/4;
 				speed = (player->dashspeed - player->mindash);
diff --git a/src/p_user.c b/src/p_user.c
index 59142edd0..3b505651b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3775,10 +3775,9 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 				{
 					player->mo->momx = player->cmomx;
 					player->mo->momy = player->cmomy;
-					player->pflags |= PF_STARTDASH|PF_SPINNING;
+					player->pflags |= (PF_USEDOWN|PF_STARTDASH|PF_SPINNING);
 					player->dashspeed = player->mindash;
 					P_SetPlayerMobjState(player->mo, S_PLAY_SPINDASH);
-					player->pflags |= PF_USEDOWN;
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_s3kab); // Make the rev sound! Previously sfx_spndsh.
 				}
@@ -3809,15 +3808,97 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					&& !player->climbing && !player->mo->momz && onground && (player->speed > FixedMul(5<<FRACBITS, player->mo->scale)
 						|| !canstand) && !(player->pflags & (PF_USEDOWN|PF_SPINNING)))
 				{
-					player->pflags |= PF_SPINNING;
+					player->pflags |= (PF_USEDOWN|PF_SPINNING);
 					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_spin);
-					player->pflags |= PF_USEDOWN;
+				}
+				else 
+				// Catapult the player from a spindash rev!
+				if (onground && !(player->pflags & PF_USEDOWN) && (player->pflags & PF_STARTDASH) && (player->pflags & PF_SPINNING))
+				{
+					player->pflags &= ~PF_STARTDASH;
+					if (player->powers[pw_carry] == CR_BRAKGOOP)
+						player->dashspeed = 0;
+
+					if (!((gametype == GT_RACE || gametype == GT_COMPETITION) && leveltime < 4*TICRATE))
+					{
+						if (player->dashspeed)
+						{
+							P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->dashspeed, player->mo->scale)); // catapult forward ho!!
+						}
+						else
+						{
+							P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+							player->pflags &= ~PF_SPINNING;
+						}
+
+						if (!player->spectator)
+							S_StartSound(player->mo, sfx_zoom);
+					}
+
+					player->dashspeed = 0;
+				}
+				break;
+			case CA2_GUNSLINGER:
+				if ((cmd->buttons & BT_USE)
+					&& !player->mo->momz && onground
+						&& canstand)
+				{
+					if (!player->dashspeed)
+					{
+						player->mo->momx = player->cmomx;
+						player->mo->momy = player->cmomy;
+						player->pflags |= PF_USEDOWN;
+						player->dashspeed = player->mindash;
+						P_SetPlayerMobjState(player->mo, S_PLAY_CHARGE);
+						if (!player->spectator)
+							S_StartSound(player->mo, sfx_s3k5a); // Make the rev sound! Previously sfx_spndsh.
+					}
+					else if (player->dashspeed < player->maxdash)
+						player->dashspeed += FRACUNIT;
+					else if (player->mo->tics != -1)
+					{
+						player->mo->tics = -1;
+						player->mo->frame = 0;
+					}
+				}
+				else if (player->dashspeed && (player->pflags & PF_USEDOWN))
+				{
+					mobj_t *cork;
+					const boolean maxspeed = (player->dashspeed >= player->maxdash);
+
+					P_SetPlayerMobjState(player->mo, S_PLAY_FIRE);
+
+					if (maxspeed && P_LookForEnemies(player, false, true) && player->mo->tracer)
+					{
+						player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, player->mo->target->x, player->mo->target->y);
+						cork = P_SpawnMissile(player->mo, player->mo->tracer, player->spinitem);
+					}
+					else
+					{
+						fixed_t z = (player->mo->z + player->mo->height/2);
+						if ((cork = P_SpawnPointMissile(player->mo, player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, FRACUNIT), player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, FRACUNIT), z, player->spinitem, player->mo->x, player->mo->y, z)))
+						{
+							cork->flags &= ~MF_NOGRAVITY;
+							if (!maxspeed)
+								cork->flags |= MF_NOCLIPTHING;
+						}
+					}
+					if (cork)
+					{
+						cork->momx = FixedDiv(FixedMul(cork->momx, player->dashspeed), mobjinfo[player->spinitem].speed);
+						cork->momy = FixedDiv(FixedMul(cork->momy, player->dashspeed), mobjinfo[player->spinitem].speed);
+						if (player->mo->tracer)
+							cork->momz = ((player->mo->tracer->z - player->mo->z) / (P_AproxDistance(player->mo->tracer->x - player->mo->x, player->mo->tracer->y - player->mo->y) / player->dashspeed));
+					}
+					P_SetTarget(&player->mo->tracer, NULL);
+					player->dashspeed = 0;
 				}
 				break;
 			case CA2_MELEE: // Melee attack
-				if (!(player->panim == PA_ABILITY2) && (cmd->buttons & BT_USE)
+				if (player->panim != PA_ABILITY2 && (cmd->buttons & BT_USE)
 				&& !player->mo->momz && onground && !(player->pflags & PF_USEDOWN)
 				&& canstand)
 				{
@@ -3870,33 +3951,6 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 		}
 	}
 
-	// Catapult the player from a spindash rev!
-	if (onground && !(player->pflags & PF_USEDOWN) && (player->pflags & PF_STARTDASH) && (player->pflags & PF_SPINNING))
-	{
-		player->pflags &= ~PF_STARTDASH;
-		if (player->powers[pw_carry] == CR_BRAKGOOP)
-			player->dashspeed = 0;
-
-		if (!((gametype == GT_RACE || gametype == GT_COMPETITION) && leveltime < 4*TICRATE))
-		{
-			if (player->dashspeed)
-			{
-				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-				P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->dashspeed, player->mo->scale)); // catapult forward ho!!
-			}
-			else
-			{
-				P_SetPlayerMobjState(player->mo, S_PLAY_STND);
-				player->pflags &= ~PF_SPINNING;
-			}
-
-			if (!player->spectator)
-				S_StartSound(player->mo, sfx_zoom);
-		}
-
-		player->dashspeed = 0;
-	}
-
 	if (onground && player->pflags & PF_STARTDASH)
 	{
 		if (player->mo->state-states != S_PLAY_SPINDASH)
@@ -4211,7 +4265,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						if (player->charability == CA_HOMINGTHOK && !player->homing)
 						{
-							if (P_LookForEnemies(player, true))
+							if (P_LookForEnemies(player, true, false))
 							{
 								if (player->mo->tracer)
 									player->homing = 3*TICRATE;
@@ -4885,13 +4939,14 @@ static void P_3dMovement(player_t *player)
 		// Allow a bit of movement while spinning
 		if (player->pflags & PF_SPINNING)
 		{
-			if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0))
+			if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
+			|| (player->pflags & PF_STARTDASH))
 				movepushforward = 0;
-			else if (!(player->pflags & PF_STARTDASH))
-				movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
 			else
-				movepushforward = 0;
+				movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
 		}
+		else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
+			movepushforward = 0;
 
 		movepushforward = FixedMul(movepushforward, player->mo->scale);
 
@@ -4928,13 +4983,14 @@ static void P_3dMovement(player_t *player)
 			{
 				// Stupid little movement prohibitor hack
 				// that REALLY shouldn't belong in analog code.
-				if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0))
+				if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
+				|| (player->pflags & PF_STARTDASH))
 					movepushforward = 0;
-				else if (!(player->pflags & PF_STARTDASH))
-					movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
 				else
-					movepushforward = 0;
+					movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
 			}
+			else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
+				movepushforward = 0;
 
 			movepushsideangle = controldirection;
 
@@ -4964,11 +5020,13 @@ static void P_3dMovement(player_t *player)
 		// Allow a bit of movement while spinning
 		if (player->pflags & PF_SPINNING)
 		{
-			if (!(player->pflags & PF_STARTDASH))
-				movepushside = FixedDiv(movepushside,16*FRACUNIT);
-			else
+			if ((player->pflags & PF_STARTDASH))
 				movepushside = 0;
+			else
+				movepushside = FixedDiv(movepushside,16*FRACUNIT);
 		}
+		else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
+			movepushside = 0;
 
 		// Finally move the player now that his speed/direction has been decided.
 		movepushside = FixedMul(movepushside, player->mo->scale);
@@ -6555,6 +6613,7 @@ static void P_MovePlayer(player_t *player)
 	// Control relinquishing stuff!
 	if ((player->powers[pw_carry] == CR_BRAKGOOP)
 	|| (player->pflags & PF_GLIDING && player->skidtime)
+	|| (player->charability2 == CA2_GUNSLINGER && player->mo->state-states == S_PLAY_FIRE)
 	|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 		player->pflags |= PF_FULLSTASIS;
 	else if (player->powers[pw_nocontrol])
@@ -7165,7 +7224,7 @@ static void P_MovePlayer(player_t *player)
 							case SH_ATTRACT:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								player->homing = 2;
-								if (P_LookForEnemies(player, false) && player->mo->tracer)
+								if (P_LookForEnemies(player, false, false) && player->mo->tracer)
 								{
 									player->pflags &= ~PF_NOJUMPDAMAGE;
 									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
@@ -7812,8 +7871,9 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 // P_LookForEnemies
 // Looks for something you can hit - Used for homing attack
 // If nonenemies is true, includes monitors and springs!
+// If abovehorizontal is true, you can look up, but your total vertical range is limited to compensate.
 //
-boolean P_LookForEnemies(player_t *player, boolean nonenemies)
+boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehorizontal)
 {
 	mobj_t *mo;
 	thinker_t *think;
@@ -7845,13 +7905,30 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies)
 		if (mo->type == MT_DETON) // Don't be STUPID, Sonic!
 			continue;
 
-		if (((mo->z > player->mo->z+FixedMul(MAXSTEPMOVE, player->mo->scale)) && !(player->mo->eflags & MFE_VERTICALFLIP))
-		|| ((mo->z+mo->height < player->mo->z+player->mo->height-FixedMul(MAXSTEPMOVE, player->mo->scale)) && (player->mo->eflags & MFE_VERTICALFLIP))) // Reverse gravity check - Flame.
-			continue; // Don't home upwards!
+		{
+			fixed_t dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
 
-		if (P_AproxDistance(P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y),
-			player->mo->z-mo->z) > FixedMul(RING_DIST, player->mo->scale))
-			continue; // out of range
+			if (abovehorizontal)
+			{
+				angle_t ang = R_PointToAngle2(0, player->mo->z, dist, mo->z) + ANGLE_45;
+				if (ang > ANGLE_90)
+					continue; // Don't home outside of desired angle!
+			}
+			else // Don't home upwards!
+			{
+				if (player->mo->eflags & MFE_VERTICALFLIP)
+				{
+					if (mo->z > player->mo->z+FixedMul(MAXSTEPMOVE, player->mo->scale))
+						continue;
+				}
+				else if (mo->z+mo->height < player->mo->z+player->mo->height-FixedMul(MAXSTEPMOVE, player->mo->scale))
+					continue;
+			}
+
+			if (P_AproxDistance(dist,
+				player->mo->z-mo->z) > FixedMul(RING_DIST, player->mo->scale))
+				continue; // out of range
+		}
 
 		if ((twodlevel || player->mo->flags2 & MF2_TWOD)
 		&& abs(player->mo->y-mo->y) > player->mo->radius)

From a6f5357cbd97650a8570051cccfd16406834d7b4 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 21 Mar 2017 02:06:24 +0000
Subject: [PATCH 080/119] * fixed spindash * fixed whitespace

---
 src/p_user.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 3b505651b..55debb70b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3813,7 +3813,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_spin);
 				}
-				else 
+				else
 				// Catapult the player from a spindash rev!
 				if (onground && !(player->pflags & PF_USEDOWN) && (player->pflags & PF_STARTDASH) && (player->pflags & PF_SPINNING))
 				{
@@ -3826,7 +3826,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						if (player->dashspeed)
 						{
 							P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->dashspeed, player->mo->scale)); // catapult forward ho!!
+							P_InstaThrust(player->mo, player->mo->angle, (player->speed = FixedMul(player->dashspeed, player->mo->scale))); // catapult forward ho!!
 						}
 						else
 						{

From 9d9f3e5e1a46f7c0e807609b46f169cc085ba7f4 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 21 Mar 2017 15:59:13 +0000
Subject: [PATCH 081/119] Changed the race HUD to bounce a bit because I'm
 having fun.

---
 src/st_stuff.c | 32 ++++++++++++++++++++++++--------
 1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/src/st_stuff.c b/src/st_stuff.c
index f1d6bc76a..fb28f2378 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1486,14 +1486,30 @@ static void ST_drawMatchHUD(void)
 
 static inline void ST_drawRaceHUD(void)
 {
-	if (leveltime > TICRATE && leveltime <= 2*TICRATE)
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(race3->width))/2), (INT32)(SCY(BASEVIDHEIGHT/2)), V_NOSCALESTART, race3);
-	else if (leveltime > 2*TICRATE && leveltime <= 3*TICRATE)
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(race2->width))/2), (INT32)(SCY(BASEVIDHEIGHT/2)), V_NOSCALESTART, race2);
-	else if (leveltime > 3*TICRATE && leveltime <= 4*TICRATE)
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(race1->width))/2), (INT32)(SCY(BASEVIDHEIGHT/2)), V_NOSCALESTART, race1);
-	else if (leveltime > 4*TICRATE && leveltime <= 5*TICRATE)
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(racego->width))/2), (INT32)(SCY(BASEVIDHEIGHT/2)), V_NOSCALESTART, racego);
+	if (leveltime >= TICRATE && leveltime < 5*TICRATE)
+	{
+		INT32 height = (BASEVIDHEIGHT/2);
+		INT32 bounce = (leveltime % TICRATE);
+		patch_t **racenum;
+		switch (leveltime/TICRATE)
+		{
+			case 1:
+				racenum = &race3;
+				break;
+			case 2:
+				racenum = &race2;
+				break;
+			case 3:
+				racenum = &race1;
+				break;
+			default:
+				racenum = &racego;
+				break;
+		}
+		if (bounce < 3)
+			height -= (2 - bounce);
+		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT((*racenum)->width))/2), (INT32)(SCY(height)), V_NOSCALESTART, *racenum);
+	}
 
 	if (circuitmap)
 	{

From 7ac0373dbf428477b67fc4f9d83b00cdb481d480 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 21 Mar 2017 16:04:49 +0000
Subject: [PATCH 082/119] After usability complaints:

* CA2_MELEE/CA_TWINSPIN combination abilities (ground hit, somersalt) disabled.
* Enabled movement controls whilst using CA2_MELEE in the air.
* CA2_GUNSLINGER no longer requires charging. In exchange, it involves a cooldown in which you can't move.
* CA2_GUNSLINGER lock-on distance improved, and aiming allowance reduced to compensate.
* Lots of cleanup/gravflip allowances for homing/targeting code.

* Unrelated: Flickies now properly face the direction they're supposed to when getting knocked out of the egg capsule.
* Unrelated: Thok trail fades like spindash trail now.
* Unrelated: Using CA_HOMINGATTACK when nowhere near an enemy uncurls you.
---
 src/dehacked.c    |   2 +-
 src/info.c        |   5 +-
 src/info.h        |   3 +-
 src/lua_baselib.c |   4 +-
 src/p_local.h     |   2 +-
 src/p_mobj.c      |  17 +++---
 src/p_user.c      | 140 ++++++++++++++++++++++------------------------
 7 files changed, 80 insertions(+), 93 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 345184048..590061ea1 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3937,8 +3937,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_BOUNCE_LANDING",
 
 	// CA2_GUNSLINGER
-	"S_PLAY_CHARGE",
 	"S_PLAY_FIRE",
+	"S_PLAY_FIRE_FINISH",
 
 	// CA_TWINSPIN
 	"S_PLAY_TWINSPIN",
diff --git a/src/info.c b/src/info.c
index 4434a6908..e6adb69c3 100644
--- a/src/info.c
+++ b/src/info.c
@@ -419,7 +419,6 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"BNCE",
 	"BLND",
 
-	"CHRG",
 	"FIRE",
 
 	"TWIN",
@@ -533,8 +532,8 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_BLND|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_BOUNCE,  0, S_PLAY_BOUNCE_LANDING}, // S_PLAY_BOUNCE_LANDING
 
 	// CA2_GUNSLINGER
-	{SPR_PLAY, SPR2_CHRG,                 2, {NULL},           0,  0, S_PLAY_CHARGE}, // S_PLAY_CHARGE
-	{SPR_PLAY, SPR2_FIRE|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_STND,  0, S_PLAY_FIRE},   // S_PLAY_FIRE
+	{SPR_PLAY, SPR2_FIRE|FF_SPR2ENDSTATE,  2, {NULL}, S_PLAY_FIRE_FINISH, 0, S_PLAY_FIRE},   // S_PLAY_FIRE
+	{SPR_PLAY, SPR2_FIRE,                 15, {NULL},        S_PLAY_STND, 0, S_PLAY_STND},   // S_PLAY_FIRE_FINISH
 
 	// CA_TWINSPIN
 	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
diff --git a/src/info.h b/src/info.h
index 15f25b493..78ed50592 100644
--- a/src/info.h
+++ b/src/info.h
@@ -632,7 +632,6 @@ enum playersprite
 	SPR2_BNCE, // bounce
 	SPR2_BLND, // bounce landing
 
-	SPR2_CHRG, // charge
 	SPR2_FIRE, // fire
 
 	SPR2_TWIN, // twinspin
@@ -743,8 +742,8 @@ typedef enum state
 	S_PLAY_BOUNCE_LANDING,
 
 	// CA2_GUNSLINGER
-	S_PLAY_CHARGE,
 	S_PLAY_FIRE,
+	S_PLAY_FIRE_FINISH,
 
 	// CA_TWINSPIN
 	S_PLAY_TWINSPIN,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index ffafb7f34..1040f5901 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1055,12 +1055,12 @@ static int lib_pLookForEnemies(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
 	boolean nonenemies = lua_opttrueboolean(L, 2);
-	boolean abovehorizontal = lua_opttrueboolean(L, 3);
+	boolean bullet = lua_opttrueboolean(L, 3);
 	NOHUD
 	INLEVEL
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
-	lua_pushboolean(L, P_LookForEnemies(player, nonenemies, abovehorizontal));
+	lua_pushboolean(L, P_LookForEnemies(player, nonenemies, bullet));
 	return 1;
 }
 
diff --git a/src/p_local.h b/src/p_local.h
index b7738ed98..14d96f693 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -174,7 +174,7 @@ fixed_t P_ReturnThrustX(mobj_t *mo, angle_t angle, fixed_t move);
 fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move);
 void P_InstaThrustEvenIn2D(mobj_t *mo, angle_t angle, fixed_t move);
 
-boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehorizontal);
+boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet);
 void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
 void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
 boolean P_SuperReady(player_t *player);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 5a3651a54..696a68738 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -280,10 +280,6 @@ UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
 			spr2 = SPR2_ROLL;
 			break;
 
-		case SPR2_FIRE:
-			spr2 = SPR2_CHRG;
-			break;
-
 		case SPR2_TWIN:
 			spr2 = SPR2_ROLL;
 			break;
@@ -474,8 +470,8 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		player->panim = PA_ABILITY;
 		break;
 	case S_PLAY_SPINDASH: // ...but the act of SPINDASHING is charability2 specific.
-	case S_PLAY_CHARGE:
 	case S_PLAY_FIRE:
+	case S_PLAY_FIRE_FINISH:
 	case S_PLAY_MELEE:
 	case S_PLAY_MELEE_FINISH:
 	case S_PLAY_MELEE_LANDING:
@@ -523,7 +519,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 				else
 					mobj->tics = 1;
 			}
-			else if (player->panim == PA_ABILITY2 && (player->charability2 == CA2_SPINDASH || state == S_PLAY_CHARGE))
+			else if (player->panim == PA_ABILITY2 && player->charability2 == CA2_SPINDASH)
 			{
 				fixed_t step = (player->maxdash - player->mindash)/4;
 				speed = (player->dashspeed - player->mindash);
@@ -3210,18 +3206,19 @@ static void P_PlayerZMovement(mobj_t *mo)
 					}
 				}
 
-				if (mo->health)
+				if (mo->health && !P_CheckDeathPitCollide(mo))
 				{
 					if (mo->player->pflags & PF_GLIDING) // ground gliding
 					{
 						mo->player->skidtime = TICRATE;
 						mo->tics = -1;
 					}
-					else if (mo->player->charability2 == CA2_MELEE && ((mo->player->charability == CA_TWINSPIN && mo->player->panim == PA_ABILITY) || (mo->player->panim == PA_ABILITY2 && mo->state-states != S_PLAY_MELEE_LANDING)))
+					else if (mo->player->charability2 == CA2_MELEE && (mo->player->panim == PA_ABILITY2 && mo->state-states != S_PLAY_MELEE_LANDING))
 					{
 						P_SetPlayerMobjState(mo, S_PLAY_MELEE_LANDING);
 						mo->tics = (mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(mo->movefactor)))>>FRACBITS;
 						S_StartSound(mo, sfx_s3k8b);
+						mo->player->pflags |= PF_FULLSTASIS;
 					}
 					else if (mo->player->pflags & PF_JUMPED || (mo->player->pflags & (PF_SPINNING|PF_USEDOWN)) != (PF_SPINNING|PF_USEDOWN)
 					|| mo->player->powers[pw_tailsfly] || mo->state-states == S_PLAY_FLY_TIRED)
@@ -3302,7 +3299,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 						}
 					}
 
-					if (mo->player->pflags & PF_BOUNCING && !P_CheckDeathPitCollide(mo))
+					if (mo->player->pflags & PF_BOUNCING)
 					{
 						mo->momz *= -1;
 						P_DoAbilityBounce(mo->player, true);
@@ -7276,7 +7273,6 @@ void P_MobjThinker(mobj_t *mobj)
 					ns = 4 * FRACUNIT;
 					mo2->momx = FixedMul(FINESINE(fa),ns);
 					mo2->momy = FixedMul(FINECOSINE(fa),ns);
-					mo2->angle = fa << ANGLETOFINESHIFT;
 
 					if (P_RandomChance(FRACUNIT/4)) // I filled a spreadsheet trying to get the equivalent chance to the original P_RandomByte hack!
 						S_StartSound(mo2, mobj->info->deathsound);
@@ -7288,6 +7284,7 @@ void P_MobjThinker(mobj_t *mobj)
 					P_SetTarget(&flicky->target, mo2);
 					flicky->momx = mo2->momx;
 					flicky->momy = mo2->momy;
+					flicky->angle = fa << ANGLETOFINESHIFT;
 				}
 
 				mobj->fuse--;
diff --git a/src/p_user.c b/src/p_user.c
index 55debb70b..17f643c57 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1576,6 +1576,12 @@ void P_SpawnThokMobj(player_t *player)
 		// scale
 		P_SetScale(mobj, player->mo->scale);
 		mobj->destscale = player->mo->scale;
+
+		if (type == MT_THOK) // spintrail-specific modification for MT_THOK
+		{
+			mobj->frame = FF_TRANS70;
+			mobj->fuse = mobj->tics;
+		}
 	}
 
 	P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do
@@ -3842,59 +3848,43 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 				}
 				break;
 			case CA2_GUNSLINGER:
-				if ((cmd->buttons & BT_USE)
-					&& !player->mo->momz && onground
+				if ((cmd->buttons & BT_USE) && !(player->pflags & PF_USEDOWN)
+					&& !player->mo->momz && onground // && !player->weapondelay
 						&& canstand)
 				{
-					if (!player->dashspeed)
-					{
-						player->mo->momx = player->cmomx;
-						player->mo->momy = player->cmomy;
-						player->pflags |= PF_USEDOWN;
-						player->dashspeed = player->mindash;
-						P_SetPlayerMobjState(player->mo, S_PLAY_CHARGE);
-						if (!player->spectator)
-							S_StartSound(player->mo, sfx_s3k5a); // Make the rev sound! Previously sfx_spndsh.
-					}
-					else if (player->dashspeed < player->maxdash)
-						player->dashspeed += FRACUNIT;
-					else if (player->mo->tics != -1)
-					{
-						player->mo->tics = -1;
-						player->mo->frame = 0;
-					}
-				}
-				else if (player->dashspeed && (player->pflags & PF_USEDOWN))
-				{
-					mobj_t *cork;
-					const boolean maxspeed = (player->dashspeed >= player->maxdash);
+					mobj_t *bullet;
 
 					P_SetPlayerMobjState(player->mo, S_PLAY_FIRE);
 
-					if (maxspeed && P_LookForEnemies(player, false, true) && player->mo->tracer)
+#define zpos(posmo) (posmo->z + (posmo->height - mobjinfo[player->spinitem].height)/2)
+					if (P_LookForEnemies(player, false, true) && player->mo->tracer)
 					{
-						player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, player->mo->target->x, player->mo->target->y);
-						cork = P_SpawnMissile(player->mo, player->mo->tracer, player->spinitem);
+						bullet = P_SpawnPointMissile(player->mo, player->mo->tracer->x, player->mo->tracer->y, zpos(player->mo->tracer), player->spinitem, player->mo->x, player->mo->y, zpos(player->mo));
+						if (!demoplayback || P_AnalogMove(player))
+						{
+							if (player == &players[consoleplayer])
+								localangle = player->mo->angle;
+							else if (player == &players[secondarydisplayplayer])
+								localangle2 = player->mo->angle;
+						}
 					}
 					else
 					{
-						fixed_t z = (player->mo->z + player->mo->height/2);
-						if ((cork = P_SpawnPointMissile(player->mo, player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, FRACUNIT), player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, FRACUNIT), z, player->spinitem, player->mo->x, player->mo->y, z)))
+						bullet = P_SpawnPointMissile(player->mo, player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, FRACUNIT), player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, FRACUNIT), zpos(player->mo), player->spinitem, player->mo->x, player->mo->y, zpos(player->mo));
+						if (bullet)
 						{
-							cork->flags &= ~MF_NOGRAVITY;
-							if (!maxspeed)
-								cork->flags |= MF_NOCLIPTHING;
+							bullet->flags &= ~MF_NOGRAVITY;
+							bullet->momx >>= 1;
+							bullet->momy >>= 1;
 						}
 					}
-					if (cork)
-					{
-						cork->momx = FixedDiv(FixedMul(cork->momx, player->dashspeed), mobjinfo[player->spinitem].speed);
-						cork->momy = FixedDiv(FixedMul(cork->momy, player->dashspeed), mobjinfo[player->spinitem].speed);
-						if (player->mo->tracer)
-							cork->momz = ((player->mo->tracer->z - player->mo->z) / (P_AproxDistance(player->mo->tracer->x - player->mo->x, player->mo->tracer->y - player->mo->y) / player->dashspeed));
-					}
+#undef zpos
+
 					P_SetTarget(&player->mo->tracer, NULL);
-					player->dashspeed = 0;
+					player->mo->momx >>= 1;
+					player->mo->momy >>= 1;
+					player->pflags |= PF_USEDOWN;
+					// player->weapondelay = TICRATE/2;
 				}
 				break;
 			case CA2_MELEE: // Melee attack
@@ -3903,16 +3893,18 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 				&& canstand)
 				{
 					P_ResetPlayer(player);
+					player->pflags |= PF_THOKKED;
+#if 0
 					if ((player->charability == CA_TWINSPIN) && (player->speed > FixedMul(player->runspeed, player->mo->scale)))
 					{
 						P_DoJump(player, false);
 						player->jumping = 0;
 						player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2); // NOT 1.5 times the jump height, but 2.25 times.
-						player->pflags |= PF_THOKKED;
 						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
 						S_StartSound(player->mo, sfx_s3k8b);
 					}
 					else
+#endif
 					{
 						player->mo->z += P_MobjFlip(player->mo);
 						P_SetObjectMomZ(player->mo, player->mindash, false);
@@ -4265,10 +4257,13 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						if (player->charability == CA_HOMINGTHOK && !player->homing)
 						{
-							if (P_LookForEnemies(player, true, false))
+							player->pflags &= ~PF_NOJUMPDAMAGE;
+							if (P_LookForEnemies(player, true, false) && player->mo->tracer)
+								player->homing = 3*TICRATE;
+							else
 							{
-								if (player->mo->tracer)
-									player->homing = 3*TICRATE;
+								P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+								player->pflags &= ~PF_JUMPED;
 							}
 						}
 
@@ -4945,8 +4940,6 @@ static void P_3dMovement(player_t *player)
 			else
 				movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
 		}
-		else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
-			movepushforward = 0;
 
 		movepushforward = FixedMul(movepushforward, player->mo->scale);
 
@@ -4989,8 +4982,6 @@ static void P_3dMovement(player_t *player)
 				else
 					movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
 			}
-			else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
-				movepushforward = 0;
 
 			movepushsideangle = controldirection;
 
@@ -5025,8 +5016,6 @@ static void P_3dMovement(player_t *player)
 			else
 				movepushside = FixedDiv(movepushside,16*FRACUNIT);
 		}
-		else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
-			movepushside = 0;
 
 		// Finally move the player now that his speed/direction has been decided.
 		movepushside = FixedMul(movepushside, player->mo->scale);
@@ -6613,8 +6602,8 @@ static void P_MovePlayer(player_t *player)
 	// Control relinquishing stuff!
 	if ((player->powers[pw_carry] == CR_BRAKGOOP)
 	|| (player->pflags & PF_GLIDING && player->skidtime)
-	|| (player->charability2 == CA2_GUNSLINGER && player->mo->state-states == S_PLAY_FIRE)
-	|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+	|| (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
+	|| (player->charability2 == CA2_MELEE && player->mo->state-states == S_PLAY_MELEE_LANDING))
 		player->pflags |= PF_FULLSTASIS;
 	else if (player->powers[pw_nocontrol])
 	{
@@ -7871,15 +7860,18 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 // P_LookForEnemies
 // Looks for something you can hit - Used for homing attack
 // If nonenemies is true, includes monitors and springs!
-// If abovehorizontal is true, you can look up, but your total vertical range is limited to compensate.
+// If bullet is true, you can look up and the distance is further,
+// but your total angle span you can look is limited to compensate.
 //
-boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehorizontal)
+boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 {
 	mobj_t *mo;
 	thinker_t *think;
 	mobj_t *closestmo = NULL;
-	angle_t an;
 	const UINT32 targetmask = (MF_ENEMY|MF_BOSS|(nonenemies ? (MF_MONITOR|MF_SPRING) : 0));
+	const fixed_t maxdist = FixedMul((bullet ? RING_DIST*2 : RING_DIST), player->mo->scale);
+	const angle_t span = (bullet ? ANG30 : ANGLE_90);
+	fixed_t dist, closestdist = 0;
 
 	for (think = thinkercap.next; think != &thinkercap; think = think->next)
 	{
@@ -7906,11 +7898,11 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehori
 			continue;
 
 		{
-			fixed_t dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
-
-			if (abovehorizontal)
+			fixed_t zdist = (player->mo->z + player->mo->height/2) - (mo->z + mo->height/2);
+			dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
+			if (bullet)
 			{
-				angle_t ang = R_PointToAngle2(0, player->mo->z, dist, mo->z) + ANGLE_45;
+				angle_t ang = R_PointToAngle2(0, 0, dist, zdist) + ANGLE_45;
 				if (ang > ANGLE_90)
 					continue; // Don't home outside of desired angle!
 			}
@@ -7918,15 +7910,15 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehori
 			{
 				if (player->mo->eflags & MFE_VERTICALFLIP)
 				{
-					if (mo->z > player->mo->z+FixedMul(MAXSTEPMOVE, player->mo->scale))
-						continue;
+					if (mo->z+mo->height < player->mo->z+player->mo->height-FixedMul(MAXSTEPMOVE, player->mo->scale))
+							continue;
 				}
-				else if (mo->z+mo->height < player->mo->z+player->mo->height-FixedMul(MAXSTEPMOVE, player->mo->scale))
+				else if (mo->z > player->mo->z+FixedMul(MAXSTEPMOVE, player->mo->scale))
 					continue;
 			}
 
-			if (P_AproxDistance(dist,
-				player->mo->z-mo->z) > FixedMul(RING_DIST, player->mo->scale))
+			dist = P_AproxDistance(dist, zdist);
+			if (dist > maxdist)
 				continue; // out of range
 		}
 
@@ -7937,20 +7929,17 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehori
 		if (mo->type == MT_PLAYER) // Don't chase after other players!
 			continue;
 
-		if (closestmo && P_AproxDistance(P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y),
-			player->mo->z-mo->z) > P_AproxDistance(P_AproxDistance(player->mo->x-closestmo->x,
-			player->mo->y-closestmo->y), player->mo->z-closestmo->z))
+		if (closestmo && dist > closestdist)
 			continue;
 
-		an = R_PointToAngle2(player->mo->x, player->mo->y, mo->x, mo->y) - player->mo->angle;
-
-		if (an > ANGLE_90 && an < ANGLE_270)
+		if ((R_PointToAngle2(player->mo->x, player->mo->y, mo->x, mo->y) - player->mo->angle + span) > span*2)
 			continue; // behind back
 
 		if (!P_CheckSight(player->mo, mo))
 			continue; // out of sight
 
 		closestmo = mo;
+		closestdist = dist;
 	}
 
 	if (closestmo)
@@ -7966,6 +7955,7 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean abovehori
 
 void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 {
+	fixed_t zdist;
 	fixed_t dist;
 	fixed_t ns = 0;
 
@@ -7986,8 +7976,8 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 	}
 
 	// change slope
-	dist = P_AproxDistance(P_AproxDistance(enemy->x - source->x, enemy->y - source->y),
-		enemy->z - source->z);
+	zdist = (P_MobjFlip(source) ? (enemy->z + enemy->height) - (source->z + source->height) : (enemy->z - source->z));
+	dist = P_AproxDistance(P_AproxDistance(enemy->x - source->x, enemy->y - source->y), zdist);
 
 	if (dist < 1)
 		dist = 1;
@@ -8011,7 +8001,7 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 
 	source->momx = FixedMul(FixedDiv(enemy->x - source->x, dist), ns);
 	source->momy = FixedMul(FixedDiv(enemy->y - source->y, dist), ns);
-	source->momz = FixedMul(FixedDiv(enemy->z - source->z, dist), ns);
+	source->momz = FixedMul(FixedDiv(zdist, dist), ns);
 }
 
 // Search for emeralds
@@ -9121,7 +9111,9 @@ void P_PlayerThink(player_t *player)
 		if (player->panim != PA_ABILITY)
 			P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
 	}
-	else if ((player->pflags & PF_JUMPED) && !player->powers[pw_super] && ((player->charflags & SF_NOJUMPSPIN && player->pflags & PF_NOJUMPDAMAGE && player->panim != PA_ROLL) || (!(player->charflags & SF_NOJUMPSPIN) && player->panim != PA_JUMP)))
+	else if ((player->pflags & PF_JUMPED)
+	&& ((player->charflags & SF_NOJUMPSPIN && !(player->pflags & PF_NOJUMPDAMAGE) && player->panim != PA_ROLL)
+	|| (!(player->charflags & SF_NOJUMPSPIN) && player->panim != PA_JUMP)))
 	{
 		if (!(player->charflags & SF_NOJUMPSPIN))
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);

From 90893c02a3aa05b176474ea0c5e9caf6c0a59a2a Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 21 Mar 2017 23:24:57 +0000
Subject: [PATCH 083/119] * Added restriction to prevent combination of
 CA2_GUNSLINGER and ringslinger on. * Swapped usage of player->spinitem to
 player->revitem for bullet type selection, since spinitem is used coming out
 of zoom tubes (which are universal). * Switched the optional boolean for
 "bullet" in the Lua wrapper for P_LookForEnemies to be false by default. *
 Allowed CA2_GUNSLINGER users to target Detons. * Cleaned up some code here
 and there.

---
 src/lua_baselib.c |  2 +-
 src/p_user.c      | 16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 1040f5901..2b4028a0e 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1055,7 +1055,7 @@ static int lib_pLookForEnemies(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
 	boolean nonenemies = lua_opttrueboolean(L, 2);
-	boolean bullet = lua_opttrueboolean(L, 3);
+	boolean bullet = lua_optboolean(L, 3);
 	NOHUD
 	INLEVEL
 	if (!player)
diff --git a/src/p_user.c b/src/p_user.c
index 17f643c57..d83cb3139 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -969,6 +969,7 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 	P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_TRANS);
 
 	player->mo->momx = player->mo->momy = player->mo->momz = 0;
+	player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 
 	if (giverings)
 		player->rings = 50;
@@ -3849,17 +3850,17 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 				break;
 			case CA2_GUNSLINGER:
 				if ((cmd->buttons & BT_USE) && !(player->pflags & PF_USEDOWN)
-					&& !player->mo->momz && onground // && !player->weapondelay
+					&& !player->mo->momz && onground && !player->weapondelay
 						&& canstand)
 				{
 					mobj_t *bullet;
 
 					P_SetPlayerMobjState(player->mo, S_PLAY_FIRE);
 
-#define zpos(posmo) (posmo->z + (posmo->height - mobjinfo[player->spinitem].height)/2)
+#define zpos(posmo) (posmo->z + (posmo->height - mobjinfo[player->revitem].height)/2)
 					if (P_LookForEnemies(player, false, true) && player->mo->tracer)
 					{
-						bullet = P_SpawnPointMissile(player->mo, player->mo->tracer->x, player->mo->tracer->y, zpos(player->mo->tracer), player->spinitem, player->mo->x, player->mo->y, zpos(player->mo));
+						bullet = P_SpawnPointMissile(player->mo, player->mo->tracer->x, player->mo->tracer->y, zpos(player->mo->tracer), player->revitem, player->mo->x, player->mo->y, zpos(player->mo));
 						if (!demoplayback || P_AnalogMove(player))
 						{
 							if (player == &players[consoleplayer])
@@ -3870,7 +3871,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					}
 					else
 					{
-						bullet = P_SpawnPointMissile(player->mo, player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, FRACUNIT), player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, FRACUNIT), zpos(player->mo), player->spinitem, player->mo->x, player->mo->y, zpos(player->mo));
+						bullet = P_SpawnPointMissile(player->mo, player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, FRACUNIT), player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, FRACUNIT), zpos(player->mo), player->revitem, player->mo->x, player->mo->y, zpos(player->mo));
 						if (bullet)
 						{
 							bullet->flags &= ~MF_NOGRAVITY;
@@ -3884,7 +3885,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					player->mo->momx >>= 1;
 					player->mo->momy >>= 1;
 					player->pflags |= PF_USEDOWN;
-					// player->weapondelay = TICRATE/2;
+					P_SetWeaponDelay(player, TICRATE/2);
 				}
 				break;
 			case CA2_MELEE: // Melee attack
@@ -7894,7 +7895,7 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 		if ((mo->flags & (MF_ENEMY|MF_BOSS)) && !(mo->flags & MF_SHOOTABLE)) // don't aim at something you can't shoot at anyway (see Egg Guard or Minus)
 			continue;
 
-		if (mo->type == MT_DETON) // Don't be STUPID, Sonic!
+		if (!bullet && mo->type == MT_DETON) // Don't be STUPID, Sonic!
 			continue;
 
 		{
@@ -7902,8 +7903,7 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 			dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
 			if (bullet)
 			{
-				angle_t ang = R_PointToAngle2(0, 0, dist, zdist) + ANGLE_45;
-				if (ang > ANGLE_90)
+				if ((R_PointToAngle2(0, 0, dist, zdist) + ANGLE_45) > ANGLE_90)
 					continue; // Don't home outside of desired angle!
 			}
 			else // Don't home upwards!

From ddf8db12aff93bf93521237fc8d501b540b75cdf Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Wed, 22 Mar 2017 18:51:30 +0000
Subject: [PATCH 084/119] Big commit, sorry. I broke several things trying to
 get other things to work and it's only now that all the code that worked
 yesterday works today!

*P_LookForEnemies is now side-effect-less and only provides a pointer to the found mobj
*player-jumping is dead, long live PF_STARTJUMP
*per Mystic's request, CA2_GUNSLINGER has a targeting icon. It also has a more restricted vertical aiming range.
*mobj for this is in the game as requested
*fast teetering animation flag
*general code cleanup
---
 src/d_clisrv.c          |   2 -
 src/d_clisrv.h          |   1 -
 src/d_player.h          |  13 ++-
 src/dehacked.c          |  29 +++---
 src/hardware/hw_light.c |   5 +-
 src/info.c              |  95 ++++++++++++++++----
 src/info.h              |  26 ++++--
 src/lua_baselib.c       |   2 +-
 src/lua_playerlib.c     |   4 -
 src/p_floor.c           |   5 +-
 src/p_local.h           |   2 +-
 src/p_map.c             |   6 +-
 src/p_mobj.c            |  28 ++++--
 src/p_saveg.c           |   2 -
 src/p_user.c            | 194 ++++++++++++++++++++--------------------
 src/st_stuff.c          |   2 +-
 16 files changed, 250 insertions(+), 166 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 847c22169..2db27a693 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -552,7 +552,6 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 	rsp->playerspinheight = (fixed_t)LONG(players[i].spinheight);
 
 	rsp->speed = (fixed_t)LONG(players[i].speed);
-	rsp->jumping = players[i].jumping;
 	rsp->secondjump = players[i].secondjump;
 	rsp->fly1 = players[i].fly1;
 	rsp->glidetime = (tic_t)LONG(players[i].glidetime);
@@ -682,7 +681,6 @@ static void resynch_read_player(resynch_pak *rsp)
 	players[i].spinheight = (fixed_t)LONG(rsp->playerspinheight);
 
 	players[i].speed = (fixed_t)LONG(rsp->speed);
-	players[i].jumping = rsp->jumping;
 	players[i].secondjump = rsp->secondjump;
 	players[i].fly1 = rsp->fly1;
 	players[i].glidetime = (tic_t)LONG(rsp->glidetime);
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index c2ec1ad58..1ca82fdc5 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -196,7 +196,6 @@ typedef struct
 	fixed_t playerspinheight;
 
 	fixed_t speed;
-	UINT8 jumping;
 	UINT8 secondjump;
 	UINT8 fly1;
 	tic_t glidetime;
diff --git a/src/d_player.h b/src/d_player.h
index 8db170089..9f8bcd8b4 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -45,6 +45,7 @@ typedef enum
 	SF_MARIODAMAGE      = SF_NOJUMPDAMAGE|SF_STOMPDAMAGE, // The Mario method of being able to damage enemies, etc.
 	SF_MACHINE          = 1<<10, // Beep boop. Are you a robot?
 	SF_DASHMODE         = 1<<11, // Sonic Advance 2 style top speed increase?
+	SF_FASTEDGE         = 1<<12, // Faster edge teeter?
 	// free up to and including 1<<31
 } skinflags_t;
 
@@ -119,9 +120,8 @@ typedef enum
 	// Did you get a time-over?
 	PF_TIMEOVER = 1<<10,
 
-	PF_TEMPSLOT1 = 1<<11,
-
 	// Character action status
+	PF_STARTJUMP = 1<<11,
 	PF_JUMPED    = 1<<12,
 	PF_SPINNING  = 1<<13,
 	PF_STARTDASH = 1<<14,
@@ -133,7 +133,8 @@ typedef enum
 	// Sliding (usually in water) like Labyrinth/Oil Ocean
 	PF_SLIDING   = 1<<17,
 
-	PF_TEMPSLOT2         = 1<<18,
+	// Bouncing
+	PF_BOUNCING  = 1<<18,
 
 	/*** NIGHTS STUFF ***/
 	PF_TRANSFERTOCLOSEST = 1<<19,
@@ -158,10 +159,7 @@ typedef enum
 	// Jump damage?
 	PF_NOJUMPDAMAGE   = 1<<29,
 
-	// Bouncing
-	PF_BOUNCING          = 1<<30
-
-	// 1<<31 is free
+	// up to 1<<31 is free
 } pflags_t;
 
 typedef enum
@@ -381,7 +379,6 @@ typedef struct player_s
 	UINT8 gotcontinue; // Got continue from this stage?
 
 	fixed_t speed; // Player's speed (distance formula of MOMX and MOMY values)
-	UINT8 jumping; // Holding down jump button
 	UINT8 secondjump; // Jump counter
 
 	UINT8 fly1; // Tails flying
diff --git a/src/dehacked.c b/src/dehacked.c
index 590061ea1..2661fc2fd 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -5910,14 +5910,18 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FOUR2",
 	"S_FIVE2",
 
+	"S_LOCKON",
+
 	// Tag Sign
-	"S_TTAG1",
+	"S_TTAG",
 
 	// Got Flag Sign
-	"S_GOTFLAG1",
-	"S_GOTFLAG2",
-	"S_GOTFLAG3",
-	"S_GOTFLAG4",
+	"S_GOTREDFLAG1",
+	"S_GOTREDFLAG2",
+	"S_GOTBLUEFLAG1",
+	"S_GOTBLUEFLAG2",
+
+	"S_CORK",
 
 	// Red Ring
 	"S_RRNG1",
@@ -6708,9 +6712,10 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_SCORE", // score logo
 	"MT_DROWNNUMBERS", // Drowning Timer
 	"MT_GOTEMERALD", // Chaos Emerald (intangible)
+	"MT_LOCKON", // Target
 	"MT_TAG", // Tag Sign
-	"MT_GOTFLAG", // Got Flag sign
-	"MT_GOTFLAG2", // Got Flag sign
+	"MT_GOTREDFLAG", // Got Flag sign
+	"MT_GOTBLUEFLAG", // Got Flag sign
 
 	// Ambient Sounds
 	"MT_AWATERA", // Ambient Water Sound 1
@@ -6724,6 +6729,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_RANDOMAMBIENT",
 	"MT_RANDOMAMBIENT2",
 
+	"MT_CORK",
+
 	// Ring Weapons
 	"MT_REDRING",
 	"MT_BOUNCERING",
@@ -6947,9 +6954,8 @@ static const char *const PLAYERFLAG_LIST[] = {
 	// Did you get a time-over?
 	"TIMEOVER",
 
-	"TEMPSLOT1",
-
 	// Character action status
+	"STARTJUMP",
 	"JUMPED",
 	"SPINNING",
 	"STARTDASH",
@@ -6961,7 +6967,8 @@ static const char *const PLAYERFLAG_LIST[] = {
 	// Sliding (usually in water) like Labyrinth/Oil Ocean
 	"SLIDING",
 
-	"TEMPSLOT2",
+	// Bouncing
+	"BOUNCING",
 
 	/*** NIGHTS STUFF ***/
 	"TRANSFERTOCLOSEST",
@@ -6979,7 +6986,6 @@ static const char *const PLAYERFLAG_LIST[] = {
 	"CANCARRY", // Can carry?
 	"SHIELDABILITY", // Thokked with shield ability
 	"NOJUMPDAMAGE", // No jump damage
-	"BOUNCING",
 
 	NULL // stop loop here.
 };
@@ -7381,6 +7387,7 @@ struct {
 	{"SF_MARIODAMAGE",SF_MARIODAMAGE},
 	{"SF_MACHINE",SF_MACHINE},
 	{"SF_DASHMODE",SF_DASHMODE},
+	{"SF_FASTEDGE",SF_FASTEDGE},
 
 	// Character abilities!
 	// Primary
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index cdd778caa..a49a788e6 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -404,7 +404,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_SPLA
 	&lspr[NOLIGHT],     // SPR_SMOK
 	&lspr[NOLIGHT],     // SPR_BUBL
-	&lspr[SUPERSPARK_L], // SPR_WZAP
+	&lspr[RINGLIGHT_L], // SPR_WZAP
 	&lspr[SUPERSPARK_L], // SPR_TFOG
 	&lspr[NIGHTSLIGHT_L],     // SPR_SEED // Sonic CD flower seed
 	&lspr[NOLIGHT],     // SPR_PRTL
@@ -412,9 +412,12 @@ light_t *t_lspr[NUMSPRITES] =
 	// Game Indicators
 	&lspr[NOLIGHT],     // SPR_SCOR
 	&lspr[NOLIGHT],     // SPR_DRWN
+	&lspr[NOLIGHT],     // SPR_LCKN
 	&lspr[NOLIGHT],     // SPR_TTAG
 	&lspr[NOLIGHT],     // SPR_GFLG
 
+	&lspr[NOLIGHT],     // SPR_CORK
+
 	// Ring Weapons
 	&lspr[RINGLIGHT_L],     // SPR_RRNG
 	&lspr[RINGLIGHT_L],     // SPR_RNGB
diff --git a/src/info.c b/src/info.c
index e6adb69c3..491e7cdb0 100644
--- a/src/info.c
+++ b/src/info.c
@@ -301,9 +301,12 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Game Indicators
 	"SCOR", // Score logo
 	"DRWN", // Drowning Timer
+	"LCKN", // Target
 	"TTAG", // Tag Sign
 	"GFLG", // Got Flag sign
 
+	"CORK",
+
 	// Ring Weapons
 	"RRNG", // Red Ring
 	"RNGB", // Bounce Ring
@@ -507,7 +510,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_JUMP,                 1, {NULL}, 0,  0, S_PLAY_JUMP}, // S_PLAY_JUMP
 	{SPR_PLAY, SPR2_SPNG,                 2, {NULL}, 0,  0, S_PLAY_SPRING}, // S_PLAY_SPRING
 	{SPR_PLAY, SPR2_FALL,                 2, {NULL}, 0,  0, S_PLAY_FALL}, // S_PLAY_FALL
-	{SPR_PLAY, SPR2_EDGE|FF_ANIMATE,     -1, {NULL}, 0, 12, S_NULL},      // S_PLAY_EDGE
+	{SPR_PLAY, SPR2_EDGE,                12, {NULL}, 0,  0, S_PLAY_EDGE}, // S_PLAY_EDGE
 	{SPR_PLAY, SPR2_RIDE,                 4, {NULL}, 0,  0, S_PLAY_RIDE}, // S_PLAY_RIDE
 
 	// CA2_SPINDASH
@@ -2527,13 +2530,17 @@ state_t states[NUMSTATES] =
 	{SPR_DRWN, 10, 40, {NULL}, 0, 0, S_NULL}, // S_FOUR2
 	{SPR_DRWN, 11, 40, {NULL}, 0, 0, S_NULL}, // S_FIVE2
 
-	{SPR_TTAG, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_TTAG1
+	{SPR_LCKN, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON
+
+	{SPR_TTAG, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_TTAG
 
 	// CTF Sign
-	{SPR_GFLG, 0, 1, {NULL}, 0, 0, S_GOTFLAG2}, // S_GOTFLAG1
-	{SPR_GFLG, 1, 1, {NULL}, 0, 0, S_NULL},     // S_GOTFLAG2
-	{SPR_GFLG, 0, 1, {NULL}, 0, 0, S_GOTFLAG4}, // S_GOTFLAG3
-	{SPR_GFLG, 2, 1, {NULL}, 0, 0, S_NULL},     // S_GOTFLAG4
+	{SPR_GFLG, 0, 1, {NULL}, 0, 0, S_GOTREDFLAG2}, // S_GOTREDFLAG1
+	{SPR_GFLG, 1, 1, {NULL}, 0, 0, S_NULL},     // S_GOTREDFLAG2
+	{SPR_GFLG, 0, 1, {NULL}, 0, 0, S_GOTBLUEFLAG2}, // S_GOTBLUEFLAG1
+	{SPR_GFLG, 2, 1, {NULL}, 0, 0, S_NULL},     // S_GOTBLUEFLAG2
+
+	{SPR_CORK, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
 
 	// Red Rings (thrown)
 	{SPR_RRNG, FF_FULLBRIGHT,   1, {A_ThrownRing}, 0, 0, S_RRNG2}, // S_RRNG1
@@ -12024,7 +12031,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // speed
 		8*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
-		0,              // display offset
+		113,            // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
@@ -12051,7 +12058,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // speed
 		8*FRACUNIT,     // radius
 		16*FRACUNIT,    // height
-		0,              // display offset
+		112,            // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
@@ -12059,9 +12066,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TAG
+	{           // MT_LOCKON
 		-1,             // doomednum
-		S_TTAG1,        // spawnstate
+		S_LOCKON,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12078,7 +12085,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
-		0,              // display offset
+		111,            // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
@@ -12086,9 +12093,36 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GOTFLAG
+	{           // MT_TAG
 		-1,             // doomednum
-		S_GOTFLAG1,     // spawnstate
+		S_TTAG,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		111,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_GOTREDFLAG
+		-1,             // doomednum
+		S_GOTREDFLAG1,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12105,7 +12139,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // speed
 		64*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
-		0,              // display offset
+		111,            // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
@@ -12113,9 +12147,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GOTFLAG2
+	{           // MT_GOTBLUEFLAG2
 		-1,             // doomednum
-		S_GOTFLAG3,     // spawnstate
+		S_GOTBLUEFLAG1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12132,7 +12166,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // speed
 		64*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
-		0,              // display offset
+		111,            // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
@@ -12418,6 +12452,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_CORK
+		-1,             // doomednum
+		S_CORK,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_itemup,     // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SMOKE1,       // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_REDRING
 		-1,             // doomednum
 		S_RRNG1,        // spawnstate
diff --git a/src/info.h b/src/info.h
index 78ed50592..4501357b7 100644
--- a/src/info.h
+++ b/src/info.h
@@ -450,7 +450,7 @@ typedef enum sprite
 	SPR_ARMB, // Armageddon Shield Ring, Back
 	SPR_WIND, // Whirlwind Shield Orb
 	SPR_MAGN, // Attract Shield Orb
-	SPR_ELEM, // Elemental Shield Orb and Fire
+	SPR_ELEM, // Elemental Shield Orb
 	SPR_FORC, // Force Shield Orb
 	SPR_PITY, // Pity Shield Orb
 	SPR_FIRS, // Flame Shield Orb
@@ -507,9 +507,12 @@ typedef enum sprite
 	// Game Indicators
 	SPR_SCOR, // Score logo
 	SPR_DRWN, // Drowning Timer
+	SPR_LCKN, // Target
 	SPR_TTAG, // Tag Sign
 	SPR_GFLG, // Got Flag sign
 
+	SPR_CORK,
+
 	// Ring Weapons
 	SPR_RRNG, // Red Ring
 	SPR_RNGB, // Bounce Ring
@@ -2717,14 +2720,18 @@ typedef enum state
 	S_FOUR2,
 	S_FIVE2,
 
+	S_LOCKON,
+
 	// Tag Sign
-	S_TTAG1,
+	S_TTAG,
 
 	// Got Flag Sign
-	S_GOTFLAG1,
-	S_GOTFLAG2,
-	S_GOTFLAG3,
-	S_GOTFLAG4,
+	S_GOTREDFLAG1,
+	S_GOTREDFLAG2,
+	S_GOTBLUEFLAG1,
+	S_GOTBLUEFLAG2,
+
+	S_CORK,
 
 	// Red Ring
 	S_RRNG1,
@@ -3534,9 +3541,10 @@ typedef enum mobj_type
 	MT_SCORE, // score logo
 	MT_DROWNNUMBERS, // Drowning Timer
 	MT_GOTEMERALD, // Chaos Emerald (intangible)
+	MT_LOCKON, // Target
 	MT_TAG, // Tag Sign
-	MT_GOTFLAG, // Got Flag sign
-	MT_GOTFLAG2, // Got Flag sign
+	MT_GOTREDFLAG, // Got Flag sign
+	MT_GOTBLUEFLAG, // Got Flag sign
 
 	// Ambient Sounds
 	MT_AWATERA, // Ambient Water Sound 1
@@ -3550,6 +3558,8 @@ typedef enum mobj_type
 	MT_RANDOMAMBIENT,
 	MT_RANDOMAMBIENT2,
 
+	MT_CORK,
+
 	// Ring Weapons
 	MT_REDRING,
 	MT_BOUNCERING,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 2b4028a0e..53fa9bc55 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1060,7 +1060,7 @@ static int lib_pLookForEnemies(lua_State *L)
 	INLEVEL
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
-	lua_pushboolean(L, P_LookForEnemies(player, nonenemies, bullet));
+	LUA_PushUserdata(L, P_LookForEnemies(player, nonenemies, bullet), META_MOBJ);
 	return 1;
 }
 
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 0900528ed..622309425 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -194,8 +194,6 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->gotcontinue);
 	else if (fastcmp(field,"speed"))
 		lua_pushfixed(L, plr->speed);
-	else if (fastcmp(field,"jumping"))
-		lua_pushboolean(L, plr->jumping);
 	else if (fastcmp(field,"secondjump"))
 		lua_pushinteger(L, plr->secondjump);
 	else if (fastcmp(field,"fly1"))
@@ -459,8 +457,6 @@ static int player_set(lua_State *L)
 		plr->gotcontinue = (UINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"speed"))
 		plr->speed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"jumping"))
-		plr->jumping = luaL_checkboolean(L, 3);
 	else if (fastcmp(field,"secondjump"))
 		plr->secondjump = (UINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"fly1"))
diff --git a/src/p_floor.c b/src/p_floor.c
index f401271d1..071ebcb86 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -1776,9 +1776,10 @@ static mobj_t *SearchMarioNode(msecnode_t *node)
 		case MT_SCORE:
 		case MT_DROWNNUMBERS:
 		case MT_GOTEMERALD:
+		case MT_LOCKON:
 		case MT_TAG:
-		case MT_GOTFLAG:
-		case MT_GOTFLAG2:
+		case MT_GOTREDFLAG:
+		case MT_GOTBLUEFLAG:
 		case MT_HOOP:
 		case MT_HOOPCOLLIDE:
 		case MT_NIGHTSCORE:
diff --git a/src/p_local.h b/src/p_local.h
index 14d96f693..78ef65192 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -174,7 +174,7 @@ fixed_t P_ReturnThrustX(mobj_t *mo, angle_t angle, fixed_t move);
 fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move);
 void P_InstaThrustEvenIn2D(mobj_t *mo, angle_t angle, fixed_t move);
 
-boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet);
+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_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
 boolean P_SuperReady(player_t *player);
diff --git a/src/p_map.c b/src/p_map.c
index 7ff6301e2..7e6add39c 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -115,7 +115,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	fixed_t offx, offy;
 	fixed_t vertispeed = spring->info->mass;
 	fixed_t horizspeed = spring->info->damage;
-	UINT8 jumping, secondjump;
+	UINT8 secondjump;
 
 	if (object->eflags & MFE_SPRUNG) // Object was already sprung this tic
 		return false;
@@ -214,8 +214,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			}
 		}
 
-		pflags = object->player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_SHIELDABILITY|PF_BOUNCING); // I still need these.
-		jumping = object->player->jumping;
+		pflags = object->player->pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_SHIELDABILITY|PF_BOUNCING); // I still need these.
 		secondjump = object->player->secondjump;
 		P_ResetPlayer(object->player);
 
@@ -230,7 +229,6 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			|| (pflags & (PF_JUMPED|PF_SPINNING) && (object->player->panim == PA_ROLL || object->player->panim == PA_JUMP || object->player->panim == PA_FALL)))
 			{
 				object->player->pflags |= pflags;
-				object->player->jumping = jumping;
 				object->player->secondjump = secondjump;
 			}
 			else
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 696a68738..a235af5b3 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -504,7 +504,9 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		mobj->tics = st->tics;
 
 		// Adjust the player's animation speed to match their velocity.
-		if (!(disableSpeedAdjust || player->charflags & SF_NOSPEEDADJUST))
+		if (player->panim == PA_EDGE && (player->charflags & SF_FASTEDGE))
+			mobj->tics = 2;
+		else if (!(disableSpeedAdjust || player->charflags & SF_NOSPEEDADJUST))
 		{
 			fixed_t speed;// = FixedDiv(player->speed, FixedMul(mobj->scale, player->mo->movefactor));
 			if (player->panim == PA_FALL)
@@ -3263,8 +3265,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 					if (!(mo->player->pflags & PF_GLIDING))
 						mo->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 
-					mo->player->pflags &= ~(PF_THOKKED|PF_CANCARRY/*|PF_GLIDING*/);
-					mo->player->jumping = 0;
+					mo->player->pflags &= ~(PF_STARTJUMP|PF_THOKKED|PF_CANCARRY/*|PF_GLIDING*/);
 					mo->player->secondjump = 0;
 					mo->player->glidetime = 0;
 					mo->player->climbing = 0;
@@ -4253,8 +4254,8 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	}
 	else
 	{
-		if (!(mobj->player->powers[pw_carry] == CR_NIGHTSMODE)) // "jumping" is used for drilling
-			mobj->player->jumping = 0;
+		if (!(mobj->player->powers[pw_carry] == CR_NIGHTSMODE)) // used for drilling
+			mobj->player->pflags &= ~PF_STARTJUMP;
 		mobj->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 		if (mobj->player->secondjump || mobj->player->powers[pw_tailsfly])
 		{
@@ -7002,6 +7003,23 @@ void P_MobjThinker(mobj_t *mobj)
 					return;
 				}
 				break;
+			case MT_LOCKON:
+				if (!mobj->target)
+				{
+					P_RemoveMobj(mobj);
+					return;
+				}
+				mobj->x = mobj->target->x;
+				mobj->y = mobj->target->y;
+
+				mobj->destscale = mobj->target->destscale;
+				P_SetScale(mobj, mobj->target->scale);
+
+				if (!(mobj->target->eflags & MFE_VERTICALFLIP))
+					mobj->z = mobj->target->z + mobj->target->height + FixedMul(16*FRACUNIT, mobj->target->scale);
+				else
+					mobj->z = mobj->target->z - FixedMul(16*FRACUNIT, mobj->target->scale) - mobj->height;
+				break;
 			case MT_DROWNNUMBERS:
 				if (!mobj->target)
 				{
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 6abb4d14c..3853dc7e6 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -152,7 +152,6 @@ static void P_NetArchivePlayers(void)
 		WRITESINT8(save_p, players[i].xtralife);
 		WRITEUINT8(save_p, players[i].gotcontinue);
 		WRITEFIXED(save_p, players[i].speed);
-		WRITEUINT8(save_p, players[i].jumping);
 		WRITEUINT8(save_p, players[i].secondjump);
 		WRITEUINT8(save_p, players[i].fly1);
 		WRITEUINT8(save_p, players[i].scoreadd);
@@ -332,7 +331,6 @@ static void P_NetUnArchivePlayers(void)
 		players[i].xtralife = READSINT8(save_p); // Ring Extra Life counter
 		players[i].gotcontinue = READUINT8(save_p); // got continue from stage
 		players[i].speed = READFIXED(save_p); // Player's speed (distance formula of MOMX and MOMY values)
-		players[i].jumping = READUINT8(save_p); // Jump counter
 		players[i].secondjump = READUINT8(save_p);
 		players[i].fly1 = READUINT8(save_p); // Tails flying
 		players[i].scoreadd = READUINT8(save_p); // Used for multiple enemy attack bonus
diff --git a/src/p_user.c b/src/p_user.c
index d83cb3139..80906e1a4 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -579,9 +579,8 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->powers[pw_carry] = CR_NONE;
 
 	player->powers[pw_underwater] = 0;
-	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
+	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
 	player->secondjump = 0;
-	player->jumping = 0;
 	player->homing = 0;
 	player->climbing = 0;
 	player->mo->fuse = 0;
@@ -873,12 +872,11 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 // Useful when you want to kill everything the player is doing.
 void P_ResetPlayer(player_t *player)
 {
-	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
+	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
 
 	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_BRAKGOOP))
 		player->powers[pw_carry] = CR_NONE;
 
-	player->jumping = 0;
 	player->secondjump = 0;
 	player->glidetime = 0;
 	player->homing = 0;
@@ -2345,33 +2343,18 @@ static void P_DoPlayerHeadSigns(player_t *player)
 			}
 		}
 	}
-	else if (gametype == GT_CTF)
+	else if (gametype == GT_CTF && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))) // If you have the flag (duh).
 	{
-		if (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)) // If you have the flag (duh).
+		// Spawn a got-flag message over the head of the player that
+		// has it (but not on your own screen if you have the flag).
+		if (splitscreen || player != &players[consoleplayer])
 		{
-			// Spawn a got-flag message over the head of the player that
-			// has it (but not on your own screen if you have the flag).
-			if (splitscreen || player != &players[consoleplayer])
-			{
-				if (player->gotflag & GF_REDFLAG)
-				{
-					if (!(player->mo->eflags & MFE_VERTICALFLIP))
-						P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
-							player->mo->z+P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, MT_GOTFLAG);
-					else
-						P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
-							player->mo->z+player->mo->height-P_GetPlayerHeight(player)-mobjinfo[MT_GOTFLAG].height-FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, MT_GOTFLAG)->eflags |= MFE_VERTICALFLIP;
-				}
-				if (player->gotflag & GF_BLUEFLAG)
-				{
-					if (!(player->mo->eflags & MFE_VERTICALFLIP))
-						P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
-							player->mo->z+P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, MT_GOTFLAG2);
-					else
-						P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
-							player->mo->z+player->mo->height-P_GetPlayerHeight(player)-mobjinfo[MT_GOTFLAG2].height-FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, MT_GOTFLAG2)->eflags |= MFE_VERTICALFLIP;
-				}
-			}
+			if (!(player->mo->eflags & MFE_VERTICALFLIP))
+				P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
+					player->mo->z+P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, ((player->gotflag & GF_REDFLAG) ? MT_GOTREDFLAG : MT_GOTBLUEFLAG));
+			else
+				P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
+					player->mo->z+player->mo->height-P_GetPlayerHeight(player)-mobjinfo[MT_GOTREDFLAG].height-FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, ((player->gotflag & GF_REDFLAG) ? MT_GOTREDFLAG : MT_GOTBLUEFLAG))->eflags |= MFE_VERTICALFLIP; // yes, MT_GOTREDFLAG's height is used for both of them. Doesn't really matter - they should both always be the same height.
 		}
 	}
 }
@@ -3672,7 +3655,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		if (player->mo->eflags & MFE_UNDERWATER)
 			player->mo->momz = FixedMul(player->mo->momz, FixedDiv(117*FRACUNIT, 200*FRACUNIT));
 
-		player->jumping = 1;
+		player->pflags |= PF_STARTJUMP;
 	}
 
 	factor = player->jumpfactor;
@@ -3849,43 +3832,60 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 				}
 				break;
 			case CA2_GUNSLINGER:
-				if ((cmd->buttons & BT_USE) && !(player->pflags & PF_USEDOWN)
-					&& !player->mo->momz && onground && !player->weapondelay
-						&& canstand)
+				if (!player->mo->momz && onground && !player->weapondelay && canstand)
 				{
-					mobj_t *bullet;
-
-					P_SetPlayerMobjState(player->mo, S_PLAY_FIRE);
-
-#define zpos(posmo) (posmo->z + (posmo->height - mobjinfo[player->revitem].height)/2)
-					if (P_LookForEnemies(player, false, true) && player->mo->tracer)
-					{
-						bullet = P_SpawnPointMissile(player->mo, player->mo->tracer->x, player->mo->tracer->y, zpos(player->mo->tracer), player->revitem, player->mo->x, player->mo->y, zpos(player->mo));
-						if (!demoplayback || P_AnalogMove(player))
-						{
-							if (player == &players[consoleplayer])
-								localangle = player->mo->angle;
-							else if (player == &players[secondarydisplayplayer])
-								localangle2 = player->mo->angle;
-						}
-					}
+					if (player->speed > FixedMul(10<<FRACBITS, player->mo->scale))
+					{}
 					else
 					{
-						bullet = P_SpawnPointMissile(player->mo, player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, FRACUNIT), player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, FRACUNIT), zpos(player->mo), player->revitem, player->mo->x, player->mo->y, zpos(player->mo));
-						if (bullet)
+						mobj_t *lockon = P_LookForEnemies(player, false, true);
+						if (lockon)
 						{
-							bullet->flags &= ~MF_NOGRAVITY;
-							bullet->momx >>= 1;
-							bullet->momy >>= 1;
+							if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+							{
+								mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON);
+								visual->eflags |= (lockon->eflags & MFE_VERTICALFLIP);
+								visual->target = lockon;
+							}
 						}
-					}
+						if ((cmd->buttons & BT_USE) && !(player->pflags & PF_USEDOWN))
+						{
+							mobj_t *bullet;
+
+							P_SetPlayerMobjState(player->mo, S_PLAY_FIRE);
+
+#define zpos(posmo) (posmo->z + (posmo->height - mobjinfo[player->revitem].height)/2)
+							if (lockon)
+							{
+								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
+								bullet = P_SpawnPointMissile(player->mo, lockon->x, lockon->y, zpos(lockon), player->revitem, player->mo->x, player->mo->y, zpos(player->mo));
+								if (!demoplayback || P_AnalogMove(player))
+								{
+									if (player == &players[consoleplayer])
+										localangle = player->mo->angle;
+									else if (player == &players[secondarydisplayplayer])
+										localangle2 = player->mo->angle;
+								}
+							}
+							else
+							{
+								bullet = P_SpawnPointMissile(player->mo, player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, FRACUNIT), player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, FRACUNIT), zpos(player->mo), player->revitem, player->mo->x, player->mo->y, zpos(player->mo));
+								if (bullet)
+								{
+									bullet->flags &= ~MF_NOGRAVITY;
+									bullet->momx >>= 1;
+									bullet->momy >>= 1;
+								}
+							}
 #undef zpos
 
-					P_SetTarget(&player->mo->tracer, NULL);
-					player->mo->momx >>= 1;
-					player->mo->momy >>= 1;
-					player->pflags |= PF_USEDOWN;
-					P_SetWeaponDelay(player, TICRATE/2);
+							P_SetTarget(&player->mo->tracer, NULL);
+							player->mo->momx >>= 1;
+							player->mo->momy >>= 1;
+							player->pflags |= PF_USEDOWN;
+							P_SetWeaponDelay(player, TICRATE/2);
+						}
+					}
 				}
 				break;
 			case CA2_MELEE: // Melee attack
@@ -3899,7 +3899,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					if ((player->charability == CA_TWINSPIN) && (player->speed > FixedMul(player->runspeed, player->mo->scale)))
 					{
 						P_DoJump(player, false);
-						player->jumping = 0;
+						player->pflags &= ~PF_STARTJUMP;
 						player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2); // NOT 1.5 times the jump height, but 2.25 times.
 						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
 						S_StartSound(player->mo, sfx_s3k8b);
@@ -3970,10 +3970,9 @@ void P_DoJumpShield(player_t *player)
 
 	player->pflags &= ~PF_JUMPED;
 	P_DoJump(player, false);
-	player->jumping = 0;
 	player->secondjump = 0;
 	player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-	player->pflags &= ~(PF_SPINNING|PF_BOUNCING);
+	player->pflags &= ~(PF_STARTJUMP|PF_SPINNING|PF_BOUNCING);
 	if (electric)
 	{
 		mobj_t *spark;
@@ -4013,7 +4012,7 @@ void P_DoBubbleBounce(player_t *player)
 	if (player->charflags & SF_NOJUMPSPIN)
 		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 	player->pflags |= PF_THOKKED;
-	player->jumping = 0;
+	player->pflags &= ~PF_STARTJUMP;
 	player->secondjump = UINT8_MAX;
 	player->mo->momz = FixedMul(player->mo->momz, 5*FRACUNIT/4);
 }
@@ -4036,7 +4035,7 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz)
 		else if (player->mo->eflags & MFE_UNDERWATER)
 			prevmomz /= 2;
 		P_DoJump(player, false);
-		player->jumping = 0;
+		player->pflags &= ~(PF_STARTJUMP|PF_JUMPED);
 		player->mo->momz = (FixedMul(player->mo->momz, 3*FRACUNIT/2) + prevmomz)/2;
 	}
 	S_StartSound(player->mo, sfx_boingf);
@@ -4258,14 +4257,19 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						if (player->charability == CA_HOMINGTHOK && !player->homing)
 						{
-							player->pflags &= ~PF_NOJUMPDAMAGE;
-							if (P_LookForEnemies(player, true, false) && player->mo->tracer)
+							mobj_t *lockon = P_LookForEnemies(player, true, false);
+							if (lockon)
+							{
+								P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
+								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
 								player->homing = 3*TICRATE;
+							}
 							else
 							{
 								P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 								player->pflags &= ~PF_JUMPED;
 							}
+							player->pflags &= ~PF_NOJUMPDAMAGE;
 						}
 
 						player->pflags &= ~(PF_SPINNING|PF_STARTDASH);
@@ -4451,10 +4455,10 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		}
 
 		// If letting go of the jump button while still on ascent, cut the jump height.
-		if (player->pflags & PF_JUMPED && P_MobjFlip(player->mo)*player->mo->momz > 0 && player->jumping == 1)
+		if (((player->pflags & (PF_JUMPED|PF_STARTJUMP)) == (PF_JUMPED|PF_STARTJUMP)) && (P_MobjFlip(player->mo)*player->mo->momz > 0))
 		{
 			player->mo->momz >>= 1;
-			player->jumping = 0;
+			player->pflags &= ~PF_STARTJUMP;
 		}
 	}
 }
@@ -6042,21 +6046,19 @@ static void P_NiGHTSMovement(player_t *player)
 
 	if (player->bumpertime)
 	{
-		player->jumping = 1;
-		player->pflags |= PF_DRILLING;
+		player->pflags |= (PF_STARTJUMP|PF_DRILLING);
 		newangle = (INT16)player->flyangle;
 	}
 	else if (cmd->buttons & BT_JUMP && player->drillmeter && player->drilldelay == 0)
 	{
-		if (!player->jumping)
+		if (!(player->pflags & PF_STARTJUMP))
 			firstdrill = true;
 
-		player->jumping = 1;
-		player->pflags |= PF_DRILLING;
+		player->pflags |= (PF_STARTJUMP|PF_DRILLING);
 	}
 	else
 	{
-		player->jumping = 0;
+		player->pflags &= ~PF_STARTJUMP;
 
 		if (cmd->sidemove != 0)
 			moved = true;
@@ -6816,8 +6818,7 @@ static void P_MovePlayer(player_t *player)
 	if (onground && player->pflags & PF_JUMPED && !(player->pflags & PF_GLIDING)
 	&& P_MobjFlip(player->mo)*player->mo->momz < 0)
 	{
-		player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY);
-		player->jumping = 0;
+		player->pflags &= ~(PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY);
 		player->secondjump = 0;
 		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
 	}
@@ -7177,8 +7178,7 @@ static void P_MovePlayer(player_t *player)
 					}
 
 					player->mo->momz = 0;
-					player->pflags &= ~PF_SPINNING;
-					player->jumping = 0; // don't cut jump height after bouncing off something
+					player->pflags &= ~(PF_STARTJUMP|PF_SPINNING);
 				}
 			}
 			else
@@ -7214,15 +7214,20 @@ static void P_MovePlayer(player_t *player)
 							case SH_ATTRACT:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								player->homing = 2;
-								if (P_LookForEnemies(player, false, false) && player->mo->tracer)
 								{
-									player->pflags &= ~PF_NOJUMPDAMAGE;
-									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-									S_StartSound(player->mo, sfx_s3k40);
-									player->homing = 3*TICRATE;
+									mobj_t *lockon = P_LookForEnemies(player, false, false);
+									if (lockon)
+									{
+										P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
+										player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
+										player->pflags &= ~PF_NOJUMPDAMAGE;
+										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+										S_StartSound(player->mo, sfx_s3k40);
+										player->homing = 3*TICRATE;
+									}
+									else
+										S_StartSound(player->mo, sfx_s3ka6);
 								}
-								else
-									S_StartSound(player->mo, sfx_s3ka6);
 								break;
 							// Elemental/Bubblewrap shield activation
 							case SH_ELEMENTAL:
@@ -7864,7 +7869,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 // If bullet is true, you can look up and the distance is further,
 // but your total angle span you can look is limited to compensate.
 //
-boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
+mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 {
 	mobj_t *mo;
 	thinker_t *think;
@@ -7880,7 +7885,8 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 			continue; // not a mobj thinker
 
 		mo = (mobj_t *)think;
-		if (!(mo->flags & targetmask))
+		if (!(mo->flags & targetmask
+		|| mo->type == MT_FAKEMOBILE)) // hehehehe
 			continue; // not a valid target
 
 		if (mo->health <= 0) // dead
@@ -7903,7 +7909,7 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 			dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
 			if (bullet)
 			{
-				if ((R_PointToAngle2(0, 0, dist, zdist) + ANGLE_45) > ANGLE_90)
+				if ((R_PointToAngle2(0, 0, dist, zdist) + span) > span*2)
 					continue; // Don't home outside of desired angle!
 			}
 			else // Don't home upwards!
@@ -7942,15 +7948,7 @@ boolean P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 		closestdist = dist;
 	}
 
-	if (closestmo)
-	{
-		// Found a target monster
-		P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, closestmo));
-		player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, closestmo->x, closestmo->y);
-		return true;
-	}
-
-	return false;
+	return closestmo;
 }
 
 void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
diff --git a/src/st_stuff.c b/src/st_stuff.c
index fb28f2378..e5d005b23 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -586,7 +586,7 @@ static void ST_drawDebugInfo(void)
 
 		// Flags
 		V_DrawRightAlignedString(304-92, height - 72, V_MONOSPACE, "PF:");
-		V_DrawString(304-90,             height - 72, (stplyr->jumping) ? V_GREENMAP : V_REDMAP, "JM");
+		V_DrawString(304-90,             height - 72, (stplyr->pflags & PF_STARTJUMP) ? V_GREENMAP : V_REDMAP, "SJ");
 		V_DrawString(304-72,             height - 72, (stplyr->pflags & PF_JUMPED) ? V_GREENMAP : V_REDMAP, "JD");
 		V_DrawString(304-54,             height - 72, (stplyr->pflags & PF_SPINNING) ? V_GREENMAP : V_REDMAP, "SP");
 		V_DrawString(304-36,             height - 72, (stplyr->pflags & PF_STARTDASH) ? V_GREENMAP : V_REDMAP, "ST");

From fa29f7deca954c7f962b22178e26b452febec4dd Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 23 Mar 2017 19:11:22 +0000
Subject: [PATCH 085/119] A bunch of stuff again.

* Lock on targets bob now, and are used for CA_HOMINGTHOK and SH_ATTRACT as well.
* Flag stuff is now animated like it was designed to be but which was kinda messed up.
* Cork sounds.
* P_SpawnLockOn(player_t player, mobj_t lockon, statenum_t state) for Lua.
* Added homing/firing CA2_GUNSLINGER stuff at egg guard shields.
* Fixed homing stuff wrt egg guard shields and sea egg balloons.
* Fixed attract orb goin' gold when doing a CA_HOMINGTHOK homing ability.
* Fixed positioning of player during homing attack.
* Cleaned up lockon spawn code.
---
 src/dehacked.c    | 13 ++++-----
 src/info.c        | 49 ++++++++-------------------------
 src/info.h        | 13 ++++-----
 src/lua_baselib.c | 23 ++++++++++++++++
 src/p_floor.c     |  3 +-
 src/p_inter.c     |  2 ++
 src/p_mobj.c      | 10 ++++---
 src/p_user.c      | 70 +++++++++++++++++++++++++++++++----------------
 src/sounds.c      |  2 ++
 src/sounds.h      |  2 ++
 10 files changed, 106 insertions(+), 81 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 2661fc2fd..61ecc5f6b 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -5910,16 +5910,16 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FOUR2",
 	"S_FIVE2",
 
-	"S_LOCKON",
+	"S_LOCKON1",
+	"S_LOCKON2",
 
 	// Tag Sign
 	"S_TTAG",
 
 	// Got Flag Sign
-	"S_GOTREDFLAG1",
-	"S_GOTREDFLAG2",
-	"S_GOTBLUEFLAG1",
-	"S_GOTBLUEFLAG2",
+	"S_GOTFLAG",
+	"S_GOTREDFLAG",
+	"S_GOTBLUEFLAG",
 
 	"S_CORK",
 
@@ -6714,8 +6714,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_GOTEMERALD", // Chaos Emerald (intangible)
 	"MT_LOCKON", // Target
 	"MT_TAG", // Tag Sign
-	"MT_GOTREDFLAG", // Got Flag sign
-	"MT_GOTBLUEFLAG", // Got Flag sign
+	"MT_GOTFLAG", // Got Flag sign
 
 	// Ambient Sounds
 	"MT_AWATERA", // Ambient Water Sound 1
diff --git a/src/info.c b/src/info.c
index 491e7cdb0..2d1694a05 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2530,15 +2530,15 @@ state_t states[NUMSTATES] =
 	{SPR_DRWN, 10, 40, {NULL}, 0, 0, S_NULL}, // S_FOUR2
 	{SPR_DRWN, 11, 40, {NULL}, 0, 0, S_NULL}, // S_FIVE2
 
-	{SPR_LCKN, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON
+	{SPR_LCKN,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON1
+	{SPR_LCKN, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON2
 
 	{SPR_TTAG, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_TTAG
 
 	// CTF Sign
-	{SPR_GFLG, 0, 1, {NULL}, 0, 0, S_GOTREDFLAG2}, // S_GOTREDFLAG1
-	{SPR_GFLG, 1, 1, {NULL}, 0, 0, S_NULL},     // S_GOTREDFLAG2
-	{SPR_GFLG, 0, 1, {NULL}, 0, 0, S_GOTBLUEFLAG2}, // S_GOTBLUEFLAG1
-	{SPR_GFLG, 2, 1, {NULL}, 0, 0, S_NULL},     // S_GOTBLUEFLAG2
+	{SPR_GFLG,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTFLAG
+	{SPR_GFLG, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTREDFLAG
+	{SPR_GFLG, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTBLUEFLAG
 
 	{SPR_CORK, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
 
@@ -12068,7 +12068,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_LOCKON
 		-1,             // doomednum
-		S_LOCKON,       // spawnstate
+		S_LOCKON1,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12089,7 +12089,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -12120,36 +12120,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GOTREDFLAG
+	{           // MT_GOTFLAG
 		-1,             // doomednum
-		S_GOTREDFLAG1,  // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		8,              // speed
-		64*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
-		111,            // display offset
-		16,             // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
-		S_NULL          // raisestate
-	},
-
-	{           // MT_GOTBLUEFLAG2
-		-1,             // doomednum
-		S_GOTBLUEFLAG1, // spawnstate
+		S_GOTFLAG,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12457,7 +12430,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_CORK,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_itemup,     // seesound
+		sfx_corkp,      // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -12467,7 +12440,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_SMOKE1,       // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
+		sfx_corkh,      // deathsound
 		60*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
 		16*FRACUNIT,    // height
diff --git a/src/info.h b/src/info.h
index 4501357b7..4b21e98ec 100644
--- a/src/info.h
+++ b/src/info.h
@@ -2720,16 +2720,16 @@ typedef enum state
 	S_FOUR2,
 	S_FIVE2,
 
-	S_LOCKON,
+	S_LOCKON1,
+	S_LOCKON2,
 
 	// Tag Sign
 	S_TTAG,
 
 	// Got Flag Sign
-	S_GOTREDFLAG1,
-	S_GOTREDFLAG2,
-	S_GOTBLUEFLAG1,
-	S_GOTBLUEFLAG2,
+	S_GOTFLAG,
+	S_GOTREDFLAG,
+	S_GOTBLUEFLAG,
 
 	S_CORK,
 
@@ -3543,8 +3543,7 @@ typedef enum mobj_type
 	MT_GOTEMERALD, // Chaos Emerald (intangible)
 	MT_LOCKON, // Target
 	MT_TAG, // Tag Sign
-	MT_GOTREDFLAG, // Got Flag sign
-	MT_GOTBLUEFLAG, // Got Flag sign
+	MT_GOTFLAG, // Got Flag sign
 
 	// Ambient Sounds
 	MT_AWATERA, // Ambient Water Sound 1
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 53fa9bc55..ef8e025b8 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -449,6 +449,28 @@ static int lib_pIsValidSprite2(lua_State *L)
 	return 1;
 }
 
+// P_SpawnLockOn doesn't exist either, but we want to expose making a local mobj without encouraging hacks.
+
+static int lib_pSpawnLockOn(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	mobj_t *lockon = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+	statenum_t state = luaL_checkinteger(L, 3);
+	NOHUD
+	INLEVEL
+	if (!lockon)
+		return LUA_ErrInvalid(L, "mobj_t");
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+	{
+		mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+		visual->target = lockon;
+		P_SetMobjStateNF(visual, state);
+	}
+	return 0;
+}
+
 static int lib_pSpawnMissile(lua_State *L)
 {
 	mobj_t *source = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@@ -2328,6 +2350,7 @@ static luaL_Reg lib[] = {
 	{"P_SpawnMobj",lib_pSpawnMobj},
 	{"P_RemoveMobj",lib_pRemoveMobj},
 	{"P_IsValidSprite2", lib_pIsValidSprite2},
+	{"P_SpawnLockOn", lib_pSpawnLockOn},
 	{"P_SpawnMissile",lib_pSpawnMissile},
 	{"P_SpawnXYZMissile",lib_pSpawnXYZMissile},
 	{"P_SpawnPointMissile",lib_pSpawnPointMissile},
diff --git a/src/p_floor.c b/src/p_floor.c
index 071ebcb86..d16c8b9ff 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -1778,8 +1778,7 @@ static mobj_t *SearchMarioNode(msecnode_t *node)
 		case MT_GOTEMERALD:
 		case MT_LOCKON:
 		case MT_TAG:
-		case MT_GOTREDFLAG:
-		case MT_GOTBLUEFLAG:
+		case MT_GOTFLAG:
 		case MT_HOOP:
 		case MT_HOOPCOLLIDE:
 		case MT_NIGHTSCORE:
diff --git a/src/p_inter.c b/src/p_inter.c
index 36e800bf0..f5255a2f7 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1348,6 +1348,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
 				}
+				player->homing = 0;
 
 				// Play a bounce sound?
 				S_StartSound(toucher, special->info->painsound);
@@ -1408,6 +1409,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 						P_SetPlayerMobjState(toucher, S_PLAY_FALL);
 					}
+					player->homing = 0;
 
 					// Play a bounce sound?
 					S_StartSound(toucher, special->info->painsound);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index a235af5b3..2fde531d1 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -6868,7 +6868,7 @@ void P_MobjThinker(mobj_t *mobj)
 					return;
 				if (/*(mobj->target) -- the following is implicit by P_AddShield
 				&& (mobj->target->player)
-				&& */ (mobj->target->player->homing))
+				&& */ (mobj->target->player->homing) && (mobj->target->player->pflags & PF_SHIELDABILITY))
 				{
 					P_SetMobjState(mobj, mobj->info->painstate);
 					mobj->tics++;
@@ -7012,13 +7012,15 @@ void P_MobjThinker(mobj_t *mobj)
 				mobj->x = mobj->target->x;
 				mobj->y = mobj->target->y;
 
+				mobj->eflags |= (mobj->target->eflags & MFE_VERTICALFLIP);
+
 				mobj->destscale = mobj->target->destscale;
 				P_SetScale(mobj, mobj->target->scale);
 
-				if (!(mobj->target->eflags & MFE_VERTICALFLIP))
-					mobj->z = mobj->target->z + mobj->target->height + FixedMul(16*FRACUNIT, mobj->target->scale);
+				if (!(mobj->eflags & MFE_VERTICALFLIP))
+					mobj->z = mobj->target->z + mobj->target->height + FixedMul((16 + abs((leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale);
 				else
-					mobj->z = mobj->target->z - FixedMul(16*FRACUNIT, mobj->target->scale) - mobj->height;
+					mobj->z = mobj->target->z - FixedMul((16 + abs((leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale) - mobj->height;
 				break;
 			case MT_DROWNNUMBERS:
 				if (!mobj->target)
diff --git a/src/p_user.c b/src/p_user.c
index 80906e1a4..f60edf79d 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2349,12 +2349,17 @@ static void P_DoPlayerHeadSigns(player_t *player)
 		// has it (but not on your own screen if you have the flag).
 		if (splitscreen || player != &players[consoleplayer])
 		{
-			if (!(player->mo->eflags & MFE_VERTICALFLIP))
-				P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
-					player->mo->z+P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, ((player->gotflag & GF_REDFLAG) ? MT_GOTREDFLAG : MT_GOTBLUEFLAG));
+			mobj_t *sign = P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
+					player->mo->z+player->mo->momz, MT_GOTFLAG);
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+			{
+				sign->z += player->mo->height-P_GetPlayerHeight(player)-mobjinfo[MT_GOTFLAG].height-FixedMul(16*FRACUNIT, player->mo->scale);
+				sign->eflags |= MFE_VERTICALFLIP;
+			}
 			else
-				P_SpawnMobj(player->mo->x+player->mo->momx, player->mo->y+player->mo->momy,
-					player->mo->z+player->mo->height-P_GetPlayerHeight(player)-mobjinfo[MT_GOTREDFLAG].height-FixedMul(16*FRACUNIT, player->mo->scale)+player->mo->momz, ((player->gotflag & GF_REDFLAG) ? MT_GOTREDFLAG : MT_GOTBLUEFLAG))->eflags |= MFE_VERTICALFLIP; // yes, MT_GOTREDFLAG's height is used for both of them. Doesn't really matter - they should both always be the same height.
+				sign->z += P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale);
+			if (leveltime & 1)
+				P_SetMobjStateNF(sign, (player->gotflag & GF_REDFLAG) ? S_GOTREDFLAG : S_GOTBLUEFLAG);
 		}
 	}
 }
@@ -3843,8 +3848,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						{
 							if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
 							{
-								mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON);
-								visual->eflags |= (lockon->eflags & MFE_VERTICALFLIP);
+								mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 								visual->target = lockon;
 							}
 						}
@@ -4102,9 +4106,20 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range)
 //
 static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 {
+	mobj_t *lockon = NULL;
+
 	if (player->pflags & PF_JUMPSTASIS)
 		return;
 
+	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charability2 == CA2_MULTIABILITY)) && (lockon = P_LookForEnemies(player, true, false)) && !((leveltime & 1) && (lockon->flags & (MF_ENEMY|MF_BOSS)) && player->powers[pw_shield] == SH_ATTRACT))
+	{
+		if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+		{
+			mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+			visual->target = lockon;
+		}
+	}
+
 	if (cmd->buttons & BT_USE && !(player->pflags & PF_JUMPDOWN) && !player->exiting && !P_PlayerInPain(player))
 	{
 		if (player->mo->tracer && player->powers[pw_carry] == CR_MACESPIN)
@@ -4255,9 +4270,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						P_SpawnThokMobj(player);
 
-						if (player->charability == CA_HOMINGTHOK && !player->homing)
+						if (player->charability == CA_HOMINGTHOK)
 						{
-							mobj_t *lockon = P_LookForEnemies(player, true, false);
 							if (lockon)
 							{
 								P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
@@ -7161,6 +7175,19 @@ static void P_MovePlayer(player_t *player)
 
 	if (player->pflags & PF_JUMPED && !player->exiting && player->mo->health)
 	{
+		mobj_t *lockon = NULL;
+		if (!player->powers[pw_super] && player->powers[pw_shield] == SH_ATTRACT && !(player->pflags & PF_THOKKED))
+		{
+			if ((lockon = P_LookForEnemies(player, false, false)) && !(!(leveltime & 1) && player->charability == CA_HOMINGTHOK))
+			{
+				if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+				{
+					mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+					visual->target = lockon;
+					P_SetMobjStateNF(visual, visual->info->spawnstate+1);
+				}
+			}
+		}
 		if (cmd->buttons & BT_USE) // Spin button effects
 		{
 			if (player->powers[pw_super]) // Super can't use shield actives, only passives
@@ -7214,20 +7241,16 @@ static void P_MovePlayer(player_t *player)
 							case SH_ATTRACT:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								player->homing = 2;
+								if (lockon)
 								{
-									mobj_t *lockon = P_LookForEnemies(player, false, false);
-									if (lockon)
-									{
-										P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
-										player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
-										player->pflags &= ~PF_NOJUMPDAMAGE;
-										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-										S_StartSound(player->mo, sfx_s3k40);
-										player->homing = 3*TICRATE;
-									}
-									else
-										S_StartSound(player->mo, sfx_s3ka6);
+									P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
+									player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
+									player->pflags &= ~PF_NOJUMPDAMAGE;
+									S_StartSound(player->mo, sfx_s3k40);
+									player->homing = 3*TICRATE;
 								}
+								else
+									S_StartSound(player->mo, sfx_s3ka6);
 								break;
 							// Elemental/Bubblewrap shield activation
 							case SH_ELEMENTAL:
@@ -7886,7 +7909,8 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 
 		mo = (mobj_t *)think;
 		if (!(mo->flags & targetmask
-		|| mo->type == MT_FAKEMOBILE)) // hehehehe
+		|| mo->type == MT_FAKEMOBILE // hehehehe
+		|| mo->type == MT_EGGSHIELD))
 			continue; // not a valid target
 
 		if (mo->health <= 0) // dead
@@ -7974,7 +7998,7 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 	}
 
 	// change slope
-	zdist = (P_MobjFlip(source) ? (enemy->z + enemy->height) - (source->z + source->height) : (enemy->z - source->z));
+	zdist = ((P_MobjFlip(source) == -1) ? (enemy->z + enemy->height) - (source->z + source->height) : (enemy->z - source->z));
 	dist = P_AproxDistance(P_AproxDistance(enemy->x - source->x, enemy->y - source->y), zdist);
 
 	if (dist < 1)
diff --git a/src/sounds.c b/src/sounds.c
index b2758923d..395015296 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -189,6 +189,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"mswarp", false,  60, 16, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"mspogo", false,  60,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"boingf", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"corkp",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"corkh",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
 
   // Menu, interface
   {"chchng", false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
diff --git a/src/sounds.h b/src/sounds.h
index 42eeee31f..0442ebb05 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -252,6 +252,8 @@ typedef enum
 	sfx_mswarp,
 	sfx_mspogo,
 	sfx_boingf,
+	sfx_corkp,
+	sfx_corkh,
 
 	// Menu, interface
 	sfx_chchng,

From b29193aa98eaa2fe9abc8192162e731a0cac28e5 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Thu, 23 Mar 2017 21:24:31 -0400
Subject: [PATCH 086/119] CircleCI: first try

---
 .circleci/config.yml | 53 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 .circleci/config.yml

diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 000000000..8b954bc70
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,53 @@
+version: 2
+jobs:
+  build:
+    working_directory: /root/SRB2
+    docker:
+      - image: debian:jessie
+        environment:
+          CC: ccache gcc -m32
+          PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
+          LIBGME_CFLAGS: -I/usr/include/
+          LIBGME_LDFLAGS: -lgme
+          CCACHE_COMPRESS: true
+    steps:
+      - run:
+          name: Add i386 arch
+          command: dpkg --add-architecture i386
+      #- restore_cache:
+      #    keys:
+      #      - v1-SRB2-APT
+      - run:
+          name: Update APT listing
+          command: apt-get -qq update
+      - run:
+          name: Install SDK
+          command: apt-get -qq -y install git build-essential nasm libpng12-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 gettext ccache wget gcc-multilib ca-certificates
+      #- save_cache:
+      #    key: v1-SRB2-APT
+      #    paths:
+      #      - /var/cache/apt/
+      - checkout
+      #- restore_cache:
+      #    keys:
+      #      - v1-SRB2-{{ .Branch }}
+      - run:
+          name: Setup cache
+          command: mkdir -p /root/srb2_cache
+      #- run:
+      #    name: Download SRB2 Resources
+      #    command: wget --verbose --server-response -c http://rosenthalcastle.org/srb2/SRB2-v2115-assets-2.7z -O /root/srb2_cache/SRB2-v2115-assets-2.7z
+      - run:
+          name: Compile
+          command: make -C src LINUX=1 GCC49=1 WARNINGMODE=1 -k
+      - store_artifacts:
+          path: /root/SRB2/bin/Linux/Release/
+          destination: bin
+      #- save_cache:
+      #    key: v1-SRB2-{{ .Branch }}
+      #    paths:
+      #      - /root/.ccache
+      #      - /root/srb2_cache
+
+
+

From 59d91e0793f61b7dbb93a4cb17db89a1b5892999 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Thu, 23 Mar 2017 23:13:31 -0400
Subject: [PATCH 087/119] build: r_bsp.c:213:23: warning: inlining failed in
 call to 'R_DoorClosed': call is unlikely and code size would grow [-Winline]

---
 src/r_bsp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/r_bsp.c b/src/r_bsp.c
index 2562cff66..44cb991a7 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -210,7 +210,7 @@ void R_PortalClearClipSegs(INT32 start, INT32 end)
 //
 // It assumes that Doom has already ruled out a door being closed because
 // of front-back closure (e.g. front floor is taller than back ceiling).
-static inline INT32 R_DoorClosed(void)
+static INT32 R_DoorClosed(void)
 {
 	return
 

From c5d15ad5978ae3c99880789d92f4e1a06ac8f1f6 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Thu, 23 Mar 2017 23:15:01 -0400
Subject: [PATCH 088/119] CircleCI: force -Wno-unsuffixed-float-constants

---
 .circleci/config.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 8b954bc70..61c9ce501 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -10,6 +10,7 @@ jobs:
           LIBGME_CFLAGS: -I/usr/include/
           LIBGME_LDFLAGS: -lgme
           CCACHE_COMPRESS: true
+          WFLAGS: -Wno-unsuffixed-float-constants
     steps:
       - run:
           name: Add i386 arch

From ade354c27d19fb7a561bc3538304a24c137cbabc Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Thu, 23 Mar 2017 23:19:25 -0400
Subject: [PATCH 089/119] CircleCI: error on warnings

---
 .circleci/config.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 61c9ce501..288468230 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -40,7 +40,7 @@ jobs:
       #    command: wget --verbose --server-response -c http://rosenthalcastle.org/srb2/SRB2-v2115-assets-2.7z -O /root/srb2_cache/SRB2-v2115-assets-2.7z
       - run:
           name: Compile
-          command: make -C src LINUX=1 GCC49=1 WARNINGMODE=1 -k
+          command: make -C src LINUX=1 GCC49=1 ERRORMODE=1 -k
       - store_artifacts:
           path: /root/SRB2/bin/Linux/Release/
           destination: bin

From aaaab40f6c35adfb284e4eff88520cdb944c2324 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Thu, 23 Mar 2017 23:42:28 -0400
Subject: [PATCH 090/119] CircleCI: cache APT and ccache

---
 .circleci/config.yml | 35 +++++++++++++++++++----------------
 1 file changed, 19 insertions(+), 16 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 288468230..34b0faa7d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -15,23 +15,26 @@ jobs:
       - run:
           name: Add i386 arch
           command: dpkg --add-architecture i386
-      #- restore_cache:
-      #    keys:
-      #      - v1-SRB2-APT
       - run:
           name: Update APT listing
           command: apt-get -qq update
+      - run:
+          name: Support S3 upload
+          command: apt-get -qq -y install ca-certificates
+      - restore_cache:
+          keys:
+            - v1-SRB2-APT
       - run:
           name: Install SDK
-          command: apt-get -qq -y install git build-essential nasm libpng12-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 gettext ccache wget gcc-multilib ca-certificates
-      #- save_cache:
-      #    key: v1-SRB2-APT
-      #    paths:
-      #      - /var/cache/apt/
+          command: apt-get -qq -y install git build-essential nasm libpng12-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 gettext ccache wget gcc-multilib
+      - save_cache:
+          key: v1-SRB2-APT
+          paths:
+            - /var/cache/apt/archives
       - checkout
-      #- restore_cache:
-      #    keys:
-      #      - v1-SRB2-{{ .Branch }}
+      - restore_cache:
+          keys:
+            - v1-SRB2-{{ .Branch }}
       - run:
           name: Setup cache
           command: mkdir -p /root/srb2_cache
@@ -44,11 +47,11 @@ jobs:
       - store_artifacts:
           path: /root/SRB2/bin/Linux/Release/
           destination: bin
-      #- save_cache:
-      #    key: v1-SRB2-{{ .Branch }}
-      #    paths:
-      #      - /root/.ccache
-      #      - /root/srb2_cache
+      - save_cache:
+          key: v1-SRB2-{{ .Branch }}
+          paths:
+            - /root/.ccache
+            - /root/srb2_cache
 
 
 

From c85c277a48dd6b1b98c3e89027e1a52587c8d2e3 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Fri, 24 Mar 2017 08:27:41 -0400
Subject: [PATCH 091/119] CircleCI: move GCC49 check to debian's env

---
 .circleci/config.yml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 34b0faa7d..dff58840b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,10 +7,11 @@ jobs:
         environment:
           CC: ccache gcc -m32
           PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
-          LIBGME_CFLAGS: -I/usr/include/
+          LIBGME_CFLAGS: -I/usr/include
           LIBGME_LDFLAGS: -lgme
           CCACHE_COMPRESS: true
           WFLAGS: -Wno-unsuffixed-float-constants
+          GCC49: true
     steps:
       - run:
           name: Add i386 arch
@@ -43,7 +44,7 @@ jobs:
       #    command: wget --verbose --server-response -c http://rosenthalcastle.org/srb2/SRB2-v2115-assets-2.7z -O /root/srb2_cache/SRB2-v2115-assets-2.7z
       - run:
           name: Compile
-          command: make -C src LINUX=1 GCC49=1 ERRORMODE=1 -k
+          command: make -C src LINUX=1 ERRORMODE=1 -k
       - store_artifacts:
           path: /root/SRB2/bin/Linux/Release/
           destination: bin

From 99b2c888212c3211e0bf0335e934f5228246ad9f Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Fri, 24 Mar 2017 08:28:31 -0400
Subject: [PATCH 092/119] README: add CircleCI's Status badge

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index eb06156b4..d16071454 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
 
 [![Build status](https://ci.appveyor.com/api/projects/status/399d4hcw9yy7hg2y?svg=true)](https://ci.appveyor.com/project/STJr/srb2)
 [![Build status](https://travis-ci.org/STJr/SRB2.svg?branch=master)](https://travis-ci.org/STJr/SRB2)
+[![CircleCI](https://circleci.com/gh/STJr/SRB2/tree/master.svg?style=svg)](https://circleci.com/gh/STJr/SRB2/tree/master)
 
 [Sonic Robo Blast 2](https://srb2.org/) is a 3D Sonic the Hedgehog fangame based on a modified version of [Doom Legacy](http://doomlegacy.sourceforge.net/).
 

From ac75267ef2964c57b8956ecdbfffb96a1456c407 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Fri, 24 Mar 2017 08:29:54 -0400
Subject: [PATCH 093/119] CircleCI: build on Ubuntu as well

---
 .circleci/config.yml | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index dff58840b..6330d86c5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -12,6 +12,15 @@ jobs:
           CCACHE_COMPRESS: true
           WFLAGS: -Wno-unsuffixed-float-constants
           GCC49: true
+      - image: ubuntu:trusty
+        environment:
+          CC: ccache gcc -m32
+          PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
+          LIBGME_CFLAGS: -I/usr/include
+          LIBGME_LDFLAGS: -lgme
+          CCACHE_COMPRESS: true
+          WFLAGS: -Wno-unsuffixed-float-constants
+          GCC48: true
     steps:
       - run:
           name: Add i386 arch

From 29c19b62ef16b9f8867f24bb1b306e8287a97a4c Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Fri, 24 Mar 2017 08:33:14 -0400
Subject: [PATCH 094/119] CircleCi: Ubuntu docker image is broken

---
 .circleci/config.yml | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6330d86c5..5efcaab4a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -12,15 +12,15 @@ jobs:
           CCACHE_COMPRESS: true
           WFLAGS: -Wno-unsuffixed-float-constants
           GCC49: true
-      - image: ubuntu:trusty
-        environment:
-          CC: ccache gcc -m32
-          PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
-          LIBGME_CFLAGS: -I/usr/include
-          LIBGME_LDFLAGS: -lgme
-          CCACHE_COMPRESS: true
-          WFLAGS: -Wno-unsuffixed-float-constants
-          GCC48: true
+      #- image: ubuntu:trusty
+      #  environment:
+      #    CC: ccache gcc -m32
+      #    PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
+      #    LIBGME_CFLAGS: -I/usr/include
+      #    LIBGME_LDFLAGS: -lgme
+      #    CCACHE_COMPRESS: true
+      #    WFLAGS: -Wno-unsuffixed-float-constants
+      #    GCC48: true
     steps:
       - run:
           name: Add i386 arch

From 03ecb0d1644e58fa306d56f6dc226cff979a3bdc Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Fri, 24 Mar 2017 09:10:18 -0400
Subject: [PATCH 095/119] CircleCI: add upx

---
 .circleci/config.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 5efcaab4a..18a95b8a9 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -36,7 +36,7 @@ jobs:
             - v1-SRB2-APT
       - run:
           name: Install SDK
-          command: apt-get -qq -y install git build-essential nasm libpng12-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 gettext ccache wget gcc-multilib
+          command: apt-get -qq -y install git build-essential nasm libpng12-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 gettext ccache wget gcc-multilib upx
       - save_cache:
           key: v1-SRB2-APT
           paths:

From 52a79754d344a419a8edb9aa719e8cda78258978 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Fri, 24 Mar 2017 09:12:00 -0400
Subject: [PATCH 096/119] CircleCI: keep build cache with checksum of
 depend.dep

---
 .circleci/config.yml | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 18a95b8a9..b5c43d017 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -42,15 +42,12 @@ jobs:
           paths:
             - /var/cache/apt/archives
       - checkout
+      - run:
+          name: Clean build
+          command: make -C src LINUX=1 clean
       - restore_cache:
           keys:
-            - v1-SRB2-{{ .Branch }}
-      - run:
-          name: Setup cache
-          command: mkdir -p /root/srb2_cache
-      #- run:
-      #    name: Download SRB2 Resources
-      #    command: wget --verbose --server-response -c http://rosenthalcastle.org/srb2/SRB2-v2115-assets-2.7z -O /root/srb2_cache/SRB2-v2115-assets-2.7z
+            - v1-SRB2-{{ .Branch }}-{{ checksum "objs/Linux/SDL/Release/depend.dep" }}
       - run:
           name: Compile
           command: make -C src LINUX=1 ERRORMODE=1 -k
@@ -58,10 +55,9 @@ jobs:
           path: /root/SRB2/bin/Linux/Release/
           destination: bin
       - save_cache:
-          key: v1-SRB2-{{ .Branch }}
+          key: v1-SRB2-{{ .Branch }}-{{ checksum "objs/Linux/SDL/Release/depend.dep" }}
           paths:
             - /root/.ccache
-            - /root/srb2_cache
 
 
 

From 0bd093faf7011dc989368307f1ea253df0cf4b28 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Fri, 24 Mar 2017 13:51:23 +0000
Subject: [PATCH 097/119] * Fixed weird hitbox thing when using a shield
 ability that puts you into a spin (when CA2_NOJUMPSPIN). * Fixed bug with the
 magnet shield homing attack and charability homing attack not properly
 updating the player's target/tracer. * Made flashing of lockon/got flag icons
 slower.

---
 src/p_user.c | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index f60edf79d..a54eac68d 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2358,8 +2358,13 @@ static void P_DoPlayerHeadSigns(player_t *player)
 			}
 			else
 				sign->z += P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale);
-			if (leveltime & 1)
-				P_SetMobjStateNF(sign, (player->gotflag & GF_REDFLAG) ? S_GOTREDFLAG : S_GOTBLUEFLAG);
+			if (leveltime & 4)
+			{
+				if (player->gotflag & GF_REDFLAG)
+					P_SetMobjStateNF(sign, S_GOTREDFLAG);
+			}
+			else if (player->gotflag & GF_BLUEFLAG)
+				P_SetMobjStateNF(sign, S_GOTBLUEFLAG);
 		}
 	}
 }
@@ -4111,7 +4116,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	if (player->pflags & PF_JUMPSTASIS)
 		return;
 
-	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charability2 == CA2_MULTIABILITY)) && (lockon = P_LookForEnemies(player, true, false)) && !((leveltime & 1) && (lockon->flags & (MF_ENEMY|MF_BOSS)) && player->powers[pw_shield] == SH_ATTRACT))
+	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charability2 == CA2_MULTIABILITY)) && (lockon = P_LookForEnemies(player, true, false)) && !((leveltime & 4) && (lockon->flags & (MF_ENEMY|MF_BOSS)) && player->powers[pw_shield] == SH_ATTRACT))
 	{
 		if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
 		{
@@ -4272,9 +4277,9 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						if (player->charability == CA_HOMINGTHOK)
 						{
+							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
 							if (lockon)
 							{
-								P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
 								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
 								player->homing = 3*TICRATE;
 							}
@@ -7178,7 +7183,7 @@ static void P_MovePlayer(player_t *player)
 		mobj_t *lockon = NULL;
 		if (!player->powers[pw_super] && player->powers[pw_shield] == SH_ATTRACT && !(player->pflags & PF_THOKKED))
 		{
-			if ((lockon = P_LookForEnemies(player, false, false)) && !(!(leveltime & 1) && player->charability == CA_HOMINGTHOK))
+			if ((lockon = P_LookForEnemies(player, false, false)) && !(!(leveltime & 4) && player->charability == CA_HOMINGTHOK))
 			{
 				if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
 				{
@@ -7241,9 +7246,9 @@ static void P_MovePlayer(player_t *player)
 							case SH_ATTRACT:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								player->homing = 2;
+								P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
 								if (lockon)
 								{
-									P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
 									player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
 									player->pflags &= ~PF_NOJUMPDAMAGE;
 									S_StartSound(player->mo, sfx_s3k40);
@@ -7376,7 +7381,7 @@ static void P_MovePlayer(player_t *player)
 
 		// Less height while spinning. Good for spinning under things...?
 		if ((player->mo->state == &states[player->mo->info->painstate])
-		|| (!(player->charflags & SF_NOJUMPSPIN) && (player->pflags & PF_JUMPED))
+		|| ((player->pflags & PF_JUMPED) && !(player->pflags & PF_NOJUMPDAMAGE && player->charflags & SF_NOJUMPSPIN))
 		|| (player->pflags & PF_SPINNING)
 		|| player->powers[pw_tailsfly] || player->pflags & PF_GLIDING
 		|| (player->charability == CA_FLY && player->mo->state-states == S_PLAY_FLY_TIRED))

From dbcbcf5da3af1f6e8cb6b46102613b3cb0818780 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 25 Mar 2017 03:13:02 -0400
Subject: [PATCH 098/119] Changed defaults, camera settings save

Camera settings: distance from 128 to 160, height from 20 to 32, speed
from 0.25 to 0.3
Mouse sensitivity from 35 to 12
Default control setup is Mystic's proposed scheme from the New Player
Experiences topic (https://mb.srb2.org/showthread.php?t=42095)

Also, camera settings now save to config, and they no longer try to
reset back to the default at every chance it gets (you can die, start a
new game, or exit, all while still keeping your preferred setting)
---
 src/g_game.c  | 12 +++++++-----
 src/g_input.c | 23 ++++++++++-------------
 src/p_setup.c | 11 ++++++-----
 src/p_user.c  | 16 ++++++++--------
 4 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index 1eef85ada..f3ccbce99 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1565,11 +1565,12 @@ static void Analog_OnChange(void)
 		return;
 
 	// cameras are not initialized at this point
+	// Salt: Grooooaaan... I know that cameras aren't initalized yet and that analog needs a farther camera, but directly overwriting someone's settings should not be the way to do it!
 
-	if (leveltime > 1)
+	/*if (leveltime > 1)
 		CV_SetValue(&cv_cam_dist, 128);
 	if (cv_analog.value || demoplayback)
-		CV_SetValue(&cv_cam_dist, 192);
+		CV_SetValue(&cv_cam_dist, 192);*/
 
 	if (!cv_chasecam.value && cv_analog.value) {
 		CV_SetValue(&cv_analog, 0);
@@ -1590,11 +1591,12 @@ static void Analog2_OnChange(void)
 		return;
 
 	// cameras are not initialized at this point
-
-	if (leveltime > 1)
+	// Salt: Grooooaaan... I know that cameras aren't initalized yet and that analog needs a farther camera, but directly overwriting someone's settings should not be the way to do it!
+	
+	/*if (leveltime > 1)
 		CV_SetValue(&cv_cam2_dist, 128);
 	if (cv_analog2.value)
-		CV_SetValue(&cv_cam2_dist, 192);
+		CV_SetValue(&cv_cam2_dist, 192);*/
 
 	if (!cv_chasecam2.value && cv_analog2.value) {
 		CV_SetValue(&cv_analog2, 0);
diff --git a/src/g_input.c b/src/g_input.c
index b004384c0..a538df06c 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -25,10 +25,10 @@ static CV_PossibleValue_t mousesens_cons_t[] = {{1, "MIN"}, {MAXMOUSESENSITIVITY
 static CV_PossibleValue_t onecontrolperkey_cons_t[] = {{1, "One"}, {2, "Several"}, {0, NULL}};
 
 // mouse values are used once
-consvar_t cv_mousesens = {"mousesens", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousesens2 = {"mousesens2", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens = {"mouseysens", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens2 = {"mouseysens2", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousesens = {"mousesens", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousesens2 = {"mousesens2", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouseysens = {"mouseysens", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouseysens2 = {"mouseysens2", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_controlperkey = {"controlperkey", "One", CV_SAVE, onecontrolperkey_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 INT32 mousex, mousey;
@@ -1154,10 +1154,8 @@ void G_Controldefault(void)
 #else
 void G_Controldefault(void)
 {
-	gamecontrol[gc_forward    ][0] = KEY_UPARROW;
-	gamecontrol[gc_forward    ][1] = 'w';
-	gamecontrol[gc_backward   ][0] = KEY_DOWNARROW;
-	gamecontrol[gc_backward   ][1] = 's';
+	gamecontrol[gc_forward    ][0] = 'w';
+	gamecontrol[gc_backward   ][0] = 's';
 	gamecontrol[gc_strafeleft ][0] = 'a';
 	gamecontrol[gc_straferight][0] = 'd';
 	gamecontrol[gc_turnleft   ][0] = KEY_LEFTARROW;
@@ -1178,19 +1176,18 @@ void G_Controldefault(void)
 	gamecontrol[gc_fire       ][1] = KEY_MOUSE1+0;
 	gamecontrol[gc_firenormal ][0] = 'c';
 	gamecontrol[gc_tossflag   ][0] = '\'';
-	gamecontrol[gc_use        ][0] = 'x';
+	gamecontrol[gc_use        ][0] = KEY_LSHIFT;
 	gamecontrol[gc_camtoggle  ][0] = 'v';
 	gamecontrol[gc_camleft    ][0] = '[';
 	gamecontrol[gc_camright   ][0] = ']';
 	gamecontrol[gc_camreset   ][0] = 'r';
-	gamecontrol[gc_lookup     ][0] = KEY_PGUP;
-	gamecontrol[gc_lookdown   ][0] = KEY_PGDN;
+	gamecontrol[gc_lookup     ][0] = KEY_UPARROW;
+	gamecontrol[gc_lookdown   ][0] = KEY_DOWNARROW;
 	gamecontrol[gc_centerview ][0] = KEY_END;
 	gamecontrol[gc_talkkey    ][0] = 't';
 	gamecontrol[gc_teamkey    ][0] = 'y';
 	gamecontrol[gc_scores     ][0] = KEY_TAB;
-	gamecontrol[gc_jump       ][0] = 'z';
-	gamecontrol[gc_jump       ][1] = KEY_MOUSE1+1;
+	gamecontrol[gc_jump       ][0] = KEY_SPACE;
 	gamecontrol[gc_console    ][0] = KEY_CONSOLE;
 	gamecontrol[gc_pause      ][0] = KEY_PAUSE;
 #ifdef WMINPUT
diff --git a/src/p_setup.c b/src/p_setup.c
index 6df103255..97bcb61f1 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2583,8 +2583,8 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	if (!dedicated)
 	{
-		if (!cv_cam_speed.changed)
-			CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);
+		//if (!cv_cam_speed.changed)
+			//CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);
 
 		if (!cv_chasecam.changed)
 			CV_SetValue(&cv_chasecam, chase);
@@ -2880,8 +2880,9 @@ boolean P_SetupLevel(boolean skipprecip)
 				camera.angle = FixedAngle((fixed_t)thing->angle << FRACBITS);
 			}
 		}
-
-		if (!cv_cam_height.changed)
+		
+		// Salt: I don't understand *why* it does this, but this overwrites the player's setting, even though it looks like it shouldn't do that unless if it's already the default
+		/*if (!cv_cam_height.changed)
 			CV_Set(&cv_cam_height, cv_cam_height.defaultvalue);
 
 		if (!cv_cam_dist.changed)
@@ -2897,7 +2898,7 @@ boolean P_SetupLevel(boolean skipprecip)
 			CV_Set(&cv_cam2_dist, cv_cam2_dist.defaultvalue);
 
 		if (!cv_cam2_rotate.changed)
-			CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue);
+			CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue);*/
 
 		if (!cv_analog.changed)
 			CV_SetValue(&cv_analog, 0);
diff --git a/src/p_user.c b/src/p_user.c
index 905c3be62..8b1589c42 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8020,18 +8020,18 @@ static CV_PossibleValue_t CV_CamSpeed[] = {{0, "MIN"}, {1*FRACUNIT, "MAX"}, {0,
 static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {45, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}};
 
-consvar_t cv_cam_dist = {"cam_dist", "128", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_height = {"cam_height", "20", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_dist = {"cam_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_height = {"cam_height", "32", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_still = {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_speed = {"cam_speed", "0.25", CV_FLOAT, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_speed = {"cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotate = {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", 0, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_dist = {"cam2_dist", "128", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_height = {"cam2_height", "20", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_dist = {"cam2_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_height = {"cam2_height", "32", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_still = {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_speed = {"cam2_speed", "0.25", CV_FLOAT, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_speed = {"cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotate = {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_rotspeed = {"cam2_rotspeed", "10", 0, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_rotspeed = {"cam2_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 fixed_t t_cam_dist = -42;
 fixed_t t_cam_height = -42;

From 8d9b616f003db05b04ad583d84cecf1f056a5e7c Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 25 Mar 2017 12:42:39 +0000
Subject: [PATCH 099/119] Fixed repeatable bounces sending you hog-wild if you
 went into water shallow enough to only spend a single frame in before hitting
 the ground.

---
 src/p_mobj.c | 1 +
 src/p_user.c | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index 2fde531d1..ac2f4c5c4 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3302,6 +3302,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 
 					if (mo->player->pflags & PF_BOUNCING)
 					{
+						P_MobjCheckWater(mo);
 						mo->momz *= -1;
 						P_DoAbilityBounce(mo->player, true);
 						if (mo->player->scoreadd)
diff --git a/src/p_user.c b/src/p_user.c
index a54eac68d..f10ae1605 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4017,6 +4017,7 @@ void P_DoBubbleBounce(player_t *player)
 {
 	player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SHIELDABILITY);
 	S_StartSound(player->mo, sfx_s3k44);
+	P_MobjCheckWater(player->mo);
 	P_DoJump(player, false);
 	if (player->charflags & SF_NOJUMPSPIN)
 		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);

From 805784a1756d6f05fd180cbb19bbf261e84a687c Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 25 Mar 2017 12:51:38 +0000
Subject: [PATCH 100/119] Made it possible for nojumpspin characters to juke
 the Minus into exposing itself by jumping over it slowly, allowing its
 vulnerability to be exploited.

---
 src/p_enemy.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_enemy.c b/src/p_enemy.c
index bbb89338f..c1c6c5ebb 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -4520,7 +4520,7 @@ void A_MinusDigging(mobj_t *actor)
 
 	// If we're close enough to our target, pop out of the ground
 	if (P_AproxDistance(actor->target->x-actor->x, actor->target->y-actor->y) < actor->radius
-		&& abs(actor->target->z - actor->z) < actor->height)
+		&& abs(actor->target->z - actor->z) < 2*actor->height)
 		P_SetMobjState(actor, actor->info->missilestate);
 
 	// Snap to ground

From 698e53a5185d6136d1d8095b69d1ef690a5f3cee Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Sat, 25 Mar 2017 14:06:27 +0000
Subject: [PATCH 101/119] Per Mystic's request, sped up hammer animation.

---
 src/info.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/info.c b/src/info.c
index 2d1694a05..7d6e1fede 100644
--- a/src/info.c
+++ b/src/info.c
@@ -542,7 +542,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
 
 	// CA2_MELEE
-	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
+	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
 	{SPR_PLAY, SPR2_MLEE,                70, {NULL},                   0, 0, S_PLAY_FALL},  // S_PLAY_MELEE_FINISH
 	{SPR_PLAY, SPR2_MLEL,                35, {NULL},                   0, 0, S_PLAY_WALK},  // S_PLAY_MELEE_LANDING
 

From e5245c508dc0a1fe86b6409101b16c6e3082cfea Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sun, 26 Mar 2017 17:11:04 -0400
Subject: [PATCH 102/119] Reimplemented analog mode cam_dist increase, slight
 comment updates

Analog mode's camera is now x1.2 of your actual cam_dist setting,
instead of being strictly 192. "analog on" seems to reset this modifier
every once in a while, but "useranalog on" prevents this. The wiki
itself says that you should only change useranalog though, so it
shouldn't be too big of a deal.
---
 src/g_game.c  | 12 ------------
 src/p_setup.c | 23 +++++++++++------------
 src/p_user.c  |  4 ++++
 3 files changed, 15 insertions(+), 24 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index f3ccbce99..137f8609a 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1565,12 +1565,6 @@ static void Analog_OnChange(void)
 		return;
 
 	// cameras are not initialized at this point
-	// Salt: Grooooaaan... I know that cameras aren't initalized yet and that analog needs a farther camera, but directly overwriting someone's settings should not be the way to do it!
-
-	/*if (leveltime > 1)
-		CV_SetValue(&cv_cam_dist, 128);
-	if (cv_analog.value || demoplayback)
-		CV_SetValue(&cv_cam_dist, 192);*/
 
 	if (!cv_chasecam.value && cv_analog.value) {
 		CV_SetValue(&cv_analog, 0);
@@ -1591,12 +1585,6 @@ static void Analog2_OnChange(void)
 		return;
 
 	// cameras are not initialized at this point
-	// Salt: Grooooaaan... I know that cameras aren't initalized yet and that analog needs a farther camera, but directly overwriting someone's settings should not be the way to do it!
-	
-	/*if (leveltime > 1)
-		CV_SetValue(&cv_cam2_dist, 128);
-	if (cv_analog2.value)
-		CV_SetValue(&cv_cam2_dist, 192);*/
 
 	if (!cv_chasecam2.value && cv_analog2.value) {
 		CV_SetValue(&cv_analog2, 0);
diff --git a/src/p_setup.c b/src/p_setup.c
index 97bcb61f1..e718637c9 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2583,8 +2583,9 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	if (!dedicated)
 	{
-		//if (!cv_cam_speed.changed)
-			//CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);
+		// Salt: CV_ClearChangedFlags() messes with your settings :(
+		/*if (!cv_cam_speed.changed)
+			CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);*/
 
 		if (!cv_chasecam.changed)
 			CV_SetValue(&cv_chasecam, chase);
@@ -2881,24 +2882,22 @@ boolean P_SetupLevel(boolean skipprecip)
 			}
 		}
 		
-		// Salt: I don't understand *why* it does this, but this overwrites the player's setting, even though it looks like it shouldn't do that unless if it's already the default
+		// Salt: CV_ClearChangedFlags() messes with your settings :(
 		/*if (!cv_cam_height.changed)
 			CV_Set(&cv_cam_height, cv_cam_height.defaultvalue);
-
-		if (!cv_cam_dist.changed)
-			CV_Set(&cv_cam_dist, cv_cam_dist.defaultvalue);
-
-		if (!cv_cam_rotate.changed)
-			CV_Set(&cv_cam_rotate, cv_cam_rotate.defaultvalue);
-
 		if (!cv_cam2_height.changed)
 			CV_Set(&cv_cam2_height, cv_cam2_height.defaultvalue);
 
+		if (!cv_cam_dist.changed)
+			CV_Set(&cv_cam_dist, cv_cam_dist.defaultvalue);
 		if (!cv_cam2_dist.changed)
-			CV_Set(&cv_cam2_dist, cv_cam2_dist.defaultvalue);
+			CV_Set(&cv_cam2_dist, cv_cam2_dist.defaultvalue);*/
 
+		// Though, I don't think anyone would care about cam_rotate being reset back to the only value that makes sense :P
+		if (!cv_cam_rotate.changed)
+			CV_Set(&cv_cam_rotate, cv_cam_rotate.defaultvalue);
 		if (!cv_cam2_rotate.changed)
-			CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue);*/
+			CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue);
 
 		if (!cv_analog.changed)
 			CV_SetValue(&cv_analog, 0);
diff --git a/src/p_user.c b/src/p_user.c
index 8b1589c42..1e527607e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8289,6 +8289,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	{
 		dist = camdist;
 
+		// x1.2 dist for analog
+		if (P_AnalogMove(player))
+			dist = FixedMul(dist, 6*FRACUNIT/5);
+		
 		if (player->climbing || player->exiting || player->playerstate == PST_DEAD || (player->powers[pw_carry] && player->powers[pw_carry] != CR_PLAYER))
 			dist <<= 1;
 	}

From 302305ccf510251b61c39ce1d44cef7f5efe3f1e Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 27 Mar 2017 14:22:03 +0100
Subject: [PATCH 103/119] Fixing SF_NOSUPERSPIN, which has been broken ever
 since the super walk animation was added.

---
 src/p_mobj.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index ac2f4c5c4..c729b7aa7 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -408,10 +408,15 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		return P_SetPlayerMobjState(player->mo, S_PLAY_FLY);
 
 	// Catch SF_NOSUPERSPIN jumps for Supers
-	if (player->powers[pw_super])
+	if (player->powers[pw_super] && (player->charflags & SF_NOSUPERSPIN))
 	{
-		if ((player->charflags & SF_NOSUPERSPIN)
-		&& (state == S_PLAY_ROLL || state == S_PLAY_JUMP))
+		if (state == S_PLAY_JUMP)
+		{
+			if (player->mo->state-states == S_PLAY_WALK)
+				return P_SetPlayerMobjState(mobj, S_PLAY_FLOAT);
+			return true;
+		}
+		else if (player->mo->state-states == S_PLAY_FLOAT && state == S_PLAY_STND)
 			return true;
 	}
 	// You were in pain state after taking a hit, and you're moving out of pain state now?

From 79f2d616fc7e54c8073e0c68a489853f61c7abc8 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 27 Mar 2017 14:37:40 +0100
Subject: [PATCH 104/119] Modified melee/twinspin springboost.

---
 src/p_map.c | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index 7e6add39c..04858b5df 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -130,19 +130,13 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		return false;
 	}
 
-#ifdef ESLOPE
-	object->standingslope = NULL; // Okay, now we can't return - no launching off at silly angles for you.
-#endif
-
 	if (object->player
 	&& ((object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
 	|| (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)))
 	{
 		S_StartSound(object, sfx_s3k8b);
-#define scalefactor ((4*FRACUNIT)/3)
-		horizspeed = FixedMul(horizspeed, scalefactor);
-		vertispeed = FixedMul(vertispeed, FixedSqrt(scalefactor));
-#undef scalefactor
+		horizspeed = FixedMul(horizspeed, (4*FRACUNIT)/3);
+		vertispeed = FixedMul(vertispeed, (6*FRACUNIT)/5); // aprox square root of above
 	}
 
 	object->eflags |= MFE_SPRUNG; // apply this flag asap!
@@ -239,6 +233,11 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		else
 			P_SetPlayerMobjState(object, S_PLAY_FALL);
 	}
+
+#ifdef ESLOPE
+	object->standingslope = NULL; // Okay, now we know it's not going to be relevant - no launching off at silly angles for you.
+#endif
+
 	return true;
 }
 

From 5065fa302a49d61810e769e696fb9c6fa5da66fc Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 27 Mar 2017 15:03:21 +0100
Subject: [PATCH 105/119] Mutiability flag-ised as requested by Rob.

---
 src/d_player.h |  2 +-
 src/dehacked.c |  2 +-
 src/p_user.c   | 55 ++++++++++++++++++++++----------------------------
 3 files changed, 26 insertions(+), 33 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index 9f8bcd8b4..4e4a53a08 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -46,6 +46,7 @@ typedef enum
 	SF_MACHINE          = 1<<10, // Beep boop. Are you a robot?
 	SF_DASHMODE         = 1<<11, // Sonic Advance 2 style top speed increase?
 	SF_FASTEDGE         = 1<<12, // Faster edge teeter?
+	SF_MULTIABILITY     = 1<<13, // Revenge of Final Demo.
 	// free up to and including 1<<31
 } skinflags_t;
 
@@ -75,7 +76,6 @@ typedef enum
 {
 	CA2_NONE=0,
 	CA2_SPINDASH,
-	CA2_MULTIABILITY,
 	CA2_GUNSLINGER,
 	CA2_MELEE
 } charability2_t;
diff --git a/src/dehacked.c b/src/dehacked.c
index 61ecc5f6b..2658267ae 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -7387,6 +7387,7 @@ struct {
 	{"SF_MACHINE",SF_MACHINE},
 	{"SF_DASHMODE",SF_DASHMODE},
 	{"SF_FASTEDGE",SF_FASTEDGE},
+	{"SF_MULTIABILITY",SF_MULTIABILITY},
 
 	// Character abilities!
 	// Primary
@@ -7409,7 +7410,6 @@ struct {
 	// Secondary
 	{"CA2_NONE",CA2_NONE}, // now slot 0!
 	{"CA2_SPINDASH",CA2_SPINDASH},
-	{"CA2_MULTIABILITY",CA2_MULTIABILITY},
 	{"CA2_GUNSLINGER",CA2_GUNSLINGER},
 	{"CA2_MELEE",CA2_MELEE},
 
diff --git a/src/p_user.c b/src/p_user.c
index f10ae1605..c69cd3840 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3628,7 +3628,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			player->mo->momz = 7*FRACUNIT;
 			if (player->charability == CA_JUMPBOOST && onground)
 			{
-				if (player->charability2 == CA2_MULTIABILITY)
+				if (player->charflags & SF_MULTIABILITY)
 					player->mo->momz += FixedMul(FRACUNIT/4, dist6);
 				else
 					player->mo->momz += FixedMul(FRACUNIT/8, dist6);
@@ -3643,7 +3643,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			// Add a boost for super characters with float/slowfall and multiability.
 			if (player->charability == CA_JUMPBOOST)
 			{
-				if (player->charability2 == CA2_MULTIABILITY)
+				if (player->charflags & SF_MULTIABILITY)
 					player->mo->momz += FixedMul(FRACUNIT/4, dist6);
 				else
 					player->mo->momz += FixedMul(FRACUNIT/8, dist6);
@@ -3654,7 +3654,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			player->mo->momz = 39*(FRACUNIT/4); // Default jump momentum.
 			if (player->charability == CA_JUMPBOOST && onground)
 			{
-				if (player->charability2 == CA2_MULTIABILITY)
+				if (player->charflags & SF_MULTIABILITY)
 					player->mo->momz += FixedMul(FRACUNIT/4, dist6);
 				else
 					player->mo->momz += FixedMul(FRACUNIT/8, dist6);
@@ -3673,7 +3673,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 	if (twodlevel || (player->mo->flags2 & MF2_TWOD))
 		factor += player->jumpfactor / 10;
 
-	if (player->charability2 == CA2_MULTIABILITY && player->charability == CA_DOUBLEJUMP)
+	if (player->charflags & SF_MULTIABILITY && player->charability == CA_DOUBLEJUMP)
 		factor -= max(0, player->secondjump * player->jumpfactor / ((player->actionspd >> FRACBITS) + 1)); // Reduce the jump height each time
 
 	P_SetObjectMomZ(player->mo, FixedMul(factor, player->mo->momz), false); // Custom height
@@ -4117,7 +4117,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	if (player->pflags & PF_JUMPSTASIS)
 		return;
 
-	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charability2 == CA2_MULTIABILITY)) && (lockon = P_LookForEnemies(player, true, false)) && !((leveltime & 4) && (lockon->flags & (MF_ENEMY|MF_BOSS)) && player->powers[pw_shield] == SH_ATTRACT))
+	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY)) && (lockon = P_LookForEnemies(player, true, false)))
 	{
 		if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
 		{
@@ -4146,7 +4146,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				case CA_TELEKINESIS:
 					if (player->pflags & PF_JUMPED)
 					{
-						if (!(player->pflags & PF_THOKKED) || (player->charability2 == CA2_MULTIABILITY))
+						if (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY))
 						{
 							P_Telekinesis(player,
 								-FixedMul(player->actionspd, player->mo->scale), // -ve thrust (pulling towards player)
@@ -4241,7 +4241,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				case CA_JUMPTHOK: // Credit goes to CZ64 and Sryder13 for the original
 					// Now it's Sonic's abilities turn!
 					// THOK!
-					if (!(player->pflags & PF_THOKKED) || (player->charability2 == CA2_MULTIABILITY))
+					if (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY))
 					{
 						// Catapult the player
 						fixed_t actionspd = player->actionspd;
@@ -4315,7 +4315,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					break;
 				case CA_GLIDEANDCLIMB:
 					// Now Knuckles-type abilities are checked.
-					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						INT32 glidespeed = player->actionspd;
 
@@ -4328,7 +4328,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					}
 					break;
 				case CA_DOUBLEJUMP: // Double-Jump
-					if (!(player->pflags & PF_THOKKED) || ((player->charability2 == CA2_MULTIABILITY) && (player->secondjump < (player->actionspd >> FRACBITS))))
+					if (!(player->pflags & PF_THOKKED) || ((player->charflags & SF_MULTIABILITY) && (player->secondjump < (player->actionspd >> FRACBITS))))
 					{
 						player->pflags |= PF_THOKKED;
 						player->pflags &= ~PF_JUMPED;
@@ -4338,7 +4338,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					break;
 				case CA_FLOAT: // Float
 				case CA_SLOWFALL: // Slow descent hover
-					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE)
 							P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
@@ -4352,7 +4352,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					}
 					break;
 				case CA_TELEKINESIS:
-					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						P_Telekinesis(player,
 							FixedMul(player->actionspd, player->mo->scale), // +ve thrust (pushing away from player)
@@ -4360,7 +4360,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					}
 					break;
 				case CA_FALLSWITCH:
-					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						player->mo->momz = -player->mo->momz;
 						P_SpawnThokMobj(player);
@@ -4368,7 +4368,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					}
 					break;
 				case CA_AIRDRILL:
-					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						player->flyangle = 56 + (60-(player->actionspd>>FRACBITS))/3;
 						player->pflags |= PF_THOKKED;
@@ -4376,7 +4376,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					}
 					break;
 				case CA_BOUNCE:
-					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE);
 						player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
@@ -4387,7 +4387,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					}
 					break;
 				case CA_TWINSPIN:
-					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
+					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						player->pflags |= PF_THOKKED;
 						S_StartSound(player->mo, sfx_s3k42);
@@ -4461,17 +4461,15 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		// Repeat abilities, but not double jump!
 		if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP)
 		{
-			if (player->charability2 == CA2_MULTIABILITY)
+			if (player->charflags & SF_MULTIABILITY)
 			{
-				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
-				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+				player->pflags |= (PF_JUMPED|PF_NOJUMPDAMAGE);
 				player->secondjump = 0;
 			}
 			else
-			{
-				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 				player->secondjump = 2;
-			}
+
+			P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 		}
 
 		// If letting go of the jump button while still on ascent, cut the jump height.
@@ -6917,7 +6915,7 @@ static void P_MovePlayer(player_t *player)
 			P_ResetPlayer(player); // down, stop gliding.
 			if (onground)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-			else if (player->charability2 == CA2_MULTIABILITY)
+			else if (player->charflags & SF_MULTIABILITY)
 			{
 				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
@@ -6954,7 +6952,7 @@ static void P_MovePlayer(player_t *player)
 			player->pflags |= PF_THOKKED;
 			if (onground)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-			else if (player->charability2 == CA2_MULTIABILITY)
+			else if (player->charflags & SF_MULTIABILITY)
 			{
 				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
@@ -7034,7 +7032,7 @@ static void P_MovePlayer(player_t *player)
 		{
 			const fixed_t actionspd = player->actionspd/100;
 
-			if (player->charability2 == CA2_MULTIABILITY)
+			if (player->charflags & SF_MULTIABILITY)
 			{
 				// Adventure-style flying by just holding the button down
 				if (cmd->buttons & BT_JUMP && !(player->pflags & PF_STASIS) && !player->exiting)
@@ -9139,8 +9137,8 @@ void P_PlayerThink(player_t *player)
 		if (player->panim != PA_ABILITY)
 			P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
 	}
-	else if ((player->pflags & PF_JUMPED)
-	&& ((player->charflags & SF_NOJUMPSPIN && !(player->pflags & PF_NOJUMPDAMAGE) && player->panim != PA_ROLL)
+	else if ((player->pflags & PF_JUMPED && !(player->pflags & PF_NOJUMPDAMAGE))
+	&& ((player->charflags & SF_NOJUMPSPIN && player->panim != PA_ROLL)
 	|| (!(player->charflags & SF_NOJUMPSPIN) && player->panim != PA_JUMP)))
 	{
 		if (!(player->charflags & SF_NOJUMPSPIN))
@@ -9806,11 +9804,6 @@ void P_PlayerAfterThink(player_t *player)
 	}
 	else if (player->pflags & PF_SLIDING)
 		P_SetPlayerMobjState(player->mo, player->mo->info->painstate);
-	else if (player->pflags & PF_JUMPED
-	&& ((!player->powers[pw_super] && player->panim != PA_JUMP)
-	|| player->mo->state == &states[player->mo->info->painstate])
-	&& !(player->charflags & SF_NOJUMPSPIN))
-		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 
 	/* if (player->powers[pw_carry] == CR_NONE && player->mo->tracer && !player->homing)
 		P_SetTarget(&player->mo->tracer, NULL);

From 734d215b35dfe1e879d686ce6222cfcc80703e05 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 27 Mar 2017 15:29:19 +0100
Subject: [PATCH 106/119] Corrected oversight.

---
 src/p_user.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_user.c b/src/p_user.c
index c69cd3840..008d69285 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -7182,7 +7182,7 @@ static void P_MovePlayer(player_t *player)
 		mobj_t *lockon = NULL;
 		if (!player->powers[pw_super] && player->powers[pw_shield] == SH_ATTRACT && !(player->pflags & PF_THOKKED))
 		{
-			if ((lockon = P_LookForEnemies(player, false, false)) && !(!(leveltime & 4) && player->charability == CA_HOMINGTHOK))
+			if ((lockon = P_LookForEnemies(player, false, false)))
 			{
 				if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
 				{

From 48777e60ed8a13aaa397279b683979f9dd462a1f Mon Sep 17 00:00:00 2001
From: Monster Iestyn <iestynjealous@ntlworld.com>
Date: Wed, 29 Mar 2017 21:27:44 +0100
Subject: [PATCH 107/119] check i not add, silly

---
 src/m_misc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/m_misc.c b/src/m_misc.c
index cfe73d88f..d88643ec4 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -585,7 +585,7 @@ static const char *Newsnapshotfile(const char *pathname, const char *ext)
 
 		i += add * result;
 
-		if (add < 0 || add > 9999)
+		if (i < 0 || i > 9999)
 			return NULL;
 	}
 

From b5d1bff18abffc07c10829aa6a8cabfb82a3c221 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Wed, 29 Mar 2017 18:30:45 -0400
Subject: [PATCH 108/119] Scaled down cam_height default

25 was picked over 24 due to 160/25 being exactly proportional to
128/20.
---
 src/p_user.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 1e527607e..a6df976fe 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8021,13 +8021,13 @@ static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {45, "MAX"}, {0, NULL
 static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}};
 
 consvar_t cv_cam_dist = {"cam_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_height = {"cam_height", "32", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_height = {"cam_height", "25", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_still = {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_speed = {"cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotate = {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_dist = {"cam2_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_height = {"cam2_height", "32", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_height = {"cam2_height", "25", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_still = {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_speed = {"cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotate = {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange, 0, NULL, NULL, 0, 0, NULL};

From bf29b5c6d188dfe25e0d6c5b614b254d1502ca5b Mon Sep 17 00:00:00 2001
From: Monster Iestyn <iestynjealous@ntlworld.com>
Date: Sat, 1 Apr 2017 20:16:48 +0100
Subject: [PATCH 109/119] Print warning message in console if line->next is
 NULL

---
 src/p_spec.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/p_spec.c b/src/p_spec.c
index 3d83561a9..a699cbc25 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3039,7 +3039,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 443: // Calls a named Lua function
 #ifdef HAVE_BLUA
-			LUAh_LinedefExecute(line, mo, callsec);
+			if (line->text)
+				LUAh_LinedefExecute(line, mo, callsec);
+			else
+				CONS_Alert(CONS_WARNING, "Linedef %d is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", line-lines);
 #else
 			CONS_Alert(CONS_ERROR, "The map is trying to run a Lua script, but this exe was not compiled with Lua support!\n");
 #endif

From 4fa188cf01c6c9aceed293237d5532b6abe641d3 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Sat, 1 Apr 2017 17:22:24 -0400
Subject: [PATCH 110/119] build: fix 64-bit builds

---
 src/p_spec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_spec.c b/src/p_spec.c
index a699cbc25..48c0f58b3 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3042,7 +3042,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			if (line->text)
 				LUAh_LinedefExecute(line, mo, callsec);
 			else
-				CONS_Alert(CONS_WARNING, "Linedef %d is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", line-lines);
+				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(line-lines));
 #else
 			CONS_Alert(CONS_ERROR, "The map is trying to run a Lua script, but this exe was not compiled with Lua support!\n");
 #endif

From 643daa3394405f25f4221fa11e8a5071423bea36 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 3 Apr 2017 13:23:05 +0100
Subject: [PATCH 111/119] Updated coloropposite table for the purpose of the
 .wads under development.

---
 src/r_draw.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/r_draw.c b/src/r_draw.c
index 9cd0d2944..29f57b35d 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -194,10 +194,10 @@ const UINT8 Color_Opposite[MAXSKINCOLORS*2] =
 	SKINCOLOR_BROWN,8,   	// SKINCOLOR_PEACH - ditto
 	SKINCOLOR_PEACH,8,   	// SKINCOLOR_BROWN - ditto
 	SKINCOLOR_GREEN,5,  	// SKINCOLOR_RED
-	SKINCOLOR_LAVENDER,8,   // SKINCOLOR_CRIMSON - ditto
-	SKINCOLOR_BLUE,12,  	// SKINCOLOR_ORANGE
+	SKINCOLOR_CYAN,8,  		// SKINCOLOR_CRIMSON - ditto
+	SKINCOLOR_BLUE,11,  	// SKINCOLOR_ORANGE
 	SKINCOLOR_TAN,8,   		// SKINCOLOR_RUST - ditto
-	SKINCOLOR_CYAN,8,       // SKINCOLOR_GOLD - ditto
+	SKINCOLOR_LAVENDER,8,	// SKINCOLOR_GOLD
 	SKINCOLOR_TEAL,8,   	// SKINCOLOR_YELLOW - ditto
 	SKINCOLOR_RUST,8,   	// SKINCOLOR_TAN - ditto
 	SKINCOLOR_MAGENTA,3, 	// SKINCOLOR_MOSS
@@ -206,15 +206,15 @@ const UINT8 Color_Opposite[MAXSKINCOLORS*2] =
 	SKINCOLOR_PASTEL,8,   	// SKINCOLOR_EMERALD - ditto
 	SKINCOLOR_ROSY,8,   	// SKINCOLOR_AQUA - ditto
 	SKINCOLOR_YELLOW,8,   	// SKINCOLOR_TEAL - ditto
-	SKINCOLOR_GOLD,8,   	// SKINCOLOR_CYAN - ditto
+	SKINCOLOR_CRIMSON,8,    // SKINCOLOR_CYAN - ditto
 	SKINCOLOR_ORANGE,9, 	// SKINCOLOR_BLUE
 	SKINCOLOR_PINK,8,   	// SKINCOLOR_AZURE - ditto
 	SKINCOLOR_EMERALD,8,   	// SKINCOLOR_PASTEL - ditto
-	SKINCOLOR_PERIDOT,10,   // SKINCOLOR_PURPLE - ditto
-	SKINCOLOR_CRIMSON,12,   // SKINCOLOR_LAVENDER - ditto
+	SKINCOLOR_PERIDOT,10,   // SKINCOLOR_PURPLE
+	SKINCOLOR_GOLD,10,      // SKINCOLOR_LAVENDER
 	SKINCOLOR_MOSS,8,   	// SKINCOLOR_MAGENTA - ditto
 	SKINCOLOR_AZURE,8,   	// SKINCOLOR_PINK - ditto
-	SKINCOLOR_AQUA,14   	// SKINCOLOR_ROSY - ditto
+	SKINCOLOR_AQUA,14   	// SKINCOLOR_ROSY
 };
 
 CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];

From 1c737f33c57572bf9d860a0ba319d53b79d0bef3 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 3 Apr 2017 13:33:05 +0100
Subject: [PATCH 112/119] Camerascale fix to apply to more stuff.

---
 src/p_user.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 008d69285..7a72f360c 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8374,7 +8374,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		camspeed = cv_cam_speed.value;
 		camstill = cv_cam_still.value;
 		camrotate = cv_cam_rotate.value;
-		camdist = FixedMul(cv_cam_dist.value, FixedMul(player->camerascale, mo->scale));
+		camdist = FixedMul(cv_cam_dist.value, mo->scale);
 		camheight = FixedMul(cv_cam_height.value, FixedMul(player->camerascale, mo->scale));
 	}
 	else // Camera 2
@@ -8382,7 +8382,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		camspeed = cv_cam2_speed.value;
 		camstill = cv_cam2_still.value;
 		camrotate = cv_cam2_rotate.value;
-		camdist = FixedMul(cv_cam2_dist.value, FixedMul(player->camerascale, mo->scale));
+		camdist = FixedMul(cv_cam2_dist.value, mo->scale);
 		camheight = FixedMul(cv_cam2_height.value, FixedMul(player->camerascale, mo->scale));
 	}
 
@@ -8497,6 +8497,8 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			dist <<= 1;
 	}
 
+	dist = FixedMul(dist, player->camerascale);
+
 	checkdist = dist;
 
 	if (checkdist < 128*FRACUNIT)

From 0c792ff67aa47948ade9d51a3fc90245fd5a3b86 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 3 Apr 2017 16:05:13 +0100
Subject: [PATCH 113/119] Add missing flag reads.

---
 src/r_things.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/r_things.c b/src/r_things.c
index d3faa3644..2f8e7c91a 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2939,6 +2939,8 @@ void R_AddSkins(UINT16 wadnum)
 			GETFLAG(MARIODAMAGE)
 			GETFLAG(MACHINE)
 			GETFLAG(DASHMODE)
+			GETFLAG(FASTEDGE)
+			GETFLAG(MULTIABILITY)
 #undef GETFLAG
 
 			else // let's check if it's a sound, otherwise error out

From 1e64b096fd54a6258dd148ae4fc33f95638b26ae Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 3 Apr 2017 18:28:02 +0100
Subject: [PATCH 114/119] Bugfixes and code cleanup.

* Getting hit whilst on a swinging mace doesn't send you off in roll/jump state.
* P_GetJumpFlags replaces a bunch of copypasted code.
* Homing attacking a spring sends you directly on its path, rather than rocketing off in the direction you were preiously hurtling in.
---
 src/lua_baselib.c | 12 +++++++
 src/p_local.h     |  1 +
 src/p_map.c       |  4 +--
 src/p_user.c      | 84 +++++++++++++++++++++++++++--------------------
 src/st_stuff.c    |  2 +-
 5 files changed, 64 insertions(+), 39 deletions(-)

diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index ef8e025b8..71f6a7e65 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -815,6 +815,17 @@ static int lib_pStealPlayerScore(lua_State *L)
 	return 0;
 }
 
+static int lib_pGetJumpFlags(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	NOHUD
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	lua_pushinteger(L, P_GetJumpFlags(player));
+	return 1;
+}
+
 static int lib_pPlayerInPain(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@@ -2378,6 +2389,7 @@ static luaL_Reg lib[] = {
 	{"P_GetPlayerControlDirection",lib_pGetPlayerControlDirection},
 	{"P_AddPlayerScore",lib_pAddPlayerScore},
 	{"P_StealPlayerScore",lib_pStealPlayerScore},
+	{"P_GetJumpFlags",lib_pGetJumpFlags},
 	{"P_PlayerInPain",lib_pPlayerInPain},
 	{"P_DoPlayerPain",lib_pDoPlayerPain},
 	{"P_ResetPlayer",lib_pResetPlayer},
diff --git a/src/p_local.h b/src/p_local.h
index 78ef65192..a1b07e952 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -130,6 +130,7 @@ void P_ResetCamera(player_t *player, camera_t *thiscam);
 boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam);
 void P_SlideCameraMove(camera_t *thiscam);
 boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled);
+pflags_t P_GetJumpFlags(player_t *player);
 boolean P_PlayerInPain(player_t *player);
 void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor);
 void P_ResetPlayer(player_t *player);
diff --git a/src/p_map.c b/src/p_map.c
index 04858b5df..a74c9f1e2 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -142,7 +142,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	object->eflags |= MFE_SPRUNG; // apply this flag asap!
 	spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify
 
-	if (horizspeed && vertispeed) // Mimic SA
+	if ((horizspeed && vertispeed) || (object->player && object->player->homing)) // Mimic SA
 	{
 		object->momx = object->momy = 0;
 		P_TryMove(object, spring->x, spring->y, true);
@@ -214,7 +214,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 
 		if (spring->info->painchance)
 		{
-			object->player->pflags |= (PF_JUMPED|((object->player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+			object->player->pflags |= P_GetJumpFlags(object->player);
 			P_SetPlayerMobjState(object, S_PLAY_JUMP);
 		}
 		else if (!vertispeed || (pflags & PF_BOUNCING)) // horizontal spring or bouncing
diff --git a/src/p_user.c b/src/p_user.c
index 7a72f360c..b17466991 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -769,6 +769,13 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	player->powers[pw_carry] = CR_NIGHTSMODE;
 }
 
+pflags_t P_GetJumpFlags(player_t *player)
+{
+	if (player->charflags & SF_NOJUMPDAMAGE)
+		return (PF_JUMPED|PF_NOJUMPDAMAGE);
+	return PF_JUMPED;
+}
+
 //
 // P_PlayerInPain
 //
@@ -1677,7 +1684,7 @@ void P_DoPlayerExit(player_t *player)
 	if (player->climbing)
 	{
 		player->climbing = 0;
-		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+		player->pflags |= P_GetJumpFlags(player);
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 	}
 	player->powers[pw_underwater] = 0;
@@ -2012,7 +2019,7 @@ static void P_CheckBouncySectors(player_t *player)
 						if (player->pflags & PF_SPINNING)
 						{
 							player->pflags &= ~PF_SPINNING;
-							player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+							player->pflags |= P_GetJumpFlags(player);
 							player->pflags |= PF_THOKKED;
 						}
 					}
@@ -2024,7 +2031,7 @@ static void P_CheckBouncySectors(player_t *player)
 						if (player->pflags & PF_SPINNING)
 						{
 							player->pflags &= ~PF_SPINNING;
-							player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+							player->pflags |= P_GetJumpFlags(player);
 							player->pflags |= PF_THOKKED;
 						}
 					}
@@ -2032,7 +2039,7 @@ static void P_CheckBouncySectors(player_t *player)
 					if ((player->pflags & PF_SPINNING) && player->speed < FixedMul(1<<FRACBITS, player->mo->scale) && player->mo->momz)
 					{
 						player->pflags &= ~PF_SPINNING;
-						player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+						player->pflags |= P_GetJumpFlags(player);
 					}
 
 					goto bouncydone;
@@ -2769,21 +2776,21 @@ static void P_DoClimbing(player_t *player)
 				P_InstaThrust(player->mo, player->mo->angle, FixedMul(4*FRACUNIT, player->mo->scale)); // Lil' boost up.
 
 			player->climbing = 0;
-			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+			player->pflags |= P_GetJumpFlags(player);
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 
 		if (skyclimber)
 		{
 			player->climbing = 0;
-			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+			player->pflags |= P_GetJumpFlags(player);
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 	}
 	else
 	{
 		player->climbing = 0;
-		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+		player->pflags |= P_GetJumpFlags(player);
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 	}
 
@@ -2801,7 +2808,7 @@ static void P_DoClimbing(player_t *player)
 	if (cmd->buttons & BT_USE && !(player->pflags & PF_JUMPSTASIS))
 	{
 		player->climbing = 0;
-		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+		player->pflags |= P_GetJumpFlags(player);
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		P_SetObjectMomZ(player->mo, 4*FRACUNIT, false);
 		P_InstaThrust(player->mo, player->mo->angle, FixedMul(-4*FRACUNIT, player->mo->scale));
@@ -3697,7 +3704,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 	}
 	player->mo->eflags &= ~MFE_APPLYPMOMZ;
 
-	player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));;
+	player->pflags |= P_GetJumpFlags(player);;
 
 	if (soundandstate)
 	{
@@ -6848,7 +6855,7 @@ static void P_MovePlayer(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 		else
 		{
-			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+			player->pflags |= P_GetJumpFlags(player);
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 		player->pflags &= ~PF_GLIDING;
@@ -6863,7 +6870,7 @@ static void P_MovePlayer(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 		else
 		{
-			player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+			player->pflags |= P_GetJumpFlags(player);
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 		player->pflags &= ~PF_BOUNCING;
@@ -6917,7 +6924,7 @@ static void P_MovePlayer(player_t *player)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 			else if (player->charflags & SF_MULTIABILITY)
 			{
-				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+				player->pflags |= P_GetJumpFlags(player);
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 			}
 			else
@@ -6954,7 +6961,7 @@ static void P_MovePlayer(player_t *player)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 			else if (player->charflags & SF_MULTIABILITY)
 			{
-				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+				player->pflags |= P_GetJumpFlags(player);
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 			}
 			else
@@ -7011,7 +7018,7 @@ static void P_MovePlayer(player_t *player)
 				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
 			else
 			{
-				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+				player->pflags |= P_GetJumpFlags(player);
 				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 			}
 		}
@@ -7681,7 +7688,7 @@ static void P_DoRopeHang(player_t *player)
 	{
 		P_SetTarget(&player->mo->tracer, NULL);
 
-		player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+		player->pflags |= P_GetJumpFlags(player);
 		player->powers[pw_carry] = CR_NONE;
 
 		if (!(player->pflags & PF_SLIDING) && (player->pflags & PF_JUMPED)
@@ -7779,7 +7786,7 @@ static void P_DoRopeHang(player_t *player)
 		{
 			if (player->mo->tracer->flags & MF_SLIDEME)
 			{
-				player->pflags |= (PF_JUMPED|((player->charflags & SF_NOJUMPDAMAGE) ? PF_NOJUMPDAMAGE : 0));
+				player->pflags |= P_GetJumpFlags(player);
 
 				if (!(player->pflags & PF_SLIDING) && (player->pflags & PF_JUMPED)
 				&& !(player->panim == PA_JUMP))
@@ -9134,7 +9141,9 @@ void P_PlayerThink(player_t *player)
 		}
 	}
 #endif
-	if (player->pflags & PF_GLIDING)
+	if (!player->mo->health)
+		;
+	else if (player->pflags & PF_GLIDING)
 	{
 		if (player->panim != PA_ABILITY)
 			P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
@@ -9918,28 +9927,31 @@ void P_PlayerAfterThink(player_t *player)
 		player->mo->momy = (player->mo->tracer->y - player->mo->y)*2;
 		player->mo->momz = (player->mo->tracer->z - (player->mo->height-player->mo->tracer->height/2) - player->mo->z)*2;
 		P_TeleportMove(player->mo, player->mo->tracer->x, player->mo->tracer->y, player->mo->tracer->z - (player->mo->height-player->mo->tracer->height/2));
-		player->pflags |= PF_JUMPED;
-		player->pflags &= ~PF_NOJUMPDAMAGE;
-		player->secondjump = 0;
-		player->pflags &= ~PF_THOKKED;
-
-		if (cmd->forwardmove > 0)
-			player->mo->tracer->target->lastlook += 2;
-		else if (cmd->forwardmove < 0 && player->mo->tracer->target->lastlook > player->mo->tracer->target->movecount)
-			player->mo->tracer->target->lastlook -= 2;
-
-		if (!(player->mo->tracer->target->flags & MF_SLIDEME) // Noclimb on chain parameters gives this
-		&& !(twodlevel || player->mo->flags2 & MF2_TWOD)) // why on earth would you want to turn them in 2D mode?
+		if (!player->powers[pw_flashing]) // handle getting hurt
 		{
-			player->mo->tracer->target->health += cmd->sidemove;
-			player->mo->angle += cmd->sidemove<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
+			player->pflags |= PF_JUMPED;
+			player->pflags &= ~PF_NOJUMPDAMAGE;
+			player->secondjump = 0;
+			player->pflags &= ~PF_THOKKED;
 
-			if (!demoplayback || P_AnalogMove(player))
+			if (cmd->forwardmove > 0)
+				player->mo->tracer->target->lastlook += 2;
+			else if (cmd->forwardmove < 0 && player->mo->tracer->target->lastlook > player->mo->tracer->target->movecount)
+				player->mo->tracer->target->lastlook -= 2;
+
+			if (!(player->mo->tracer->target->flags & MF_SLIDEME) // Noclimb on chain parameters gives this
+			&& !(twodlevel || player->mo->flags2 & MF2_TWOD)) // why on earth would you want to turn them in 2D mode?
 			{
-				if (player == &players[consoleplayer])
-					localangle = player->mo->angle; // Adjust the local control angle.
-				else if (player == &players[secondarydisplayplayer])
-					localangle2 = player->mo->angle;
+				player->mo->tracer->target->health += cmd->sidemove;
+				player->mo->angle += cmd->sidemove<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
+
+				if (!demoplayback || P_AnalogMove(player))
+				{
+					if (player == &players[consoleplayer])
+						localangle = player->mo->angle; // Adjust the local control angle.
+					else if (player == &players[secondarydisplayplayer])
+						localangle2 = player->mo->angle;
+				}
 			}
 		}
 	}
diff --git a/src/st_stuff.c b/src/st_stuff.c
index e5d005b23..315dcd068 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -581,7 +581,7 @@ static void ST_drawDebugInfo(void)
 	{
 		V_DrawRightAlignedString(320, height - 104, V_MONOSPACE, va("SHIELD: %5x", stplyr->powers[pw_shield]));
 		V_DrawRightAlignedString(320, height - 96,  V_MONOSPACE, va("SCALE: %5d%%", (stplyr->mo->scale*100)/FRACUNIT));
-		V_DrawRightAlignedString(320, height - 88,  V_MONOSPACE, va("DASH: %3d/%3d", stplyr->dashspeed>>FRACBITS, stplyr->maxdash>>FRACBITS));
+		V_DrawRightAlignedString(320, height - 88,  V_MONOSPACE, va("CARRY: %5x", stplyr->powers[pw_carry]));
 		V_DrawRightAlignedString(320, height - 80,  V_MONOSPACE, va("AIR: %4d, %3d", stplyr->powers[pw_underwater], stplyr->powers[pw_spacetime]));
 
 		// Flags

From caeb5157833c5d77ad05a2549854f810bdff6c71 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Mon, 3 Apr 2017 19:01:30 +0100
Subject: [PATCH 115/119] On request, swapped out MT_PARTICLE dust for
 MT_SPINDUST dust.

---
 src/p_user.c | 31 +++++++++++++++++--------------
 1 file changed, 17 insertions(+), 14 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index b17466991..bccf930dd 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -6541,16 +6541,18 @@ static void P_SkidStuff(player_t *player)
 		// Spawn a particle every 3 tics.
 		else if (!(player->skidtime % 3))
 		{
-			mobj_t *particle = P_SpawnMobj(player->mo->x + P_RandomRange(-player->mo->radius, player->mo->radius), player->mo->y + P_RandomRange(-player->mo->radius, player->mo->radius),
-				player->mo->z + (player->mo->eflags & MFE_VERTICALFLIP ? player->mo->height - mobjinfo[MT_PARTICLE].height : 0),
-				MT_PARTICLE);
+			mobj_t *particle = P_SpawnMobjFromMobj(player->mo, P_RandomRange(-player->mo->radius, player->mo->radius), P_RandomRange(-player->mo->radius, player->mo->radius), 0, MT_SPINDUST);
 			particle->tics = 10;
 
-			particle->eflags |= player->mo->eflags & MFE_VERTICALFLIP;
-			P_SetScale(particle, player->mo->scale >> 2);
-			particle->destscale = player->mo->scale << 2;
-			particle->scalespeed = FixedMul(particle->scalespeed, player->mo->scale); // scale the scaling speed!
+			particle->destscale = (2*player->mo->scale)/3;
+			P_SetScale(particle, particle->destscale);
 			P_SetObjectMomZ(particle, FRACUNIT, false);
+
+			if (player->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)) // overrides fire version
+				P_SetMobjState(particle, S_SPINDUST_BUBBLE1);
+			else if (player->powers[pw_shield] == SH_ELEMENTAL)
+				P_SetMobjState(particle, S_SPINDUST_FIRE1);
+
 			S_StartSound(player->mo, sfx_s3k7e); // the proper "Knuckles eats dirt" sfx.
 		}
 	}
@@ -6562,16 +6564,17 @@ static void P_SkidStuff(player_t *player)
 			// Spawn a particle every 3 tics.
 			if (!(player->skidtime % 3))
 			{
-				mobj_t *particle = P_SpawnMobj(player->mo->x, player->mo->y,
-					player->mo->z + (player->mo->eflags & MFE_VERTICALFLIP ? player->mo->height - mobjinfo[MT_PARTICLE].height : 0),
-					MT_PARTICLE);
+				mobj_t *particle = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_SPINDUST);
 				particle->tics = 10;
 
-				particle->eflags |= player->mo->eflags & MFE_VERTICALFLIP;
-				P_SetScale(particle, player->mo->scale >> 2);
-				particle->destscale = player->mo->scale << 2;
-				particle->scalespeed = FixedMul(particle->scalespeed, player->mo->scale); // scale the scaling speed!
+				particle->destscale = (2*player->mo->scale)/3;
+				P_SetScale(particle, particle->destscale);
 				P_SetObjectMomZ(particle, FRACUNIT, false);
+
+				if (player->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)) // overrides fire version
+					P_SetMobjState(particle, S_SPINDUST_BUBBLE1);
+				else if (player->powers[pw_shield] == SH_ELEMENTAL)
+					P_SetMobjState(particle, S_SPINDUST_FIRE1);
 			}
 		}
 		else if (P_AproxDistance(pmx, pmy) >= FixedMul(player->runspeed/2, player->mo->scale) // if you were moving faster than half your run speed last frame

From d5575669faad985c8f1b9ed2ca444aeb5d1595d4 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 4 Apr 2017 16:50:33 +0100
Subject: [PATCH 116/119] Handled some of MI's concerns.

---
 src/p_mobj.c   |  3 +++
 src/st_stuff.c | 12 ++++++------
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index c729b7aa7..520f9ad07 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2541,6 +2541,9 @@ boolean P_CheckDeathPitCollide(mobj_t *mo)
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
 
+	if (mo->player && mo->player->pflags & PF_GODMODE)
+		return false;
+
 	if (((mo->z <= mo->subsector->sector->floorheight
 		&& !(mo->eflags & MFE_VERTICALFLIP) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_FLOOR))
 	|| (mo->z + mo->height >= mo->subsector->sector->ceilingheight
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 315dcd068..4af3199a1 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1490,25 +1490,25 @@ static inline void ST_drawRaceHUD(void)
 	{
 		INT32 height = (BASEVIDHEIGHT/2);
 		INT32 bounce = (leveltime % TICRATE);
-		patch_t **racenum;
+		patch_t *racenum;
 		switch (leveltime/TICRATE)
 		{
 			case 1:
-				racenum = &race3;
+				racenum = race3;
 				break;
 			case 2:
-				racenum = &race2;
+				racenum = race2;
 				break;
 			case 3:
-				racenum = &race1;
+				racenum = race1;
 				break;
 			default:
-				racenum = &racego;
+				racenum = racego;
 				break;
 		}
 		if (bounce < 3)
 			height -= (2 - bounce);
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT((*racenum)->width))/2), (INT32)(SCY(height)), V_NOSCALESTART, *racenum);
+		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(racenum->width))/2), (INT32)(SCY(height)), V_NOSCALESTART, racenum);
 	}
 
 	if (circuitmap)

From 6c923b26e74eaf4e16cbf962be7db9fe0c571777 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 4 Apr 2017 21:29:44 +0100
Subject: [PATCH 117/119] woops forgot to push this

---
 src/m_menu.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/m_menu.c b/src/m_menu.c
index bbdaee621..fb8aeedad 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2424,14 +2424,20 @@ boolean M_Responder(event_t *ev)
 			M_NextOpt();
 			S_StartSound(NULL, sfx_menu1);
 			if (currentMenu == &MISC_ChangeGameTypeDef)
+			{
 				Z_Free(char_notes);
+				char_notes = NULL;
+			}
 			return true;
 
 		case KEY_UPARROW:
 			M_PrevOpt();
 			S_StartSound(NULL, sfx_menu1);
 			if (currentMenu == &MISC_ChangeGameTypeDef)
+			{
 				Z_Free(char_notes);
+				char_notes = NULL;
+			}
 			return true;
 
 		case KEY_LEFTARROW:

From 89f518a8a3fc0abe71e15cc0ea1147cc4b77445f Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Tue, 4 Apr 2017 23:12:43 +0100
Subject: [PATCH 118/119] FuriousFox discovered a crash, realised I wasn't
 careful here.

---
 src/p_user.c | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 44077d679..d46f5130e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -9829,10 +9829,7 @@ void P_PlayerAfterThink(player_t *player)
 		player->mo->height = FixedDiv(P_GetPlayerHeight(player), FixedDiv(14*FRACUNIT,10*FRACUNIT));
 
 		if (player->mo->tracer->player && !(player->mo->tracer->player->pflags & PF_CANCARRY))
-		{
 			player->powers[pw_carry] = CR_NONE;
-			P_SetTarget(&player->mo->tracer, NULL);
-		}
 
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 		{
@@ -9840,10 +9837,7 @@ void P_PlayerAfterThink(player_t *player)
 				&& (player->mo->tracer->eflags & MFE_VERTICALFLIP)) // Reverse gravity check for the carrier - Flame
 				player->mo->z = player->mo->tracer->z + player->mo->tracer->height + FixedMul(FRACUNIT, player->mo->scale);
 			else
-			{
 				player->powers[pw_carry] = CR_NONE;
-				P_SetTarget(&player->mo->tracer, NULL);
-			}
 		}
 		else
 		{
@@ -9851,10 +9845,7 @@ void P_PlayerAfterThink(player_t *player)
 				&& !(player->mo->tracer->eflags & MFE_VERTICALFLIP)) // Correct gravity check for the carrier - Flame
 				player->mo->z = player->mo->tracer->z - player->mo->height - FixedMul(FRACUNIT, player->mo->scale);
 			else
-			{
 				player->powers[pw_carry] = CR_NONE;
-				P_SetTarget(&player->mo->tracer, NULL);
-			}
 		}
 
 		if (player->mo->tracer->health <= 0)
@@ -9881,12 +9872,12 @@ void P_PlayerAfterThink(player_t *player)
 		}
 
 		if (P_AproxDistance(player->mo->x - player->mo->tracer->x, player->mo->y - player->mo->tracer->y) > player->mo->radius)
-		{
 			player->powers[pw_carry] = CR_NONE;
-			P_SetTarget(&player->mo->tracer, NULL);
-		}
 
-		P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+		if (player->powers[pw_carry] != CR_NONE)
+			P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+		else
+			P_SetTarget(&player->mo->tracer, NULL);
 
 		if (player-players == consoleplayer && botingame)
 			CV_SetValue(&cv_analog2, (player->powers[pw_carry] != CR_PLAYER));

From 73ea33d3a1740e5bdc849f9f43ee649d468979d0 Mon Sep 17 00:00:00 2001
From: toasterbabe <rollerorbital@gmail.com>
Date: Thu, 6 Apr 2017 16:42:52 +0100
Subject: [PATCH 119/119] Fixing the fuckup I made when creating the new
 palette.

---
 src/r_draw.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/r_draw.c b/src/r_draw.c
index 29f57b35d..30b60a0e0 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -420,11 +420,11 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 			if (i <= 11)
 				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1));
 			else if (i == 12)
-				dest_colormap[starttranscolor + i] = 0x2c; // Darkest 4
-			else if (i == 13)
-				dest_colormap[starttranscolor + i] = 0xfe; // Darkest 3
+				dest_colormap[starttranscolor + i] = 0x2c;
+			else if (i <= 14)
+				dest_colormap[starttranscolor + i] = 0x2d;
 			else
-				dest_colormap[starttranscolor + i] = 0x2d + i - 14; // Darkest 2 and 1
+				dest_colormap[starttranscolor + i] = 0x48;
 		}
 		break;
 
@@ -530,7 +530,7 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
 		{
 			if (i == 15)
-				dest_colormap[starttranscolor + i] = 0x1F; //Darkest 1
+				dest_colormap[starttranscolor + i] = 0xfe; //Darkest 1
 			else if (i == 14)
 				dest_colormap[starttranscolor + i] = 0xfd; //Darkest 2
 			else