diff --git a/src/dehacked.c b/src/dehacked.c
index 89f44cb3b6f310dc139017e4dfcb37c322c8c445..8a9c151628bee8d538236fb69c97614ea22e9afc 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -64,6 +64,7 @@ memset(used_spr,0,sizeof(UINT8) * ((NUMSPRITEFREESLOTS / 8) + 1));\
 static mobjtype_t get_mobjtype(const char *word);
 static statenum_t get_state(const char *word);
 static spritenum_t get_sprite(const char *word);
+static playersprite_t get_sprite2(const char *word);
 static sfxenum_t get_sfx(const char *word);
 #ifdef MUSICSLOT_COMPATIBILITY
 static UINT16 get_mus(const char *word, UINT8 dehacked_mode);
@@ -771,6 +772,49 @@ static void readspritelight(MYFILE *f, INT32 num)
 }
 #endif // HWRENDER
 
+static void readsprite2(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word, *word2;
+	char *tmp;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+
+			if (fastcmp(word, "DEFAULT"))
+				spr2defaults[num] = get_number(word2);
+			else
+				deh_warning("Sprite2 %s: unknown word '%s'", spr2names[num], word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
 static const struct {
 	const char *name;
 	const UINT16 flag;
@@ -3067,9 +3111,21 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 						ignorelines(f);
 					}
 				}
-				else if (fastcmp(word, "LIGHT"))
+				else if (fastcmp(word, "SPRITE2"))
 				{
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_sprite2(word2); // find a sprite by name
+					if (i < (INT32)free_spr2 && i >= (INT32)SPR2_FIRSTFREESLOT)
+						readsprite2(f, i);
+					else
+					{
+						deh_warning("Sprite2 number %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
+						ignorelines(f);
+					}
+				}
 #ifdef HWRENDER
+				else if (fastcmp(word, "LIGHT"))
+				{
 					// TODO: Read lights by name
 					if (i > 0 && i < NUMLIGHTS)
 						readlight(f, i);
@@ -3078,22 +3134,20 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 						deh_warning("Light number %d out of range (1 - %d)", i, NUMLIGHTS-1);
 						ignorelines(f);
 					}
-#endif
 				}
 				else if (fastcmp(word, "SPRITE"))
 				{
-#ifdef HWRENDER
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
 						i = get_sprite(word2); // find a sprite by name
-					if (i < NUMSPRITES && i >= 0)
+					if (i < NUMSPRITES && i > 0)
 						readspritelight(f, i);
 					else
 					{
 						deh_warning("Sprite number %d out of range (0 - %d)", i, NUMSPRITES-1);
 						ignorelines(f);
 					}
-#endif
 				}
+#endif
 				else if (fastcmp(word, "LEVEL"))
 				{
 					// Support using the actual map name,
@@ -3357,6 +3411,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_STND",
 	"S_PLAY_WAIT",
 	"S_PLAY_WALK",
+	"S_PLAY_SKID",
 	"S_PLAY_RUN",
 	"S_PLAY_DASH",
 	"S_PLAY_PAIN",
@@ -7345,6 +7400,20 @@ static spritenum_t get_sprite(const char *word)
 	return SPR_NULL;
 }
 
+static playersprite_t get_sprite2(const char *word)
+{ // Returns the value of SPR2_ enumerations
+	playersprite_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("SPR2_",word,5))
+		word += 5; // take off the SPR2_
+	for (i = 0; i < NUMPLAYERSPRITES; i++)
+		if (!spr2names[i][4] && memcmp(word,spr2names[i],4)==0)
+			return i;
+	deh_warning("Couldn't find sprite named 'SPR2_%s'",word);
+	return SPR2_STND;
+}
+
 static sfxenum_t get_sfx(const char *word)
 { // Returns the value of SFX_ enumerations
 	sfxenum_t i;
@@ -7790,7 +7859,7 @@ static inline int lib_freeslot(lua_State *L)
 		else if (fastcmp(type, "SPR2"))
 		{
 			// Search if we already have an SPR2 by that name...
-			enum playersprite i;
+			playersprite_t i;
 			for (i = SPR2_FIRSTFREESLOT; i < free_spr2; i++)
 				if (memcmp(spr2names[i],word,4) == 0)
 					break;
@@ -7969,7 +8038,7 @@ static inline int lib_getenum(lua_State *L)
 		if (mathlib) return luaL_error(L, "sprite '%s' could not be found.\n", word);
 		return 0;
 	}
-	else if (fastncmp("SPR2_",word,4)) {
+	else if (fastncmp("SPR2_",word,5)) {
 		p = word+5;
 		for (i = 0; i < (fixed_t)free_spr2; i++)
 			if (!spr2names[i][4])
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index df2c9f59a60b675f5aacfdce1be23a30064af3bd..708da2c157c655e0c3d714a1db955de63cd3df94 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -28,6 +28,7 @@
 
 #include "../doomdef.h"
 #include "../doomstat.h"
+#include "../fastcmp.h"
 
 #ifdef HWRENDER
 #include "hw_drv.h"
@@ -265,6 +266,9 @@ static void md2_freeModel (md2_model_t *model)
 			free(model->frames);
 		}
 
+		if (model->spr2frames)
+			free(model->spr2frames);
+
 		if (model->glCommandBuffer)
 			free(model->glCommandBuffer);
 
@@ -395,6 +399,39 @@ static md2_model_t *md2_readModel(const char *filename)
 			}
 
 			strcpy(model->frames[i].name, frame->name);
+			if (frame->name[0] == 'S')
+			{
+				boolean super;
+				if ((super = (fastncmp("UPER", frame->name+1, 4))) // SUPER
+					|| fastncmp("PR2_", frame->name+1, 4)) // SPR2_
+				{
+					UINT8 spr2;
+					for (spr2 = 0; spr2 < free_spr2; spr2++)
+						if (fastncmp(frame->name+5,spr2names[spr2],3)
+						&& ((frame->name[8] == spr2names[spr2][3])
+							|| (frame->name[8] == '.' && spr2names[spr2][3] == '_')))
+							break;
+
+					if (spr2 < free_spr2)
+					{
+						if (!model->spr2frames)
+						{
+							model->spr2frames = calloc(sizeof (size_t), 2*NUMPLAYERSPRITES*2);
+							if (!model->spr2frames)
+							{
+								md2_freeModel (model);
+								fclose(file);
+								return 0;
+							}
+						}
+						if (super)
+							spr2 |= FF_SPR2SUPER;
+						if (model->spr2frames[spr2*2 + 1]++ == 0) // numspr2frames
+							model->spr2frames[spr2*2] = i; // starting frame
+						CONS_Debug(DBG_RENDER, "frame %s, sprite2 %s - starting frame %d, number of frames %d\n", frame->name, spr2names[spr2 & ~FF_SPR2SUPER], model->spr2frames[spr2*2], model->spr2frames[spr2*2 + 1]);
+					}
+				}
+			}
 			for (j = 0; j < model->header.numVertices; j++)
 			{
 				model->frames[i].vertices[j].vertex[0] = (float) ((INT32) frame->alias_vertices[j].vertex[0]) * frame->scale[0] + frame->translate[0];
@@ -1078,6 +1115,51 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, con
 	res?
 	run?
 	*/
+
+static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *player)
+{
+	UINT8 super = 0, i = 0;
+
+	if (!md2 || !skin)
+		return 0;
+
+	while (!(md2->model->spr2frames[spr2*2 + 1])
+		&& spr2 != SPR2_STND
+		&& ++i != 32) // recursion limiter
+	{
+		if (spr2 & FF_SPR2SUPER)
+		{
+			super = FF_SPR2SUPER;
+			spr2 &= ~FF_SPR2SUPER;
+			continue;
+		}
+
+		switch(spr2)
+		{
+
+		// Normal special cases.
+		case SPR2_JUMP:
+			spr2 = ((player
+					? player->charflags
+					: skin->flags)
+					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
+			break;
+		case SPR2_TIRE:
+			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
+			break;
+
+		// Use the handy list, that's what it's there for!
+		default:
+			spr2 = spr2defaults[spr2];
+			break;
+		}
+
+		spr2 |= super;
+	}
+
+	return spr2;
+}
+
 #define NORMALFOG 0x00000000
 #define FADEFOG 0x19000000
 void HWR_DrawMD2(gr_vissprite_t *spr)
@@ -1225,32 +1307,71 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			tics = spr->mobj->anim_duration;
 		}
 
-		//FIXME: this is not yet correct
-		frame = (spr->mobj->frame & FF_FRAMEMASK) % md2->model->header.numFrames;
-		buff = md2->model->glCommandBuffer;
-		curr = &md2->model->frames[frame];
-		if (cv_grmd2.value == 1)
+#define INTERPOLERATION_LIMIT TICRATE/4
+
+		if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY && md2->model->spr2frames)
 		{
-			// frames are handled differently for states with FF_ANIMATE, so get the next frame differently for the interpolation
-			if (spr->mobj->frame & FF_ANIMATE)
+			UINT8 spr2 = P_GetModelSprite2(md2, spr->mobj->skin, spr->mobj->sprite2, spr->mobj->player);
+			UINT8 mod = md2->model->spr2frames[spr2*2 + 1] ? md2->model->spr2frames[spr2*2 + 1] : md2->model->header.numFrames;
+			if (mod > ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes)
+				mod = ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes;
+			//FIXME: this is not yet correct
+			frame = (spr->mobj->frame & FF_FRAMEMASK);
+			if (frame >= mod)
+				frame = 0;
+			buff = md2->model->glCommandBuffer;
+			curr = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
+			if (cv_grmd2.value == 1 && tics <= INTERPOLERATION_LIMIT)
 			{
-				UINT32 nextframe = (spr->mobj->frame & FF_FRAMEMASK) + 1;
-				if (nextframe >= (UINT32)spr->mobj->state->var1)
-					nextframe = (spr->mobj->state->frame & FF_FRAMEMASK);
-				nextframe %= md2->model->header.numFrames;
-				next = &md2->model->frames[nextframe];
+				if (durs > INTERPOLERATION_LIMIT)
+					durs = INTERPOLERATION_LIMIT;
+
+				if (spr->mobj->frame & FF_ANIMATE
+					|| (spr->mobj->state->nextstate != S_NULL
+					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite
+					&& (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) == spr->mobj->sprite2))
+				{
+					if (++frame >= mod)
+						frame = 0;
+					if (frame || !(spr->mobj->state->frame & FF_SPR2ENDSTATE))
+						next = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
+				}
 			}
-			else
+		}
+		else
+		{
+			//FIXME: this is not yet correct
+			frame = (spr->mobj->frame & FF_FRAMEMASK) % md2->model->header.numFrames;
+			buff = md2->model->glCommandBuffer;
+			curr = &md2->model->frames[frame];
+			if (cv_grmd2.value == 1 && tics <= INTERPOLERATION_LIMIT)
 			{
-				if (spr->mobj->state->nextstate != S_NULL && states[spr->mobj->state->nextstate].sprite != SPR_NULL
-					&& !(spr->mobj->player && spr->mobj->state->nextstate == S_PLAY_WAIT && spr->mobj->state == &states[S_PLAY_STND]))
+				if (durs > INTERPOLERATION_LIMIT)
+					durs = INTERPOLERATION_LIMIT;
+
+				// frames are handled differently for states with FF_ANIMATE, so get the next frame differently for the interpolation
+				if (spr->mobj->frame & FF_ANIMATE)
 				{
-					const UINT32 nextframe = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->header.numFrames;
+					UINT32 nextframe = (spr->mobj->frame & FF_FRAMEMASK) + 1;
+					if (nextframe >= (UINT32)spr->mobj->state->var1)
+						nextframe = (spr->mobj->state->frame & FF_FRAMEMASK);
+					nextframe %= md2->model->header.numFrames;
 					next = &md2->model->frames[nextframe];
 				}
+				else
+				{
+					if (spr->mobj->state->nextstate != S_NULL
+					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite)
+					{
+						const UINT32 nextframe = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->header.numFrames;
+						next = &md2->model->frames[nextframe];
+					}
+				}
 			}
 		}
 
