diff --git a/src/lua_hook.h b/src/lua_hook.h
index fae3bb7e6d1672a50f72d7d5e58c524bb65317c1..da2dcdc380461cefd958b9f42bfbe738a98d724b 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -41,7 +41,7 @@ enum hook {
 	hook_BotAI,
 	hook_LinedefExecute,
 	hook_PlayerMsg,
-	hook_DeathMsg,
+	hook_HurtMsg,
 
 	hook_MAX // last hook
 };
@@ -54,8 +54,9 @@ void LUAh_ThinkFrame(void); // Hook for frame (after mobj and player thinkers)
 boolean LUAh_MobjHook(mobj_t *mo, enum hook which);
 boolean LUAh_PlayerHook(player_t *plr, enum hook which);
 #define LUAh_MobjSpawn(mo) LUAh_MobjHook(mo, hook_MobjSpawn) // Hook for P_SpawnMobj by mobj type
-UINT8 LUAh_MobjCollide(mobj_t *thing1, mobj_t *thing2); // Hook for PIT_CheckThing by (thing) mobj type
-UINT8 LUAh_MobjMoveCollide(mobj_t *thing1, mobj_t *thing2); // Hook for PIT_CheckThing by (tmthing) mobj type
+UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which);
+#define LUAh_MobjCollide(thing1, thing2) LUAh_MobjCollideHook(thing1, thing2, hook_MobjCollide) // Hook for PIT_CheckThing by (thing) mobj type
+#define LUAh_MobjMoveCollide(thing1, thing2) LUAh_MobjCollideHook(thing1, thing2, hook_MobjMoveCollide) // Hook for PIT_CheckThing by (tmthing) mobj type
 boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher); // Hook for P_TouchSpecialThing by mobj type
 #define LUAh_MobjFuse(mo) LUAh_MobjHook(mo, hook_MobjFuse) // Hook for mobj->fuse == 0 by mobj type
 #define LUAh_MobjThinker(mo) LUAh_MobjHook(mo, hook_MobjThinker) // Hook for P_MobjThinker or P_SceneryThinker by mobj type
@@ -73,6 +74,6 @@ 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
 boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector); // Hook for linedef executors
 boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg); // Hook for chat messages
-boolean LUAh_DeathMsg(player_t *player, mobj_t *inflictor, mobj_t *source); // Hook for hurt messages
+boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source); // Hook for hurt messages
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 532726ac223afa4d686029cc0917731e405c0cfc..c314ed045ffaec25888facb1b36c24290a52e293 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -56,23 +56,40 @@ const char *const hookNames[hook_MAX+1] = {
 	NULL
 };
 
