diff --git a/src/b_bot.c b/src/b_bot.c
index d3635f32c5d75ca1ad56c199241ebfa91c79ea0f..93a853dee1f1800e4d9e911ee1e26894b53f3eb3 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -75,7 +75,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 		return;
 
 	// Lua can handle it!
-	if (LUAh_BotAI(sonic, tails, cmd))
+	if (LUA_HookBotAI(sonic, tails, cmd))
 		return;
 
 	if (tails->player->powers[pw_carry] == CR_MACESPIN || tails->player->powers[pw_carry] == CR_GENERIC)
@@ -363,7 +363,7 @@ void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
 	CV_SetValue(&cv_analog[1], false);
 
 	// Let Lua scripts build ticcmds
-	if (LUAh_BotTiccmd(player, cmd))
+	if (LUA_HookTiccmd(player, cmd, Hook(BotTiccmd)))
 		return;
 
 	// We don't have any main character AI, sorry. D:
@@ -461,7 +461,7 @@ boolean B_CheckRespawn(player_t *player)
 
 	// B_RespawnBot doesn't do anything if the condition above this isn't met
 	{
-		UINT8 shouldForce = LUAh_BotRespawn(sonic, tails);
+		UINT8 shouldForce = LUA_Hook2Mobj(sonic, tails, Mobj_Hook(BotRespawn));
 
 		if (P_MobjWasRemoved(sonic) || P_MobjWasRemoved(tails))
 			return (shouldForce == 1); // mobj was removed
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 14fc1aea5dcadb6118fed4f6432a681e1284a574..91918ed35d12b0ccdeaeb7971df73b6b0c73a4d6 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2537,14 +2537,14 @@ static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 		}
 	}
 
-	LUAh_PlayerQuit(&players[playernum], reason); // Lua hook for player quitting
+	LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting
 
 	// don't look through someone's view who isn't there
 	if (playernum == displayplayer)
 	{
 		// Call ViewpointSwitch hooks here.
 		// The viewpoint was forcibly changed.
-		LUAh_ViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
+		LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
 		displayplayer = consoleplayer;
 	}
 
@@ -3025,7 +3025,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 	if (pnum == consoleplayer)
 	{
 		if (Playing())
-			LUAh_GameQuit();
+			LUA_Hook(GameQuit);
 #ifdef DUMPCONSISTENCY
 		if (msg == KICK_MSG_CON_FAIL) SV_SavedGame();
 #endif
@@ -3445,7 +3445,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 		COM_BufAddText(va("sayto %d %s\n", newplayernum, motd));
 
 	if (!rejoined)
-		LUAh_PlayerJoin(newplayernum);
+		LUA_HookInt(newplayernum, Hook(PlayerJoin));
 }
 
 static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
@@ -3726,7 +3726,7 @@ static void HandleShutdown(SINT8 node)
 {
 	(void)node;
 	if (Playing())
-		LUAh_GameQuit();
+		LUA_Hook(GameQuit);
 	D_QuitNetGame();
 	CL_Reset();
 	D_StartTitle();
@@ -3742,7 +3742,7 @@ static void HandleTimeout(SINT8 node)
 {
 	(void)node;
 	if (Playing())
-		LUAh_GameQuit();
+		LUA_Hook(GameQuit);
 	D_QuitNetGame();
 	CL_Reset();
 	D_StartTitle();
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 31c10f58a8e0dbe7f709c0ffd98e63772fe175d6..baad9bcdf53c2a78d25dbd96542051131ecf10a8 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2103,7 +2103,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	}
 
 	mapnumber = M_MapNumber(mapname[3], mapname[4]);
-	LUAh_MapChange(mapnumber);
+	LUA_HookInt(mapnumber, Hook(MapChange));
 
 	G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene, FLS);
 	if (demoplayback && !timingdemo)
@@ -2688,7 +2688,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	}
 
 	// Don't switch team, just go away, please, go awaayyyy, aaauuauugghhhghgh
-	if (!LUAh_TeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled))
+	if (!LUA_HookTeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled))
 		return;
 
 	//no status changes after hidetime
@@ -2849,7 +2849,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		// Call ViewpointSwitch hooks here.
 		// The viewpoint was forcibly changed.
 		if (displayplayer != consoleplayer) // You're already viewing yourself. No big deal.
-			LUAh_ViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
+			LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
 		displayplayer = consoleplayer;
 	}
 
@@ -3607,7 +3607,7 @@ static void Command_Playintro_f(void)
 FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void)
 {
 	if (Playing())
-		LUAh_GameQuit();
+		LUA_Hook(GameQuit);
 	I_Quit();
 }
 
@@ -4270,7 +4270,7 @@ void Command_ExitGame_f(void)
 	INT32 i;
 
 	if (Playing())
-		LUAh_GameQuit();
+		LUA_Hook(GameQuit);
 
 	D_QuitNetGame();
 	CL_Reset();
diff --git a/src/doomtype.h b/src/doomtype.h
index 4e13ba96d3795bb9992369a48b7aacd27500ea1a..8bfedbe9250ab7d48f06c6f459508cf21aa90556 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -367,6 +367,8 @@ typedef UINT32 tic_t;
 #define UINT2RGBA(a) (UINT32)((a&0xff)<<24)|((a&0xff00)<<8)|((a&0xff0000)>>8)|(((UINT32)a&0xff000000)>>24)
 #endif
 
+#define TOSTR(x) #x
+
 /* preprocessor dumb and needs second macro to expand input */
 #define WSTRING2(s) L ## s
 #define WSTRING(s) WSTRING2 (s)
diff --git a/src/g_demo.c b/src/g_demo.c
index 593fd77234a329ba89140e9ad82b98a5e993c22d..e4af7086c21a88b9dc3d3e1658268241b9b9149a 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -1956,7 +1956,7 @@ void G_DoPlayDemo(char *defdemoname)
 	// Set skin
 	SetPlayerSkin(0, skin);
 
-	LUAh_MapChange(gamemap);
+	LUA_HookInt(gamemap, Hook(MapChange));
 	displayplayer = consoleplayer = 0;
 	memset(playeringame,0,sizeof(playeringame));
 	playeringame[0] = true;
diff --git a/src/g_game.c b/src/g_game.c
index 283113bbeaec2a78b7756b6361d90e33764ec315..c0aaf6af724bdcb1016cad8d9dbc8c3c4228544a 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1689,7 +1689,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 
 		cmd->angleturn = orighookangle;
 
-		LUAh_PlayerCmd(player, cmd);
+		LUA_HookTiccmd(player, cmd, Hook(PlayerCmd));
 
 		extra = cmd->angleturn - orighookangle;
 		cmd->angleturn = origangle + extra;
@@ -1703,7 +1703,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	{
 		// Call ViewpointSwitch hooks here.
 		// The viewpoint was forcibly changed.
-		LUAh_ViewpointSwitch(player, &players[consoleplayer], true);
+		LUA_HookViewpointSwitch(player, &players[consoleplayer], true);
 		displayplayer = consoleplayer;
 	}
 
@@ -2076,7 +2076,7 @@ boolean G_Responder(event_t *ev)
 					continue;
 
 				// Call ViewpointSwitch hooks here.
-				canSwitchView = LUAh_ViewpointSwitch(&players[consoleplayer], &players[displayplayer], false);
+				canSwitchView = LUA_HookViewpointSwitch(&players[consoleplayer], &players[displayplayer], false);
 				if (canSwitchView == 1) // Set viewpoint to this player
 					break;
 				else if (canSwitchView == 2) // Skip this player
@@ -2713,7 +2713,7 @@ void G_SpawnPlayer(INT32 playernum)
 
 	P_SpawnPlayer(playernum);
 	G_MovePlayerToSpawnOrStarpost(playernum);
-	LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :)
+	LUA_HookPlayer(&players[playernum], Hook(PlayerSpawn)); // Lua hook for player spawning :)
 }
 
 void G_MovePlayerToSpawnOrStarpost(INT32 playernum)
@@ -3092,7 +3092,7 @@ void G_DoReborn(INT32 playernum)
 		}
 		else
 		{
-			LUAh_MapChange(gamemap);
+			LUA_HookInt(gamemap, Hook(MapChange));
 			titlecardforreload = true;
 			G_DoLoadLevel(true);
 			titlecardforreload = false;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 7e9144f98fd995ae3b2218f76472f3a92792db56..9516b466bc0ee82ad18aa6c2d51d4a5aabea4bbe 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -684,7 +684,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 
 	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
 
-	if (LUAh_PlayerMsg(playernum, target, flags, msg))
+	if (LUA_HookPlayerMsg(playernum, target, flags, msg))
 		return;
 
 	if (spam_eatmsg)
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 796f3a9d287dcf0e3997d6963949b33111dceb0d..f44a2e305f5bbc7553eab8f9ea3a708601201174 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -12,111 +12,100 @@
 
 #include "r_defs.h"
 #include "d_player.h"
+#include "s_sound.h"
 
-enum hook {
-	hook_NetVars=0,
-	hook_MapChange,
-	hook_MapLoad,
-	hook_PlayerJoin,
-	hook_PreThinkFrame,
-	hook_ThinkFrame,
-	hook_PostThinkFrame,
-	hook_MobjSpawn,
-	hook_MobjCollide,
-	hook_MobjLineCollide,
-	hook_MobjMoveCollide,
-	hook_TouchSpecial,
-	hook_MobjFuse,
-	hook_MobjThinker,
-	hook_BossThinker,
-	hook_ShouldDamage,
-	hook_MobjDamage,
-	hook_MobjDeath,
-	hook_BossDeath,
-	hook_MobjRemoved,
-	hook_JumpSpecial,
-	hook_AbilitySpecial,
-	hook_SpinSpecial,
-	hook_JumpSpinSpecial,
-	hook_BotTiccmd,
-	hook_BotAI,
-	hook_BotRespawn,
-	hook_LinedefExecute,
-	hook_PlayerMsg,
-	hook_HurtMsg,
-	hook_PlayerSpawn,
-	hook_ShieldSpawn,
-	hook_ShieldSpecial,
-	hook_MobjMoveBlocked,
-	hook_MapThingSpawn,
-	hook_FollowMobj,
-	hook_PlayerCanDamage,
-	hook_PlayerQuit,
-	hook_IntermissionThinker,
-	hook_TeamSwitch,
-	hook_ViewpointSwitch,
-	hook_SeenPlayer,
-	hook_PlayerThink,
-	hook_ShouldJingleContinue,
-	hook_GameQuit,
-	hook_PlayerCmd,
-	hook_MusicChange,
+#define Mobj_Hook_List(X) \
+	X (MobjSpawn),/* P_SpawnMobj */\
+	X (MobjCollide),/* PIT_CheckThing */\
+	X (MobjLineCollide),/* ditto */\
+	X (MobjMoveCollide),/* tritto */\
+	X (TouchSpecial),/* P_TouchSpecialThing */\
+	X (MobjFuse),/* when mobj->fuse runs out */\
+	X (MobjThinker),/* P_MobjThinker, P_SceneryThinker */\
+	X (BossThinker),/* P_GenericBossThinker */\
+	X (ShouldDamage),/* P_DamageMobj (Should mobj take damage?) */\
+	X (MobjDamage),/* P_DamageMobj (Mobj actually takes damage!) */\
+	X (MobjDeath),/* P_KillMobj */\
+	X (BossDeath),/* A_BossDeath */\
+	X (MobjRemoved),/* P_RemoveMobj */\
+	X (BotRespawn),/* B_CheckRespawn */\
+	X (MobjMoveBlocked),/* P_XYMovement (when movement is blocked) */\
+	X (MapThingSpawn),/* P_SpawnMapThing */\
+	X (FollowMobj),/* P_PlayerAfterThink Smiles mobj-following */\
 
-	hook_MAX // last hook
-};
-extern const char *const hookNames[];
+#define Hook_List(X) \
+	X (NetVars),/* add to archive table (netsave) */\
+	X (MapChange),/* (before map load) */\
+	X (MapLoad),\
+	X (PlayerJoin),/* Got_AddPlayer */\
+	X (PreThinkFrame)/* frame (before mobj and player thinkers) */,\
+	X (ThinkFrame),/* frame (after mobj and player thinkers) */\
+	X (PostThinkFrame),/* frame (at end of tick, ie after overlays, precipitation, specials) */\
+	X (JumpSpecial),/* P_DoJumpStuff (Any-jumping) */\
+	X (AbilitySpecial),/* P_DoJumpStuff (Double-jumping) */\
+	X (SpinSpecial),/* P_DoSpinAbility (Spin button effect) */\
+	X (JumpSpinSpecial),/* P_DoJumpStuff (Spin button effect (mid-air)) */\
+	X (BotTiccmd),/* B_BuildTiccmd */\
+	X (PlayerMsg),/* chat messages */\
+	X (HurtMsg),/* imhurttin */\
+	X (PlayerSpawn),/* G_SpawnPlayer */\
+	X (ShieldSpawn),/* P_SpawnShieldOrb */\
+	X (ShieldSpecial),/* shield abilities */\
+	X (PlayerCanDamage),/* P_PlayerCanDamage */\
+	X (PlayerQuit),\
+	X (IntermissionThinker),/* Y_Ticker */\
+	X (TeamSwitch),/* team switching in... uh... *what* speak, spit it the fuck out */\
+	X (ViewpointSwitch),/* spy mode (no trickstabs) */\
+	X (SeenPlayer),/* MT_NAMECHECK */\
+	X (PlayerThink),/* P_PlayerThink */\
+	X (GameQuit),\
+	X (PlayerCmd),/* building the player's ticcmd struct (Ported from SRB2Kart) */\
+	X (MusicChange),\
+
+#define String_Hook_List(X) \
+	X (BotAI),/* B_BuildTailsTiccmd by skin name */\
+	X (LinedefExecute),\
+	X (ShouldJingleContinue),/* should jingle of the given music continue playing */\
+
+#define   Mobj_Hook(name)   mobjhook_ ## name
+#define        Hook(name)       hook_ ## name
+#define String_Hook(name) stringhook_ ## name
+
+enum {   Mobj_Hook_List   (Mobj_Hook)    Mobj_Hook(MAX) };
+enum {        Hook_List        (Hook)         Hook(MAX) };
+enum { String_Hook_List (String_Hook)  String_Hook(MAX) };
 
 extern boolean hook_cmd_running;
 
-void LUAh_MapChange(INT16 mapnumber); // Hook for map change (before load)
-void LUAh_MapLoad(void); // Hook for map load
-void LUAh_PlayerJoin(int playernum); // Hook for Got_AddPlayer
-void LUAh_PreThinkFrame(void); // Hook for frame (before mobj and player thinkers)
-void LUAh_ThinkFrame(void); // Hook for frame (after mobj and player thinkers)
-void LUAh_PostThinkFrame(void); // Hook for frame (at end of tick, ie after overlays, precipitation, specials)
-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_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which);
-UINT8 LUAh_MobjLineCollideHook(mobj_t *thing, line_t *line, enum hook which);
-#define LUAh_MobjCollide(thing1, thing2) LUAh_MobjCollideHook(thing1, thing2, hook_MobjCollide) // Hook for PIT_CheckThing by (thing) mobj type
-#define LUAh_MobjLineCollide(thing, line) LUAh_MobjLineCollideHook(thing, line, hook_MobjLineCollide) // 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
-boolean LUAh_MobjThinker(mobj_t *mo); // Hook for P_MobjThinker or P_SceneryThinker by mobj type
-#define LUAh_BossThinker(mo) LUAh_MobjHook(mo, hook_BossThinker) // Hook for P_GenericBossThinker by mobj type
-UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); // Hook for P_DamageMobj by mobj type (Should mobj take damage?)
-boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); // Hook for P_DamageMobj by mobj type (Mobj actually takes damage!)
-boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); // Hook for P_KillMobj by mobj type
-#define LUAh_BossDeath(mo) LUAh_MobjHook(mo, hook_BossDeath) // Hook for A_BossDeath by mobj type
-#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_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
-boolean LUAh_BotRespawn(mobj_t *sonic, mobj_t *tails); // Hook for B_CheckRespawn
-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_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); // Hook for hurt messages
-#define LUAh_PlayerSpawn(player) LUAh_PlayerHook(player, hook_PlayerSpawn) // Hook for G_SpawnPlayer
-#define LUAh_ShieldSpawn(player) LUAh_PlayerHook(player, hook_ShieldSpawn) // Hook for P_SpawnShieldOrb
-#define LUAh_ShieldSpecial(player) LUAh_PlayerHook(player, hook_ShieldSpecial) // Hook for shield abilities
-#define LUAh_MobjMoveBlocked(mo) LUAh_MobjHook(mo, hook_MobjMoveBlocked) // Hook for P_XYMovement (when movement is blocked)
-boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type
-boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following
-UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage
-void LUAh_PlayerQuit(player_t *plr, kickreason_t reason); // Hook for player quitting
-void LUAh_IntermissionThinker(void); // Hook for Y_Ticker
-boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh....
-UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode
+/* dead simple, LUA_Hook(GameQuit) */
+void    LUA_Hook(int hook);
+#define LUA_Hook(type) LUA_Hook(Hook(type))
+
+int  LUA_HookMobj(mobj_t *, int hook);
+int  LUA_Hook2Mobj(mobj_t *, mobj_t *, int hook);
+void LUA_HookInt(INT32 integer, int hook);
+int  LUA_HookPlayer(player_t *, int hook);
+int  LUA_HookTiccmd(player_t *, ticcmd_t *, int hook);
+
+void LUA_HookThinkFrame(void);
+int  LUA_HookMobjLineCollide(mobj_t *, line_t *);
+int  LUA_HookTouchSpecial(mobj_t *special, mobj_t *toucher);
+int  LUA_HookShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype);
+int  LUA_HookMobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype);
+int  LUA_HookMobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype);
+int  LUA_HookBotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd);
+void LUA_HookLinedefExecute(line_t *, mobj_t *, sector_t *);
+int  LUA_HookPlayerMsg(int source, int target, int flags, char *msg);
+int  LUA_HookHurtMsg(player_t *, mobj_t *inflictor, mobj_t *source, UINT8 damagetype);
+int  LUA_HookMapThingSpawn(mobj_t *, mapthing_t *);
+int  LUA_HookFollowMobj(player_t *, mobj_t *);
+int  LUA_HookPlayerCanDamage(player_t *, mobj_t *);
+void LUA_HookPlayerQuit(player_t *, kickreason_t);
+int  LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble);
+int  LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced);
 #ifdef SEENAMES
-boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend); // Hook for MT_NAMECHECK
+int  LUA_HookSeenPlayer(player_t *player, player_t *seenfriend);
 #endif
-#define LUAh_PlayerThink(player) LUAh_PlayerHook(player, hook_PlayerThink) // Hook for P_PlayerThink
-boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname); // Hook for whether a jingle of the given music should continue playing
-void LUAh_GameQuit(void); // Hook for game quitting
-boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd); // Hook for building player's ticcmd struct (Ported from SRB2Kart)
-boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping, UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms); // Hook for music changes
\ No newline at end of file
+int  LUA_HookShouldJingleContinue(player_t *, const char *musname);
+int  LUA_HookPlayerCmd(player_t *, ticcmd_t *);
+int  LUA_HookMusicChange(const char *oldname, struct MusicChange *);
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 117aa48a303e7ba7945f2b970cb20d0292cced2a..dce67cef7a2a2b6e903b1693ba566629bded2308 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -27,1948 +27,1038 @@
 #include "d_netcmd.h" // for cv_perfstats
 #include "i_system.h" // I_GetTimeMicros
 
-static UINT8 hooksAvailable[(hook_MAX/8)+1];
-
-const char *const hookNames[hook_MAX+1] = {
-	"NetVars",
-	"MapChange",
-	"MapLoad",
-	"PlayerJoin",
-	"PreThinkFrame",
-	"ThinkFrame",
-	"PostThinkFrame",
-	"MobjSpawn",
-	"MobjCollide",
-	"MobjLineCollide",
-	"MobjMoveCollide",
-	"TouchSpecial",
-	"MobjFuse",
-	"MobjThinker",
-	"BossThinker",
-	"ShouldDamage",
-	"MobjDamage",
-	"MobjDeath",
-	"BossDeath",
-	"MobjRemoved",
-	"JumpSpecial",
-	"AbilitySpecial",
-	"SpinSpecial",
-	"JumpSpinSpecial",
-	"BotTiccmd",
-	"BotAI",
-	"BotRespawn",
-	"LinedefExecute",
-	"PlayerMsg",
-	"HurtMsg",
-	"PlayerSpawn",
-	"ShieldSpawn",
-	"ShieldSpecial",
-	"MobjMoveBlocked",
-	"MapThingSpawn",
-	"FollowMobj",
-	"PlayerCanDamage",
-	"PlayerQuit",
-	"IntermissionThinker",
-	"TeamSwitch",
-	"ViewpointSwitch",
-	"SeenPlayer",
-	"PlayerThink",
-	"ShouldJingleContinue",
-	"GameQuit",
-	"PlayerCmd",
-	"MusicChange",
-	NULL
-};
+#undef LUA_Hook
 
-// Hook metadata
-struct hook_s
-{
-	struct hook_s *next;
-	enum hook type;
-	UINT16 id;
-	union {
-		mobjtype_t mt;
-		char *str;
-	} s;
-	boolean error;
+/* =========================================================================
+                                  ABSTRACTION
+   ========================================================================= */
+
+static const char * const mobjHookNames[] = { Mobj_Hook_List (TOSTR)  NULL };
+static const char * const     hookNames[] = {      Hook_List (TOSTR)  NULL };
+
+static const char * const stringHookNames[] = {
+	String_Hook_List (TOSTR)  NULL
 };
-typedef struct hook_s* hook_p;
 
-#define FMT_HOOKID "hook_%d"
+/* TODO: remove when doomtype version is merged */
 
-// For each mobj type, a linked list to its thinker and collision hooks.
-// That way, we don't have to iterate through all the hooks.
-// We could do that with all other mobj hooks, but it would probably just be
-// a waste of memory since they are only called occasionally. Probably...
-static hook_p mobjthinkerhooks[NUMMOBJTYPES];
-static hook_p mobjcollidehooks[NUMMOBJTYPES];
+#define BIT_ARRAY_LENGTH(n) (((n) + 7) >> 3)
 
-// For each mobj type, a linked list for other mobj hooks
-static hook_p mobjhooks[NUMMOBJTYPES];
+static inline void set_bit_array (UINT8 *array, const int n) {
+	array[n >> 3] |= 1 << (n & 7);
+}
 
-// A linked list for player hooks
-static hook_p playerhooks;
+static inline int in_bit_array (const UINT8 *array, const int n) {
+	return array[n >> 3] & (1 << (n & 7));
+}
 
-// A linked list for linedef executor hooks
-static hook_p linedefexecutorhooks;
+typedef struct {
+	int numGeneric;
+	int ref;
+} stringhook_t;
 
-// For other hooks, a unique linked list
-hook_p roothook;
+static int hookRefs[Hook(MAX)];
+static int mobjHookRefs[NUMMOBJTYPES][Mobj_Hook(MAX)];
 
-static void PushHook(lua_State *L, hook_p hookp)
-{
-	lua_pushfstring(L, FMT_HOOKID, hookp->id);
-	lua_gettable(L, LUA_REGISTRYINDEX);
-}
+static stringhook_t stringHooks[String_Hook(MAX)];
 
-// Takes hook, function, and additional arguments (mobj type to act on, etc.)
-static int lib_addHook(lua_State *L)
-{
-	static struct hook_s hook = {NULL, 0, 0, {0}, false};
-	static UINT32 nextid;
-	hook_p hookp, *lastp;
+static int hookReg;
 
-	hook.type = luaL_checkoption(L, 1, NULL, hookNames);
-	lua_remove(L, 1);
+// After a hook errors once, don't print the error again.
+static UINT8 * hooksErrored;
 
-	luaL_checktype(L, 1, LUA_TFUNCTION);
+static boolean mobj_hook_available(int hook_type, mobjtype_t mobj_type)
+{
+	return
+		(
+				mobjHookRefs [MT_NULL] [hook_type] > 0 ||
+				mobjHookRefs[mobj_type][hook_type] > 0
+		);
+}
 
-	if (!lua_lumploading)
-		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+static int hook_in_list
+(
+		const char * const         name,
+		const char * const * const list
+){
+	int type;
 
-	switch(hook.type)
+	for (type = 0; list[type] != NULL; ++type)
 	{
-	// Take a mobjtype enum which this hook is specifically for.
-	case hook_MobjSpawn:
-	case hook_MobjCollide:
-	case hook_MobjLineCollide:
-	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:
-	case hook_HurtMsg:
-	case hook_MobjMoveBlocked:
-	case hook_MapThingSpawn:
-	case hook_FollowMobj:
-		hook.s.mt = MT_NULL;
-		if (lua_isnumber(L, 2))
-			hook.s.mt = lua_tonumber(L, 2);
-		luaL_argcheck(L, hook.s.mt < NUMMOBJTYPES, 2, "invalid mobjtype_t");
-		break;
-	case hook_BotAI:
-	case hook_ShouldJingleContinue:
-		hook.s.str = NULL;
-		if (lua_isstring(L, 2))
-		{ // lowercase copy
-			hook.s.str = Z_StrDup(lua_tostring(L, 2));
-			strlwr(hook.s.str);
-		}
-		break;
-	case hook_LinedefExecute: // Linedef executor functions
-		hook.s.str = Z_StrDup(luaL_checkstring(L, 2));
-		strupr(hook.s.str);
-		break;
-	default:
-		break;
+		if (strcmp(name, list[type]) == 0)
+			break;
 	}
-	lua_settop(L, 1); // lua stack contains only the function now.
 
-	hooksAvailable[hook.type/8] |= 1<<(hook.type%8);
+	return type;
+}
 
-	// set hook.id to the highest id + 1
-	hook.id = nextid++;
+static void get_table(lua_State *L)
+{
+	lua_pushvalue(L, -1);
+	lua_rawget(L, -3);
 
-	// Special cases for some hook types (see the comments above mobjthinkerhooks declaration)
-	switch(hook.type)
+	if (lua_isnil(L, -1))
 	{
-	case hook_MobjThinker:
-		lastp = &mobjthinkerhooks[hook.s.mt];
-		break;
-	case hook_MobjCollide:
-	case hook_MobjLineCollide:
-	case hook_MobjMoveCollide:
-		lastp = &mobjcollidehooks[hook.s.mt];
-		break;
-	case hook_MobjSpawn:
-	case hook_TouchSpecial:
-	case hook_MobjFuse:
-	case hook_BossThinker:
-	case hook_ShouldDamage:
-	case hook_MobjDamage:
-	case hook_MobjDeath:
-	case hook_BossDeath:
-	case hook_MobjRemoved:
-	case hook_MobjMoveBlocked:
-	case hook_MapThingSpawn:
-	case hook_FollowMobj:
-		lastp = &mobjhooks[hook.s.mt];
-		break;
-	case hook_JumpSpecial:
-	case hook_AbilitySpecial:
-	case hook_SpinSpecial:
-	case hook_JumpSpinSpecial:
-	case hook_PlayerSpawn:
-	case hook_PlayerCanDamage:
-	case hook_TeamSwitch:
-	case hook_ViewpointSwitch:
-	case hook_SeenPlayer:
-	case hook_ShieldSpawn:
-	case hook_ShieldSpecial:
-	case hook_PlayerThink:
-		lastp = &playerhooks;
-		break;
-	case hook_LinedefExecute:
-		lastp = &linedefexecutorhooks;
-		break;
-	default:
-		lastp = &roothook;
-		break;
+		lua_pop(L, 1);
+		lua_createtable(L, 1, 0);
+		lua_pushvalue(L, -2);
+		lua_pushvalue(L, -2);
+		lua_rawset(L, -5);
 	}
 
-	// iterate the hook metadata structs
-	// set lastp to the last hook struct's "next" pointer.
-	for (hookp = *lastp; hookp; hookp = hookp->next)
-		lastp = &hookp->next;
-	// 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;
+	lua_remove(L, -2);
+}
 
-	// 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;
+static void new_hook_table(lua_State *L, int *ref)
+{
+	if (*ref > 0)
+		lua_getref(L, *ref);
+	else
+	{
+		lua_newtable(L);
+		lua_pushvalue(L, -1);
+		*ref = luaL_ref(L, LUA_REGISTRYINDEX);
+	}
 }
 
-int LUA_HookLib(lua_State *L)
+static void add_hook(lua_State *L, int id)
 {
-	memset(hooksAvailable,0,sizeof(UINT8[(hook_MAX/8)+1]));
-	roothook = NULL;
-	lua_register(L, "addHook", lib_addHook);
-	return 0;
+	lua_pushnumber(L, 1 + id);
+	lua_rawseti(L, -2, 1 + lua_objlen(L, -2));
 }
 
-boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
+static void add_mobj_hook(lua_State *L, int hook_type, int id)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
-		return false;
+	mobjtype_t   mobj_type = luaL_optnumber(L, 3, MT_NULL);
 
-	I_Assert(mo->type < NUMMOBJTYPES);
+	luaL_argcheck(L, mobj_type < NUMMOBJTYPES, 3, "invalid mobjtype_t");
 
-	if (!(mobjhooks[MT_NULL] || mobjhooks[mo->type]))
-		return false;
+	new_hook_table(L, &mobjHookRefs[mobj_type][hook_type]);
+	add_hook(L, id);
+}
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+static void add_string_hook(lua_State *L, int type, int id)
+{
+	stringhook_t * hook = &stringHooks[type];
+
+	char * string = NULL;
 
-	// Look for all generic mobj hooks
-	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+	switch (type)
 	{
-		if (hookp->type != which)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-			LUA_PushUserdata(gL, mo, META_MOBJ);
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 1, 1)) {
-			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);
+		case String_Hook(BotAI):
+		case String_Hook(ShouldJingleContinue):
+			if (lua_isstring(L, 3))
+			{ // lowercase copy
+				string = Z_StrDup(lua_tostring(L, 3));
+				strlwr(string);
+			}
+			break;
+
+		case String_Hook(LinedefExecute):
+			string = Z_StrDup(luaL_checkstring(L, 3));
+			strupr(string);
+			break;
 	}
 
