diff --git a/src/d_player.h b/src/d_player.h
index 465bbe3f735d30c490403461ff73eda9257fe7c2..f57eb90cff9fc2ed6cc8ed14d6902d98d11bc5dc 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -72,7 +72,8 @@ typedef enum
 {
 	CA2_NONE=0,
 	CA2_SPINDASH,
-	CA2_MULTIABILITY
+	CA2_MULTIABILITY,
+	CA2_MELEE
 } charability2_t;
 
 //
@@ -178,6 +179,7 @@ typedef enum
 	PA_SPRING,
 	PA_FALL,
 	PA_ABILITY,
+	PA_ABILITY2,
 	PA_RIDE
 } panim_t;
 
diff --git a/src/dehacked.c b/src/dehacked.c
index d174aede1e3ab12c55b8ee8da0fdec6cb8f43371..8c307569fb8561216a15a11af6dc466dfed103f2 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3797,6 +3797,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	// CA_TWINSPIN
 	"S_PLAY_TWINSPIN",
 
+	// CA2_MELEE
+	"S_PLAY_MELEE",
+	"S_PLAY_MELEE_FINISH",
+
 	// SF_SUPERANIMS
 	"S_PLAY_SUPER_STND",
 	"S_PLAY_SUPER_WALK",
@@ -7180,6 +7184,7 @@ struct {
 	{"CA2_NONE",CA2_NONE}, // now slot 0!
 	{"CA2_SPINDASH",CA2_SPINDASH},
 	{"CA2_MULTIABILITY",CA2_MULTIABILITY},
+	{"CA2_MELEE",CA2_MELEE},
 
 	// Sound flags
 	{"SF_TOTALLYSINGLE",SF_TOTALLYSINGLE},
@@ -7233,6 +7238,7 @@ struct {
 	{"PA_SPRING",PA_SPRING},
 	{"PA_FALL",PA_FALL},
 	{"PA_ABILITY",PA_ABILITY},
+	{"PA_ABILITY2",PA_ABILITY2},
 	{"PA_RIDE",PA_RIDE},
 
 	// Current weapon
diff --git a/src/info.c b/src/info.c
index 4deabefdf3e261213f14b4aabe0e5b532dce5107..15abd24846d09064cda9f43021de93064c59be4e 100644
--- a/src/info.c
+++ b/src/info.c
@@ -88,6 +88,8 @@ char spr2names[NUMPLAYERSPRITES][5] =
 
 	"TWIN",
 
+	"MLEE",
+
 	"TRNS",
 	"SSTD",
 	"SWLK",
@@ -163,6 +165,10 @@ state_t states[NUMSTATES] =
 	// CA_TWINSPIN
 	{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
+
 	// 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
diff --git a/src/info.h b/src/info.h
index ccb223c6ce34deb44c8e523fac7e02a95171cb20..3c55424be10077f8b91624d6255f8eb3548c31a9 100644
--- a/src/info.h
+++ b/src/info.h
@@ -609,7 +609,9 @@ enum playersprite
 	SPR2_CLNG,
 	SPR2_CLMB,
 
-	SPR2_TWIN, // twinspin
+	SPR2_TWIN,
+
+	SPR2_MLEE,
 
 	SPR2_TRNS,
 	SPR2_SSTD,
@@ -680,6 +682,10 @@ typedef enum state
 	// CA_TWINSPIN
 	S_PLAY_TWINSPIN,
 
+	// CA2_MELEE
+	S_PLAY_MELEE,
+	S_PLAY_MELEE_FINISH,
+
 	// SF_SUPERANIMS
 	S_PLAY_SUPER_STND,
 	S_PLAY_SUPER_WALK,
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 804d99e12e5728a49015829644b6b827ce649613..bed32edac529d473a94a17af2c976a7ae46a48cc 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -69,7 +69,7 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source); // Ho
 #define LUAh_MobjRemoved(mo) LUAh_MobjHook(mo, hook_MobjRemoved) // Hook for P_RemoveMobj by mobj type
 #define LUAh_JumpSpecial(player) LUAh_PlayerHook(player, hook_JumpSpecial) // Hook for P_DoJumpStuff (Any-jumping)
 #define LUAh_AbilitySpecial(player) LUAh_PlayerHook(player, hook_AbilitySpecial) // Hook for P_DoJumpStuff (Double-jumping)
-#define LUAh_SpinSpecial(player) LUAh_PlayerHook(player, hook_SpinSpecial) // Hook for P_DoSpinDash (Spin button effect)
+#define LUAh_SpinSpecial(player) LUAh_PlayerHook(player, hook_SpinSpecial) // Hook for P_DoSpinAbility (Spin button effect)
 #define LUAh_JumpSpinSpecial(player) LUAh_PlayerHook(player, hook_JumpSpinSpecial) // Hook for P_DoJumpStuff (Spin button effect (mid-air))
 boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd); // Hook for B_BuildTiccmd
 boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd); // Hook for B_BuildTailsTiccmd by skin name
