diff --git a/src/f_finale.c b/src/f_finale.c
index e8757c18adcaad692faac6d83542eccd00650a10..401ab45b13813899534074a809cdc92817f90715 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -41,6 +41,7 @@
 #include "console.h"
 
 #include "lua_hud.h"
+#include "lua_hook.h"
 
 // Stage of animation:
 // 0 = text, 1 = art screen
@@ -3423,7 +3424,7 @@ void F_TitleScreenDrawer(void)
 	}
 
 luahook:
-	LUAh_TitleHUD();
+	LUA_HUDHOOK(title);
 }
 
 // separate animation timer for backgrounds, since we also count
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index e0eaf8fb178f1252b60b93e329d2f14d05858e53..be215ff2135e9482b743cee1336809f7be5aaed0 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2104,7 +2104,7 @@ void HU_Drawer(void)
 		}
 		else
 			HU_DrawCoopOverlay();
-		LUAh_ScoresHUD();
+		LUA_HUDHOOK(scores);
 	}
 
 	if (gamestate != GS_LEVEL)
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 1af1697a7b9f937b449fe98777f572dcb342048b..1c01d1d6656622a2d703641747eb9c71865eaa23 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -78,6 +78,13 @@ automatically.
 	X (LinedefExecute),\
 	X (ShouldJingleContinue),/* should jingle of the given music continue playing */\
 
+#define HUD_HOOK_LIST(X) \
+	X (game),\
+	X (scores),/* emblems/multiplayer list */\
+	X (title),/* titlescreen */\
+	X (titlecard),\
+	X (intermission),\
+
 /*
 I chose to access the hook enums through a macro as well. This could provide
 a hint to lookup the macro's definition instead of the enum's definition.
@@ -88,22 +95,26 @@ grepped and found in the lists above.
 
 #define   MOBJ_HOOK(name)   mobjhook_ ## name
 #define        HOOK(name)       hook_ ## name
+#define    HUD_HOOK(name)    hudhook_ ## name
 #define STRING_HOOK(name) stringhook_ ## name
 
 #define ENUM(X) enum { X ## _LIST (X)  X(MAX) }
 
 ENUM   (MOBJ_HOOK);
 ENUM        (HOOK);
+ENUM    (HUD_HOOK);
 ENUM (STRING_HOOK);
 
 #undef ENUM
 
 /* dead simple, LUA_HOOK(GameQuit) */
 #define LUA_HOOK(type) LUA_HookVoid(HOOK(type))
+#define LUA_HUDHOOK(type) LUA_HookHUD(HUD_HOOK(type))
 
 extern boolean hook_cmd_running;
 
 void LUA_HookVoid(int hook);
+void LUA_HookHUD(int hook);
 
 int  LUA_HookMobj(mobj_t *, int hook);
 int  LUA_Hook2Mobj(mobj_t *, mobj_t *, int hook);
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 5815f17b327a22472b63af26f10cd055f380dfa8..96cb04543c43bd5740778230325c587bd4342cd3 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -36,6 +36,7 @@
 
 LIST   (mobjHookNames,   MOBJ_HOOK_LIST);
 LIST       (hookNames,        HOOK_LIST);
+LIST    (hudHookNames,    HUD_HOOK_LIST);
 LIST (stringHookNames, STRING_HOOK_LIST);
 
 #undef LIST
@@ -51,6 +52,7 @@ typedef struct {
 } stringhook_t;
 
 static hook_t hookIds[HOOK(MAX)];
+static hook_t hudHookIds[HUD_HOOK(MAX)];
 static hook_t mobjHookIds[NUMMOBJTYPES][MOBJ_HOOK(MAX)];
 
 // Lua tables are used to lookup string hook ids.
@@ -171,6 +173,12 @@ static void add_mobj_hook(lua_State *L, int hook_type)
 	add_hook(&mobjHookIds[mobj_type][hook_type]);
 }
 