-	for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
+	new_hook_table(L, &hook->ref);
+
+	if (string)
 	{
-		if (hookp->type != which)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-			LUA_PushUserdata(gL, mo, META_MOBJ);
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 1, 1)) {
-			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_pushstring(L, string);
+		get_table(L);
+		add_hook(L, id);
+	}
+	else
+	{
+		lua_pushnumber(L, 1 + id);
+		lua_rawseti(L, -2, ++hook->numGeneric);
 	}
-
-	lua_settop(gL, 0);
-	return hooked;
 }
 
-boolean LUAh_PlayerHook(player_t *plr, enum hook which)
+// Takes hook, function, and additional arguments (mobj type to act on, etc.)
+static int lib_addHook(lua_State *L)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	static int nextid;
 
-	for (hookp = playerhooks; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != which)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-			LUA_PushUserdata(gL, plr, META_PLAYER);
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 1, 1)) {
-			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);
-	}
+	const char * name;
+	int type;
 
-	lua_settop(gL, 0);
-	return hooked;
-}
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
 
-// Hook for map change (before load)
-void LUAh_MapChange(INT16 mapnumber)
-{
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_MapChange/8] & (1<<(hook_MapChange%8))))
-		return;
+	name = luaL_checkstring(L, 1);
+	luaL_checktype(L, 2, LUA_TFUNCTION);
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-	lua_pushinteger(gL, mapnumber);
+	/* this is a very special case */
+	if (( type = hook_in_list(name, stringHookNames) ) < String_Hook(MAX))
+	{
+		add_string_hook(L, type, nextid);
+	}
+	else if (( type = hook_in_list(name, mobjHookNames) ) < Mobj_Hook(MAX))
+	{
+		add_mobj_hook(L, type, nextid);
+	}
+	else if (( type = hook_in_list(name, hookNames) ) < Hook(MAX))
+	{
+		new_hook_table(L, &hookRefs[type]);
+		add_hook(L, nextid);
+	}
+	else
+	{
+		return luaL_argerror(L, 1, lua_pushfstring(L, "invalid hook " LUA_QS, name));
+	}
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	if (!(nextid & 7))
 	{
-		if (hookp->type != hook_MapChange)
-			continue;
-
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 0, 1)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-		}
+		Z_Realloc(hooksErrored,
+				BIT_ARRAY_LENGTH (nextid + 1) * sizeof *hooksErrored,
+				PU_STATIC, &hooksErrored);
+		hooksErrored[nextid >> 3] = 0;
 	}
 
-	lua_settop(gL, 0);
+	// set the hook function in the registry.
+	lua_getref(L, hookReg);
+	lua_pushvalue(L, 2);/* the function */
+	lua_rawseti(L, -2, ++nextid);
+
+	return 0;
 }
 
-// Hook for map load
-void LUAh_MapLoad(void)
+int LUA_HookLib(lua_State *L)
 {
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_MapLoad/8] & (1<<(hook_MapLoad%8))))
-		return;
+	new_hook_table(L, &hookReg);
+	lua_register(L, "addHook", lib_addHook);
+	return 0;
+}
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-	lua_pushinteger(gL, gamemap);
+typedef struct Hook_State Hook_State;
+typedef void (*Hook_Callback)(Hook_State *);
+
+struct Hook_State {
+	int          status;/* return status to calling function */
+	void       * userdata;
+	int          ref;/* ref for primary hook table */
+	int          hook_type;/* Hook or Hook(MAX) + Mobj_Hook */
+	mobjtype_t   mobj_type;
+	const char * string;/* used to fetch table, ran first if set */
+	int          top;/* index of last argument passed to hook */
+	int          id;/* id to fetch function from registry */
+	int          values;/* num arguments passed to hook */
+	int          results;/* num values returned by hook */
+	Hook_Callback results_handler;/* callback when hook successfully returns */
+};
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_MapLoad)
-			continue;
-
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 0, 1)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-		}
-	}
+enum {
+	HINDEX = 1,/* hook registry */
+	EINDEX = 2,/* error handler */
+	SINDEX = 3,/* string itself is pushed in case of string hook */
+};
 
-	lua_settop(gL, 0);
+static void push_error_handler(void)
+{
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
 }
 
-// Hook for Got_AddPlayer
-void LUAh_PlayerJoin(int playernum)
+/* repush hook string */
+static void push_string(void)
 {
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_PlayerJoin/8] & (1<<(hook_PlayerJoin%8))))
-		return;
+	lua_pushvalue(gL, SINDEX);
+}
 
+static boolean start_hook_stack(void)
+{
 	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-	lua_pushinteger(gL, playernum);
+	lua_getref(gL, hookReg);
+	push_error_handler();
+	return true;
+}
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
+static boolean init_hook_type
+(
+		Hook_State * hook,
+		int          status,
+		int          hook_type,
+		mobjtype_t   mobj_type,
+		const char * string,
+		int          ref
+){
+	boolean ready;
+
+	hook->status = status;
+
+	if (mobj_type > 0)
+		ready = mobj_hook_available(hook_type, mobj_type);
+	else
+		ready = ref > 0;
+
+	if (ready)
 	{
-		if (hookp->type != hook_PlayerJoin)
-			continue;
-
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 0, 1)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-		}
+		hook->ref = ref;
+		hook->hook_type = hook_type;
+		hook->mobj_type = mobj_type;
+		hook->string = string;
+		return start_hook_stack();
 	}
-
-	lua_settop(gL, 0);
+	else
+		return false;
 }
 
-// Hook for frame (before mobj and player thinkers)
-void LUAh_PreThinkFrame(void)
-{
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_PreThinkFrame/8] & (1<<(hook_PreThinkFrame%8))))
-		return;
+static boolean prepare_hook
+(
+		Hook_State * hook,
+		int default_status,
+		int hook_type
+){
+	return init_hook_type(hook, default_status,
+			hook_type, 0, NULL,
+			hookRefs[hook_type]);
+}
 
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+static boolean prepare_mobj_hook
+(
+		Hook_State * hook,
+		int          default_status,
+		int          hook_type,
+		mobjtype_t   mobj_type
+){
+	return init_hook_type(hook, default_status,
+			hook_type, mobj_type, NULL,
+			mobjHookRefs[mobj_type][hook_type]);
+}
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
+static boolean prepare_string_hook
+(
+		Hook_State * hook,
+		int          default_status,
+		int          hook_type,
+		const char * string
+){
+	if (init_hook_type(hook, default_status,
+				hook_type, 0, string,
+				stringHooks[hook_type].ref))
 	{
-		if (hookp->type != hook_PreThinkFrame)
-			continue;
-
-		PushHook(gL, hookp);
-		if (lua_pcall(gL, 0, 0, 1)) {
-			if (!hookp->error || cv_debug & DBG_LUA)
-				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-			hookp->error = true;
-		}
+		lua_pushstring(gL, string);
+		return true;
 	}
+	else
+		return false;
+}
 
-	lua_pop(gL, 1); // Pop error handler
+static void init_hook_call
+(
+		Hook_State * hook,
+		int    values,
+		int    results,
+		Hook_Callback results_handler
+){
+	hook->top = lua_gettop(gL);
+	hook->values = values;
+	hook->results = results;
+	hook->results_handler = results_handler;
 }
 
-// Hook for frame (after mobj and player thinkers)
-void LUAh_ThinkFrame(void)
+static void get_hook_table(Hook_State *hook)
 {
-	hook_p hookp;
-	// variables used by perf stats
-	int hook_index = 0;
-	int time_taken = 0;
-	if (!gL || !(hooksAvailable[hook_ThinkFrame/8] & (1<<(hook_ThinkFrame%8))))
-		return;
+	lua_getref(gL, hook->ref);
+}
 
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+static void get_hook(Hook_State *hook, int n)
+{
+	lua_rawgeti(gL, -1, n);
+	hook->id = lua_tonumber(gL, -1) - 1;
+	lua_rawget(gL, HINDEX);
+}
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
+static int call_single_hook_no_copy(Hook_State *hook)
+{
+	if (lua_pcall(gL, hook->values, hook->results, EINDEX) == 0)
 	{
-		if (hookp->type != hook_ThinkFrame)
-			continue;
-
-		if (cv_perfstats.value == 3)
-			time_taken = I_GetTimeMicros();
-		PushHook(gL, hookp);
-		if (lua_pcall(gL, 0, 0, 1)) {
-			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 (hook->results > 0)
+		{
+			(*hook->results_handler)(hook);
+			lua_pop(gL, hook->results);
 		}
-		if (cv_perfstats.value == 3)
+	}
+	else
+	{
+		/* print the error message once */
+		if (cv_debug & DBG_LUA || !in_bit_array(hooksErrored, hook->id))
 		{
-			lua_Debug ar;
-			time_taken = I_GetTimeMicros() - time_taken;
-			// we need the function, let's just retrieve it again
-			PushHook(gL, hookp);
-			lua_getinfo(gL, ">S", &ar);
-			PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src);
-			hook_index++;
+			CONS_Alert(CONS_WARNING, "%s\n", lua_tostring(gL, -1));
+			set_bit_array(hooksErrored, hook->id);
 		}
+		lua_pop(gL, 1);
 	}
 
-	lua_pop(gL, 1); // Pop error handler
+	return 1;
 }
 