+// Hook metadata
+struct hook_s
+{
+	struct hook_s *next;
+	enum hook type;
+	UINT16 id;
+	union {
+		mobjtype_t mt;
+		char *skinname;
+		char *funcname;
+	} s;
+	boolean error;
+};
+typedef struct hook_s* hook_p;
+
+#define FMT_HOOKID "hook_%04x"
+
+hook_p roothook;
+
 // Takes hook, function, and additional arguments (mobj type to act on, etc.)
 static int lib_addHook(lua_State *L)
 {
-	UINT16 hook;
-	boolean notable = false;
-	boolean subtable = false;
-	UINT32 subindex = 0;
-	char *subfield = NULL;
-	const char *lsubfield = NULL;
+	static struct hook_s hook = {NULL, 0, 0, {0}, false};
+	hook_p hookp, *lastp;
+
+	hook.type = luaL_checkoption(L, 1, NULL, hookNames);
+	lua_remove(L, 1);
 
-	hook = (UINT16)luaL_checkoption(L, 1, NULL, hookNames);
-	luaL_checktype(L, 2, LUA_TFUNCTION);
+	luaL_checktype(L, 1, LUA_TFUNCTION);
 
 	if (hud_running)
 		return luaL_error(L, "HUD rendering code should not call this function!");
 
-	switch(hook)
+	switch(hook.type)
 	{
 	// Take a mobjtype enum which this hook is specifically for.
 	case hook_MobjSpawn:
@@ -87,277 +104,140 @@ static int lib_addHook(lua_State *L)
 	case hook_MobjDeath:
 	case hook_BossDeath:
 	case hook_MobjRemoved:
-		subtable = true;
-		if (lua_isnumber(L, 3))
-			subindex = (UINT32)luaL_checkinteger(L, 3);
-		else
-			lsubfield = "a";
-		lua_settop(L, 2);
+	case hook_HurtMsg:
+		hook.s.mt = MT_NULL;
+		if (lua_isnumber(L, 2))
+			hook.s.mt = lua_tonumber(L, 2);
 		break;
-	case hook_BotAI: // Only one AI function per skin, please!
-		notable = true;
-		subtable = true;
-		subfield = ZZ_Alloc(strlen(luaL_checkstring(L, 3))+1);
+	case hook_BotAI:
+		hook.s.skinname = NULL;
+		if (lua_isstring(L, 2))
 		{ // lowercase copy
-			char *p = subfield;
-			const char *s = luaL_checkstring(L, 3);
+			const char *s = lua_tostring(L, 2);
+			char *p = hook.s.skinname = ZZ_Alloc(strlen(s)+1);
 			do {
 				*p = tolower(*s);
 				++p;
 			} while(*(++s));
 			*p = 0;
 		}
-		lua_settop(L, 3);
 		break;
-	case hook_LinedefExecute: // Get one linedef executor function by name
-		notable = true;
-		subtable = true;
-		subfield = ZZ_Alloc(strlen(luaL_checkstring(L, 3))+1);
+	case hook_LinedefExecute: // Linedef executor functions
 		{ // uppercase copy
-			char *p = subfield;
-			const char *s = luaL_checkstring(L, 3);
+			const char *s = luaL_checkstring(L, 2);
+			char *p = hook.s.funcname = ZZ_Alloc(strlen(s)+1);
 			do {
 				*p = toupper(*s);
 				++p;
 			} while(*(++s));
 			*p = 0;
 		}
-		lua_settop(L, 3);
 		break;
 	default:
-		lua_settop(L, 2);
 		break;
 	}
+	lua_settop(L, 1); // lua stack contains only the function now.
 
-	lua_getfield(L, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(L, -1));
+	hooksAvailable[hook.type/8] |= 1<<(hook.type%8);
 
-	// This hook type only allows one entry, not an array of hooks.
-	// New hooks will overwrite the previous ones, and the stack is one table shorter.
-	if (notable)
+	// iterate the hook metadata structs
+	// set hook.id to the highest id + 1
+	// set lastp to the last hook struct's "next" pointer.
+	lastp = &roothook;
+	hook.id = 0;
+	for (hookp = roothook; hookp; hookp = hookp->next)
 	{
-		if (subtable)
-		{
-			lua_rawgeti(L, -1, hook);
-			lua_remove(L, -2); // pop "hook"
-			I_Assert(lua_istable(L, -1));
-			lua_pushvalue(L, 2);
-			if (subfield)
-				lua_setfield(L, -2, subfield);
-			else if (lsubfield)
-				lua_setfield(L, -2, lsubfield);
-			else
-				lua_rawseti(L, -2, subindex);
-		} else {
-			lua_pushvalue(L, 2);
-			lua_rawseti(L, -2, hook);
-		}
-		hooksAvailable[hook/8] |= 1<<(hook%8);
-		return 0;
+		if (hookp->id >= hook.id)
+			hook.id = hookp->id+1;
+		lastp = &hookp->next;
 	}
 
-	// Fetch the hook's table from the registry.
-	// It should always exist, since LUA_HookLib creates a table for every hook.
-	lua_rawgeti(L, -1, hook);
-	lua_remove(L, -2); // pop "hook"
-	I_Assert(lua_istable(L, -1));
-	if (subtable)
-	{
-		// Fetch a subtable based on index
-		if (subfield)
-			lua_getfield(L, -1, subfield);
-		else if (lsubfield)
-			lua_getfield(L, -1, lsubfield);
-		else
-			lua_rawgeti(L, -1, subindex);
-
-		// Subtable doesn't exist, make one now.
-		if (lua_isnil(L, -1))
-		{
-			lua_pop(L, 1);
-			lua_newtable(L);
-
-			// Store a link to the subtable for later.
-			lua_pushvalue(L, -1);
-			if (subfield)
-				lua_setfield(L, -3, subfield);
-			else if (lsubfield)
-				lua_setfield(L, -3, lsubfield);
-			else
-				lua_rawseti(L, -3, subindex);
-	}	}
-
-	// Add function to the table.
-	lua_pushvalue(L, 2);
-	lua_rawseti(L, -2, (int)(lua_objlen(L, -2) + 1));
-
-	if (subfield)
-		Z_Free(subfield);
-
-	hooksAvailable[hook/8] |= 1<<(hook%8);
+	// allocate a permanent memory struct to stuff hook.
+	hookp = ZZ_Alloc(sizeof(struct hook_s));
+	memcpy(hookp, &hook, sizeof(struct hook_s));
+	// tack it onto the end of the linked list.
+	*lastp = hookp;
+
+	// set the hook function in the registry.
+	lua_pushfstring(L, FMT_HOOKID, hook.id);
+	lua_pushvalue(L, 1);
+	lua_settable(L, LUA_REGISTRYINDEX);
 	return 0;
 }
 
 int LUA_HookLib(lua_State *L)
 {
-	// Create all registry tables
-	enum hook i;
 	memset(hooksAvailable,0,sizeof(UINT8[(hook_MAX/8)+1]));
-
-	lua_newtable(L);
-	for (i = 0; i < hook_MAX; i++)
-	{
-		lua_newtable(L);
-		switch(i)
-		{
-		default:
-			break;
-		case hook_MobjSpawn:
-		case hook_MobjCollide:
-		case hook_MobjMoveCollide:
-		case hook_TouchSpecial:
-		case hook_MobjFuse:
-		case hook_MobjThinker:
-		case hook_BossThinker:
-		case hook_ShouldDamage:
-		case hook_MobjDamage:
-		case hook_MobjDeath:
-		case hook_BossDeath:
-		case hook_MobjRemoved:
-			lua_pushstring(L, "a");
-			lua_newtable(L);
-			lua_rawset(L, -3);
-			break;
-		}
-		lua_rawseti(L, -2, i);
-	}
-	lua_setfield(L, LUA_REGISTRYINDEX, "hook");
+	roothook = NULL;
 	lua_register(L, "addHook", lib_addHook);
 	return 0;
 }
 
 boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 {
+	hook_p hookp;
 	boolean hooked = false;
 	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
 		return false;
 
-	// clear the stack (just in case)
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, which);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// generic subtable
-	lua_pushstring(gL, "a");
-	lua_rawget(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
 	LUA_PushUserdata(gL, mo, META_MOBJ);
-	lua_pushnil(gL);
-	while (lua_next(gL, -3)) {
-		CONS_Debug(DBG_LUA, "MobjHook: Calling hook_%s for generic mobj types\n", hookNames[which]);
-		lua_pushvalue(gL, -3); // mo
-		// stack is: hook_Mobj table, subtable "a", mobj, i, function, mobj
-		if (lua_pcall(gL, 1, 1, 0)) {
-			// A run-time error occurred.
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
-			lua_pop(gL, 1);
-			// Remove this function from the hook table to prevent further errors.
-			lua_pushvalue(gL, -1); // key
-			lua_pushnil(gL); // value
-			lua_rawset(gL, -5); // table
-			CONS_Printf("Hook removed.\n");
-		}
-		else
-		{
-			if (lua_toboolean(gL, -1))
-				hooked = true;
-			lua_pop(gL, 1);
-		}
-	}
-	// stack is: hook_Mobj table, subtable "a", mobj
-	lua_remove(gL, -2); // pop subtable, leave mobj
-
-	// mobjtype subtable
-	// stack is: hook_Mobj table, mobj
-	lua_rawgeti(gL, -2, mo->type);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 3); // pop hook_Mobj table, mobj, and nil
-		// the stack should now be empty.
-		return false;
-	}
-	lua_remove(gL, -3); // remove hook table
-	// stack is: mobj, mobjtype subtable
-	lua_insert(gL, lua_gettop(gL)-1); // swap subtable with mobj
-	// stack is: mobjtype subtable, mobj
-
-	lua_pushnil(gL);
-	while (lua_next(gL, -3)) {
-		CONS_Debug(DBG_LUA, "MobjHook: Calling hook_%s for mobj type %d\n", hookNames[which], mo->type);
-		lua_pushvalue(gL, -3); // mo
-		// stack is: mobjtype subtable, mobj, i, function, mobj
-		if (lua_pcall(gL, 1, 1, 0)) {
-			// A run-time error occurred.
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
-			lua_pop(gL, 1);
-			// Remove this function from the hook table to prevent further errors.
-			lua_pushvalue(gL, -1); // key
-			lua_pushnil(gL); // value
-			lua_rawset(gL, -5); // table
-			CONS_Printf("Hook removed.\n");
-		}
-		else
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == which
+		&& (hookp->s.mt == MT_NULL || hookp->s.mt == mo->type))
 		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -2);
