Skip to content
Snippets Groups Projects
Select Git revision
  • prefabs
  • macros
  • next default protected
  • fix-binary-mtag
  • binary-multitagging
  • final-hours
  • lua-minmax-plus-bruh-moments
  • release-candidate
  • load-netgame-error-fixes
  • unix-rdynamic
  • easings
  • ksf-wadfiles
  • raise-skin-limit
  • software-slope-plane-uv-fix-attempt
  • more-slope-copying
  • pantelegraph
  • master
  • prerelease
  • prerelease-serious
  • test-next
  • SRB2_release_2.2.8
  • SRB2_release_2.2.7
  • SRB2_release_2.2.6
  • SRB2_release_2.2.5
  • SRB2_release_2.2.4
  • SRB2_release_2.2.3
  • SRB2_release_2.2.2
  • SRB2_release_2.2.1
  • SRB2_release_2.2.0
  • SRB2_release_2.1.25
  • SRB2_release_2.1.24
  • SRB2_release_2.1.23
  • SRB2_release_2.1.22
  • SRB2_release_2.1.21
  • SRB2_release_2.1.20
  • SRB2_release_2.1.19
  • SRB2_release_2.1.18
  • td-release-v1.0.0
  • SRB2_release_2.1.17
  • SRB2_release_2.1.16a
40 results

lua_hooklib.c

