diff --git a/comptime.bat b/comptime.bat
index 77879d5eef585f9c712e853cb842dc963ea5ca58..c03f088b23b23ba43abc9839e39a99c78317a618 100644
--- a/comptime.bat
+++ b/comptime.bat
@@ -16,7 +16,7 @@ set GIT=%2
 if "%GIT%"=="" set GIT=git
 for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse --abbrev-ref HEAD`) do @set BRA=%%s
 for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse HEAD`) do @set REV=%%s
-for /f "tokens=* usebackq" %%s in (`%GIT% log -1 --format^=%%s`) do @set GL1=%%s
+for /f "tokens=* usebackq" %%s in (`%GIT% log -1 --format^=%%f`) do @set GL1=%%s
 set REV=%REV:~0,8%
 goto filwri
 
diff --git a/comptime.sh b/comptime.sh
index e37ba6ad78de5454637644cc14627e09dc979e11..3338ecc35506d8b9e9b001679da881b21e81772c 100755
--- a/comptime.sh
+++ b/comptime.sh
@@ -19,7 +19,7 @@ EOF
 versiongit() {
 	gitbranch="$(git rev-parse --abbrev-ref HEAD)"
 	gitversion="$(git rev-parse HEAD | cut -c -8)"
-	gitsubject="$(git log -1 --format=%s)"
+	gitsubject="$(git log -1 --format=%f)"
 	version "$gitbranch" "$gitversion" "$gitsubject";
 	exit 0
 }
diff --git a/src/blua/lbaselib.c b/src/blua/lbaselib.c
index 0fc222038dd97dbc4306018a88f87b69b612c167..2bb3d9cf09d030e439231c08a591141518c62b25 100644
--- a/src/blua/lbaselib.c
+++ b/src/blua/lbaselib.c
@@ -275,18 +275,36 @@ static int luaB_dofile (lua_State *L) {
 	int n = lua_gettop(L);
 
 	if (!W_FileHasFolders(wadfiles[numwadfiles - 1]))
-		luaL_error(L, "dofile() only works with PK3 files");
+		luaL_error(L, "dofile() only works with PK3 files and folders");
 
 	snprintf(fullfilename, sizeof(fullfilename), "Lua/%s", filename);
 	lumpnum = W_CheckNumForFullNamePK3(fullfilename, numwadfiles - 1, 0);
 	if (lumpnum == INT16_MAX)
 		luaL_error(L, "can't find script " LUA_QS, fullfilename);
 
-	LUA_LoadLump(numwadfiles - 1, lumpnum, false);
+	LUA_DoLump(numwadfiles - 1, lumpnum, false);
 
 	return lua_gettop(L) - n;
 }
 