+			if (lua_pcall(gL, 1, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
 			if (lua_toboolean(gL, -1))
 				hooked = true;
 			lua_pop(gL, 1);
 		}
-	}
 
-	lua_pop(gL, 2); // pop mobj and subtable
-	// the stack should now be empty.
+	lua_pop(gL, 1);
 
-	lua_gc(gL, LUA_GCSTEP, 3);
+	lua_gc(gL, LUA_GCSTEP, 1);
 	return hooked;
 }
 
 boolean LUAh_PlayerHook(player_t *plr, enum hook which)
 {
+	hook_p hookp;
 	boolean hooked = false;
 	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
 		return false;
 
-	// clear the stack (just in case)
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, which);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
 	LUA_PushUserdata(gL, plr, META_PLAYER);
 
-	lua_pushnil(gL);
-	while (lua_next(gL, -3) != 0) {
-		lua_pushvalue(gL, -3); // player
-		if (lua_pcall(gL, 1, 1, 0)) { // pops hook function, player, pushes 1 return result
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == which)
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -2);
+			if (lua_pcall(gL, 1, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (lua_toboolean(gL, -1)) // if return true,
-			hooked = true; // override vanilla behavior
-		lua_pop(gL, 1); // pop return value
-	}
 
-	lua_pop(gL, -1);
+	lua_pop(gL, 1);
+
 	lua_gc(gL, LUA_GCSTEP, 1);
 	return hooked;
 }