+static void add_hud_hook(lua_State *L, int idx)
+{
+	add_hook(&hudHookIds[luaL_checkoption(L,
+				idx, "game", hudHookNames)]);
+}
+
 static void add_hook_ref(lua_State *L, int idx)
 {
 	if (!(nextid & 7))
@@ -213,6 +221,10 @@ static int lib_addHook(lua_State *L)
 	{
 		add_hook(&hookIds[type]);
 	}
+	else if (strcmp(name, "HUD") == 0)
+	{
+		add_hud_hook(L, 3);
+	}
 	else
 	{
 		return luaL_argerror(L, 1, lua_pushfstring(L, "invalid hook " LUA_QS, name));
@@ -233,6 +245,23 @@ int LUA_HookLib(lua_State *L)
 	return 0;
 }
 
+/* TODO: remove in next backwards incompatible release */
+#if MODID == 18
+int lib_hudadd(lua_State *L);/* yeah compiler */
+int lib_hudadd(lua_State *L)
+{
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+
+	luaL_checktype(L, 1, LUA_TFUNCTION);
+
+	add_hud_hook(L, 2);
+	add_hook_ref(L, 1);
+
+	return 0;
+}
+#endif
+
 typedef struct Hook_State Hook_State;
 typedef void (*Hook_Callback)(Hook_State *);
 
@@ -610,6 +639,24 @@ int LUA_HookKey(INT32 keycode, int hook_type)
 	return hook.status;
 }
 
+void LUA_HookHUD(int hook_type)
+{
+	const hook_t * map = &hudHookIds[hook_type];
+	Hook_State hook;
+	if (map->numHooks > 0)
+	{
+		start_hook_stack();
+		begin_hook_values(&hook);
+
+		LUA_SetHudHook(hook_type);
+
+		hud_running = true; // local hook
+		init_hook_call(&hook, 0, res_none);
+		call_mapped(&hook, map);
+		hud_running = false;
+	}
+}
+
 /* =========================================================================
                                SPECIALIZED HOOKS
    ========================================================================= */
diff --git a/src/lua_hud.h b/src/lua_hud.h
index d2f5bceca6f0d56745d2a40c3826ea1e594b9718..c1d2d164b97d6952612e088d80f0bd256a117d4b 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -47,8 +47,4 @@ extern boolean hud_running;
 
 boolean LUA_HudEnabled(enum hud option);
 
-void LUAh_GameHUD(player_t *stplyr);
-void LUAh_ScoresHUD(void);
-void LUAh_TitleHUD(void);
-void LUAh_TitleCardHUD(player_t *stplayr);
-void LUAh_IntermissionHUD(boolean failedstage);
+void LUA_SetHudHook(int hook);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 9a3e676d5fd6168fd64bfaaa44466a5e85e8949c..b60cdcde09e3857680a982d78478e71739afef82 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -23,18 +23,18 @@
 #include "v_video.h"
 #include "w_wad.h"
 #include "z_zone.h"
+#include "y_inter.h"
 
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h"
+#include "lua_hook.h"
 
 #define HUDONLY if (!hud_running) return luaL_error(L, "HUD rendering code should not be called outside of rendering hooks!");
 
 boolean hud_running = false;
 static UINT8 hud_enabled[(hud_MAX/8)+1];
 