+// Edited to load PK3 entries instead
+static int luaB_loadfile (lua_State *L) {
+  const char *filename = luaL_checkstring(L, 1);
+  char fullfilename[256];
+  UINT16 lumpnum;
+
+	if (!W_FileHasFolders(wadfiles[numwadfiles - 1]))
+    luaL_error(L, "loadfile() only works with PK3 files and folders");
+
+  snprintf(fullfilename, sizeof(fullfilename), "Lua/%s", filename);
+  lumpnum = W_CheckNumForFullNamePK3(fullfilename, numwadfiles - 1, 0);
+  if (lumpnum == INT16_MAX)
+    luaL_error(L, "can't find script " LUA_QS, fullfilename);
+
+  LUA_LoadLump(numwadfiles - 1, lumpnum);
+
+  return 1;
+}
 
 static int luaB_assert (lua_State *L) {
   luaL_checkany(L, 1);
@@ -406,6 +424,7 @@ static const luaL_Reg base_funcs[] = {
   {"collectgarbage", luaB_collectgarbage},
   {"error", luaB_error},
   {"dofile", luaB_dofile},
+  {"loadfile", luaB_loadfile},
   {"gcinfo", luaB_gcinfo},
   {"getfenv", luaB_getfenv},
   {"getmetatable", luaB_getmetatable},
diff --git a/src/console.c b/src/console.c
index 706ed66332144d3e9f0b61aacfd163f544a9fca3..7747cc3f0df03cb1a7ba6b9509e28c16f1579524 100644
--- a/src/console.c
+++ b/src/console.c
@@ -220,13 +220,16 @@ static char *bindtable[NUMINPUTS];
 static void CONS_Bind_f(void)
 {
 	size_t na;
+	char *newcmd;
+	//size_t newlen = 0;
+	unsigned int i;
 	INT32 key;
 
 	na = COM_Argc();
 
-	if (na != 2 && na != 3)
+	if (na < 2)
 	{
-		CONS_Printf(M_GetText("bind <keyname> [<command>]: create shortcut keys to command(s)\n"));
+		CONS_Printf(M_GetText("bind <keyname> [<command>] [<arg1>] [...]: create shortcut keys to command(s)\n"));
 		CONS_Printf("\x82%s", M_GetText("Bind table :\n"));
 		na = 0;
 		for (key = 0; key < NUMINPUTS; key++)
@@ -250,8 +253,36 @@ static void CONS_Bind_f(void)
 	Z_Free(bindtable[key]);
 	bindtable[key] = NULL;
 
-	if (na == 3)
-		bindtable[key] = Z_StrDup(COM_Argv(2));
+	if (na < 3)
+		return;
+
+	for (i = 2; i < na; ++i)
+	{
+		const char *arg = COM_Argv(i);
+
+		// on the second iteration, and after
+		if (i > 2)
+		{
+			size_t newlen = strlen(bindtable[key]) + strlen(arg) + 1; // new length, allow space for ' ' and '\0'
+			size_t curpos = newcmd - bindtable[key]; // offset from newcmd to original pointer
+
+			newcmd = bindtable[key] = Z_Realloc(bindtable[key], newlen, PU_STATIC, NULL);
+			newcmd += curpos; // reapply offset
+
+			newcmd[0] = ' '; // replace previous '\0' w/ ' '
+			++newcmd; // make sure later strcpy doesnt overwrite ' '
+		}
+		// first iteration
+		else
+			// allocate space for argument and a ' ' or '\0'
+			newcmd = bindtable[key] = Z_Calloc(strlen(arg) + 1, PU_STATIC, NULL);
+
+		// the copy
+		strcpy(newcmd, arg);
+
+		// move window past copied argument for next iteration
+		newcmd += strlen(arg);
+	}
 }
 
 //======================================================================
diff --git a/src/deh_tables.c b/src/deh_tables.c
index b53cd00c8fc23149a9a9310776335d59e7edd27a..baa8ece163ecff24dc6392ad7f48e9477f7409bc 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -370,8 +370,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
 	"S_XDEATHSTATE",
 	"S_RAISESTATE",
 
-	// Thok
+	// Thok effect and spin trail
 	"S_THOK",
+	"S_THOKEFFECT",
 
 	// Player
 	"S_PLAY_STND",
@@ -3560,7 +3561,8 @@ const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity t
 	"MT_NULL",
 	"MT_UNKNOWN",
 
-	"MT_THOK", // Thok! mobj
+	"MT_THOK", // Spin trail mobj
+	"MT_THOKEFFECT", // Thok boom effect
 	"MT_PLAYER",
 	"MT_TAILSOVERLAY", // c:
 	"MT_METALJETFUME",
diff --git a/src/g_demo.c b/src/g_demo.c
index f39efad8e68fe81a37eed65925dc8c1ae394dae3..8a8ad1259f9c39d4666b64c0cbdd0108400a6dc9 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -801,6 +801,19 @@ void G_GhostTicker(void)
 						if (!P_MobjWasRemoved(mobj))
 							mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<<FF_TRANSSHIFT; // P_SpawnGhostMobj sets trans50, we want trans60
 					}
+					else if (type == MT_THOKEFFECT)
+					{
+						mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, FixedDiv(g->mo->height, g->mo->scale)*3/4, type);
+						mobj->angle = g->mo->angle + ANGLE_90;
+						mobj->fuse = 7;
+						mobj->scale = g->mo->scale / 3;
+						mobj->destscale = 10 * g->mo->scale;
+						mobj->colorized = true;
+						mobj->color = g->mo->color;
+						mobj->momx = -g->mo->momx / 2;
+						mobj->momy = -g->mo->momy / 2;
+					}
+
 					else
 					{
 						mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK);
@@ -1107,6 +1120,18 @@ void G_ReadMetalTic(mobj_t *metal)
 				{
 					mobj = P_SpawnGhostMobj(metal); // does a large portion of the work for us
 				}
+				else if (type == MT_THOKEFFECT)
+				{
+					mobj = P_SpawnMobjFromMobj(metal, 0, 0, FixedDiv(metal->height, metal->scale)*3/4, type);
+					mobj->angle = metal->angle + ANGLE_90;
+					mobj->fuse = 7;
+					mobj->scale = metal->scale / 3;
+					mobj->destscale = 10 * metal->scale;
+					mobj->colorized = true;
+					mobj->color = metal->color;
+					mobj->momx = -metal->momx / 2;
+					mobj->momy = -metal->momy / 2;
+				}
 				else
 				{
 					mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK);
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index b02f2824aad4437cdbd5f496534e019893870557..3b660cc70c36515dab60b4ae3eabb914b5de7d5d 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -283,6 +283,7 @@ enum hwdsetspecialstate
 	HWD_SET_SHADERS,
 	HWD_SET_TEXTUREFILTERMODE,
 	HWD_SET_TEXTUREANISOTROPICMODE,
+	HWD_SET_WIREFRAME,
 	HWD_NUMSTATE
 };
 
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index bcfdfa960d7b723ecb0be2437667bcf09b1030e9..94f01e2991ab8629676198272d76696c42d3afe1 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -138,6 +138,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_UNKN
 
 	&lspr[NOLIGHT],     // SPR_THOK