-// Hook for frame (at end of tick, ie after overlays, precipitation, specials)
-void LUAh_PostThinkFrame(void)
+static int call_single_hook(Hook_State *hook)
 {
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_PostThinkFrame/8] & (1<<(hook_PostThinkFrame%8))))
-		return;
+	int i;
 
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	for (i = -(hook->values) + 1; i <= 0; ++i)
+		lua_pushvalue(gL, hook->top + i);
+
+	return call_single_hook_no_copy(hook);
+}
+
+static int call_hook_table_for(Hook_State *hook, int n)
+{
+	int k;
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	for (k = 1; k <= n; ++k)
 	{
-		if (hookp->type != hook_PostThinkFrame)
-			continue;
-
-		PushHook(gL, hookp);
-		if (lua_pcall(gL, 0, 0, 1)) {
-			if (!hookp->error || cv_debug & DBG_LUA)
-				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-			hookp->error = true;
-		}
+		get_hook(hook, k);
+		call_single_hook(hook);
 	}
 
-	lua_pop(gL, 1); // Pop error handler
+	return n;
 }
 
-// Hook for mobj collisions
-UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
+static int call_hook_table(Hook_State *hook)
 {
-	hook_p hookp;
-	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
-		return 0;
+	return call_hook_table_for(hook, lua_objlen(gL, -1));
+}
 
-	I_Assert(thing1->type < NUMMOBJTYPES);
+static int call_ref(Hook_State *hook, int ref)
+{
+	int calls;
+
+	if (ref > 0)
+	{
+		lua_getref(gL, ref);
+		calls = call_hook_table(hook);
 
-	if (!(mobjcollidehooks[MT_NULL] || mobjcollidehooks[thing1->type]))
+		return calls;
+	}
+	else
 		return 0;
+}
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+static int call_string_hooks(Hook_State *hook)
+{
+	const int numGeneric = stringHooks[hook->hook_type].numGeneric;
 
-	// Look for all generic mobj collision hooks
-	for (hookp = mobjcollidehooks[MT_NULL]; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != which)
-			continue;
+	int calls = 0;
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, thing1, META_MOBJ);
-			LUA_PushUserdata(gL, thing2, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
-	}
+	get_hook_table(hook);
 
-	for (hookp = mobjcollidehooks[thing1->type]; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != which)
-			continue;
+	/* call generic string hooks first */
+	calls += call_hook_table_for(hook, numGeneric);
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, thing1, META_MOBJ);
-			LUA_PushUserdata(gL, thing2, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
-	}
+	push_string();
+	lua_rawget(gL, -2);
+	calls += call_hook_table(hook);
 
-	lua_settop(gL, 0);
-	return shouldCollide;
+	return calls;
 }
 
-UINT8 LUAh_MobjLineCollideHook(mobj_t *thing, line_t *line, enum hook which)
+static int call_generic_mobj_hooks(Hook_State *hook)
 {
-	hook_p hookp;
-	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
-		return 0;
-
-	I_Assert(thing->type < NUMMOBJTYPES);
+	const int ref = mobjHookRefs[MT_NULL][hook->hook_type];
+	return call_ref(hook, ref);
+}
 
-	if (!(mobjcollidehooks[MT_NULL] || mobjcollidehooks[thing->type]))
-		return 0;
+static int call_hooks
+(
+		Hook_State * hook,
+		int        values,
+		int        results,
+		Hook_Callback results_handler
+){
+	int calls = 0;
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	init_hook_call(hook, values, results, results_handler);
 
-	// Look for all generic mobj collision hooks
-	for (hookp = mobjcollidehooks[MT_NULL]; hookp; hookp = hookp->next)
+	if (hook->string)
 	{
-		if (hookp->type != which)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, thing, META_MOBJ);
-			LUA_PushUserdata(gL, line, META_LINE);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
+		calls += call_string_hooks(hook);
 	}
-
-	for (hookp = mobjcollidehooks[thing->type]; hookp; hookp = hookp->next)
+	else
 	{
-		if (hookp->type != which)
-			continue;
+		if (hook->mobj_type > 0)
+			calls += call_generic_mobj_hooks(hook);
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, thing, META_MOBJ);
-			LUA_PushUserdata(gL, line, META_LINE);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
+		calls += call_ref(hook, hook->ref);
+
+		if (hook->mobj_type > 0)
+			ps_lua_mobjhooks += calls;
 	}
 
 	lua_settop(gL, 0);
-	return shouldCollide;
+
+	return calls;
 }
 
-// Hook for mobj thinkers
-boolean LUAh_MobjThinker(mobj_t *mo)
-{
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_MobjThinker/8] & (1<<(hook_MobjThinker%8))))
-		return false;
+/* =========================================================================
+                            COMMON RESULT HANDLERS
+   ========================================================================= */
 
-	I_Assert(mo->type < NUMMOBJTYPES);
+#define res_none NULL
 
-	if (!(mobjthinkerhooks[MT_NULL] || mobjthinkerhooks[mo->type]))
-		return false;
+static void res_true(Hook_State *hook)
+{
+	if (lua_toboolean(gL, -1))
+		hook->status = true;
+}
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+static void res_false(Hook_State *hook)
+{
+	if (!lua_isnil(gL, -1) && !lua_toboolean(gL, -1))
+		hook->status = false;
+}
 
-	// Look for all generic mobj thinker hooks
-	for (hookp = mobjthinkerhooks[MT_NULL]; hookp; hookp = hookp->next)
+static void res_force(Hook_State *hook)
+{
+	if (!lua_isnil(gL, -1))
 	{
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-			LUA_PushUserdata(gL, mo, META_MOBJ);
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 1, 1)) {
-			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);
+			hook->status = 1; // Force yes
+		else
+			hook->status = 2; // Force no
 	}
+}
 
-	for (hookp = mobjthinkerhooks[mo->type]; hookp; hookp = hookp->next)
+/* =========================================================================
+                               GENERALISED HOOKS
+   ========================================================================= */
+
+int LUA_HookMobj(mobj_t *mobj, int hook_type)
+{
+	Hook_State hook;
+	if (prepare_mobj_hook(&hook, false, hook_type, mobj->type))
 	{
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-			LUA_PushUserdata(gL, mo, META_MOBJ);
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2);
-		if (lua_pcall(gL, 1, 1, 1)) {
-			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_PushUserdata(gL, mobj, META_MOBJ);
+		call_hooks(&hook, 1, 1, res_true);
 	}
-
-	lua_settop(gL, 0);
-	return hooked;
+	return hook.status;
 }
 
-// Hook for P_TouchSpecialThing by mobj type
-boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
+int LUA_Hook2Mobj(mobj_t *t1, mobj_t *t2, int hook_type)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_TouchSpecial/8] & (1<<(hook_TouchSpecial%8))))
-		return false;
-
-	I_Assert(special->type < NUMMOBJTYPES);
-
-	if (!(mobjhooks[MT_NULL] || mobjhooks[special->type]))
-		return false;
+	Hook_State hook;
+	if (prepare_mobj_hook(&hook, 0, hook_type, t1->type))
+	{
+		LUA_PushUserdata(gL, t1, META_MOBJ);
+		LUA_PushUserdata(gL, t2, META_MOBJ);
+		call_hooks(&hook, 2, 1, res_force);
+	}
+	return hook.status;
+}
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+void LUA_Hook(int type)
+{
+	Hook_State hook;
+	if (prepare_hook(&hook, 0, type))
+		call_hooks(&hook, 0, 0, res_none);
+}
 
-	// Look for all generic touch special hooks
-	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+void LUA_HookInt(INT32 number, int hook_type)
+{
+	Hook_State hook;
+	if (prepare_hook(&hook, 0, hook_type))
 	{
-		if (hookp->type != hook_TouchSpecial)
-			continue;
+		lua_pushinteger(gL, number);
+		call_hooks(&hook, 1, 0, res_none);
+	}
+}
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, special, META_MOBJ);
-			LUA_PushUserdata(gL, toucher, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
+int LUA_HookPlayer(player_t *player, int hook_type)
+{
+	Hook_State hook;
+	if (prepare_hook(&hook, false, hook_type))
+	{
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		call_hooks(&hook, 1, 1, res_true);
 	}
+	return hook.status;
+}
 
-	for (hookp = mobjhooks[special->type]; hookp; hookp = hookp->next)
+int LUA_HookTiccmd(player_t *player, ticcmd_t *cmd, int hook_type)
+{
+	Hook_State hook;
+	if (prepare_hook(&hook, false, hook_type))
 	{
-		if (hookp->type != hook_TouchSpecial)
-			continue;
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		LUA_PushUserdata(gL, cmd, META_TICCMD);
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, special, META_MOBJ);
-			LUA_PushUserdata(gL, toucher, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
-	}
+		if (hook_type == Hook(PlayerCmd))
+			hook_cmd_running = true;
 
-	lua_settop(gL, 0);
-	return hooked;
+		call_hooks(&hook, 2, 1, res_true);
+
+		if (hook_type == Hook(PlayerCmd))
+			hook_cmd_running = false;
+	}
+	return hook.status;
 }
 
-// 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, UINT8 damagetype)
-{
-	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;
+/* =========================================================================
+                               SPECIALIZED HOOKS
+   ========================================================================= */
 
-	I_Assert(target->type < NUMMOBJTYPES);
+void LUA_HookThinkFrame(void)
+{
+	// variables used by perf stats
+	int hook_index = 0;
+	int time_taken = 0;
 
-	if (!(mobjhooks[MT_NULL] || mobjhooks[target->type]))
-		return 0;
+	Hook_State hook;
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	int n;
+	int k;
 
-	// Look for all generic should damage hooks
-	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+	if (prepare_hook(&hook, 0, Hook(ThinkFrame)))
 	{
-		if (hookp->type != hook_ShouldDamage)
-			continue;
+		init_hook_call(&hook, 0, 0, res_none);
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, target, META_MOBJ);
-			LUA_PushUserdata(gL, inflictor, META_MOBJ);
-			LUA_PushUserdata(gL, source, META_MOBJ);
-			lua_pushinteger(gL, damage);
-			lua_pushinteger(gL, damagetype);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		if (lua_pcall(gL, 5, 1, 1)) {
-			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))
+		get_hook_table(&hook);
+		n = lua_objlen(gL, -1);
+
+		for (k = 1; k <= n; ++k)
 		{
-			if (lua_toboolean(gL, -1))
-				shouldDamage = 1; // Force yes
-			else
-				shouldDamage = 2; // Force no
+			get_hook(&hook, k);
+
+			if (cv_perfstats.value == 3)
+			{
+				lua_pushvalue(gL, -1);/* need the function again */
+				time_taken = I_GetTimeMicros();
+			}
+
+			call_single_hook(&hook);
+
+			if (cv_perfstats.value == 3)
+			{
+				lua_Debug ar;
+				time_taken = I_GetTimeMicros() - time_taken;
+				lua_getinfo(gL, ">S", &ar);
+				PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src);
+				hook_index++;
+			}
 		}
-		lua_pop(gL, 1);
+
+		lua_settop(gL, 0);
 	}
+}
 
-	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
+int LUA_HookMobjLineCollide(mobj_t *mobj, line_t *line)
+{
+	Hook_State hook;
+	if (prepare_mobj_hook(&hook, 0, Mobj_Hook(MobjLineCollide), mobj->type))
 	{
-		if (hookp->type != hook_ShouldDamage)
-			continue;
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, target, META_MOBJ);
-			LUA_PushUserdata(gL, inflictor, META_MOBJ);
-			LUA_PushUserdata(gL, source, META_MOBJ);
-			lua_pushinteger(gL, damage);
-			lua_pushinteger(gL, damagetype);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		if (lua_pcall(gL, 5, 1, 1)) {
-			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);
+		LUA_PushUserdata(gL, mobj, META_MOBJ);
+		LUA_PushUserdata(gL, line, META_LINE);
+		call_hooks(&hook, 2, 1, res_force);
 	}
-
-	lua_settop(gL, 0);
-	return shouldDamage;
+	return hook.status;
 }
 
-// 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, UINT8 damagetype)
+int LUA_HookTouchSpecial(mobj_t *special, mobj_t *toucher)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_MobjDamage/8] & (1<<(hook_MobjDamage%8))))
-		return false;
-
-	I_Assert(target->type < NUMMOBJTYPES);
-
-	if (!(mobjhooks[MT_NULL] || mobjhooks[target->type]))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	// Look for all generic mobj damage hooks
-	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_mobj_hook(&hook, false, Mobj_Hook(TouchSpecial), special->type))
 	{
-		if (hookp->type != hook_MobjDamage)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, target, META_MOBJ);
-			LUA_PushUserdata(gL, inflictor, META_MOBJ);
-			LUA_PushUserdata(gL, source, META_MOBJ);
-			lua_pushinteger(gL, damage);
-			lua_pushinteger(gL, damagetype);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		if (lua_pcall(gL, 5, 1, 1)) {
-			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_PushUserdata(gL, special, META_MOBJ);
+		LUA_PushUserdata(gL, toucher, META_MOBJ);
+		call_hooks(&hook, 2, 1, res_true);
 	}
+	return hook.status;
+}
 
