diff --git a/src/d_player.h b/src/d_player.h
index ef1c1219fe95ed3e2296696371ea2b9e404b0941..f2f14e88684bae2b98ec7769fa0cf214d87ce5f0 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -62,7 +62,8 @@ typedef enum
 	CA_FALLSWITCH,
 	CA_JUMPBOOST,
 	CA_AIRDRILL,
-	CA_JUMPTHOK
+	CA_JUMPTHOK,
+	CA_TWINSPIN
 } charability_t;
 
 //Secondary skin abilities
diff --git a/src/dehacked.c b/src/dehacked.c
index 3b5e3089da4011b703adcfc7fbb8125019586410..59d75946a47087ebbdf183e68c951a1ca45d6d33 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3793,6 +3793,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_CLING",
 	"S_PLAY_CLIMB",
 
+	// CA_TWINSPIN
+	"S_PLAY_TWINSPIN",
+
 	// SF_SUPERANIMS
 	"S_PLAY_SUPER_STND",
 	"S_PLAY_SUPER_WALK",
@@ -7122,6 +7125,7 @@ struct {
 	{"CA_JUMPBOOST",CA_JUMPBOOST},
 	{"CA_AIRDRILL",CA_AIRDRILL},
 	{"CA_JUMPTHOK",CA_JUMPTHOK},
+	{"CA_TWINSPIN",CA_TWINSPIN},
 	// Secondary
 	{"CA2_NONE",CA2_NONE}, // now slot 0!
 	{"CA2_SPINDASH",CA2_SPINDASH},
diff --git a/src/info.c b/src/info.c
index badc285458755018d1dfc114e6b81193352ca6e3..c5ebc85311e7fa1c478c134276584be6846acf74 100644
--- a/src/info.c
+++ b/src/info.c
@@ -85,6 +85,8 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"CLNG",
 	"CLMB",
 
+	"TWIN",
+
 	"TRNS",
 	"SSTD",
 	"SWLK",
@@ -145,17 +147,20 @@ state_t states[NUMSTATES] =
 	{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
 
-	// Tails abilities
+	// 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
 	{SPR_PLAY, SPR2_TIRE,  12, {NULL}, 0, 0, S_PLAY_FLY_TIRED}, // S_PLAY_FLY_TIRED
 
-	// Knuckles abilities
+	// CA_GLIDEANDCLIMB
 	{SPR_PLAY, SPR2_GLID,   2, {NULL}, 0, 0, S_PLAY_GLIDE}, // S_PLAY_GLIDE
 	{SPR_PLAY, SPR2_CLNG,   6, {NULL}, 0, 0, S_PLAY_CLING}, // S_PLAY_CLING
 	{SPR_PLAY, SPR2_CLMB|FF_MIDDLESTARTCHANCE, 5, {NULL}, 0, 0, S_PLAY_CLIMB}, // S_PLAY_CLIMB
 
-	// Super Sonic
+	// CA_TWINSPIN
+	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
+
+	// SF_SUPERANIMS
 	{SPR_PLAY, SPR2_SSTD,   7, {NULL}, 0, 0, S_PLAY_SUPER_STND}, // 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
@@ -172,7 +177,7 @@ state_t states[NUMSTATES] =
 	{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
 
-	// Transforming into Super
+	// 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
diff --git a/src/info.h b/src/info.h
index 610839060dbcbfcfbbf2eb6bba2b08ffa578ce87..809901aa77aa5f269e2b5e90cf3fa9df4efb1b3e 100644
--- a/src/info.h
+++ b/src/info.h
@@ -604,6 +604,8 @@ enum playersprite
 	SPR2_CLNG,
 	SPR2_CLMB,
 
+	SPR2_TWIN, // twinspin
+
 	SPR2_TRNS,
 	SPR2_SSTD,
 	SPR2_SWLK,
@@ -652,13 +654,13 @@ typedef enum state
 	S_PLAY_SPIN,
 	S_PLAY_DASH,
 	S_PLAY_GASP,
-	S_PLAY_JUMP, // spin jump (todo: make jump separate from spring up for non-spin chars too?)
+	S_PLAY_JUMP, // spin jump
 	S_PLAY_SPRING,
 	S_PLAY_FALL,
 	S_PLAY_EDGE,
 	S_PLAY_RIDE,
 
-	// CA_FLY
+	// CA_FLY/SWIM
 	S_PLAY_FLY,
 	S_PLAY_SWIM,
 	S_PLAY_FLY_TIRED,
@@ -668,6 +670,9 @@ typedef enum state
 	S_PLAY_CLING,
 	S_PLAY_CLIMB,
 
+	// CA_TWINSPIN
+	S_PLAY_TWINSPIN,
+
 	// SF_SUPERANIMS
 	S_PLAY_SUPER_STND,
 	S_PLAY_SUPER_WALK,
diff --git a/src/p_inter.c b/src/p_inter.c
index 9c3b36a7e75120a8e345de4623900d80f3d8aebb..eb733a1c6cf63ce082d543335ef0a8a90309f780 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -300,7 +300,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		}
 
 		if (((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| ((player->pflags & PF_JUMPED) && !(player->charflags & SF_NOJUMPDAMAGE))
+		|| ((player->pflags & PF_JUMPED) && !(player->charflags & SF_NOJUMPDAMAGE && !(player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| ((player->charflags & SF_STOMPDAMAGE) && (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?
@@ -345,7 +345,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			P_DamageMobj(toucher, special, special, 1, 0);
 		}
 		else if (((player->pflags & PF_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| ((player->pflags & PF_JUMPED) && !(player->charflags & SF_NOJUMPDAMAGE))
+		|| ((player->pflags & PF_JUMPED) && !(player->charflags & SF_NOJUMPDAMAGE && !(player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
 		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
 		|| ((player->charflags & SF_STOMPDAMAGE) && (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?
diff --git a/src/p_map.c b/src/p_map.c
index 447a31b75c573fe1baacf1c9b94c62efb07e45ef..05cb03c221b8641040693527341aadf033b9df4a 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -939,7 +939,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->charflags & SF_NOJUMPDAMAGE))
+				|| ((tmthing->player->pflags & PF_JUMPED) && !(tmthing->player->charflags & SF_NOJUMPDAMAGE && !(tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
 				|| ((tmthing->player->charflags & SF_STOMPDAMAGE) && (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height/2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0))))
 			{
 				SINT8 flipval = P_MobjFlip(thing); // Save this value in case monitor gets removed.
@@ -964,7 +964,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	// unless it's a CTF team monitor and you're on the wrong team
 	else if (thing->flags & MF_MONITOR && tmthing->player
 	&& (tmthing->player->pflags & (PF_SPINNING|PF_GLIDING)
-		|| ((tmthing->player->pflags & PF_JUMPED) && !(tmthing->player->charflags & SF_NOJUMPDAMAGE))
+		|| ((tmthing->player->pflags & PF_JUMPED) && !(tmthing->player->charflags & SF_NOJUMPDAMAGE && !(tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
 		|| ((tmthing->player->charflags & SF_STOMPDAMAGE) && (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height/2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0)))
 	&& !((thing->type == MT_REDRINGBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_BLUERINGBOX && tmthing->player->ctfteam != 2)))
 		;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 82809a93952a9df6bf22d6c03d564d896901a256..bff4bb09c31ea7398f4c8693f6d4ffb825e65282 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -163,6 +163,10 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		I_Error("P_SetPlayerMobjState used for non-player mobj. Use P_SetMobjState instead!\n(Mobj type: %d, State: %d)", mobj->type, state);
 #endif
 
+	// Catch falling for nojumpspin
+	if ((state == S_PLAY_JUMP) && (player->charflags & SF_NOJUMPSPIN) && (P_MobjFlip(mobj)*mobj->momz < 0 || player->pflags & PF_THOKKED))
+		return P_SetPlayerMobjState(mobj, S_PLAY_FALL);
+
 	// Catch state changes for Super Sonic
 	if (player->powers[pw_super] && (player->charflags & SF_SUPERANIMS))
 	{
@@ -256,6 +260,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 	case S_PLAY_FLY:
 	case S_PLAY_SWIM:
 	case S_PLAY_GLIDE:
+	case S_PLAY_TWINSPIN:
 		player->panim = PA_ABILITY;
 		break;
 	case S_PLAY_RIDE:
@@ -390,6 +395,10 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 					spr2 = SPR2_CLMB;
 					break;
 
+				case SPR2_TWIN:
+					spr2 = SPR2_SPIN;
+					break;
+
 				// Super sprites fallback to regular sprites
 				case SPR2_SWLK:
 					spr2 = SPR2_WALK;
@@ -591,12 +600,7 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state)
 			if (frame >= numframes)
 			{
 				if (st->frame & FF_SPR2ENDSTATE)
-				{
-					if (st->var1 == S_NULL)
-						frame--; // no frame advancement
-					else
-						return P_SetPlayerMobjState(mobj, st->var1);
-				}
+					return P_SetPlayerMobjState(mobj, st->var1); // Differs from P_SetPlayerMobjState - allows object to be removed via S_NULL
 				else
 					frame = 0;
 			}
diff --git a/src/p_user.c b/src/p_user.c
index 6f7313db72576750a6f69841ebc491d5834581e0..f624d3b10223eac021282077495feecffb00eeda 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4175,7 +4175,6 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						player->pflags |= PF_THOKKED;
 					}
 					break;
-
 				case CA_AIRDRILL:
 					if (!(player->pflags & PF_THOKKED) || player->charability2 == CA2_MULTIABILITY)
 					{
@@ -4184,6 +4183,15 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						S_StartSound(player->mo, sfx_spndsh);
 					}
 					break;
+				case CA_TWINSPIN: 
+					if(!(player->pflags & PF_THOKKED)) 
+					{ 
+						player->pflags |= PF_THOKKED; 
+						S_StartSound(player->mo, sfx_s3k42);
+						player->mo->frame = 0;
+						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
+					} 
+					break;
 				default:
 					break;
 			}