-static UINT8 hudAvailable; // hud hooks field
-
 // must match enum hud in lua_hud.h
 static const char *const hud_disable_options[] = {
 	"stagetitle",
@@ -95,21 +95,6 @@ static const char *const patch_opt[] = {
 	"topoffset",
 	NULL};
 
-enum hudhook {
-	hudhook_game = 0,
-	hudhook_scores,
-	hudhook_intermission,
-	hudhook_title,
-	hudhook_titlecard
-};
-static const char *const hudhook_opt[] = {
-	"game",
-	"scores",
-	"intermission",
-	"title",
-	"titlecard",
-	NULL};
-
 // alignment types for v.drawString
 enum align {
 	align_left = 0,
@@ -1152,6 +1137,8 @@ static luaL_Reg lib_draw[] = {
 	{NULL, NULL}
 };
 
+static int lib_draw_ref;
+
 //
 // lib_hud
 //
@@ -1186,28 +1173,7 @@ static int lib_hudenabled(lua_State *L)
 
 
 // add a HUD element for rendering
-static int lib_hudadd(lua_State *L)
-{
-	enum hudhook field;
-
-	luaL_checktype(L, 1, LUA_TFUNCTION);
-	field = luaL_checkoption(L, 2, "game", hudhook_opt);
-
-	if (!lua_lumploading)
-		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
-
-	lua_getfield(L, LUA_REGISTRYINDEX, "HUD");
-	I_Assert(lua_istable(L, -1));
-	lua_rawgeti(L, -1, field+2); // HUD[2+]
-	I_Assert(lua_istable(L, -1));
-	lua_remove(L, -2);
-
-	lua_pushvalue(L, 1);
-	lua_rawseti(L, -2, (int)(lua_objlen(L, -2) + 1));
-
-	hudAvailable |= 1<<field;
-	return 0;
-}
+extern int lib_hudadd(lua_State *L);
 
 static luaL_Reg lib_hud[] = {
 	{"enable", lib_hudenable},
@@ -1225,26 +1191,9 @@ int LUA_HudLib(lua_State *L)
 {
 	memset(hud_enabled, 0xff, (hud_MAX/8)+1);
 
-	lua_newtable(L); // HUD registry table
-		lua_newtable(L);
-		luaL_register(L, NULL, lib_draw);
-		lua_rawseti(L, -2, 1); // HUD[1] = lib_draw
-
-		lua_newtable(L);
-		lua_rawseti(L, -2, 2); // HUD[2] = game rendering functions array
-
-		lua_newtable(L);
-		lua_rawseti(L, -2, 3); // HUD[3] = scores rendering functions array
-
-		lua_newtable(L);
-		lua_rawseti(L, -2, 4); // HUD[4] = intermission rendering functions array
-
-		lua_newtable(L);
-		lua_rawseti(L, -2, 5); // HUD[5] = title rendering functions array
-
-		lua_newtable(L);
-		lua_rawseti(L, -2, 6); // HUD[6] = title card rendering functions array
-	lua_setfield(L, LUA_REGISTRYINDEX, "HUD");
+	lua_newtable(L);
+	luaL_register(L, NULL, lib_draw);
+	lib_draw_ref = luaL_ref(L, LUA_REGISTRYINDEX);
 
 	luaL_newmetatable(L, META_HUDINFO);
 		lua_pushcfunction(L, hudinfo_get);
@@ -1296,160 +1245,29 @@ boolean LUA_HudEnabled(enum hud option)
 	return false;
 }
 
-// Hook for HUD rendering
-void LUAh_GameHUD(player_t *stplayr)
+void LUA_SetHudHook(int hook)
 {
-	if (!gL || !(hudAvailable & (1<<hudhook_game)))
-		return;
+	lua_getref(gL, lib_draw_ref);
 
-	hud_running = true;
-	lua_settop(gL, 0);
-
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 2+hudhook_game); // HUD[2] = rendering funcs
-	I_Assert(lua_istable(gL, -1));
-
-	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
-	I_Assert(lua_istable(gL, -1));
-	lua_remove(gL, -3); // pop HUD
-	LUA_PushUserdata(gL, stplayr, META_PLAYER);
-
-	if (splitscreen && stplayr == &players[secondarydisplayplayer])
-		LUA_PushUserdata(gL, &camera2, META_CAMERA);
-	else
-		LUA_PushUserdata(gL, &camera, META_CAMERA);
-
-	lua_pushnil(gL);
-	while (lua_next(gL, -5) != 0) {
-		lua_pushvalue(gL, -5); // graphics library (HUD[1])
-		lua_pushvalue(gL, -5); // stplayr
-		lua_pushvalue(gL, -5); // camera
-		LUA_Call(gL, 3, 0, 1);
-	}
-	lua_settop(gL, 0);
-	hud_running = false;
-}
-
-void LUAh_ScoresHUD(void)
-{
-	if (!gL || !(hudAvailable & (1<<hudhook_scores)))
-		return;
-
-	hud_running = true;
-	lua_settop(gL, 0);
-
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 2+hudhook_scores); // HUD[3] = rendering funcs
-	I_Assert(lua_istable(gL, -1));
-
-	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
-	I_Assert(lua_istable(gL, -1));
-	lua_remove(gL, -3); // pop HUD
-	lua_pushnil(gL);
-	while (lua_next(gL, -3) != 0) {
-		lua_pushvalue(gL, -3); // graphics library (HUD[1])
-		LUA_Call(gL, 1, 0, 1);
-	}
-	lua_settop(gL, 0);
-	hud_running = false;
-}
-
-void LUAh_TitleHUD(void)
-{
-	if (!gL || !(hudAvailable & (1<<hudhook_title)))
-		return;
-
-	hud_running = true;
-	lua_settop(gL, 0);
-
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 2+hudhook_title); // HUD[5] = rendering funcs
-	I_Assert(lua_istable(gL, -1));
-
-	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
-	I_Assert(lua_istable(gL, -1));
-	lua_remove(gL, -3); // pop HUD
-	lua_pushnil(gL);
-	while (lua_next(gL, -3) != 0) {
-		lua_pushvalue(gL, -3); // graphics library (HUD[1])
-		LUA_Call(gL, 1, 0, 1);
-	}
-	lua_settop(gL, 0);
-	hud_running = false;
-}
-
-void LUAh_TitleCardHUD(player_t *stplayr)
-{
-	if (!gL || !(hudAvailable & (1<<hudhook_titlecard)))
-		return;
-
-	hud_running = true;
-	lua_settop(gL, 0);
-
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 2+hudhook_titlecard); // HUD[6] = rendering funcs
-	I_Assert(lua_istable(gL, -1));
-
-	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
-	I_Assert(lua_istable(gL, -1));
-	lua_remove(gL, -3); // pop HUD
-
-	LUA_PushUserdata(gL, stplayr, META_PLAYER);
-	lua_pushinteger(gL, lt_ticker);
-	lua_pushinteger(gL, (lt_endtime + TICRATE));
-	lua_pushnil(gL);
-
-	while (lua_next(gL, -6) != 0) {
-		lua_pushvalue(gL, -6); // graphics library (HUD[1])
-		lua_pushvalue(gL, -6); // stplayr
-		lua_pushvalue(gL, -6); // lt_ticker
-		lua_pushvalue(gL, -6); // lt_endtime
-		LUA_Call(gL, 4, 0, 1);
-	}
-
-	lua_settop(gL, 0);
-	hud_running = false;
-}
-
-void LUAh_IntermissionHUD(boolean failedstage)
-{
-	if (!gL || !(hudAvailable & (1<<hudhook_intermission)))
-		return;
-
-	hud_running = true;
-	lua_settop(gL, 0);
-
-	lua_pushcfunction(gL, LUA_GetErrorMessage);
-
-	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
-	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 2+hudhook_intermission); // HUD[4] = rendering funcs
-	I_Assert(lua_istable(gL, -1));
-
-	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
-	I_Assert(lua_istable(gL, -1));
-	lua_remove(gL, -3); // pop HUD
-
-	lua_pushboolean(gL, failedstage); // stagefailed
-	lua_pushnil(gL);
-
-	while (lua_next(gL, -4) != 0) {
-		lua_pushvalue(gL, -4); // graphics library (HUD[1])
-		lua_pushvalue(gL, -4); // stagefailed
-		LUA_Call(gL, 2, 0, 1);
+	switch (hook)
+	{
+		case HUD_HOOK(game): {
+			camera_t *cam = (splitscreen && stplyr ==
+					&players[secondarydisplayplayer])
+				? &camera2 : &camera;
+
+			LUA_PushUserdata(gL, stplyr, META_PLAYER);
+			LUA_PushUserdata(gL, cam, META_CAMERA);
+		}	break;
+
+		case HUD_HOOK(titlecard):
+			LUA_PushUserdata(gL, stplyr, META_PLAYER);
+			lua_pushinteger(gL, lt_ticker);
+			lua_pushinteger(gL, (lt_endtime + TICRATE));
+			break;
+
+		case HUD_HOOK(intermission):
+			lua_pushboolean(gL, intertype == int_spec &&
+					stagefailed);
 	}
-	lua_settop(gL, 0);
-	hud_running = false;
 }