-	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
+static int damage_hook
+(
+		mobj_t *target,
+		mobj_t *inflictor,
+		mobj_t *source,
+		INT32   damage,
+		UINT8   damagetype,
+		int     hook_type,
+		int     values,
+		Hook_Callback results_handler
+){
+	Hook_State hook;
+	if (prepare_mobj_hook(&hook, 0, hook_type, target->type))
 	{
-		if (hookp->type != hook_MobjDamage)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, target, META_MOBJ);
-			LUA_PushUserdata(gL, inflictor, META_MOBJ);
-			LUA_PushUserdata(gL, source, META_MOBJ);
+		LUA_PushUserdata(gL, target, META_MOBJ);
+		LUA_PushUserdata(gL, inflictor, META_MOBJ);
+		LUA_PushUserdata(gL, source, META_MOBJ);
+		if (values == 5)
 			lua_pushinteger(gL, damage);
-			lua_pushinteger(gL, damagetype);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		if (lua_pcall(gL, 5, 1, 1)) {
-			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_pushinteger(gL, damagetype);
+		call_hooks(&hook, values, 1, results_handler);
 	}
-
-	lua_settop(gL, 0);
-	return hooked;
+	return hook.status;
 }
 
-// Hook for P_KillMobj by mobj type
-boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
+int LUA_HookShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_MobjDeath/8] & (1<<(hook_MobjDeath%8))))
-		return false;
-
-	I_Assert(target->type < NUMMOBJTYPES);
-
-	if (!(mobjhooks[MT_NULL] || mobjhooks[target->type]))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	// Look for all generic mobj death hooks
-	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_MobjDeath)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, target, META_MOBJ);
-			LUA_PushUserdata(gL, inflictor, META_MOBJ);
-			LUA_PushUserdata(gL, source, META_MOBJ);
-			lua_pushinteger(gL, damagetype);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		if (lua_pcall(gL, 4, 1, 1)) {
-			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);
-	}
-
-	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_MobjDeath)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, target, META_MOBJ);
-			LUA_PushUserdata(gL, inflictor, META_MOBJ);
-			LUA_PushUserdata(gL, source, META_MOBJ);
-			lua_pushinteger(gL, damagetype);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		if (lua_pcall(gL, 4, 1, 1)) {
-			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_settop(gL, 0);
-	return hooked;
+	return damage_hook(target, inflictor, source, damage, damagetype,
+			Mobj_Hook(ShouldDamage), 5, res_force);
 }
 
-// Hook for B_BuildTiccmd
-boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
+int LUA_HookMobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_BotTiccmd/8] & (1<<(hook_BotTiccmd%8))))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_BotTiccmd)
-			continue;
-
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, bot, META_PLAYER);
-			LUA_PushUserdata(gL, cmd, META_TICCMD);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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_settop(gL, 0);
-	return hooked;
+	return damage_hook(target, inflictor, source, damage, damagetype,
+			Mobj_Hook(MobjDamage), 5, res_true);
 }
 
-// Hook for B_BuildTailsTiccmd by skin name
-boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
+int LUA_HookMobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_BotAI/8] & (1<<(hook_BotAI%8))))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_BotAI
-		|| (hookp->s.str && strcmp(hookp->s.str, ((skin_t*)tails->skin)->name)))
-			continue;
-
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, sonic, META_MOBJ);
-			LUA_PushUserdata(gL, tails, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 8, 1)) {
-			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;
-		}
+	return damage_hook(target, inflictor, source, 0, damagetype,
+			Mobj_Hook(MobjDeath), 4, res_true);
+}
 
-		// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
-		if (lua_istable(gL, 2+1)) {
-			boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
-#define CHECKFIELD(field) \
-			lua_getfield(gL, 2+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, 2+1), lua_toboolean(gL, 2+2), lua_toboolean(gL, 2+3), lua_toboolean(gL, 2+4), lua_toboolean(gL, 2+5), lua_toboolean(gL, 2+6), lua_toboolean(gL, 2+7), lua_toboolean(gL, 2+8));
-
-		lua_pop(gL, 8);
-		hooked = true;
-	}
+typedef struct {
+	mobj_t   * tails;
+	ticcmd_t * cmd;
+} BotAI_State;
 
-	lua_settop(gL, 0);
-	return hooked;
+static boolean checkbotkey(const char *field)
+{
+	return lua_toboolean(gL, -1) && strcmp(lua_tostring(gL, -2), field) == 0;
 }
 
-// Hook for B_CheckRespawn
-boolean LUAh_BotRespawn(mobj_t *sonic, mobj_t *tails)
+static void res_botai(Hook_State *hook)
 {
-	hook_p hookp;
-	UINT8 shouldRespawn = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL || !(hooksAvailable[hook_BotRespawn/8] & (1<<(hook_BotRespawn%8))))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_BotRespawn)
-			continue;
+	BotAI_State *botai = hook->userdata;
+
+	int k[8];
+
+	int fields = 0;
+
+	// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
+	if (lua_istable(gL, -8)) {
+		lua_pushnil(gL); // key
+		while (lua_next(gL, -9)) {
+#define check(n, f) (checkbotkey(f) ? (k[(n)-1] = 1) : 0)
+			if (
+					check(1,    "forward") || check(2,    "backward") ||
+					check(3,       "left") || check(4,       "right") ||
+					check(5, "strafeleft") || check(6, "straferight") ||
+					check(7,       "jump") || check(8,        "spin")
+			){
+				if (8 <= ++fields)
+				{
+					lua_pop(gL, 2); // pop key and value
+					break;
+				}
+			}
 
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, sonic, META_MOBJ);
-			LUA_PushUserdata(gL, tails, META_MOBJ);
+			lua_pop(gL, 1); // pop value
+#undef check
 		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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))
+	} else {
+		while (fields < 8)
 		{
-			if (lua_toboolean(gL, -1))
-				shouldRespawn = 1; // Force yes
-			else
-				shouldRespawn = 2; // Force no
+			k[fields] = lua_toboolean(gL, -8 + fields);
+			fields++;
 		}
-		lua_pop(gL, 1);
 	}
 
-	lua_settop(gL, 0);
-	return shouldRespawn;
+	B_KeysToTiccmd(botai->tails, botai->cmd,
+			k[0],k[1],k[2],k[3],k[4],k[5],k[6],k[7]);
+
+	hook->status = true;
 }
 
-// Hook for linedef executors
-boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
+int LUA_HookBotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_LinedefExecute/8] & (1<<(hook_LinedefExecute%8))))
-		return 0;
+	const char *skin = ((skin_t *)tails->skin)->name;
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	Hook_State hook;
+	BotAI_State botai;
 
-	for (hookp = linedefexecutorhooks; hookp; hookp = hookp->next)
+	if (prepare_string_hook(&hook, false, String_Hook(BotAI), skin))
 	{
-		if (strcmp(hookp->s.str, line->stringargs[0]))
-			continue;
+		LUA_PushUserdata(gL, sonic, META_MOBJ);
+		LUA_PushUserdata(gL, tails, META_MOBJ);
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, line, META_LINE);
-			LUA_PushUserdata(gL, mo, META_MOBJ);
-			LUA_PushUserdata(gL, sector, META_SECTOR);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -4);
-		lua_pushvalue(gL, -4);
-		lua_pushvalue(gL, -4);
-		if (lua_pcall(gL, 3, 0, 1)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-		}
-		hooked = true;
+		botai.tails = tails;
+		botai.cmd   = cmd;
+
+		hook.userdata = &botai;
+
+		call_hooks(&hook, 2, 8, res_botai);
 	}
 
-	lua_settop(gL, 0);
-	return hooked;
+	return hook.status;
 }
 
-
-boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
+void LUA_HookLinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_PlayerMsg/8] & (1<<(hook_PlayerMsg%8))))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_string_hook
+			(&hook, 0, String_Hook(LinedefExecute), line->stringargs[0]))
 	{
-		if (hookp->type != hook_PlayerMsg)
-			continue;
-
-		if (lua_gettop(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
-			} else if (target == -1) { // sayteam
-				lua_pushinteger(gL, 1); // type
-				lua_pushnil(gL); // target
-			} else if (target == 0) { // say
-				lua_pushinteger(gL, 0); // type
-				lua_pushnil(gL); // target
-			} else { // sayto
-				lua_pushinteger(gL, 2); // type
-				LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
-			}
-			lua_pushstring(gL, msg); // msg
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		if (lua_pcall(gL, 4, 1, 1)) {
-			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_PushUserdata(gL, line, META_LINE);
+		LUA_PushUserdata(gL, mo, META_MOBJ);
+		LUA_PushUserdata(gL, sector, META_SECTOR);
+		ps_lua_mobjhooks += call_hooks(&hook, 3, 0, res_none);
 	}
-
-	lua_settop(gL, 0);
-	return hooked;
 }
 
-
-// Hook for hurt messages
-boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
+int LUA_HookPlayerMsg(int source, int target, int flags, char *msg)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_HurtMsg/8] & (1<<(hook_HurtMsg%8))))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_hook(&hook, false, Hook(PlayerMsg)))
 	{
-		if (hookp->type != hook_HurtMsg
-		|| (hookp->s.mt && !(inflictor && hookp->s.mt == inflictor->type)))
-			continue;
-
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			LUA_PushUserdata(gL, inflictor, META_MOBJ);
-			LUA_PushUserdata(gL, source, META_MOBJ);
-			lua_pushinteger(gL, damagetype);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		if (lua_pcall(gL, 4, 1, 1)) {
-			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_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
+		} else if (target == -1) { // sayteam
+			lua_pushinteger(gL, 1); // type
+			lua_pushnil(gL); // target
+		} else if (target == 0) { // say
+			lua_pushinteger(gL, 0); // type
+			lua_pushnil(gL); // target
+		} else { // sayto
+			lua_pushinteger(gL, 2); // type
+			LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
+		}
+		lua_pushstring(gL, msg); // msg
+		call_hooks(&hook, 4, 1, res_true);
 	}
-
-	lua_settop(gL, 0);
-	return hooked;
+	return hook.status;
 }
 
-void LUAh_NetArchiveHook(lua_CFunction archFunc)
+int LUA_HookHurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
 {
-	hook_p hookp;
-	int errorhandlerindex;
-	if (!gL || !(hooksAvailable[hook_NetVars/8] & (1<<(hook_NetVars%8))))
-		return;
-
-	// stack: tables
-	I_Assert(lua_gettop(gL) > 0);
-	I_Assert(lua_istable(gL, -1));
-
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-	errorhandlerindex = lua_gettop(gL);
-
-	// tables becomes an upvalue of archFunc
-	lua_pushvalue(gL, -2);
-	lua_pushcclosure(gL, archFunc, 1);
-	// stack: tables, archFunc
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_hook(&hook, false, Hook(HurtMsg)))
 	{
-		if (hookp->type != hook_NetVars)
-			continue;
-
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -2); // archFunc
-		if (lua_pcall(gL, 1, 0, errorhandlerindex)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-		}
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		LUA_PushUserdata(gL, inflictor, META_MOBJ);
+		LUA_PushUserdata(gL, source, META_MOBJ);
+		lua_pushinteger(gL, damagetype);
+		call_hooks(&hook, 4, 1, res_true);
 	}
-
-	lua_pop(gL, 2); // Pop archFunc and error handler
-	// stack: tables
+	return hook.status;
 }
 
-boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
+void LUA_HookNetArchive(lua_CFunction archFunc)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_MapThingSpawn/8] & (1<<(hook_MapThingSpawn%8))))
-		return false;
-
-	if (!(mobjhooks[MT_NULL] || mobjhooks[mo->type]))
-		return false;
+	const int ref = hookRefs[Hook(NetVars)];
+	Hook_State hook;
+	/* this is a remarkable case where the stack isn't reset */
+	if (ref > 0)
+	{
+		// stack: tables
+		I_Assert(lua_gettop(gL) > 0);
+		I_Assert(lua_istable(gL, -1));
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+		push_error_handler();
+		lua_getref(gL, hookReg);
 
-	// Look for all generic mobj map thing spawn hooks
-	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_MapThingSpawn)
-			continue;
+		lua_insert(gL, HINDEX);
+		lua_insert(gL, EINDEX);
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, mo, META_MOBJ);
-			LUA_PushUserdata(gL, mthing, META_MAPTHING);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
-	}
+		// tables becomes an upvalue of archFunc
+		lua_pushvalue(gL, -1);
+		lua_pushcclosure(gL, archFunc, 1);
+		// stack: tables, archFunc
 
-	for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_MapThingSpawn)
-			continue;
+		init_hook_call(&hook, 1, 0, res_none);
+		call_ref(&hook, ref);
 
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, mo, META_MOBJ);
-			LUA_PushUserdata(gL, mthing, META_MAPTHING);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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 hook table and archFunc
+		lua_remove(gL, EINDEX); // pop error handler
+		lua_remove(gL, HINDEX); // pop hook registry
+		// stack: tables
 	}
-
-	lua_settop(gL, 0);
-	return hooked;
 }
 
-// Hook for P_PlayerAfterThink Smiles mobj-following
-boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj)
+int LUA_HookMapThingSpawn(mobj_t *mobj, mapthing_t *mthing)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_FollowMobj/8] & (1<<(hook_FollowMobj%8))))
-		return 0;
-
-	if (!(mobjhooks[MT_NULL] || mobjhooks[mobj->type]))
-		return 0;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	// Look for all generic mobj follow item hooks
-	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_mobj_hook(&hook, false, Mobj_Hook(MapThingSpawn), mobj->type))
 	{
-		if (hookp->type != hook_FollowMobj)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			LUA_PushUserdata(gL, mobj, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
-	}
-
-	for (hookp = mobjhooks[mobj->type]; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_FollowMobj)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			LUA_PushUserdata(gL, mobj, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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_PushUserdata(gL, mobj, META_MOBJ);
+		LUA_PushUserdata(gL, mthing, META_MAPTHING);
+		call_hooks(&hook, 2, 1, res_true);
 	}
-
-	lua_settop(gL, 0);
-	return hooked;
+	return hook.status;
 }
 