+	&lspr[NOLIGHT],     // SPR_THKE
 	&lspr[SUPERSONIC_L],// SPR_PLAY
 
 	// Enemies
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 7df1556520c9bb46b6f05a62388ef06d553d2fbc..d6019d8125d944f6f9c7d99a1f52485969839338 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -180,6 +180,11 @@ static boolean HWR_UseShader(void)
 	return (cv_glshaders.value && gl_shadersavailable);
 }
 
+static boolean HWR_IsWireframeMode(void)
+{
+	return (cv_glwireframe.value && cv_debug);
+}
+
 void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *colormap)
 {
 	RGBA_t poly_color, tint_color, fade_color;
@@ -5915,6 +5920,9 @@ void HWR_BuildSkyDome(void)
 
 static void HWR_DrawSkyBackground(player_t *player)
 {
+	if (HWR_IsWireframeMode())
+		return;
+
 	HWD.pfnSetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated);
 
 	if (cv_glskydome.value)
@@ -6271,6 +6279,9 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	// Reset the shader state.
 	HWR_SetShaderState();
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 1);
+
 	validcount++;
 
 	if (cv_glbatching.value)
@@ -6333,6 +6344,9 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 		HWR_CreateDrawNodes();
 	}
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 0);
+
 	HWD.pfnSetTransform(NULL);
 	HWD.pfnUnSetShader();
 
@@ -6487,6 +6501,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	// Reset the shader state.
 	HWR_SetShaderState();
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 1);
+
 	ps_numbspcalls.value.i = 0;
 	ps_numpolyobjects.value.i = 0;
 	PS_START_TIMING(ps_bsptime);
@@ -6563,6 +6580,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 		HWR_CreateDrawNodes();
 	}
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 0);
+
 	HWD.pfnSetTransform(NULL);
 	HWD.pfnUnSetShader();
 
@@ -6639,6 +6659,8 @@ consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL)
 
 consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", 0, CV_OnOff, NULL);
 