@@ -365,21 +245,22 @@ boolean LUAh_PlayerHook(player_t *plr, enum hook which)
 // Hook for map change (before load)
 void LUAh_MapChange(void)
 {
+	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_MapChange/8] & (1<<(hook_MapChange%8))))
 		return;
 
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_MapChange);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
+	lua_pop(gL, -1);
 	lua_pushinteger(gL, gamemap);
-	lua_pushnil(gL);
-	while (lua_next(gL, -3) != 0) {
-		lua_pushvalue(gL, -3); // gamemap
-		LUA_Call(gL, 1);
-	}
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MapChange)
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -2);
+			LUA_Call(gL, 1);
+		}
+
 	lua_pop(gL, 1);
 	lua_gc(gL, LUA_GCSTEP, 1);
 }
@@ -387,182 +268,112 @@ void LUAh_MapChange(void)
 // Hook for map load
 void LUAh_MapLoad(void)
 {
+	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_MapLoad/8] & (1<<(hook_MapLoad%8))))
 		return;
 
 	lua_pop(gL, -1);
+	lua_pushinteger(gL, gamemap);
 
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_MapLoad);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MapLoad)
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -2);
+			LUA_Call(gL, 1);
+		}
 
-	lua_pushinteger(gL, gamemap);
-	lua_pushnil(gL);
-	while (lua_next(gL, -3) != 0) {
-		lua_pushvalue(gL, -3); // gamemap
-		LUA_Call(gL, 1);
-	}
-	lua_pop(gL, -1);
-	lua_gc(gL, LUA_GCCOLLECT, 0);
+	lua_pop(gL, 1);
+	lua_gc(gL, LUA_GCSTEP, 1);
 }
 
 // Hook for Got_AddPlayer
 void LUAh_PlayerJoin(int playernum)
 {
+	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_PlayerJoin/8] & (1<<(hook_PlayerJoin%8))))
 		return;
 
 	lua_pop(gL, -1);
+	lua_pushinteger(gL, playernum);
 
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_PlayerJoin);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_PlayerJoin)
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -2);
+			LUA_Call(gL, 1);
+		}
 
-	lua_pushinteger(gL, playernum);
-	lua_pushnil(gL);
-	while (lua_next(gL, -3) != 0) {
-		lua_pushvalue(gL, -3); // playernum
-		LUA_Call(gL, 1);
-	}
-	lua_pop(gL, -1);
+	lua_pop(gL, 1);
 	lua_gc(gL, LUA_GCCOLLECT, 0);
 }
 
 // Hook for frame (after mobj and player thinkers)
 void LUAh_ThinkFrame(void)
 {
+	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_ThinkFrame/8] & (1<<(hook_ThinkFrame%8))))
 		return;
 
 	lua_pop(gL, -1);
 
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_ThinkFrame);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	lua_pushnil(gL);
-	while (lua_next(gL, -2) != 0)
-	{
-		//LUA_Call(gL, 0);
-		if (lua_pcall(gL, 0, 0, 0))
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_ThinkFrame)
 		{
-			// A run-time error occurred.
-			CONS_Alert(CONS_WARNING,"%s\n", lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-			// Remove this function from the hook table to prevent further errors.
-			lua_pushvalue(gL, -1); // key
-			lua_pushnil(gL); // value
-			lua_rawset(gL, -4); // table
-			CONS_Printf("Hook removed.\n");
-		}
-	}
-	lua_pop(gL, -1);
-	lua_gc(gL, LUA_GCCOLLECT, 0);
-}
-
-// Hook for PIT_CheckThing by (thing) mobj type (thing1 = thing, thing2 = tmthing)
-UINT8 LUAh_MobjCollide(mobj_t *thing1, mobj_t *thing2)
-{
-	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL || !(hooksAvailable[hook_MobjCollide/8] & (1<<(hook_MobjCollide%8))))
-		return 0;
-
-	// clear the stack
-	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_MobjCollide);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// mobjtype subtable
-	lua_rawgeti(gL, -1, thing1->type);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return 0;
-	}
-	lua_remove(gL, -2); // remove hook table
-
-	LUA_PushUserdata(gL, thing1, META_MOBJ);
-	LUA_PushUserdata(gL, thing2, META_MOBJ);
-	lua_pushnil(gL);
-	while (lua_next(gL, -4)) {
-		lua_pushvalue(gL, -4); // thing1
-		lua_pushvalue(gL, -4); // thing2
-		if (lua_pcall(gL, 2, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
-			lua_pop(gL, 1);
-			continue;
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			if (lua_pcall(gL, 0, 0, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+			}
 		}
-		if (!lua_isnil(gL, -1))
-		{ // if nil, leave shouldCollide = 0.
-			if (lua_toboolean(gL, -1))
-				shouldCollide = 1; // Force yes
-			else
-				shouldCollide = 2; // Force no
-		}
-		lua_pop(gL, 1); // pop return value
-	}
-	lua_pop(gL, 3); // pop arguments and mobjtype table
 
 	lua_gc(gL, LUA_GCSTEP, 1);
-	return shouldCollide;
 }
 
-// Hook for PIT_CheckThing by (tmthing) mobj type (thing1 = tmthing, thing2 = thing)
-UINT8 LUAh_MobjMoveCollide(mobj_t *thing1, mobj_t *thing2)
+// Hook for mobj collisions
+UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
 {
+	hook_p hookp;
 	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL || !(hooksAvailable[hook_MobjMoveCollide/8] & (1<<(hook_MobjMoveCollide%8))))
+	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
 		return 0;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_MobjMoveCollide);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// mobjtype subtable