+#undef INTERPOLERATION_LIMIT
+
 		//Hurdler: it seems there is still a small problem with mobj angle
 		p.x = FIXED_TO_FLOAT(spr->mobj->x);
 		p.y = FIXED_TO_FLOAT(spr->mobj->y)+md2->offset;
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 299d1240005daba28769c1e8b5e12adfd1b67401..c7cda35af6f308c1f8fa28e0fb8358fd55542996 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -22,6 +22,7 @@
 #define _HW_MD2_H_
 
 #include "hw_glob.h"
+#include "../info.h"
 
 // magic number "IDP2" or 844121161
 #define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
@@ -111,7 +112,8 @@ typedef struct
 	md2_textureCoordinate_t *texCoords;
 	md2_triangle_t          *triangles;
 	md2_frame_t             *frames;
-	INT32                     *glCommandBuffer;
+	size_t                  *spr2frames; // size_t spr2frames[2*NUMPLAYERSPRITES][2];
+	INT32                   *glCommandBuffer;
 } ATTRPACK md2_model_t;
 
 #if defined(_MSC_VER)
diff --git a/src/info.c b/src/info.c
index 2d6c9a3d12bb9ad518506531b6ad8acd89bfae61..d62111640efa8660cc0b094dc826e76538d7872f 100644
--- a/src/info.c
+++ b/src/info.c
@@ -405,6 +405,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"STND",
 	"WAIT",
 	"WALK",