+consvar_t cv_glwireframe = CVAR_INIT ("gr_wireframe", "Off", 0, CV_OnOff, NULL);
+
 static void CV_glfiltermode_OnChange(void)
 {
 	if (rendermode == render_opengl)
@@ -6679,6 +6701,8 @@ void HWR_AddCommands(void)
 
 	CV_RegisterVar(&cv_glbatching);
 
+	CV_RegisterVar(&cv_glwireframe);
+
 #ifndef NEWCLIP
 	CV_RegisterVar(&cv_glclipwalls);
 #endif
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index cce4e8f0ac4bb48e04c5443656b8dfe1ffa35b2c..6348592af688a1e40ad0ec9dc2ff9a846508b561 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -107,6 +107,8 @@ extern consvar_t cv_glslopecontrast;
 
 extern consvar_t cv_glbatching;
 
+extern consvar_t cv_glwireframe;
+
 extern float gl_viewwidth, gl_viewheight, gl_baseviewwindowy;
 
 extern float gl_viewwindowx, gl_basewindowcentery;
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 6c0c90fc5b06136f6ad06918e3bd027b4f57779a..ea831e41dee3b7afff7eed47d4ead3f858870648 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -302,6 +302,8 @@ typedef void (APIENTRY * PFNglDisable) (GLenum cap);
 static PFNglDisable pglDisable;
 typedef void (APIENTRY * PFNglGetFloatv) (GLenum pname, GLfloat *params);
 static PFNglGetFloatv pglGetFloatv;
+typedef void (APIENTRY * PFNglPolygonMode) (GLenum, GLenum);
+static PFNglPolygonMode pglPolygonMode;
 
 /* Depth Buffer */
 typedef void (APIENTRY * PFNglClearDepth) (GLclampd depth);
@@ -476,6 +478,7 @@ boolean SetupGLfunc(void)
 	GETOPENGLFUNC(pglGetFloatv, glGetFloatv)
 	GETOPENGLFUNC(pglGetIntegerv, glGetIntegerv)
 	GETOPENGLFUNC(pglGetString, glGetString)
+	GETOPENGLFUNC(pglPolygonMode, glPolygonMode)
 
 	GETOPENGLFUNC(pglClearDepth, glClearDepth)
 	GETOPENGLFUNC(pglDepthFunc, glDepthFunc)
@@ -2474,6 +2477,10 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 				Flush(); //??? if we want to change filter mode by texture, remove this
 			break;
 
+		case HWD_SET_WIREFRAME:
+			pglPolygonMode(GL_FRONT_AND_BACK, Value ? GL_LINE : GL_FILL);
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/info.c b/src/info.c
index 5790dd7c56ce3ee5f625f63581903a6d185a3a73..d1707c86a597902fdbbba249387cfa591a11c661 100644
--- a/src/info.c
+++ b/src/info.c
@@ -33,7 +33,8 @@ char sprnames[NUMSPRITES + 1][5] =
 	"NULL", // invisible object
 	"UNKN",
 
-	"THOK", // Thok! mobj
+	"THOK", // Spin trail mobj
+	"THKE", // Thok boom effect
 	"PLAY",
 
 	// Enemies
@@ -703,8 +704,9 @@ state_t states[NUMSTATES] =
 	{SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 5, 0, S_NULL}, // S_XDEATHSTATE
 	{SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 6, 0, S_NULL}, // S_RAISESTATE
 
-	// Thok
+	// Spin trail and thok boom effect
 	{SPR_THOK, FF_TRANS50, 8, {NULL}, 0, 0, S_NULL}, // S_THOK
+	{SPR_THKE, FF_TRANS50|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_THOKEFFECT
 
 	// Player
 	{SPR_PLAY, SPR2_STND|FF_ANIMATE,    105, {NULL}, 0,  7, S_PLAY_WAIT}, // S_PLAY_STND
@@ -4078,6 +4080,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
+	
+	{           // MT_THOKEFFECT
+		-1,             // doomednum
+		S_THOKEFFECT,   // 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
+		32*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
 
 	{           // MT_PLAYER
 		-1,             // doomednum
diff --git a/src/info.h b/src/info.h
index a2d87dbdc10644edaaf34187d45495d70ba49f85..32aff18fbd27b94eb60505cc9dd0028304f8241b 100644
--- a/src/info.h
+++ b/src/info.h
@@ -580,7 +580,8 @@ typedef enum sprite
 	SPR_NULL, // invisible object
 	SPR_UNKN,
 
-	SPR_THOK, // Thok! mobj
+	SPR_THOK, // Spin trail mobj
+	SPR_THKE, // Thok boom effect
 	SPR_PLAY,
 
 	// Enemies
@@ -1182,8 +1183,9 @@ typedef enum state
 	S_XDEATHSTATE,
 	S_RAISESTATE,
 
-	// Thok
+	// Thok boom effect and spin trail
 	S_THOK,
+	S_THOKEFFECT,
 
 	// Player
 	S_PLAY_STND,
@@ -4392,7 +4394,8 @@ typedef enum mobj_type
 	MT_NULL,
 	MT_UNKNOWN,
 
-	MT_THOK, // Thok! mobj
+	MT_THOK, // Spin trail mobj
+	MT_THOKEFFECT, // Thok boom effect
 	MT_PLAYER,
 	MT_TAILSOVERLAY, // c:
 	MT_METALJETFUME,
diff --git a/src/lua_script.c b/src/lua_script.c
index b0b6eeec79654768bb7aa1815800cb1afbb30c74..f90db7bc343fc16bd92f87cd6e1da7ce1acd16ea 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -604,64 +604,114 @@ void LUA_ClearExtVars(void)
 INT32 lua_lumploading = 0;
 
 // Load a script from a MYFILE
-static inline void LUA_LoadFile(MYFILE *f, char *name, boolean noresults)
+static inline boolean LUA_LoadFile(MYFILE *f, char *name)
 {
 	int errorhandlerindex;
+	boolean success;
 
 	if (!name)
 		name = wadfiles[f->wad]->filename;
+
 	CONS_Printf("Loading Lua script from %s\n", name);
+
 	if (!gL) // Lua needs to be initialized
 		LUA_ClearState();
+
 	lua_pushinteger(gL, f->wad);
 	lua_setfield(gL, LUA_REGISTRYINDEX, "WAD");
 
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	errorhandlerindex = lua_gettop(gL);
+
+	success = !luaL_loadbuffer(gL, f->data, f->size, va("@%s",name));
+
+	if (!success) {
+		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+		lua_pop(gL,1);
+	}
+
+	lua_gc(gL, LUA_GCCOLLECT, 0);
+	lua_remove(gL, errorhandlerindex);
+
+	return success;
+}
+
+// Runs a script loaded by LUA_LoadFile.
+static inline void LUA_DoFile(boolean noresults)
+{
+	int errorhandlerindex;
+
+	if (!gL) // LUA_LoadFile should've allocated gL for us!
+		return;
+
 	lua_lumploading++; // turn on loading flag
 
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
-	errorhandlerindex = lua_gettop(gL);
-	if (luaL_loadbuffer(gL, f->data, f->size, va("@%s",name)) || lua_pcall(gL, 0, noresults ? 0 : LUA_MULTRET, lua_gettop(gL) - 1)) {
+	lua_insert(gL, -2); // move the function we're calling to the top.
+	errorhandlerindex = lua_gettop(gL) - 1;
+
+	if (lua_pcall(gL, 0, noresults ? 0 : LUA_MULTRET, lua_gettop(gL) - 1)) {
 		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
 		lua_pop(gL,1);
 	}
+
 	lua_gc(gL, LUA_GCCOLLECT, 0);
 	lua_remove(gL, errorhandlerindex);
 
 	lua_lumploading--; // turn off again
 }
 
-// Load a script from a lump
-void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults)
+static inline MYFILE *LUA_GetFile(UINT16 wad, UINT16 lump, char **name)
 {
-	MYFILE f;
-	char *name;
+	MYFILE *f = Z_Malloc(sizeof(MYFILE), PU_LUA, NULL);
 	size_t len;
-	f.wad = wad;
-	f.size = W_LumpLengthPwad(wad, lump);
-	f.data = Z_Malloc(f.size, PU_LUA, NULL);
-	W_ReadLumpPwad(wad, lump, f.data);
-	f.curpos = f.data;
+
+	f->wad = wad;
+	f->size = W_LumpLengthPwad(wad, lump);
+	f->data = Z_Malloc(f->size, PU_LUA, NULL);
+	W_ReadLumpPwad(wad, lump, f->data);
+	f->curpos = f->data;
 
 	len = strlen(wadfiles[wad]->filename); // length of file name
 
 	if (wadfiles[wad]->type == RET_LUA)
 	{
-		name = malloc(len+1);
-		strcpy(name, wadfiles[wad]->filename);
+		*name = malloc(len+1);
+		strcpy(*name, wadfiles[wad]->filename);
 	}
 	else // If it's not a .lua file, copy the lump name in too.
 	{
 		lumpinfo_t *lump_p = &wadfiles[wad]->lumpinfo[lump];
 		len += 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name
-		name = malloc(len+1);
-		sprintf(name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname);
-		name[len] = '\0';
+		*name = malloc(len+1);
+		sprintf(*name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname);
+		(*name)[len] = '\0'; // annoying that index takes priority over dereference, but w/e
 	}
 
-	LUA_LoadFile(&f, name, noresults); // actually load file!
+	return f;
+}
+
+// Load a script from a lump
+boolean LUA_LoadLump(UINT16 wad, UINT16 lump)
+{
+	char *name = NULL;
+	MYFILE *f = LUA_GetFile(wad, lump, &name);
+	boolean success = LUA_LoadFile(f, name); // actually load file!
 
 	free(name);
-	Z_Free(f.data);
+
+	Z_Free(f->data);
+	Z_Free(f);
+
+	return success;
+}
+
+void LUA_DoLump(UINT16 wad, UINT16 lump, boolean noresults)
+{
+	boolean success = LUA_LoadLump(wad, lump);
+
+	if (success)
+		LUA_DoFile(noresults); // run it
 }
 
 #ifdef LUA_ALLOW_BYTECODE
diff --git a/src/lua_script.h b/src/lua_script.h
index 53d848f8e878a4234b49fd8e38bbc94325488f61..45d2a37ffd1a636f1e6aaf6848fa868a25fba74b 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -45,7 +45,8 @@ extern INT32 lua_lumploading; // is LUA_LoadLump being called?
 
 int LUA_GetErrorMessage(lua_State *L);
 int LUA_Call(lua_State *L, int nargs, int nresults, int errorhandlerindex);
-void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults);
+boolean LUA_LoadLump(UINT16 wad, UINT16 lump);
+void LUA_DoLump(UINT16 wad, UINT16 lump, boolean noresults);
 #ifdef LUA_ALLOW_BYTECODE
 void LUA_DumpFile(const char *filename);
 #endif
diff --git a/src/lua_taglib.c b/src/lua_taglib.c
index 9e73a050c9011f796be0a2ae41399d79f2a7f836..a040a7efcf5a7a5ef6f0532669c255bce0856373 100644
--- a/src/lua_taglib.c
+++ b/src/lua_taglib.c
@@ -228,7 +228,7 @@ static int taglist_get(lua_State *L)
 	}
 	else
 	{
-		lua_getmetatable(L, 1);
+		lua_getglobal(L, "taglist");
 		lua_replace(L, 1);
 		lua_rawget(L, 1);
 		return 1;
diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c
index faff7e8dd73ddc9d08d0c006f5cb3c2cec749976..bfbe30a08ed89aba1af4bb5ef6f2a51448d00c1c 100644
--- a/src/netcode/server_connection.c
+++ b/src/netcode/server_connection.c
@@ -109,7 +109,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 	netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime);
 
 	// Exclude bots from both counts
-	netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumNodes();
+	netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumNodes() - (dedicated ? 1 : 0));
 	netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots());
 
 	netbuffer->u.serverinfo.refusereason = GetRefuseReason(node);