diff --git a/src/st_stuff.c b/src/st_stuff.c
index af14118e36cb87bb0475982345bba8bd1943306f..784666ad49e1db0492e4fa98d72dbba739893574 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -43,6 +43,7 @@
 #endif
 
 #include "lua_hud.h"
+#include "lua_hook.h"
 
 UINT16 objectsdrawn = 0;
 
@@ -1391,7 +1392,7 @@ void ST_drawTitleCard(void)
 	lt_lasttic = lt_ticker;
 
 luahook:
-	LUAh_TitleCardHUD(stplyr);
+	LUA_HUDHOOK(titlecard);
 }
 
 //
@@ -2732,7 +2733,7 @@ static void ST_overlayDrawer(void)
 		ST_drawPowerupHUD(); // same as it ever was...
 
 	if (!(netgame || multiplayer) || !hu_showscores)
-		LUAh_GameHUD(stplyr);
+		LUA_HUDHOOK(game);
 
 	// draw level title Tails
 	if (stagetitle && (!WipeInAction) && (!WipeStageTitle))
diff --git a/src/y_inter.c b/src/y_inter.c
index 6d876d7bdbf1a5d846e74eed2d34a284dfe03d22..b6528e0e6bfd5462a6bdba75cf2c968e1b5a843f 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -430,7 +430,7 @@ void Y_IntermissionDrawer(void)
 	else if (bgtile)
 		V_DrawPatchFill(bgtile);
 
-	LUAh_IntermissionHUD(intertype == int_spec && stagefailed);
+	LUA_HUDHOOK(intermission);
 	if (!LUA_HudEnabled(hud_intermissiontally))
 		goto skiptallydrawer;