diff --git a/src/p_inter.c b/src/p_inter.c
index e7d98dbe3b494902dd8484e1af4acc6cc049ea53..5f538ee8d2f3273f1ab9853536b5c9855915799c 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -302,6 +302,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->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->powers[pw_invulnerability] || player->powers[pw_super]) // Do you possess the ability to subdue the object?
 		{
@@ -347,6 +348,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->charflags & SF_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) && (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 65a5fefabdd16c487bd8e564666d6c480980231e..18321128c6c9d8f01d07a765155e5e72138869d9 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -448,11 +448,12 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
-	// CA_DASHMODE users destroy spikes and monitors, CA_TWINSPIN users destroy spikes.
+	// CA_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)
 		&& (thing->flags & (MF_MONITOR) || thing->type == MT_SPIKE))
-	|| ((tmthing->player->charability == CA_TWINSPIN) && (tmthing->player->panim == PA_ABILITY)
+	|| ((((tmthing->player->charability == CA_TWINSPIN) && (tmthing->player->panim == PA_ABILITY))
+	|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2))
 		&& (thing->type == MT_SPIKE))))
 	{
 		if ((thing->flags & (MF_MONITOR)) && (thing->health <= 0 || !(thing->flags & MF_SHOOTABLE)))
@@ -971,8 +972,12 @@ 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->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))))
+				|| ((tmthing->player->pflags & PF_JUMPED)
+					&& !(tmthing->player->charflags & SF_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)
+					&& (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.
 				fixed_t *momz = &tmthing->momz; // tmthing gets changed by P_DamageMobj, so we need a new pointer?! X_x;;
@@ -996,8 +1001,12 @@ 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->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)))
+		|| ((tmthing->player->pflags & PF_JUMPED)
+			&& !(tmthing->player->charflags & SF_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)
+			&& (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)))
 		;
 	// z checking at last
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 8901622cee5a417af65caaa63faa35e313a0c9c7..5e85e4de22ec458d8bbafd3352267ba1f5554b85 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -173,6 +173,10 @@ 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 melee into goop
+	//if (state == S_PLAY_MELEE && player->mo->eflags & MFE_GOOWATER)
+		//return P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+
 	// Catch state changes for Super Sonic
 	if (player->powers[pw_super] && (player->charflags & SF_SUPERANIMS))
 	{
@@ -275,6 +279,10 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 	case S_PLAY_TWINSPIN:
 		player->panim = PA_ABILITY;
 		break;
+	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;
@@ -414,6 +422,10 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 					spr2 = SPR2_SPIN;
 					break;
 
+				case SPR2_MLEE:
+					spr2 = SPR2_TWIN;
+					break;
+
 				// Super sprites fallback to regular sprites
 				case SPR2_SWLK:
 					spr2 = SPR2_WALK;
@@ -497,12 +509,16 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 
 			if (frame >= numframes)
 			{
-				if (st->frame & FF_SPR2ENDSTATE)
+				if (st->frame & FF_SPR2ENDSTATE) // no frame advancement
 				{
 					if (st->var1 == S_NULL)
-						frame--; // no frame advancement
+						frame--;
 					else
+					{
+						if (mobj->frame & FF_FRAMEMASK)
+							mobj->frame--;
 						return P_SetPlayerMobjState(mobj, st->var1);
+					}
 				}
 				else
 					frame = 0;
@@ -3050,6 +3066,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)
+					{
+						P_InstaThrust(mo, mo->angle, 0);
+						P_SetPlayerMobjState(mo, S_PLAY_STND);
+					}
 					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 abe6816f7d8edeb079e3c7463e86ce20aaf144f3..cacf26a15de0acba11c00118164f2751830dbbd9 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1721,6 +1721,7 @@ static void P_CheckBustableBlocks(player_t *player)
 					// or have CA_GLIDEANDCLIMB
 					// or be in dashmode with CA_DASHMODE
 					// or be using CA_TWINSPIN
+					// or be using CA2_MELEE
 					// or are drilling in NiGHTS
 					// or are recording for Metal Sonic
 					if (!(rover->flags & FF_SHATTER) && !(rover->flags & FF_SPINBUST)
@@ -1729,13 +1730,16 @@ static void P_CheckBustableBlocks(player_t *player)
 						&& !(player->charability == CA_GLIDEANDCLIMB)
 						&& !((player->charability == CA_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)
 						&& !metalrecording)
 						continue;
 
-					// Only CA_GLIDEANDCLIMB or CA_TWINSPIN users can break this rock...
+					// 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->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))))
+						&& !(player->charability == CA_GLIDEANDCLIMB
+						|| ((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
+						|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)))
 						continue;
 
 					// Height checks
@@ -3706,11 +3710,11 @@ void P_DoJump(player_t *player, boolean soundandstate)
 }
 
 //
-// P_DoSpinDash
+// P_DoSpinAbility
 //
 // Player spindash handling
 //
-static void P_DoSpinDash(player_t *player, ticcmd_t *cmd)
+static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 {
 	if (player->pflags & PF_STASIS)
 		return;
@@ -3828,6 +3832,25 @@ static void P_DoSpinDash(player_t *player, ticcmd_t *cmd)
 		P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
 	else if (onground && player->pflags & PF_SPINNING && !(player->panim == PA_ROLL))
 		P_SetPlayerMobjState(player->mo, S_PLAY_SPIN);
+
+	// Melee attack
+	if ((player->charability2 == CA2_MELEE) && !(player->panim == PA_ABILITY2) && !player->exiting
+		&& !P_PlayerInPain(player) && (cmd->buttons & BT_USE) && player->speed < FixedMul(10<<FRACBITS, player->mo->scale)
+		&& !player->mo->momz && onground && !(player->pflags & PF_USEDOWN)
+#ifdef ESLOPE
+		&& (!player->mo->standingslope || (player->mo->standingslope->flags & SL_NOPHYSICS) || abs(player->mo->standingslope->zdelta) < FRACUNIT/2)
+#endif
+		)
+	{
+		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);
+		player->pflags |= PF_USEDOWN;
+		S_StartSound(player->mo, sfx_s3k8b);
+	}
 }
 
 //