-	lua_rawgeti(gL, -1, thing1->type);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return 0;
-	}
-	lua_remove(gL, -2); // remove hook table
-
 	LUA_PushUserdata(gL, thing1, META_MOBJ);
 	LUA_PushUserdata(gL, thing2, META_MOBJ);
-	lua_pushnil(gL);
-	while (lua_next(gL, -4)) {
-		lua_pushvalue(gL, -4); // thing1
-		lua_pushvalue(gL, -4); // thing2
-		if (lua_pcall(gL, 2, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == which
+		&& (hookp->s.mt == MT_NULL || hookp->s.mt == thing1->type))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			if (lua_pcall(gL, 2, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (!lua_isnil(gL, -1))
+			{ // if nil, leave shouldCollide = 0.
+				if (lua_toboolean(gL, -1))
+					shouldCollide = 1; // Force yes
+				else
+					shouldCollide = 2; // Force no
+			}
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (!lua_isnil(gL, -1))
-		{ // if nil, leave shouldCollide = 0.
-			if (lua_toboolean(gL, -1))
-				shouldCollide = 1; // Force yes
-			else
-				shouldCollide = 2; // Force no
-		}
-		lua_pop(gL, 1); // pop return value
-	}
-	lua_pop(gL, 3); // pop arguments and mobjtype table
+
+	lua_pop(gL, 2);
 
 	lua_gc(gL, LUA_GCSTEP, 1);
 	return shouldCollide;
@@ -571,47 +382,37 @@ UINT8 LUAh_MobjMoveCollide(mobj_t *thing1, mobj_t *thing2)
 // Hook for P_TouchSpecialThing by mobj type
 boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 {
+	hook_p hookp;
 	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_TouchSpecial/8] & (1<<(hook_TouchSpecial%8))))
-		return false;
+		return 0;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// get hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_TouchSpecial);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// get mobjtype subtable
-	lua_pushinteger(gL, special->type);
-	lua_rawget(gL, 1);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return false;
-	}
-	lua_remove(gL, 1); // pop hook table off the stack
-
 	LUA_PushUserdata(gL, special, META_MOBJ);
 	LUA_PushUserdata(gL, toucher, META_MOBJ);
 
-	lua_pushnil(gL);
-	while (lua_next(gL, 1) != 0) {
-		lua_pushvalue(gL, 2); // special
-		lua_pushvalue(gL, 3); // toucher
-		if (lua_pcall(gL, 2, 1, 0)) { // pops hook function, special, toucher, pushes 1 return result
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_TouchSpecial
+		&& (hookp->s.mt == MT_NULL || hookp->s.mt == special->type))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			if (lua_pcall(gL, 2, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (lua_toboolean(gL, -1)) // if return true,
-			hooked = true; // override vanilla behavior
-		lua_pop(gL, 1); // pop return value
-	}
 
-	lua_pop(gL, -1);
+	lua_pop(gL, 2);
+
 	lua_gc(gL, LUA_GCSTEP, 1);
 	return hooked;
 }
@@ -619,53 +420,45 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 // Hook for P_DamageMobj by mobj type (Should mobj take damage?)
 UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
 {
+	hook_p hookp;
 	UINT8 shouldDamage = 0; // 0 = default, 1 = force yes, 2 = force no.
 	if (!gL || !(hooksAvailable[hook_ShouldDamage/8] & (1<<(hook_ShouldDamage%8))))
 		return 0;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_ShouldDamage);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// mobjtype subtable
-	lua_rawgeti(gL, -1, target->type);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return 0;
-	}
-	lua_remove(gL, -2); // remove hook table
-
 	LUA_PushUserdata(gL, target, META_MOBJ);
 	LUA_PushUserdata(gL, inflictor, META_MOBJ);
 	LUA_PushUserdata(gL, source, META_MOBJ);
 	lua_pushinteger(gL, damage);