diff --git a/src/p_user.c b/src/p_user.c
index 0c21a1cc15fdcf310276222879c0fb04e232a01e..5cb1d9d5aea95422c58b41493d308eb0382bd673 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2122,7 +2122,19 @@ void P_SpawnThokMobj(player_t *player)
 
 	if (type == MT_GHOST)
 		mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us
-	else
+	else if (type == MT_THOKEFFECT) // Thok boom effect for Sonic
+	{
+		mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, FixedDiv(player->mo->height, player->mo->scale)*3/4, type);
+		mobj->angle = player->mo->angle + ANGLE_90;
+		mobj->fuse = 7;
+		mobj->scale = player->mo->scale / 3;
+		mobj->destscale = 10 * player->mo->scale;
+		mobj->colorized = true;
+		mobj->color = player->mo->color;
+		mobj->momx = -player->mo->momx / 2;
+		mobj->momy = -player->mo->momy / 2;
+	}
+	else // Normal thok object handling
 	{
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 			zheight = player->mo->z + player->mo->height + FixedDiv(P_GetPlayerHeight(player) - player->mo->height, 3*FRACUNIT) - FixedMul(mobjinfo[type].height, player->mo->scale);
diff --git a/src/w_wad.c b/src/w_wad.c
index d012182f18208bd05435c9f7ee8e43d0f27256e2..051469ef57285b28003b43569de8e625856ce5e8 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -208,7 +208,7 @@ static void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 	posStart = W_CheckNumForFullNamePK3("Init.lua", wadnum, 0);
 	if (posStart != INT16_MAX)
 	{
-		LUA_LoadLump(wadnum, posStart, true);
+		LUA_DoLump(wadnum, posStart, true);
 	}
 	else
 	{
@@ -217,7 +217,7 @@ static void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 		{
 			posEnd = W_CheckNumForFolderEndPK3("Lua/", wadnum, posStart);
 			for (; posStart < posEnd; posStart++)
-				LUA_LoadLump(wadnum, posStart, true);
+				LUA_DoLump(wadnum, posStart, true);
 		}
 	}
 
@@ -250,7 +250,7 @@ static void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
 		lumpinfo_t *lump_p = wadfiles[wadnum]->lumpinfo;
 		for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++)
 			if (memcmp(lump_p->name,"LUA_",4)==0)
-				LUA_LoadLump(wadnum, lump, true);
+				LUA_DoLump(wadnum, lump, true);
 	}
 
 	{
@@ -993,7 +993,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 		DEH_LoadDehackedLumpPwad(numwadfiles - 1, 0, mainfile);
 		break;
 	case RET_LUA:
-		LUA_LoadLump(numwadfiles - 1, 0, true);
+		LUA_DoLump(numwadfiles - 1, 0, true);
 		break;
 	default:
 		break;