+	"SKID",
 	"RUN_",
 	"DASH",
 	"PAIN",
@@ -481,7 +482,89 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"SIGN",
 	"LIFE"
 };
-enum playersprite free_spr2 = SPR2_FIRSTFREESLOT;
+playersprite_t free_spr2 = SPR2_FIRSTFREESLOT;
+
+playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
+	0, // SPR2_STND,
+	0, // SPR2_WAIT,
+	0, // SPR2_WALK,
+	SPR2_WALK, // SPR2_SKID,
+	SPR2_WALK, // SPR2_RUN ,
+	SPR2_FRUN, // SPR2_DASH,
+	0, // SPR2_PAIN,
+	SPR2_PAIN, // SPR2_STUN,
+	SPR2_PAIN, // SPR2_DEAD,
+	SPR2_DEAD, // SPR2_DRWN,
+	0, // SPR2_ROLL,
+	SPR2_SPNG, // SPR2_GASP,
+	0, // SPR2_JUMP, (conditional)
+	SPR2_FALL, // SPR2_SPNG,
+	SPR2_WALK, // SPR2_FALL,
+	0, // SPR2_EDGE,
+	SPR2_FALL, // SPR2_RIDE,
+
+	SPR2_ROLL, // SPR2_SPIN,
+
+	SPR2_SPNG, // SPR2_FLY ,
+	SPR2_FLY , // SPR2_SWIM,
+	0, // SPR2_TIRE, (conditional)
+
+	SPR2_FLY , // SPR2_GLID,
+	SPR2_CLMB, // SPR2_CLNG,
+	SPR2_ROLL, // SPR2_CLMB,
+
+	SPR2_WALK, // SPR2_FLT ,
+	SPR2_RUN , // SPR2_FRUN,
+
+	SPR2_FALL, // SPR2_BNCE,
+	SPR2_ROLL, // SPR2_BLND,
+
+	0, // SPR2_FIRE,
+
+	SPR2_ROLL, // SPR2_TWIN,
+
+	SPR2_TWIN, // SPR2_MLEE,
+	0, // SPR2_MLEL,
+
+	0, // SPR2_TRNS,
+
+	FF_SPR2SUPER|SPR2_STND, // SPR2_NSTD,
+	FF_SPR2SUPER|SPR2_FLT , // SPR2_NFLT,
+	FF_SPR2SUPER|SPR2_STUN, // SPR2_NSTN,
+	SPR2_NSTN, // SPR2_NPUL,
+	FF_SPR2SUPER|SPR2_ROLL, // SPR2_NATK,
+
+	0, // SPR2_NGT0, (should never be referenced)
+	SPR2_NGT0, // SPR2_NGT1,
+	SPR2_NGT1, // SPR2_NGT2,
+	SPR2_NGT2, // SPR2_NGT3,
+	SPR2_NGT3, // SPR2_NGT4,
+	SPR2_NGT4, // SPR2_NGT5,
+	SPR2_NGT5, // SPR2_NGT6,
+	SPR2_NGT0, // SPR2_NGT7,
+	SPR2_NGT7, // SPR2_NGT8,
+	SPR2_NGT8, // SPR2_NGT9,
+	SPR2_NGT9, // SPR2_NGTA,
+	SPR2_NGTA, // SPR2_NGTB,
+	SPR2_NGTB, // SPR2_NGTC,
+
+	SPR2_NGT0, // SPR2_DRL0,
+	SPR2_NGT1, // SPR2_DRL1,
+	SPR2_NGT2, // SPR2_DRL2,
+	SPR2_NGT3, // SPR2_DRL3,
+	SPR2_NGT4, // SPR2_DRL4,
+	SPR2_NGT5, // SPR2_DRL5,
+	SPR2_NGT6, // SPR2_DRL6,
+	SPR2_NGT7, // SPR2_DRL7,
+	SPR2_NGT8, // SPR2_DRL8,
+	SPR2_NGT9, // SPR2_DRL9,
+	SPR2_NGTA, // SPR2_DRLA,
+	SPR2_NGTB, // SPR2_DRLB,
+	SPR2_NGTC, // SPR2_DRLC,
+
+	0, // SPR2_SIGN,
+	0, // SPR2_LIFE
+};
 
 // Doesn't work with g++, needs actionf_p1 (don't modify this comment)
 state_t states[NUMSTATES] =