-	lua_pushnil(gL);
-	while (lua_next(gL, -6)) {
-		lua_pushvalue(gL, -6); // target
-		lua_pushvalue(gL, -6); // inflictor
-		lua_pushvalue(gL, -6); // source
-		lua_pushvalue(gL, -6); // damage
-		if (lua_pcall(gL, 4, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_ShouldDamage
+		&& (hookp->s.mt == MT_NULL || hookp->s.mt == target->type))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			if (lua_pcall(gL, 4, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (!lua_isnil(gL, -1))
+			{
+				if (lua_toboolean(gL, -1))
+					shouldDamage = 1; // Force yes
+				else
+					shouldDamage = 2; // Force no
+			}
 			lua_pop(gL, 1);
-			continue;
-		}
-		if (!lua_isnil(gL, -1))
-		{ // if nil, leave shouldDamage = 0.
-			if (lua_toboolean(gL, -1))
-				shouldDamage = 1; // Force yes
-			else
-				shouldDamage = 2; // Force no
 		}
-		lua_pop(gL, 1); // pop return value
-	}
-	lua_pop(gL, 5); // pop arguments and mobjtype table
+
+	lua_pop(gL, 4);
 
 	lua_gc(gL, LUA_GCSTEP, 1);
 	return shouldDamage;
@@ -674,136 +467,118 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 // Hook for P_DamageMobj by mobj type (Mobj actually takes damage!)
 boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
 {
-	boolean handled = false;
+	hook_p hookp;
+	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_MobjDamage/8] & (1<<(hook_MobjDamage%8))))
-		return false;
+		return 0;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_MobjDamage);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// mobjtype subtable
-	lua_rawgeti(gL, -1, target->type);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return false;
-	}
-	lua_remove(gL, -2); // remove hook table
-
 	LUA_PushUserdata(gL, target, META_MOBJ);
 	LUA_PushUserdata(gL, inflictor, META_MOBJ);
 	LUA_PushUserdata(gL, source, META_MOBJ);
 	lua_pushinteger(gL, damage);
-	lua_pushnil(gL);
-	while (lua_next(gL, -6)) {
-		lua_pushvalue(gL, -6); // target
-		lua_pushvalue(gL, -6); // inflictor
-		lua_pushvalue(gL, -6); // source
-		lua_pushvalue(gL, -6); // damage
-		if (lua_pcall(gL, 4, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MobjDamage
+		&& (hookp->s.mt == MT_NULL || hookp->s.mt == target->type))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			if (lua_pcall(gL, 4, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (lua_toboolean(gL, -1))
-			handled = true;
-		lua_pop(gL, 1); // pop return value
-	}
-	lua_pop(gL, 5); // pop arguments and mobjtype table
+
+	lua_pop(gL, 4);
 
 	lua_gc(gL, LUA_GCSTEP, 1);
-	return handled;
+	return hooked;
 }
 
 // Hook for P_KillMobj by mobj type
 boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source)
 {
-	boolean handled = false;
+	hook_p hookp;
+	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_MobjDeath/8] & (1<<(hook_MobjDeath%8))))
-		return false;
+		return 0;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_MobjDeath);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// mobjtype subtable
-	lua_rawgeti(gL, -1, target->type);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return false;
-	}
-	lua_remove(gL, -2); // remove hook table
-
 	LUA_PushUserdata(gL, target, META_MOBJ);
 	LUA_PushUserdata(gL, inflictor, META_MOBJ);
 	LUA_PushUserdata(gL, source, META_MOBJ);
-	lua_pushnil(gL);
-	while (lua_next(gL, -5)) {
-		lua_pushvalue(gL, -5); // target
-		lua_pushvalue(gL, -5); // inflictor
-		lua_pushvalue(gL, -5); // source
-		if (lua_pcall(gL, 3, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MobjDeath
+		&& (hookp->s.mt == MT_NULL || hookp->s.mt == target->type))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -4);
+			lua_pushvalue(gL, -4);
+			lua_pushvalue(gL, -4);
+			if (lua_pcall(gL, 3, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (lua_toboolean(gL, -1))
-			handled = true;
-		lua_pop(gL, 1); // pop return value
-	}
-	lua_pop(gL, 4); // pop arguments and mobjtype table
+
+	lua_pop(gL, 3);
 
 	lua_gc(gL, LUA_GCSTEP, 1);
-	return handled;
+	return hooked;
 }
 
 // Hook for B_BuildTiccmd
 boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 {
+	hook_p hookp;
 	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_BotTiccmd/8] & (1<<(hook_BotTiccmd%8))))
 		return false;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_BotTiccmd);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
 	LUA_PushUserdata(gL, bot, META_PLAYER);
 	LUA_PushUserdata(gL, cmd, META_TICCMD);
 