-// Hook for P_PlayerCanDamage
-UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj)
+int LUA_HookFollowMobj(player_t *player, mobj_t *mobj)
 {
-	hook_p hookp;
-	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL || !(hooksAvailable[hook_PlayerCanDamage/8] & (1<<(hook_PlayerCanDamage%8))))
-		return 0;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_mobj_hook(&hook, false, Mobj_Hook(FollowMobj), mobj->type))
 	{
-		if (hookp->type != hook_PlayerCanDamage)
-			continue;
-
-		ps_lua_mobjhooks++;
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			LUA_PushUserdata(gL, mobj, META_MOBJ);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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);
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		LUA_PushUserdata(gL, mobj, META_MOBJ);
+		call_hooks(&hook, 2, 1, res_true);
 	}
-
-	lua_settop(gL, 0);
-	return shouldCollide;
+	return hook.status;
 }
 
-void LUAh_PlayerQuit(player_t *plr, kickreason_t reason)
+int LUA_HookPlayerCanDamage(player_t *player, mobj_t *mobj)
 {
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_PlayerQuit/8] & (1<<(hook_PlayerQuit%8))))
-		return;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_hook(&hook, 0, Hook(PlayerCanDamage)))
 	{
-		if (hookp->type != hook_PlayerQuit)
-			continue;
-
-	    if (lua_gettop(gL) == 1)
-	    {
-	        LUA_PushUserdata(gL, plr, META_PLAYER); // Player that quit
-	        lua_pushinteger(gL, reason); // Reason for quitting
-	    }
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 0, 1)) {
-			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-		}
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		LUA_PushUserdata(gL, mobj, META_MOBJ);
+		call_hooks(&hook, 2, 1, res_force);
 	}
-
-	lua_settop(gL, 0);
+	return hook.status;
 }
 
-// Hook for Y_Ticker
-void LUAh_IntermissionThinker(void)
+void LUA_HookPlayerQuit(player_t *plr, kickreason_t reason)
 {
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_IntermissionThinker/8] & (1<<(hook_IntermissionThinker%8))))
-		return;
-
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_hook(&hook, 0, Hook(PlayerQuit)))
 	{
-		if (hookp->type != hook_IntermissionThinker)
-			continue;
-
-		PushHook(gL, hookp);
-		if (lua_pcall(gL, 0, 0, 1)) {
-			if (!hookp->error || cv_debug & DBG_LUA)
-				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-			hookp->error = true;
-		}
+		LUA_PushUserdata(gL, plr, META_PLAYER); // Player that quit
+		lua_pushinteger(gL, reason); // Reason for quitting
+		call_hooks(&hook, 2, 0, res_none);
 	}
-
-	lua_pop(gL, 1); // Pop error handler
 }
 
-// Hook for team switching
-// It's just an edit of LUAh_ViewpointSwitch.
-boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble)
+int LUA_HookTeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble)
 {
-	hook_p hookp;
-	boolean canSwitchTeam = true;
-	if (!gL || !(hooksAvailable[hook_TeamSwitch/8] & (1<<(hook_TeamSwitch%8))))
-		return true;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_hook(&hook, true, Hook(TeamSwitch)))
 	{
-		if (hookp->type != hook_TeamSwitch)
-			continue;
-
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			lua_pushinteger(gL, newteam);
-			lua_pushboolean(gL, fromspectators);
-			lua_pushboolean(gL, tryingautobalance);
-			lua_pushboolean(gL, tryingscramble);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		lua_pushvalue(gL, -6);
-		if (lua_pcall(gL, 5, 1, 1)) {
-			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) && !lua_toboolean(gL, -1))
-			canSwitchTeam = false; // Can't switch team
-		lua_pop(gL, 1);
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		lua_pushinteger(gL, newteam);
+		lua_pushboolean(gL, fromspectators);
+		lua_pushboolean(gL, tryingautobalance);
+		lua_pushboolean(gL, tryingscramble);
+		call_hooks(&hook, 5, 1, res_false);
 	}
-
-	lua_settop(gL, 0);
-	return canSwitchTeam;
+	return hook.status;
 }
 
-// Hook for spy mode
-UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced)
+int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced)
 {
-	hook_p hookp;
-	UINT8 canSwitchView = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL || !(hooksAvailable[hook_ViewpointSwitch/8] & (1<<(hook_ViewpointSwitch%8))))
-		return 0;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	hud_running = true; // local hook
-
-	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_hook(&hook, 0, Hook(ViewpointSwitch)))
 	{
-		if (hookp->type != hook_ViewpointSwitch)
-			continue;
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		LUA_PushUserdata(gL, newdisplayplayer, META_PLAYER);
+		lua_pushboolean(gL, forced);
 
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			LUA_PushUserdata(gL, newdisplayplayer, META_PLAYER);
-			lua_pushboolean(gL, forced);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -4);
-		lua_pushvalue(gL, -4);
-		lua_pushvalue(gL, -4);
-		if (lua_pcall(gL, 3, 1, 1)) {
-			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 canSwitchView = 0.
-			if (lua_toboolean(gL, -1))
-				canSwitchView = 1; // Force viewpoint switch
-			else
-				canSwitchView = 2; // Skip viewpoint switch
-		}
-		lua_pop(gL, 1);
+		hud_running = true; // local hook
+		call_hooks(&hook, 3, 1, res_force);
+		hud_running = false;
 	}
-
-	lua_settop(gL, 0);
-
-	hud_running = false;
-
-	return canSwitchView;
+	return hook.status;
 }
 
-// Hook for MT_NAMECHECK
 #ifdef SEENAMES
-boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend)
+int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend)
 {
-	hook_p hookp;
-	boolean hasSeenPlayer = true;
-	if (!gL || !(hooksAvailable[hook_SeenPlayer/8] & (1<<(hook_SeenPlayer%8))))
-		return true;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	hud_running = true; // local hook
-
-	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_hook(&hook, true, Hook(SeenPlayer)))
 	{
-		if (hookp->type != hook_SeenPlayer)
-			continue;
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		LUA_PushUserdata(gL, seenfriend, META_PLAYER);
 
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			LUA_PushUserdata(gL, seenfriend, META_PLAYER);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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) && !lua_toboolean(gL, -1))
-			hasSeenPlayer = false; // Hasn't seen player
-		lua_pop(gL, 1);
+		hud_running = true; // local hook
+		call_hooks(&hook, 2, 1, res_false);
+		hud_running = false;
 	}
-
-	lua_settop(gL, 0);
-
-	hud_running = false;
-
-	return hasSeenPlayer;
+	return hook.status;
 }
 #endif // SEENAMES
 
-boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname)
+int LUA_HookShouldJingleContinue(player_t *player, const char *musname)
 {
-	hook_p hookp;
-	boolean keepplaying = false;
-	if (!gL || !(hooksAvailable[hook_ShouldJingleContinue/8] & (1<<(hook_ShouldJingleContinue%8))))
-		return true;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	hud_running = true; // local hook
-
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	Hook_State hook;
+	if (prepare_string_hook
+			(&hook, false, String_Hook(ShouldJingleContinue), musname))
 	{
-		if (hookp->type != hook_ShouldJingleContinue
-			|| (hookp->s.str && strcmp(hookp->s.str, musname)))
-			continue;
+		LUA_PushUserdata(gL, player, META_PLAYER);
+		push_string();
 
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			lua_pushstring(gL, musname);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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) && lua_toboolean(gL, -1))
-			keepplaying = true; // Keep playing this boolean
-		lua_pop(gL, 1);
+		hud_running = true; // local hook
+		call_hooks(&hook, 2, 1, res_true);
+		hud_running = false;
 	}
-
-	lua_settop(gL, 0);
-
-	hud_running = false;
-
-	return keepplaying;
+	return hook.status;
 }
 
-// Hook for game quitting
-void LUAh_GameQuit(void)
-{
-	hook_p hookp;
-	if (!gL || !(hooksAvailable[hook_GameQuit/8] & (1<<(hook_GameQuit%8))))
-		return;
+boolean hook_cmd_running = false;
 
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+static void update_music_name(struct MusicChange *musicchange)
+{
+	size_t length;
+	const char * new = lua_tolstring(gL, -6, &length);
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
+	if (length < 7)
 	{
-		if (hookp->type != hook_GameQuit)
-			continue;
-
-		PushHook(gL, hookp);
-		if (lua_pcall(gL, 0, 0, 1)) {
-			if (!hookp->error || cv_debug & DBG_LUA)
-				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-			lua_pop(gL, 1);
-			hookp->error = true;
-		}
+		strcpy(musicchange->newname, new);
+		lua_pushvalue(gL, -6);/* may as well keep it for next call */
+	}
+	else
+	{
+		memcpy(musicchange->newname, new, 6);
+		musicchange->newname[6] = '\0';
+		lua_pushlstring(gL, new, 6);
 	}
 
-	lua_pop(gL, 1); // Pop error handler
+	lua_replace(gL, -7);
 }
 
-// Hook for building player's ticcmd struct (Ported from SRB2Kart)
-boolean hook_cmd_running = false;
-boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd)
+static void res_musicchange(Hook_State *hook)
 {
-	hook_p hookp;
-	boolean hooked = false;
-	if (!gL || !(hooksAvailable[hook_PlayerCmd/8] & (1<<(hook_PlayerCmd%8))))
-		return false;
-
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	hook_cmd_running = true;
-	for (hookp = roothook; hookp; hookp = hookp->next)
-	{
-		if (hookp->type != hook_PlayerCmd)
-			continue;
-
-		if (lua_gettop(gL) == 1)
-		{
-			LUA_PushUserdata(gL, player, META_PLAYER);
-			LUA_PushUserdata(gL, cmd, META_TICCMD);
-		}
-		PushHook(gL, hookp);
-		lua_pushvalue(gL, -3);
-		lua_pushvalue(gL, -3);
-		if (lua_pcall(gL, 2, 1, 1)) {
-			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_settop(gL, 0);
-	hook_cmd_running = false;
-	return hooked;
+	struct MusicChange *musicchange = hook->userdata;
+
+	// output 1: true, false, or string musicname override
+	if (lua_isstring(gL, -6))
+		update_music_name(musicchange);
+	else if (lua_isboolean(gL, -6) && lua_toboolean(gL, -6))
+		hook->status = true;
+
+	// output 2: mflags override
+	if (lua_isnumber(gL, -5))
+		*musicchange->mflags = lua_tonumber(gL, -5);
+	// output 3: looping override
+	if (lua_isboolean(gL, -4))
+		*musicchange->looping = lua_toboolean(gL, -4);
+	// output 4: position override
+	if (lua_isboolean(gL, -3))
+		*musicchange->position = lua_tonumber(gL, -3);
+	// output 5: prefadems override
+	if (lua_isboolean(gL, -2))
+		*musicchange->prefadems = lua_tonumber(gL, -2);
+	// output 6: fadeinms override
+	if (lua_isboolean(gL, -1))
+		*musicchange->fadeinms = lua_tonumber(gL, -1);
 }
 
-// Hook for music changes
-boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping,
-	UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms)
+int LUA_HookMusicChange(const char *oldname, struct MusicChange *param)
 {
-	hook_p hookp;
-	boolean hooked = false;
+	Hook_State hook;
+	if (prepare_hook(&hook, false, Hook(MusicChange)))
+	{
+		int n;
+		int k;
 
-	if (!gL || !(hooksAvailable[hook_MusicChange/8] & (1<<(hook_MusicChange%8))))
-		return false;
+		init_hook_call(&hook, 7, 6, res_musicchange);
+		hook.userdata = param;
 
-	lua_settop(gL, 0);
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
+		lua_pushstring(gL, oldname);/* the only constant value */
+		lua_pushstring(gL, param->newname);/* semi constant */
 
-	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MusicChange)
-		{
-			PushHook(gL, hookp);
-			lua_pushstring(gL, oldname);
-			lua_pushstring(gL, newname);
-			lua_pushinteger(gL, *mflags);
-			lua_pushboolean(gL, *looping);
-			lua_pushinteger(gL, *position);
-			lua_pushinteger(gL, *prefadems);
-			lua_pushinteger(gL, *fadeinms);
-			if (lua_pcall(gL, 7, 6, 1)) {
-				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
-				lua_pop(gL, 1);
-				continue;
-			}
+		get_hook_table(&hook);
+		n = lua_objlen(gL, -1);
 
-			// output 1: true, false, or string musicname override
-			if (lua_isboolean(gL, -6) && lua_toboolean(gL, -6))
-				hooked = true;
-			else if (lua_isstring(gL, -6))
-				strncpy(newname, lua_tostring(gL, -6), 7);
-			// output 2: mflags override
-			if (lua_isnumber(gL, -5))
-				*mflags = lua_tonumber(gL, -5);
-			// output 3: looping override
-			if (lua_isboolean(gL, -4))
-				*looping = lua_toboolean(gL, -4);
-			// output 4: position override
-			if (lua_isboolean(gL, -3))
-				*position = lua_tonumber(gL, -3);
-			// output 5: prefadems override
-			if (lua_isboolean(gL, -2))
-				*prefadems = lua_tonumber(gL, -2);
-			// output 6: fadeinms override
-			if (lua_isboolean(gL, -1))
-				*fadeinms = lua_tonumber(gL, -1);
-
-			lua_pop(gL, 7);  // Pop returned values and error handler
+		for (k = 1; k <= n; ++k) {
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			lua_pushinteger(gL, *param->mflags);
+			lua_pushboolean(gL, *param->looping);
+			lua_pushinteger(gL, *param->position);
+			lua_pushinteger(gL, *param->prefadems);
+			lua_pushinteger(gL, *param->fadeinms);
+
+			call_single_hook_no_copy(&hook);
 		}
 
-	lua_settop(gL, 0);
-	newname[6] = 0;
-	return hooked;
-}
\ No newline at end of file
+		lua_settop(gL, 0);
+	}
+	return hook.status;
+}
diff --git a/src/lua_script.c b/src/lua_script.c
index eb4737f7655d018ced5cec7bd83625768b9b8caf..a649942b85b272787d36fce7b9509476c0439b8f 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -1628,7 +1628,7 @@ void LUA_Archive(void)
 
 	WRITEUINT32(save_p, UINT32_MAX); // end of mobjs marker, replaces mobjnum.
 