Blame
  • Forked from STJr / SRB2
    16590 commits behind the upstream repository.
    user avatar
    RedEnchilada authored
    37d37804
    History
    lua_hooklib.c 22.62 KiB
    // SONIC ROBO BLAST 2
    //-----------------------------------------------------------------------------
    // Copyright (C) 2012-2014 by John "JTE" Muniz.
    // Copyright (C) 2012-2014 by Sonic Team Junior.
    //
    // This program is free software distributed under the
    // terms of the GNU General Public License, version 2.
    // See the 'LICENSE' file for more details.
    //-----------------------------------------------------------------------------
    /// \file  lua_hooklib.c
    /// \brief hooks for Lua scripting
    
    #include "doomdef.h"
    #ifdef HAVE_BLUA
    #include "doomstat.h"
    #include "p_mobj.h"
    #include "g_game.h"
    #include "r_things.h"
    #include "b_bot.h"
    #include "z_zone.h"
    
    #include "lua_script.h"
    #include "lua_libs.h"
    #include "lua_hook.h"
    #include "lua_hud.h" // hud_running errors
    
    static UINT8 hooksAvailable[(hook_MAX/8)+1];
    
    const char *const hookNames[hook_MAX+1] = {
    	"NetVars",
    	"MapChange",
    	"MapLoad",
    	"PlayerJoin",
    	"ThinkFrame",
    	"MobjSpawn",
    	"MobjCollide",
    	"MobjMoveCollide",
    	"TouchSpecial",
    	"MobjFuse",
    	"MobjThinker",
    	"BossThinker",
    	"ShouldDamage",
    	"MobjDamage",
    	"MobjDeath",
    	"BossDeath",
    	"MobjRemoved",
    	"BotTiccmd",
    	"BotAI",
    	"LinedefExecute",
    	"PlayerMsg",
    	NULL
    };
    
    // Takes hook, function, and additional arguments (mobj type to act on, etc.)
    static int lib_addHook(lua_State *L)
    {
    	UINT16 hook;
    	boolean notable = false;
    	boolean subtable = false;
    	UINT32 subindex = 0;
    	char *subfield = NULL;
    	const char *lsubfield = NULL;
    
    	hook = (UINT16)luaL_checkoption(L, 1, NULL, hookNames);
    	luaL_checktype(L, 2, LUA_TFUNCTION);
    
    	if (hud_running)
    		return luaL_error(L, "HUD rendering code should not call this function!");
    
    	switch(hook)
    	{
    	// Take a mobjtype enum which this hook is specifically for.
    	case hook_MobjSpawn:
    	case hook_MobjCollide:
    	case hook_MobjMoveCollide:
    	case hook_TouchSpecial:
    	case hook_MobjFuse:
    	case hook_MobjThinker:
    	case hook_BossThinker:
    	case hook_ShouldDamage:
    	case hook_MobjDamage:
    	case hook_MobjDeath:
    	case hook_BossDeath:
    	case hook_MobjRemoved:
    		subtable = true;
    		if (lua_isnumber(L, 3))
    			subindex = (UINT32)luaL_checkinteger(L, 3);
    		else
    			lsubfield = "a";
    		lua_settop(L, 2);
    		break;
    	case hook_BotAI: // Only one AI function per skin, please!
    		notable = true;
    		subtable = true;
    		subfield = ZZ_Alloc(strlen(luaL_checkstring(L, 3))+1);
    		{ // lowercase copy
    			char *p = subfield;
    			const char *s = luaL_checkstring(L, 3);
    			do {
    				*p = tolower(*s);
    				++p;
    			} while(*(++s));
    			*p = 0;
    		}
    		lua_settop(L, 3);
    		break;
    	case hook_LinedefExecute: // Get one linedef executor function by name
    		notable = true;
    		subtable = true;
    		subfield = ZZ_Alloc(strlen(luaL_checkstring(L, 3))+1);
    		{ // uppercase copy
    			char *p = subfield;
    			const char *s = luaL_checkstring(L, 3);
    			do {
    				*p = toupper(*s);
    				++p;
    			} while(*(++s));
    			*p = 0;
    		}
    		lua_settop(L, 3);
    		break;
    	default:
    		lua_settop(L, 2);
    		break;
    	}
    
    	lua_getfield(L, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(L, -1));
    
    	// This hook type only allows one entry, not an array of hooks.
    	// New hooks will overwrite the previous ones, and the stack is one table shorter.
    	if (notable)
    	{
    		if (subtable)
    		{
    			lua_rawgeti(L, -1, hook);
    			lua_remove(L, -2); // pop "hook"
    			I_Assert(lua_istable(L, -1));
    			lua_pushvalue(L, 2);
    			if (subfield)
    				lua_setfield(L, -2, subfield);
    			else if (lsubfield)
    				lua_setfield(L, -2, lsubfield);
    			else
    				lua_rawseti(L, -2, subindex);
    		} else {
    			lua_pushvalue(L, 2);
    			lua_rawseti(L, -2, hook);
    		}
    		hooksAvailable[hook/8] |= 1<<(hook%8);
    		return 0;
    	}
    
    	// Fetch the hook's table from the registry.
    	// It should always exist, since LUA_HookLib creates a table for every hook.
    	lua_rawgeti(L, -1, hook);
    	lua_remove(L, -2); // pop "hook"
    	I_Assert(lua_istable(L, -1));
    	if (subtable)
    	{
    		// Fetch a subtable based on index
    		if (subfield)
    			lua_getfield(L, -1, subfield);
    		else if (lsubfield)
    			lua_getfield(L, -1, lsubfield);
    		else
    			lua_rawgeti(L, -1, subindex);
    
    		// Subtable doesn't exist, make one now.
    		if (lua_isnil(L, -1))
    		{
    			lua_pop(L, 1);
    			lua_newtable(L);
    
    			// Store a link to the subtable for later.
    			lua_pushvalue(L, -1);
    			if (subfield)
    				lua_setfield(L, -3, subfield);
    			else if (lsubfield)
    				lua_setfield(L, -3, lsubfield);
    			else
    				lua_rawseti(L, -3, subindex);
    	}	}
    
    	// Add function to the table.
    	lua_pushvalue(L, 2);
    	lua_rawseti(L, -2, (int)(lua_objlen(L, -2) + 1));
    
    	if (subfield)
    		Z_Free(subfield);
    
    	hooksAvailable[hook/8] |= 1<<(hook%8);
    	return 0;
    }
    
    int LUA_HookLib(lua_State *L)
    {
    	// Create all registry tables
    	enum hook i;
    	memset(hooksAvailable,0,sizeof(UINT8[(hook_MAX/8)+1]));
    
    	lua_newtable(L);
    	for (i = 0; i < hook_MAX; i++)
    	{
    		lua_newtable(L);
    		switch(i)
    		{
    		default:
    			break;
    		case hook_MobjSpawn:
    		case hook_MobjCollide:
    		case hook_MobjMoveCollide:
    		case hook_TouchSpecial:
    		case hook_MobjFuse:
    		case hook_MobjThinker:
    		case hook_BossThinker:
    		case hook_ShouldDamage:
    		case hook_MobjDamage:
    		case hook_MobjDeath:
    		case hook_BossDeath:
    		case hook_MobjRemoved:
    			lua_pushstring(L, "a");
    			lua_newtable(L);
    			lua_rawset(L, -3);
    			break;
    		}
    		lua_rawseti(L, -2, i);
    	}
    	lua_setfield(L, LUA_REGISTRYINDEX, "hook");
    	lua_register(L, "addHook", lib_addHook);
    	return 0;
    }
    
    boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
    {
    	boolean hooked = false;
    	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
    		return false;
    
    	// clear the stack (just in case)
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, which);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// generic subtable
    	lua_pushstring(gL, "a");
    	lua_rawget(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	LUA_PushUserdata(gL, mo, META_MOBJ);
    	lua_pushnil(gL);
    	while (lua_next(gL, -3)) {
    		CONS_Debug(DBG_LUA, "MobjHook: Calling hook_%s for generic mobj types\n", hookNames[which]);
    		lua_pushvalue(gL, -3); // mo
    		// stack is: hook_Mobj table, subtable "a", mobj, i, function, mobj
    		if (lua_pcall(gL, 1, 1, 0)) {
    			// A run-time error occurred.
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			// Remove this function from the hook table to prevent further errors.
    			lua_pushvalue(gL, -1); // key
    			lua_pushnil(gL); // value
    			lua_rawset(gL, -5); // table
    			CONS_Printf("Hook removed.\n");
    		}
    		else
    		{
    			if (lua_toboolean(gL, -1))
    				hooked = true;
    			lua_pop(gL, 1);
    		}
    	}
    	// stack is: hook_Mobj table, subtable "a", mobj
    	lua_remove(gL, -2); // pop subtable, leave mobj
    
    	// mobjtype subtable
    	// stack is: hook_Mobj table, mobj
    	lua_rawgeti(gL, -2, mo->type);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 3); // pop hook_Mobj table, mobj, and nil
    		// the stack should now be empty.
    		return false;
    	}
    	lua_remove(gL, -3); // remove hook table
    	// stack is: mobj, mobjtype subtable
    	lua_insert(gL, lua_gettop(gL)-1); // swap subtable with mobj
    	// stack is: mobjtype subtable, mobj
    
    	lua_pushnil(gL);
    	while (lua_next(gL, -3)) {
    		CONS_Debug(DBG_LUA, "MobjHook: Calling hook_%s for mobj type %d\n", hookNames[which], mo->type);
    		lua_pushvalue(gL, -3); // mo
    		// stack is: mobjtype subtable, mobj, i, function, mobj
    		if (lua_pcall(gL, 1, 1, 0)) {
    			// A run-time error occurred.
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			// Remove this function from the hook table to prevent further errors.
    			lua_pushvalue(gL, -1); // key
    			lua_pushnil(gL); // value
    			lua_rawset(gL, -5); // table
    			CONS_Printf("Hook removed.\n");
    		}
    		else
    		{
    			if (lua_toboolean(gL, -1))
    				hooked = true;
    			lua_pop(gL, 1);
    		}
    	}
    
    	lua_pop(gL, 2); // pop mobj and subtable
    	// the stack should now be empty.
    
    	lua_gc(gL, LUA_GCSTEP, 3);
    	return hooked;
    }
    
    // Hook for map change (before load)
    void LUAh_MapChange(void)
    {
    	if (!gL || !(hooksAvailable[hook_MapChange/8] & (1<<(hook_MapChange%8))))
    		return;
    
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_MapChange);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	lua_pushinteger(gL, gamemap);
    	lua_pushnil(gL);
    	while (lua_next(gL, -3) != 0) {
    		lua_pushvalue(gL, -3); // gamemap
    		LUA_Call(gL, 1);
    	}
    	lua_pop(gL, 1);
    	lua_gc(gL, LUA_GCSTEP, 1);
    }
    
    // Hook for map load
    void LUAh_MapLoad(void)
    {
    	if (!gL || !(hooksAvailable[hook_MapLoad/8] & (1<<(hook_MapLoad%8))))
    		return;
    
    	lua_pop(gL, -1);
    
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_MapLoad);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	lua_pushinteger(gL, gamemap);
    	lua_pushnil(gL);
    	while (lua_next(gL, -3) != 0) {
    		lua_pushvalue(gL, -3); // gamemap
    		LUA_Call(gL, 1);
    	}
    	lua_pop(gL, -1);
    	lua_gc(gL, LUA_GCCOLLECT, 0);
    }
    
    // Hook for Got_AddPlayer
    void LUAh_PlayerJoin(int playernum)
    {
    	if (!gL || !(hooksAvailable[hook_PlayerJoin/8] & (1<<(hook_PlayerJoin%8))))
    		return;
    
    	lua_pop(gL, -1);
    
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_PlayerJoin);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	lua_pushinteger(gL, playernum);
    	lua_pushnil(gL);
    	while (lua_next(gL, -3) != 0) {
    		lua_pushvalue(gL, -3); // playernum
    		LUA_Call(gL, 1);
    	}
    	lua_pop(gL, -1);
    	lua_gc(gL, LUA_GCCOLLECT, 0);
    }
    
    // Hook for frame (after mobj and player thinkers)
    void LUAh_ThinkFrame(void)
    {
    	if (!gL || !(hooksAvailable[hook_ThinkFrame/8] & (1<<(hook_ThinkFrame%8))))
    		return;
    
    	lua_pop(gL, -1);
    
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_ThinkFrame);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	lua_pushnil(gL);
    	while (lua_next(gL, -2) != 0)
    	{
    		//LUA_Call(gL, 0);
    		if (lua_pcall(gL, 0, 0, 0))
    		{
    			// A run-time error occurred.
    			CONS_Alert(CONS_WARNING,"%s\n", lua_tostring(gL, -1));
    			lua_pop(gL, 1);
    			// Remove this function from the hook table to prevent further errors.
    			lua_pushvalue(gL, -1); // key
    			lua_pushnil(gL); // value
    			lua_rawset(gL, -4); // table
    			CONS_Printf("Hook removed.\n");
    		}
    	}
    	lua_pop(gL, -1);
    	lua_gc(gL, LUA_GCCOLLECT, 0);
    }
    
    // Hook for PIT_CheckThing by (thing) mobj type (thing1 = thing, thing2 = tmthing)
    UINT8 LUAh_MobjCollide(mobj_t *thing1, mobj_t *thing2)
    {
    	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
    	if (!gL || !(hooksAvailable[hook_MobjCollide/8] & (1<<(hook_MobjCollide%8))))
    		return 0;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_MobjCollide);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// mobjtype subtable
    	lua_rawgeti(gL, -1, thing1->type);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return 0;
    	}
    	lua_remove(gL, -2); // remove hook table
    
    	LUA_PushUserdata(gL, thing1, META_MOBJ);
    	LUA_PushUserdata(gL, thing2, META_MOBJ);
    	lua_pushnil(gL);
    	while (lua_next(gL, -4)) {
    		lua_pushvalue(gL, -4); // thing1
    		lua_pushvalue(gL, -4); // thing2
    		if (lua_pcall(gL, 2, 1, 0)) {
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (!lua_isnil(gL, -1))
    		{ // if nil, leave shouldCollide = 0.
    			if (lua_toboolean(gL, -1))
    				shouldCollide = 1; // Force yes
    			else
    				shouldCollide = 2; // Force no
    		}
    		lua_pop(gL, 1); // pop return value
    	}
    	lua_pop(gL, 3); // pop arguments and mobjtype table
    
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return shouldCollide;
    }
    
    // Hook for PIT_CheckThing by (tmthing) mobj type (thing1 = tmthing, thing2 = thing)
    UINT8 LUAh_MobjMoveCollide(mobj_t *thing1, mobj_t *thing2)
    {
    	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
    	if (!gL || !(hooksAvailable[hook_MobjMoveCollide/8] & (1<<(hook_MobjMoveCollide%8))))
    		return 0;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_MobjMoveCollide);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// mobjtype subtable
    	lua_rawgeti(gL, -1, thing1->type);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return 0;
    	}
    	lua_remove(gL, -2); // remove hook table
    
    	LUA_PushUserdata(gL, thing1, META_MOBJ);
    	LUA_PushUserdata(gL, thing2, META_MOBJ);
    	lua_pushnil(gL);
    	while (lua_next(gL, -4)) {
    		lua_pushvalue(gL, -4); // thing1
    		lua_pushvalue(gL, -4); // thing2
    		if (lua_pcall(gL, 2, 1, 0)) {
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (!lua_isnil(gL, -1))
    		{ // if nil, leave shouldCollide = 0.
    			if (lua_toboolean(gL, -1))
    				shouldCollide = 1; // Force yes
    			else
    				shouldCollide = 2; // Force no
    		}
    		lua_pop(gL, 1); // pop return value
    	}
    	lua_pop(gL, 3); // pop arguments and mobjtype table
    
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return shouldCollide;
    }
    
    // Hook for P_TouchSpecialThing by mobj type
    boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
    {
    	boolean hooked = false;
    	if (!gL || !(hooksAvailable[hook_TouchSpecial/8] & (1<<(hook_TouchSpecial%8))))
    		return false;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// get hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_TouchSpecial);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// get mobjtype subtable
    	lua_pushinteger(gL, special->type);
    	lua_rawget(gL, 1);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return false;
    	}
    	lua_remove(gL, 1); // pop hook table off the stack
    
    	LUA_PushUserdata(gL, special, META_MOBJ);
    	LUA_PushUserdata(gL, toucher, META_MOBJ);
    
    	lua_pushnil(gL);
    	while (lua_next(gL, 1) != 0) {
    		lua_pushvalue(gL, 2); // special
    		lua_pushvalue(gL, 3); // toucher
    		if (lua_pcall(gL, 2, 1, 0)) { // pops hook function, special, toucher, pushes 1 return result
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (lua_toboolean(gL, -1)) // if return true,
    			hooked = true; // override vanilla behavior
    		lua_pop(gL, 1); // pop return value
    	}
    
    	lua_pop(gL, -1);
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return hooked;
    }
    
    // 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 shouldDamage = 0; // 0 = default, 1 = force yes, 2 = force no.
    	if (!gL || !(hooksAvailable[hook_ShouldDamage/8] & (1<<(hook_ShouldDamage%8))))
    		return 0;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_ShouldDamage);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// mobjtype subtable
    	lua_rawgeti(gL, -1, target->type);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return 0;
    	}
    	lua_remove(gL, -2); // remove hook table
    
    	LUA_PushUserdata(gL, target, META_MOBJ);
    	LUA_PushUserdata(gL, inflictor, META_MOBJ);
    	LUA_PushUserdata(gL, source, META_MOBJ);
    	lua_pushinteger(gL, damage);
    	lua_pushnil(gL);
    	while (lua_next(gL, -6)) {
    		lua_pushvalue(gL, -6); // target
    		lua_pushvalue(gL, -6); // inflictor
    		lua_pushvalue(gL, -6); // source
    		lua_pushvalue(gL, -6); // damage
    		if (lua_pcall(gL, 4, 1, 0)) {
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (!lua_isnil(gL, -1))
    		{ // if nil, leave shouldDamage = 0.
    			if (lua_toboolean(gL, -1))
    				shouldDamage = 1; // Force yes
    			else
    				shouldDamage = 2; // Force no
    		}
    		lua_pop(gL, 1); // pop return value
    	}
    	lua_pop(gL, 5); // pop arguments and mobjtype table
    
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return shouldDamage;
    }
    
    // Hook for P_DamageMobj by mobj type (Mobj actually takes damage!)
    boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
    {
    	boolean handled = false;
    	if (!gL || !(hooksAvailable[hook_MobjDamage/8] & (1<<(hook_MobjDamage%8))))
    		return false;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_MobjDamage);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// mobjtype subtable
    	lua_rawgeti(gL, -1, target->type);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return false;
    	}
    	lua_remove(gL, -2); // remove hook table
    
    	LUA_PushUserdata(gL, target, META_MOBJ);
    	LUA_PushUserdata(gL, inflictor, META_MOBJ);
    	LUA_PushUserdata(gL, source, META_MOBJ);
    	lua_pushinteger(gL, damage);
    	lua_pushnil(gL);
    	while (lua_next(gL, -6)) {
    		lua_pushvalue(gL, -6); // target
    		lua_pushvalue(gL, -6); // inflictor
    		lua_pushvalue(gL, -6); // source
    		lua_pushvalue(gL, -6); // damage
    		if (lua_pcall(gL, 4, 1, 0)) {
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (lua_toboolean(gL, -1))
    			handled = true;
    		lua_pop(gL, 1); // pop return value
    	}
    	lua_pop(gL, 5); // pop arguments and mobjtype table
    
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return handled;
    }
    
    // Hook for P_KillMobj by mobj type
    boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source)
    {
    	boolean handled = false;
    	if (!gL || !(hooksAvailable[hook_MobjDeath/8] & (1<<(hook_MobjDeath%8))))
    		return false;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_MobjDeath);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// mobjtype subtable
    	lua_rawgeti(gL, -1, target->type);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return false;
    	}
    	lua_remove(gL, -2); // remove hook table
    
    	LUA_PushUserdata(gL, target, META_MOBJ);
    	LUA_PushUserdata(gL, inflictor, META_MOBJ);
    	LUA_PushUserdata(gL, source, META_MOBJ);
    	lua_pushnil(gL);
    	while (lua_next(gL, -5)) {
    		lua_pushvalue(gL, -5); // target
    		lua_pushvalue(gL, -5); // inflictor
    		lua_pushvalue(gL, -5); // source
    		if (lua_pcall(gL, 3, 1, 0)) {
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (lua_toboolean(gL, -1))
    			handled = true;
    		lua_pop(gL, 1); // pop return value
    	}
    	lua_pop(gL, 4); // pop arguments and mobjtype table
    
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return handled;
    }
    
    // Hook for B_BuildTiccmd
    boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
    {
    	boolean hooked = false;
    	if (!gL || !(hooksAvailable[hook_BotTiccmd/8] & (1<<(hook_BotTiccmd%8))))
    		return false;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_BotTiccmd);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	LUA_PushUserdata(gL, bot, META_PLAYER);
    	LUA_PushUserdata(gL, cmd, META_TICCMD);
    
    	lua_pushnil(gL);
    	while (lua_next(gL, 1)) {
    		lua_pushvalue(gL, 2); // bot
    		lua_pushvalue(gL, 3); // cmd
    		if (lua_pcall(gL, 2, 1, 0)) {
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (lua_toboolean(gL, -1))
    			hooked = true;
    		lua_pop(gL, 1); // pop return value
    	}
    
    	lua_pop(gL, -1);
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return hooked;
    }
    
    // Hook for B_BuildTailsTiccmd by skin name
    boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
    {
    	if (!gL || !tails->skin || !(hooksAvailable[hook_BotAI/8] & (1<<(hook_BotAI%8))))
    		return false;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_BotAI);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// bot skin ai function
    	lua_getfield(gL, 1, ((skin_t *)tails->skin)->name);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return false;
    	}
    	lua_remove(gL, 1); // pop the hook table
    
    	// Takes sonic, tails
    	// Returns forward, backward, left, right, jump, spin
    	LUA_PushUserdata(gL, sonic, META_MOBJ);
    	LUA_PushUserdata(gL, tails, META_MOBJ);
    	if (lua_pcall(gL, 2, 8, 0)) {
    		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    		lua_pop(gL,-1);
    		return false;
    	}
    
    	// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
    	if (lua_istable(gL, 1)) {
    		boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
    
    #define CHECKFIELD(field) \
    		lua_getfield(gL, 1, #field);\
    		if (lua_toboolean(gL, -1))\
    			field = true;\
    		lua_pop(gL, 1);
    
    		CHECKFIELD(forward)
    		CHECKFIELD(backward)
    		CHECKFIELD(left)
    		CHECKFIELD(right)
    		CHECKFIELD(strafeleft)
    		CHECKFIELD(straferight)
    		CHECKFIELD(jump)
    		CHECKFIELD(spin)
    
    #undef CHECKFIELD
    
    		B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin);
    	} else
    		B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, 1), lua_toboolean(gL, 2), lua_toboolean(gL, 3), lua_toboolean(gL, 4), lua_toboolean(gL, 5), lua_toboolean(gL, 6), lua_toboolean(gL, 7), lua_toboolean(gL, 8));
    
    	lua_pop(gL, -1);
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return true;
    }
    
    // Hook for linedef executors
    boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo)
    {
    	if (!gL || !(hooksAvailable[hook_LinedefExecute/8] & (1<<(hook_LinedefExecute%8))))
    		return false;
    
    	// clear the stack
    	lua_pop(gL, -1);
    
    	// get hook table
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_LinedefExecute);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    
    	// get function by line text
    	lua_getfield(gL, 1, line->text);
    	if (lua_isnil(gL, -1)) {
    		lua_pop(gL, 2);
    		return false;
    	}
    	lua_remove(gL, 1); // pop hook table off the stack
    
    	LUA_PushUserdata(gL, line, META_LINE);
    	LUA_PushUserdata(gL, mo, META_MOBJ);
    	LUA_Call(gL, 2); // pops hook function, line, mo
    
    	lua_pop(gL, -1);
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return true;
    }
    
    // Hook for PlayerMsg -Red
    boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
    {
    	boolean handled = false;
    	
    	if (!gL || !(hooksAvailable[hook_PlayerMsg/8] & (1<<(hook_PlayerMsg%8))))
    		return false;
    	
    	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
    	I_Assert(lua_istable(gL, -1));
    	lua_rawgeti(gL, -1, hook_PlayerMsg);
    	lua_remove(gL, -2);
    	I_Assert(lua_istable(gL, -1));
    	
    	LUA_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
    	
    	lua_pushnil(gL);
    	
    	while (lua_next(gL, -6)) {
    		lua_pushvalue(gL, -6); // source
    		lua_pushvalue(gL, -6); // type
    		lua_pushvalue(gL, -6); // target
    		lua_pushvalue(gL, -6); // msg
    		if (lua_pcall(gL, 4, 1, 0)) {
    			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
    			lua_pop(gL, 1);
    			continue;
    		}
    		if (lua_toboolean(gL, -1))
    			handled = true;
    		lua_pop(gL, 1); // pop return value
    	}
    	lua_pop(gL, 4); // pop arguments and mobjtype table
    
    	lua_gc(gL, LUA_GCSTEP, 1);
    	return handled;
    }
    
    #endif