-	lua_pushnil(gL);
-	while (lua_next(gL, 1)) {
-		lua_pushvalue(gL, 2); // bot
-		lua_pushvalue(gL, 3); // cmd
-		if (lua_pcall(gL, 2, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_BotTiccmd)
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			if (lua_pcall(gL, 2, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (lua_toboolean(gL, -1))
-			hooked = true;
-		lua_pop(gL, 1); // pop return value
-	}
 
-	lua_pop(gL, -1);
+	lua_pop(gL, 2);
+
 	lua_gc(gL, LUA_GCSTEP, 1);
 	return hooked;
 }
@@ -811,117 +586,107 @@ boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 // Hook for B_BuildTailsTiccmd by skin name
 boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 {
-	if (!gL || !tails->skin || !(hooksAvailable[hook_BotAI/8] & (1<<(hook_BotAI%8))))
+	hook_p hookp;
+	boolean hooked = false;
+	int n;
+	if (!gL || !(hooksAvailable[hook_BotAI/8] & (1<<(hook_BotAI%8))))
 		return false;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_BotAI);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// bot skin ai function
-	lua_getfield(gL, 1, ((skin_t *)tails->skin)->name);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return false;
-	}
-	lua_remove(gL, 1); // pop the hook table
-
-	// Takes sonic, tails
-	// Returns forward, backward, left, right, jump, spin
 	LUA_PushUserdata(gL, sonic, META_MOBJ);
 	LUA_PushUserdata(gL, tails, META_MOBJ);
-	if (lua_pcall(gL, 2, 8, 0)) {
-		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
-		lua_pop(gL,-1);
-		return false;
-	}
-
-	// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
-	if (lua_istable(gL, 1)) {
-		boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
+	n = lua_gettop(gL);
 
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_BotAI
+		&& (hookp->s.skinname == NULL || !strcmp(hookp->s.skinname, ((skin_t*)tails->skin)->name)))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			if (lua_pcall(gL, 2, 8, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+
+			// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
+			if (lua_istable(gL, n+1)) {
+				boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
 #define CHECKFIELD(field) \
-		lua_getfield(gL, 1, #field);\
-		if (lua_toboolean(gL, -1))\
-			field = true;\
-		lua_pop(gL, 1);
-
-		CHECKFIELD(forward)
-		CHECKFIELD(backward)
-		CHECKFIELD(left)
-		CHECKFIELD(right)
-		CHECKFIELD(strafeleft)
-		CHECKFIELD(straferight)
-		CHECKFIELD(jump)
-		CHECKFIELD(spin)
-
+				lua_getfield(gL, n+1, #field);\
+				if (lua_toboolean(gL, -1))\
+					field = true;\
+				lua_pop(gL, 1);
+
+				CHECKFIELD(forward)
+				CHECKFIELD(backward)
+				CHECKFIELD(left)
+				CHECKFIELD(right)
+				CHECKFIELD(strafeleft)
+				CHECKFIELD(straferight)
+				CHECKFIELD(jump)
+				CHECKFIELD(spin)
 #undef CHECKFIELD
+				B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin);
+			} else
+				B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, n+1), lua_toboolean(gL, n+2), lua_toboolean(gL, n+3), lua_toboolean(gL, n+4), lua_toboolean(gL, n+5), lua_toboolean(gL, n+6), lua_toboolean(gL, n+7), lua_toboolean(gL, n+8));
 
-		B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin);
-	} else
-		B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, 1), lua_toboolean(gL, 2), lua_toboolean(gL, 3), lua_toboolean(gL, 4), lua_toboolean(gL, 5), lua_toboolean(gL, 6), lua_toboolean(gL, 7), lua_toboolean(gL, 8));
+			lua_pop(gL, 8);
+			hooked = true;
+		}
+
+	lua_pop(gL, 2);
 
-	lua_pop(gL, -1);
 	lua_gc(gL, LUA_GCSTEP, 1);
-	return true;
+	return hooked;
 }
 
 // Hook for linedef executors
 boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 {
+	hook_p hookp;
+	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_LinedefExecute/8] & (1<<(hook_LinedefExecute%8))))
-		return false;
+		return 0;
 
-	// clear the stack
 	lua_pop(gL, -1);
-
-	// get hook table
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_LinedefExecute);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	// get function by line text
-	lua_getfield(gL, 1, line->text);
-	if (lua_isnil(gL, -1)) {
-		lua_pop(gL, 2);
-		return false;
-	}
-	lua_remove(gL, 1); // pop hook table off the stack
-
 	LUA_PushUserdata(gL, line, META_LINE);
 	LUA_PushUserdata(gL, mo, META_MOBJ);
 	LUA_PushUserdata(gL, sector, META_SECTOR);
-	LUA_Call(gL, 3); // pops hook function, line, mo, sector
 