-	LUAh_NetArchiveHook(NetArchive); // call the NetArchive hook in archive mode
+	LUA_HookNetArchive(NetArchive); // call the NetArchive hook in archive mode
 	ArchiveTables();
 
 	if (gL)
@@ -1663,7 +1663,7 @@ void LUA_UnArchive(void)
 		}
 	} while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker.
 
-	LUAh_NetArchiveHook(NetUnArchive); // call the NetArchive hook in unarchive mode
+	LUA_HookNetArchive(NetUnArchive); // call the NetArchive hook in unarchive mode
 	UnArchiveTables();
 
 	if (gL)
diff --git a/src/lua_script.h b/src/lua_script.h
index 79ba0bb38a5e1aeb6af476fb4c5b01b39aeb63f2..e9104f3d5f1072fdf6729e0c3dfd8ff460b5635e 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -61,7 +61,7 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum); // lua_consolelib.c
 void LUA_CVarChanged(const char *name); // lua_consolelib.c
 int Lua_optoption(lua_State *L, int narg,
 	const char *def, const char *const lst[]);
-void LUAh_NetArchiveHook(lua_CFunction archFunc);
+void LUA_HookNetArchive(lua_CFunction archFunc);
 
 // Console wrapper
 void COM_Lua_f(void);
diff --git a/src/m_menu.c b/src/m_menu.c
index 77648f877c7adc9ff2b3b5c4fc9f0be7eb532a05..a9333e36e1ff87f057639dbd7766bae658a7e93d 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -6938,7 +6938,7 @@ static void M_UltimateCheat(INT32 choice)
 {
 	(void)choice;
 	if (Playing())
-		LUAh_GameQuit();
+		LUA_Hook(GameQuit);
 	I_Quit();
 }
 
@@ -13373,7 +13373,7 @@ void M_QuitResponse(INT32 ch)
 	if (ch != 'y' && ch != KEY_ENTER)
 		return;
 	if (Playing())
-		LUAh_GameQuit();
+		LUA_Hook(GameQuit);
 	if (!(netgame || cv_debug))
 	{
 		S_ResetCaptions();
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 22de9bc67325b1b2b85d076808d24345d04c27de..103441701eb460136c99f21072812df75f1c4d98 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -3957,7 +3957,7 @@ void A_BossDeath(mobj_t *mo)
 	}
 
 bossjustdie:
-	if (LUAh_BossDeath(mo))
+	if (LUA_HookMobj(mo, Mobj_Hook(BossDeath)))
 		return;
 	else if (P_MobjWasRemoved(mo))
 		return;
diff --git a/src/p_inter.c b/src/p_inter.c
index 415c679e4922b1d55920bc5f937c39fee1927e30..2d5f2736ac0f507c2202bf6496cfa70f4441078e 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -365,7 +365,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 	if (special->flags & (MF_ENEMY|MF_BOSS) && special->flags2 & MF2_FRET)
 		return;
 
-	if (LUAh_TouchSpecial(special, toucher) || P_MobjWasRemoved(special))
+	if (LUA_HookTouchSpecial(special, toucher) || P_MobjWasRemoved(special))
 		return;
 
 	// 0 = none, 1 = elemental pierce, 2 = bubble bounce
@@ -1935,7 +1935,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 	if (!netgame)
 		return; // Presumably it's obvious what's happening in splitscreen.
 
-	if (LUAh_HurtMsg(player, inflictor, source, damagetype))
+	if (LUA_HookHurtMsg(player, inflictor, source, damagetype))
 		return;
 
 	deadtarget = (player->mo->health <= 0);
@@ -2409,7 +2409,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	target->flags2 &= ~(MF2_SKULLFLY|MF2_NIGHTSPULL);
 	target->health = 0; // This makes it easy to check if something's dead elsewhere.
 
-	if (LUAh_MobjDeath(target, inflictor, source, damagetype) || P_MobjWasRemoved(target))
+	if (LUA_HookMobjDeath(target, inflictor, source, damagetype) || P_MobjWasRemoved(target))
 		return;
 
 	// Let EVERYONE know what happened to a player! 01-29-2002 Tails
@@ -3544,7 +3544,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 	// Everything above here can't be forced.
 	if (!metalrecording)
 	{
-		UINT8 shouldForce = LUAh_ShouldDamage(target, inflictor, source, damage, damagetype);
+		UINT8 shouldForce = LUA_HookShouldDamage(target, inflictor, source, damage, damagetype);
 		if (P_MobjWasRemoved(target))
 			return (shouldForce == 1); // mobj was removed
 		if (shouldForce == 1)
@@ -3585,7 +3585,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		if (!force && target->flags2 & MF2_FRET) // Currently flashing from being hit
 			return false;
 
-		if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target))
+		if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target))
 			return true;
 
 		if (target->health > 1)
@@ -3635,7 +3635,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 				|| (G_GametypeHasTeams() && player->ctfteam == source->player->ctfteam)))
 					return false; // Don't run eachother over in special stages and team games and such
 			}
-			if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
+			if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype))
 				return true;
 			P_NiGHTSDamage(target, source); // -5s :(
 			return true;
@@ -3689,13 +3689,13 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			if (force
 			|| (inflictor && inflictor->flags & MF_MISSILE && inflictor->flags2 & MF2_SUPERFIRE)) // Super Sonic is stunned!
 			{
-				if (!LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
+				if (!LUA_HookMobjDamage(target, inflictor, source, damage, damagetype))
 					P_SuperDamage(player, inflictor, source, damage);
 				return true;
 			}
 			return false;
 		}
-		else if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
+		else if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype))
 			return true;
 		else if (player->powers[pw_shield] || (player->bot && !ultimatemode))  //If One-Hit Shield
 		{
diff --git a/src/p_map.c b/src/p_map.c
index 922c0d9ec06c8a7f6c74d73b209ab961cc8ec571..8c548ed3cc05adafa32ad626495413284bde301c 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -747,7 +747,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			return true; // underneath
 
 		// REX HAS SEEN YOU
-		if (!LUAh_SeenPlayer(tmthing->target->player, thing->player))
+		if (!LUA_HookSeenPlayer(tmthing->target->player, thing->player))
 			return false;
 
 		seenplayer = thing->player;
@@ -937,7 +937,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	}
 
 	{
-		UINT8 shouldCollide = LUAh_MobjCollide(thing, tmthing); // checks hook for thing's type
+		UINT8 shouldCollide = LUA_Hook2Mobj(thing, tmthing, Mobj_Hook(MobjCollide)); // checks hook for thing's type
 		if (P_MobjWasRemoved(tmthing) || P_MobjWasRemoved(thing))
 			return true; // one of them was removed???
 		if (shouldCollide == 1)
@@ -945,7 +945,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		else if (shouldCollide == 2)
 			return true; // force no collide
 
-		shouldCollide = LUAh_MobjMoveCollide(tmthing, thing); // checks hook for tmthing's type
+		shouldCollide = LUA_Hook2Mobj(tmthing, thing, Mobj_Hook(MobjMoveCollide)); // checks hook for tmthing's type
 		if (P_MobjWasRemoved(tmthing) || P_MobjWasRemoved(thing))
 			return true; // one of them was removed???
 		if (shouldCollide == 1)
@@ -1927,7 +1927,7 @@ static boolean PIT_CheckLine(line_t *ld)
 	blockingline = ld;
 
 	{
-		UINT8 shouldCollide = LUAh_MobjLineCollide(tmthing, blockingline); // checks hook for thing's type
+		UINT8 shouldCollide = LUA_HookMobjLineCollide(tmthing, blockingline); // checks hook for thing's type
 		if (P_MobjWasRemoved(tmthing))
 			return true; // one of them was removed???
 		if (shouldCollide == 1)
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 7ba6d1fad979ff68c563f00693c1ba94b5fbdf93..5523fa273704ffe3a34e7766a62c0a8d7c4ded99 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1844,7 +1844,7 @@ void P_XYMovement(mobj_t *mo)
 				B_MoveBlocked(player);
 		}
 
-		if (LUAh_MobjMoveBlocked(mo))
+		if (LUA_HookMobj(mo, Mobj_Hook(MobjMoveBlocked)))
 		{
 			if (P_MobjWasRemoved(mo))
 				return;
@@ -7513,7 +7513,7 @@ static void P_RosySceneryThink(mobj_t *mobj)
 
 static void P_MobjSceneryThink(mobj_t *mobj)
 {
-	if (LUAh_MobjThinker(mobj))
+	if (LUA_HookMobj(mobj, Mobj_Hook(MobjThinker)))
 		return;
 	if (P_MobjWasRemoved(mobj))
 		return;
@@ -7861,7 +7861,7 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 
 		if (!mobj->fuse)
 		{
-			if (!LUAh_MobjFuse(mobj))
+			if (!LUA_HookMobj(mobj, Mobj_Hook(MobjFuse)))
 				P_RemoveMobj(mobj);
 			return;
 		}
@@ -7920,7 +7920,7 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 			mobj->fuse--;
 			if (!mobj->fuse)
 			{
-				if (!LUAh_MobjFuse(mobj))
+				if (!LUA_HookMobj(mobj, Mobj_Hook(MobjFuse)))
 					P_RemoveMobj(mobj);
 				return;
 			}
@@ -7949,7 +7949,7 @@ static boolean P_MobjPushableThink(mobj_t *mobj)
 
 static boolean P_MobjBossThink(mobj_t *mobj)
 {
-	if (LUAh_BossThinker(mobj))
+	if (LUA_HookMobj(mobj, Mobj_Hook(BossThinker)))
 	{
 		if (P_MobjWasRemoved(mobj))
 			return false;
@@ -9870,7 +9870,7 @@ static boolean P_FuseThink(mobj_t *mobj)
 	if (mobj->fuse)
 		return true;
 
-	if (LUAh_MobjFuse(mobj) || P_MobjWasRemoved(mobj))
+	if (LUA_HookMobj(mobj, Mobj_Hook(MobjFuse)) || P_MobjWasRemoved(mobj))
 		;
 	else if (mobj->info->flags & MF_MONITOR)
 	{
@@ -10046,13 +10046,13 @@ void P_MobjThinker(mobj_t *mobj)
 	// Check for a Lua thinker first
 	if (!mobj->player)
 	{
-		if (LUAh_MobjThinker(mobj) || P_MobjWasRemoved(mobj))
+		if (LUA_HookMobj(mobj, Mobj_Hook(MobjThinker)) || P_MobjWasRemoved(mobj))
 			return;
 	}
 	else if (!mobj->player->spectator)
 	{
 		// You cannot short-circuit the player thinker like you can other thinkers.
-		LUAh_MobjThinker(mobj);
+		LUA_HookMobj(mobj, Mobj_Hook(MobjThinker));
 		if (P_MobjWasRemoved(mobj))
 			return;
 	}
@@ -10523,7 +10523,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 
 	// DANGER! This can cause P_SpawnMobj to return NULL!
 	// Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks!
-	if (LUAh_MobjSpawn(mobj))
+	if (LUA_HookMobj(mobj, Mobj_Hook(MobjSpawn)))
 	{
 		if (P_MobjWasRemoved(mobj))
 			return NULL;
@@ -10910,7 +10910,7 @@ void P_RemoveMobj(mobj_t *mobj)
 		return; // something already removing this mobj.
 
 	mobj->thinker.function.acp1 = (actionf_p1)P_RemoveThinkerDelayed; // shh. no recursing.
-	LUAh_MobjRemoved(mobj);
+	LUA_HookMobj(mobj, Mobj_Hook(MobjRemoved));
 	mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; // needed for P_UnsetThingPosition, etc. to work.
 
 	// Rings only, please!
@@ -12556,7 +12556,7 @@ static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong)
 
 static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 {
-	boolean override = LUAh_MapThingSpawn(mobj, mthing);
+	boolean override = LUA_HookMapThingSpawn(mobj, mthing);
 
 	if (P_MobjWasRemoved(mobj))
 		return false;
diff --git a/src/p_setup.c b/src/p_setup.c
index 918ffbd4e6955348e4597963520245fd2ef353a6..01d43bd88264777116ee1a47291455fb362abd26 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -65,7 +65,7 @@
 
 #include "md5.h" // map MD5
 
-// for LUAh_MapLoad
+// for MapLoad hook
 #include "lua_script.h"
 #include "lua_hook.h"
 
@@ -4271,7 +4271,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 				G_CopyTiccmd(&players[i].cmd, &netcmds[buf][i], 1);
 		}
 		P_PreTicker(2);
-		LUAh_MapLoad();
+		LUA_HookInt(gamemap, Hook(MapLoad));
 	}
 
 	// No render mode or reloading gamestate, stop here.
diff --git a/src/p_spec.c b/src/p_spec.c
index a1afdd00ddb12c4c4253e4dfe4547a8e3391734d..59b9a06486fa9d2e654bd1b652622660972eeea8 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -35,7 +35,7 @@
 #include "v_video.h" // V_AUTOFADEOUT|V_ALLOWLOWERCASE
 #include "m_misc.h"
 #include "m_cond.h" //unlock triggers
-#include "lua_hook.h" // LUAh_LinedefExecute
+#include "lua_hook.h" // LUA_HookLinedefExecute
 #include "f_finale.h" // control text prompt
 #include "r_skins.h" // skins
 
@@ -3135,7 +3135,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 443: // Calls a named Lua function
 			if (line->stringargs[0])
-				LUAh_LinedefExecute(line, mo, callsec);
+				LUA_HookLinedefExecute(line, mo, callsec);
 			else
 				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in arg0str)\n", sizeu1(line-lines));
 			break;
diff --git a/src/p_tick.c b/src/p_tick.c
index da2a980c480a54f22d2dc2c3ef2904e96c07a1e8..930223ab950fc0e95cfb6b1c76e8a73a63990642 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -656,7 +656,7 @@ void P_Ticker(boolean run)
 		ps_lua_mobjhooks = 0;
 		ps_checkposition_calls = 0;
 
-		LUAh_PreThinkFrame();
+		LUA_Hook(PreThinkFrame);
 
 		ps_playerthink_time = I_GetTimeMicros();
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -687,7 +687,7 @@ void P_Ticker(boolean run)
 				P_PlayerAfterThink(&players[i]);
 
 		ps_lua_thinkframe_time = I_GetTimeMicros();
-		LUAh_ThinkFrame();
+		LUA_HookThinkFrame();
 		ps_lua_thinkframe_time = I_GetTimeMicros() - ps_lua_thinkframe_time;
 	}
 
@@ -760,7 +760,7 @@ void P_Ticker(boolean run)
 		if (modeattacking)
 			G_GhostTicker();
 
-		LUAh_PostThinkFrame();
+		LUA_Hook(PostThinkFrame);
 	}
 
 	P_MapEnd();
@@ -783,7 +783,7 @@ void P_PreTicker(INT32 frames)
 	{
 		P_MapStart();
 
-		LUAh_PreThinkFrame();
+		LUA_Hook(PreThinkFrame);
 
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
@@ -810,7 +810,7 @@ void P_PreTicker(INT32 frames)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
 				P_PlayerAfterThink(&players[i]);
 
-		LUAh_ThinkFrame();
+		LUA_HookThinkFrame();
 
 		// Run shield positioning
 		P_RunShields();
@@ -819,7 +819,7 @@ void P_PreTicker(INT32 frames)
 		P_UpdateSpecials();
 		P_RespawnSpecials();
 
-		LUAh_PostThinkFrame();
+		LUA_Hook(PostThinkFrame);
 
 		P_MapEnd();
 	}
diff --git a/src/p_user.c b/src/p_user.c
index c5f919c78ec4f6ee73f8535f730ef7883e549cc3..178a26126e54f68980e1294f71c6985d3408906d 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1111,7 +1111,7 @@ boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
 		return false;
 
 	{
-		UINT8 shouldCollide = LUAh_PlayerCanDamage(player, thing);
+		UINT8 shouldCollide = LUA_HookPlayerCanDamage(player, thing);
 		if (P_MobjWasRemoved(thing))
 			return false; // removed???
 		if (shouldCollide == 1)
@@ -1594,7 +1594,7 @@ boolean P_EvaluateMusicStatus(UINT16 status, const char *musname)
 				break;
 
 			case JT_OTHER:  // Other state
-				result = LUAh_ShouldJingleContinue(&players[i], musname);
+				result = LUA_HookShouldJingleContinue(&players[i], musname);
 				break;
 
 			case JT_NONE:   // Null state
@@ -1860,7 +1860,7 @@ void P_SpawnShieldOrb(player_t *player)
 		I_Error("P_SpawnShieldOrb: player->mo is NULL!\n");
 #endif
 
-	if (LUAh_ShieldSpawn(player))
+	if (LUA_HookPlayer(player, Hook(ShieldSpawn)))
 		return;
 
 	if (player->powers[pw_shield] & SH_FORCE)
@@ -4577,7 +4577,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 
 	if (cmd->buttons & BT_SPIN)
 	{
-		if (LUAh_SpinSpecial(player))
+		if (LUA_HookPlayer(player, Hook(SpinSpecial)))
 			return;
 	}
 
@@ -5043,7 +5043,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 				}
 			}
 		}