@@ -6796,7 +6819,7 @@ static void P_MovePlayer(player_t *player)
 	&& !(player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
 		P_ElementalFireTrail(player);
 
-	P_DoSpinDash(player, cmd);
+	P_DoSpinAbility(player, cmd);
 
 	// jumping
 	P_DoJumpStuff(player, cmd);
diff --git a/src/r_things.c b/src/r_things.c
index a059df7c6e8290eea223067b8fa7f62658e83b25..e15bb7d38b44a8dafcb0457f9ba90eca23980deb 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2620,15 +2620,15 @@ void R_AddSkins(UINT16 wadnum)
 			FULLPROCESS(revitem)
 #undef FULLPROCESS
 
-#define GETSPEED(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value)<<FRACBITS;
-			GETSPEED(normalspeed)
-			GETSPEED(runspeed)
-			GETSPEED(mindash)
-			GETSPEED(maxdash)
-			GETSPEED(actionspd)
-			GETSPEED(radius)
-			GETSPEED(height)
-			GETSPEED(spinheight)
+#define GETFRACBITS(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value)<<FRACBITS;
+			GETFRACBITS(normalspeed)
+			GETFRACBITS(runspeed)
+			GETFRACBITS(mindash)
+			GETFRACBITS(maxdash)
+			GETFRACBITS(actionspd)
+			GETFRACBITS(radius)
+			GETFRACBITS(height)
+			GETFRACBITS(spinheight)
 #undef GETSPEED
 
 #define GETINT(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value);