@@ -512,6 +595,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_STND|FF_ANIMATE,    105, {NULL}, 0,  7, S_PLAY_WAIT}, // S_PLAY_STND
 	{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_SKID,                 1, {NULL}, 0,  0, S_PLAY_WALK}, // S_PLAY_SKID
 	{SPR_PLAY, SPR2_RUN ,                 2, {NULL}, 0,  0, S_PLAY_RUN},  // S_PLAY_RUN
 	{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
diff --git a/src/info.h b/src/info.h
index cd79b12a98951a6116cbe9a0d7d88d6b9e8263d9..7bc7e673a6cf8e3b381dd20df5d18b1c4fe6639d 100644
--- a/src/info.h
+++ b/src/info.h
@@ -604,11 +604,12 @@ typedef enum sprite
 // 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 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
+typedef enum playersprite
 {
 	SPR2_STND = 0,
 	SPR2_WAIT,
 	SPR2_WALK,
+	SPR2_SKID,
 	SPR2_RUN ,
 	SPR2_DASH,
 	SPR2_PAIN,
@@ -690,7 +691,7 @@ enum playersprite
 	SPR2_FIRSTFREESLOT,
 	SPR2_LASTFREESLOT = 0x7f,
 	NUMPLAYERSPRITES
-};
+} playersprite_t;
 
 typedef enum state
 {
@@ -713,6 +714,7 @@ typedef enum state
 	S_PLAY_STND,
 	S_PLAY_WAIT,
 	S_PLAY_WALK,
+	S_PLAY_SKID,
 	S_PLAY_RUN,
 	S_PLAY_DASH,
 	S_PLAY_PAIN,
@@ -3193,8 +3195,9 @@ typedef struct
 extern state_t states[NUMSTATES];
 extern char sprnames[NUMSPRITES + 1][5];
 extern char spr2names[NUMPLAYERSPRITES][5];
+extern playersprite_t spr2defaults[NUMPLAYERSPRITES];
 extern state_t *astate;
-extern enum playersprite free_spr2;
+extern playersprite_t free_spr2;
 
 typedef enum mobj_type
 {
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index be14554153962e68d0aeec8c84043b2d2e156b39..b88a9712e1a0c06e137e98a2d7bcdbffa36db70f 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -462,6 +462,8 @@ static int lib_pSpawnLockOn(lua_State *L)
 		return LUA_ErrInvalid(L, "mobj_t");
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
+	if (state >= NUMSTATES)
+		return luaL_error(L, "state %d out of range (0 - %d)", state, NUMSTATES-1);
 	if (P_IsLocalPlayer(player)) // 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
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 9361abe94e9ec06cf622a7999820e169c2270316..c3803f7e246f395bd761d953b7a091b94aa28eae 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -127,6 +127,74 @@ static int lib_getSpr2name(lua_State *L)
 	return 0;
 }
 
+static int lib_getSpr2default(lua_State *L)
+{
+	UINT32 i;
+
+	lua_remove(L, 1); // don't care about spr2defaults[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else if (lua_isstring(L, 1))
+	{
+		const char *name = lua_tostring(L, 1);
+		for (i = 0; i < free_spr2; i++)
+			if (fastcmp(name, spr2names[i]))
+				break;
+	}
+	else
+		return luaL_error(L, "spr2defaults[] invalid index");
+
+	if (i >= free_spr2)
+		return 0;
+
+	lua_pushinteger(L, spr2defaults[i]);
+	return 1;
+}
+
+static int lib_setSpr2default(lua_State *L)
+{
+	UINT32 i;
+	UINT8 j = 0;
+
+	lua_remove(L, 1); // don't care about spr2defaults[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else if (lua_isstring(L, 1))
+	{
+		const char *name = lua_tostring(L, 1);
+		for (i = 0; i < free_spr2; i++)
+			if (fastcmp(name, spr2names[i]))
+				break;
+			if (i == free_spr2)
+				return luaL_error(L, "spr2defaults[] invalid index");
+	}
+	else
+		return luaL_error(L, "spr2defaults[] invalid index");
+
+	if (i < SPR2_FIRSTFREESLOT || i >= free_spr2)
+		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
+
+	if (lua_isnumber(L, 2))
+		j = lua_tonumber(L, 2);
+	else if (lua_isstring(L, 2))
+	{
+		const char *name = lua_tostring(L, 2);
+		for (j = 0; j < free_spr2; j++)
+			if (fastcmp(name, spr2names[j]))
+				break;
+			if (j == free_spr2)
+				return luaL_error(L, "spr2defaults[] invalid index");
+	}
+
+	if (j >= free_spr2)
+		j = 0; // return luaL_error(L, "spr2defaults[] set %d out of range (%d - %d)", j, 0, free_spr2-1);
+
+	spr2defaults[i] = j;
+	return 0;
+}
+
 static int lib_spr2namelen(lua_State *L)
 {
 	lua_pushinteger(L, free_spr2);
@@ -984,6 +1052,19 @@ int LUA_InfoLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "spr2names");
 
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_pushcfunction(L, lib_getSpr2default);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_setSpr2default);
+			lua_setfield(L, -2, "__newindex");
+
+			lua_pushcfunction(L, lib_spr2namelen);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "spr2defaults");
+
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
 			lua_pushcfunction(L, lib_getState);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index c201830b0276e3c65d8baa440f82d9f2bf3d79a5..93ec58d05aa53b8678b28fa8445028ffea208237 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -251,6 +251,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		player->panim = PA_EDGE;
 		break;
 	case S_PLAY_WALK:
+	case S_PLAY_SKID:
 	case S_PLAY_FLOAT:
 		player->panim = PA_WALK;
 		break;
diff --git a/src/p_user.c b/src/p_user.c
index f4e686fd3895fa04560ecf2e63d87464757c244a..7e74f0fb4d51966aa16d4522b2e76838fc356622 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4374,6 +4374,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
 							if (lockon)
 							{
+								P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
 								player->homing = 3*TICRATE;
 							}
@@ -6678,8 +6679,8 @@ 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)
 			{
-				if (player->panim != PA_WALK)
-					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				if (player->mo->state-states != S_PLAY_SKID)
+					P_SetPlayerMobjState(player->mo, S_PLAY_SKID);
 				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);
 			}
@@ -7422,6 +7423,8 @@ static void P_MovePlayer(player_t *player)
 
 				if (!(player->mo->tracer->flags & MF_BOSS))
 					player->pflags &= ~PF_THOKKED;
+
+				// P_SetPlayerMobjState(player->mo, S_PLAY_SPRING); -- Speed didn't like it, RIP
 			}
 		}
 
diff --git a/src/r_things.c b/src/r_things.c
index b2437d4acd19e4c773a1f0ac1b92fca1e9154bc6..67aa4e58574ee658af364394ba1a042e6517b41f 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2431,176 +2431,39 @@ CV_PossibleValue_t skin_cons_t[MAXSKINS+1];
 
 UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 {
-	UINT8 super = (spr2 & FF_SPR2SUPER);
+	UINT8 super = 0, i = 0;
 
 	if (!skin)
 		return 0;
 
 	while (!(skin->sprites[spr2].numframes)
-		&& spr2 != SPR2_STND)
+		&& spr2 != SPR2_STND
+		&& ++i != 32) // recursion limiter
 	{
 		if (spr2 & FF_SPR2SUPER)
 		{
+			super = FF_SPR2SUPER;
 			spr2 &= ~FF_SPR2SUPER;
 			continue;
 		}
 
 		switch(spr2)
 		{
-		case SPR2_RUN:
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_STUN:
-			spr2 = SPR2_PAIN;
-			break;
-		case SPR2_DRWN:
-			spr2 = SPR2_DEAD;
-			break;
-		case SPR2_SPIN:
-			spr2 = SPR2_ROLL;
-			break;
-		case SPR2_GASP:
-			spr2 = SPR2_SPNG;
-			break;
+
+		// Normal special cases.
 		case SPR2_JUMP:
 			spr2 = ((player
 					? player->charflags
 					: skin->flags)
 					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
 			break;
-		case SPR2_SPNG: // spring
-			spr2 = SPR2_FALL;
-			break;
-		case SPR2_FALL:
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_RIDE:
-			spr2 = SPR2_FALL;
-			break;
-
-		case SPR2_FLY :
-			spr2 = SPR2_SPNG;
-			break;
-		case SPR2_SWIM:
-			spr2 = SPR2_FLY ;
-			break;
 		case SPR2_TIRE:
 			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
 			break;
 
-		case SPR2_GLID:
-			spr2 = SPR2_FLY;
-			break;
-		case SPR2_CLMB:
-			spr2 = SPR2_ROLL;
-			break;
-		case SPR2_CLNG:
-			spr2 = SPR2_CLMB;
-			break;
-
-		case SPR2_FLT :
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_FRUN:
-			spr2 = SPR2_RUN ;
-			break;
-
-		case SPR2_DASH:
-			spr2 = SPR2_FRUN;
-			break;
-
-		case SPR2_BNCE:
-			spr2 = SPR2_FALL;
-			break;
-		case SPR2_BLND:
-			spr2 = SPR2_ROLL;
-			break;
-
-		case SPR2_TWIN:
-			spr2 = SPR2_ROLL;
-			break;
-
-		case SPR2_MLEE:
-			spr2 = SPR2_TWIN;
-			break;
-
-		// NiGHTS sprites.
-		case SPR2_NSTD:
-			spr2 = SPR2_STND;
-			super = FF_SPR2SUPER;
-			break;
-		case SPR2_NFLT:
-			spr2 = SPR2_FLT ;
-			super = FF_SPR2SUPER;
-			break;
-		case SPR2_NSTN:
-			spr2 = SPR2_STUN;
-			break;
-		case SPR2_NPUL:
-			spr2 = SPR2_NSTN;
-			break;
-		case SPR2_NATK:
-			spr2 = SPR2_ROLL;
-			super = FF_SPR2SUPER;
-			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_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.
+		// Use the handy list, that's what it's there for!
 		default:
-			spr2 = SPR2_STND;
+			spr2 = spr2defaults[spr2];
 			break;
 		}