-		if (cmd->buttons & BT_SPIN && !LUAh_ShieldSpecial(player)) // Spin button effects
+		if (cmd->buttons & BT_SPIN && !LUA_HookPlayer(player, Hook(ShieldSpecial))) // Spin button effects
 		{
 			// Force stop
 			if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
@@ -5167,7 +5167,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				// and you don't have a shield, do it!
 				P_DoSuperTransformation(player, false);
 			}
-			else if (!LUAh_JumpSpinSpecial(player))
+			else if (!LUA_HookPlayer(player, Hook(JumpSpinSpecial)))
 				switch (player->charability)
 				{
 					case CA_THOK:
@@ -5240,7 +5240,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 	if (cmd->buttons & BT_JUMP && !player->exiting && !P_PlayerInPain(player))
 	{
-		if (LUAh_JumpSpecial(player))
+		if (LUA_HookPlayer(player, Hook(JumpSpecial)))
 			;
 		// all situations below this require jump button not to be pressed already
 		else if (player->pflags & PF_JUMPDOWN)
@@ -5275,7 +5275,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		}*/
 		else if (player->pflags & PF_JUMPED)
 		{
-			if (!LUAh_AbilitySpecial(player))
+			if (!LUA_HookPlayer(player, Hook(AbilitySpecial)))
 			switch (player->charability)
 			{
 				case CA_THOK:
@@ -5468,7 +5468,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		}
 		else if (player->pflags & PF_THOKKED)
 		{
-			if (!LUAh_AbilitySpecial(player))
+			if (!LUA_HookPlayer(player, Hook(AbilitySpecial)))
 				switch (player->charability)
 				{
 					case CA_FLY:
@@ -10490,7 +10490,7 @@ boolean P_SpectatorJoinGame(player_t *player)
 		else
 			changeto = (P_RandomFixed() & 1) + 1;
 
-		if (!LUAh_TeamSwitch(player, changeto, true, false, false))
+		if (!LUA_HookTeamSwitch(player, changeto, true, false, false))
 			return false;
 
 		if (player->mo)
@@ -10507,7 +10507,7 @@ boolean P_SpectatorJoinGame(player_t *player)
 		{
 			// Call ViewpointSwitch hooks here.
 			// The viewpoint was forcibly changed.
-			LUAh_ViewpointSwitch(player, &players[consoleplayer], true);
+			LUA_HookViewpointSwitch(player, &players[consoleplayer], true);
 			displayplayer = consoleplayer;
 		}
 
@@ -10525,7 +10525,7 @@ boolean P_SpectatorJoinGame(player_t *player)
 		// respawn in place and sit there for the rest of the round.
 		if (!((gametyperules & GTR_HIDEFROZEN) && leveltime > (hidetime * TICRATE)))
 		{
-			if (!LUAh_TeamSwitch(player, 3, true, false, false))
+			if (!LUA_HookTeamSwitch(player, 3, true, false, false))
 				return false;
 			if (player->mo)
 			{
@@ -10552,7 +10552,7 @@ boolean P_SpectatorJoinGame(player_t *player)
 			{
 				// Call ViewpointSwitch hooks here.
 				// The viewpoint was forcibly changed.
-				LUAh_ViewpointSwitch(player, &players[consoleplayer], true);
+				LUA_HookViewpointSwitch(player, &players[consoleplayer], true);
 				displayplayer = consoleplayer;
 			}
 
@@ -11477,7 +11477,7 @@ void P_PlayerThink(player_t *player)
 		}
 		if (player->playerstate == PST_REBORN)
 		{
-			LUAh_PlayerThink(player);
+			LUA_HookPlayer(player, Hook(PlayerThink));
 			return;
 		}
 	}
@@ -11581,7 +11581,7 @@ void P_PlayerThink(player_t *player)
 
 			if (player->playerstate == PST_DEAD)
 			{
-				LUAh_PlayerThink(player);
+				LUA_HookPlayer(player, Hook(PlayerThink));
 				return;
 			}
 		}
@@ -11702,7 +11702,7 @@ void P_PlayerThink(player_t *player)
 	{
 		player->mo->flags2 &= ~MF2_SHADOW;
 		P_DeathThink(player);
-		LUAh_PlayerThink(player);
+		LUA_HookPlayer(player, Hook(PlayerThink));
 		return;
 	}
 
@@ -11744,7 +11744,7 @@ void P_PlayerThink(player_t *player)
 	{
 		if (P_SpectatorJoinGame(player))
 		{
-			LUAh_PlayerThink(player);
+			LUA_HookPlayer(player, Hook(PlayerThink));
 			return; // player->mo was removed.
 		}
 	}
@@ -11849,7 +11849,7 @@ void P_PlayerThink(player_t *player)
 
 	if (!player->mo)
 	{
-		LUAh_PlayerThink(player);
+		LUA_HookPlayer(player, Hook(PlayerThink));
 		return; // P_MovePlayer removed player->mo.
 	}
 
@@ -12303,7 +12303,7 @@ void P_PlayerThink(player_t *player)
 	}
 #undef dashmode
 
-	LUAh_PlayerThink(player);
+	LUA_HookPlayer(player, Hook(PlayerThink));
 
 /*
 //	Colormap verification
@@ -12863,7 +12863,7 @@ void P_PlayerAfterThink(player_t *player)
 
 		if (player->followmobj)
 		{
-			if (LUAh_FollowMobj(player, player->followmobj) || P_MobjWasRemoved(player->followmobj))
+			if (LUA_HookFollowMobj(player, player->followmobj) || P_MobjWasRemoved(player->followmobj))
 				{;}
 			else
 			{
diff --git a/src/s_sound.c b/src/s_sound.c
index 36bd454c104b02867c29d3f16f22f639ed06dc97..106a5bd237f63c1bec2889cdceb3296eaac52b9e 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -2262,6 +2262,16 @@ static void S_ChangeMusicToQueue(void)
 void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms)
 {
 	char newmusic[7];
+
+	struct MusicChange hook_param = {
+		newmusic,
+		&mflags,
+		&looping,
+		&position,
+		&prefadems,
+		&fadeinms
+	};
+
 	boolean currentmidi = (I_SongType() == MU_MID || I_SongType() == MU_MID_EX);
 	boolean midipref = cv_musicpref.value;
 
@@ -2269,7 +2279,7 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 		return;
 
 	strncpy(newmusic, mmusic, 7);
-	if (LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms))
+	if (LUA_HookMusicChange(music_name, &hook_param))
 		return;
 	newmusic[6] = 0;
 
diff --git a/src/s_sound.h b/src/s_sound.h
index 4ac3c70bf0d4a76f759e166ee0db3c682894ee5f..c872c22247c24717519f5e98b527825b32cc4478 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -265,6 +265,16 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst);
 // Music Playback
 //
 
+/* this is for the sake of the hook */
+struct MusicChange {
+	char    * newname;
+	UINT16  * mflags;
+	boolean * looping;
+	UINT32  * position;
+	UINT32  * prefadems;
+	UINT32  * fadeinms;
+};
+
 // Start music track, arbitrary, given its name, and set whether looping
 // note: music flags 12 bits for tracknum (gme, other formats with more than one track)
 //       13-15 aren't used yet
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index b8b3b9d34e8ae97648d3f1f6178511c251b9253e..8ca4f758a52d3292dbdabbd8da0afb4b94a7a570 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1058,7 +1058,7 @@ void I_GetEvent(void)
 			 	break;
 			case SDL_QUIT:
 				if (Playing())
-					LUAh_GameQuit();
+					LUA_Hook(GameQuit);
 				I_Quit();
 				break;
 		}
diff --git a/src/y_inter.c b/src/y_inter.c
index 061cbb5e1d45ed5659593b3f88944bba74e82e4d..ae4dcdaf00fb3ab850424ffe4dab0dd8fa1b09cb 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -927,7 +927,7 @@ void Y_Ticker(void)
 	if (paused || P_AutoPause())
 		return;
 
-	LUAh_IntermissionThinker();
+	LUA_Hook(IntermissionThinker);
 
 	intertic++;