-	lua_pop(gL, -1);
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_LinedefExecute
+		&& !strcmp(hookp->s.funcname, line->text))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -4);
+			lua_pushvalue(gL, -4);
+			lua_pushvalue(gL, -4);
+			LUA_Call(gL, 3);
+			hooked = true;
+		}
+
+	lua_pop(gL, 3);
+
 	lua_gc(gL, LUA_GCSTEP, 1);
-	return true;
+	return hooked;
 }
 
-// Hook for PlayerMsg -Red
+// Hook for player chat
 boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
 {
-	boolean handled = false;
-
+	hook_p hookp;
+	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_PlayerMsg/8] & (1<<(hook_PlayerMsg%8))))
 		return false;
 
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_PlayerMsg);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
+	lua_pop(gL, -1);
 	LUA_PushUserdata(gL, &players[source], META_PLAYER); // Source player
-
 	if (flags & 2 /*HU_CSAY*/) { // csay TODO: make HU_CSAY accessible outside hu_stuff.c
 		lua_pushinteger(gL, 3); // type
 		lua_pushnil(gL); // target
@@ -935,70 +700,73 @@ boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
 		lua_pushinteger(gL, 2); // type
 		LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
 	}
-
 	lua_pushstring(gL, msg); // msg
 
-	lua_pushnil(gL);
-
-	while (lua_next(gL, -6)) {
-		lua_pushvalue(gL, -6); // source
-		lua_pushvalue(gL, -6); // type
-		lua_pushvalue(gL, -6); // target
-		lua_pushvalue(gL, -6); // msg
-		if (lua_pcall(gL, 4, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_PlayerMsg)
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			lua_pushvalue(gL, -5);
+			if (lua_pcall(gL, 4, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (lua_toboolean(gL, -1))
-			handled = true;
-		lua_pop(gL, 1); // pop return value
-	}
-	lua_pop(gL, 4); // pop arguments and mobjtype table
+
+	lua_pop(gL, 4);
 
 	lua_gc(gL, LUA_GCSTEP, 1);
-	return handled;
+	return hooked;
 }
 
-// Hook for hurt messages -Red
-// The internal name is DeathMsg, but the API name is "HurtMsg". Keep that in mind. (Should this be fixed at some point?)
-// @TODO This hook should be fixed to take mobj type at the addHook parameter to compare to inflictor. (I couldn't get this to work without crashing)
-boolean LUAh_DeathMsg(player_t *player, mobj_t *inflictor, mobj_t *source)
+// Hook for hurt messages
+boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source)
 {
-	boolean handled = false;
-
-	if (!gL || !(hooksAvailable[hook_DeathMsg/8] & (1<<(hook_DeathMsg%8))))
+	hook_p hookp;
+	boolean hooked = false;
+	if (!gL || !(hooksAvailable[hook_HurtMsg/8] & (1<<(hook_HurtMsg%8))))
 		return false;
 
-	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, hook_DeathMsg);
-	lua_remove(gL, -2);
-	I_Assert(lua_istable(gL, -1));
-
-	LUA_PushUserdata(gL, player, META_PLAYER); // Player
-	LUA_PushUserdata(gL, inflictor, META_MOBJ); // Inflictor
-	LUA_PushUserdata(gL, source, META_MOBJ); // Source
-
-	lua_pushnil(gL);
+	lua_pop(gL, -1);
+	LUA_PushUserdata(gL, player, META_PLAYER);
+	LUA_PushUserdata(gL, inflictor, META_MOBJ);
+	LUA_PushUserdata(gL, source, META_MOBJ);
 
-	while (lua_next(gL, -5)) {
-		lua_pushvalue(gL, -5); // player
-		lua_pushvalue(gL, -5); // inflictor
-		lua_pushvalue(gL, -5); // source
-		if (lua_pcall(gL, 3, 1, 0)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_HurtMsg
+		&& (hookp->s.mt == MT_NULL || (inflictor && hookp->s.mt == inflictor->type)))
+		{
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -4);
+			lua_pushvalue(gL, -4);
+			lua_pushvalue(gL, -4);
+			if (lua_pcall(gL, 3, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
 			lua_pop(gL, 1);
-			continue;
 		}
-		if (lua_toboolean(gL, -1))
-			handled = true;
-		lua_pop(gL, 1); // pop return value
-	}
-	lua_pop(gL, 3); // pop arguments and mobjtype table
+
+	lua_pop(gL, 3);
 
 	lua_gc(gL, LUA_GCSTEP, 1);
-	return handled;
+	return hooked;
 }
 
 #endif
diff --git a/src/p_inter.c b/src/p_inter.c
index 8eaa4765a87f7d7d8d828fc1b32a01e2370a302b..478bd459cbc1fde6fb7a8e9823052e33988e79fc 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1478,7 +1478,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 		return; // Presumably it's obvious what's happening in splitscreen.
 
 #ifdef HAVE_BLUA
-	if (LUAh_DeathMsg(player, inflictor, source))
+	if (LUAh_HurtMsg(player, inflictor, source))
 		return;
 #endif