// BLANKART
//-----------------------------------------------------------------------------
// Copyright (C) 2012-2016 by John "JTE" Muniz.
// Copyright (C) 2012-2020 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_consolelib.c
/// \brief console modifying/etc library for Lua scripting

#include "doomdef.h"
#include "p_local.h"
#include "g_game.h"
#include "byteptr.h"
#include "z_zone.h"

#include "lua_script.h"
#include "lua_libs.h"
#include "lua_hud.h" // hud_running errors

// for functions not allowed in hud.add hooks
#define NOHUD if (hud_running)\
return luaL_error(L, "HUD rendering code should not call this function!");
// for functions not allowed in hooks or coroutines (supercedes above)
#define NOHOOK if (!lua_lumploading)\
		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");

static consvar_t *this_cvar;

static void clear_lua_stack(void)
{
	if (gL) // check if Lua is actually turned on first, you dummmy -- Monster Iestyn 04/07/18
		lua_settop(gL, 0); // clear stack
}

void Got_Luacmd(UINT8 **cp, INT32 playernum)
{
	UINT8 i, argc, flags;
	const char *argv[256];
	char buf[256];
	boolean compat;

	argc = READUINT8(*cp);
	argv[0] = (const char*)*cp;
	SKIPSTRINGN(*cp, 255);
	for (i = 1; i < argc; i++)
	{
		argv[i] = (const char*)*cp;
		SKIPSTRINGN(*cp, 255);
	}

	// don't use I_Assert here, goto the deny code below
	// to clean up and kick people who try nefarious exploits
	// like sending random junk lua commands to crash the server

	if (!gL) goto deny;

	lua_settop(gL, 0); // Just in case...
	lua_pushcfunction(gL, LUA_GetErrorMessage);

	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
	if (!lua_istable(gL, -1)) goto deny;

	strlcpy(buf, argv[0], 255);
	strlwr(buf); // must lowercase buffer
	lua_getfield(gL, -1, buf); // push command info table
	if (!lua_istable(gL, -1)) goto deny;

	lua_remove(gL, -2); // pop COM_Command

	lua_rawgeti(gL, -1, 2); // push flags from command info table
	if (lua_isboolean(gL, -1))
		flags = (lua_toboolean(gL, -1) ? 1 : 0);
	else
		flags = (UINT8)lua_tointeger(gL, -1);
	lua_pop(gL, 1); // pop flags

	// requires server/admin and the player is not one of them
	if ((flags & 1) && playernum != serverplayer && !IsPlayerAdmin(playernum))
		goto deny;

	lua_rawgeti(gL, -1, 3); // push compatmode from command info table
	compat = lua_toboolean(gL, -1);
	lua_pop(gL, 1); // pop compatmode

	lua_rawgeti(gL, -1, 1); // push function from command info table

	// although honestly this should be true anyway
	// BUT GODDAMNIT I SAID NO I_ASSERTS SO NO I_ASSERTS IT IS
	if (!lua_isfunction(gL, -1)) goto deny;

	lua_remove(gL, -2); // pop command info table

	if (!lua_checkstack(gL, argc)) // player + command arguments
	{
		clear_lua_stack();
		CONS_Alert(CONS_WARNING, "lua command stack overflow from %s (%d, need %d more)\n", player_names[playernum], lua_gettop(gL), argc);
		return;
	}

	LUA_PushUserdata(gL, &players[playernum], META_PLAYER);
	for (i = 1; i < argc; i++)
	{
		strlcpy(buf, argv[i], 255);
		lua_pushstring(gL, buf);
	}
	LUA_Call(gL, (int)argc, 0, 1, compat, true); // argc is 1-based, so this will cover the player we passed too.

	lua_pop(gL, 1); // Pop LUA_GetErrorMessage (lua_pcall doesn't pop it)

	return;

deny:
	//must be hacked/buggy client
	clear_lua_stack();

	CONS_Alert(CONS_WARNING, M_GetText("Illegal lua command received from %s\n"), player_names[playernum]);
	if (server)
		SendKick(playernum, KICK_MSG_CON_FAIL);
}

// Wrapper for COM_AddCommand commands
void COM_Lua_f(void)
{
	char *buf, *p;
	UINT8 i;
	UINT16 len;
	UINT16 flags;
	UINT8 lpn = 0;
	INT32 playernum = consoleplayer;
	boolean compat;

	I_Assert(gL != NULL);

	lua_settop(gL, 0); // Just in case...
	lua_pushcfunction(gL, LUA_GetErrorMessage);

	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
	I_Assert(lua_istable(gL, -1));

	// use buf temporarily -- must use lowercased string
	buf = Z_StrDup(COM_Argv(0));
	strlwr(buf);
	lua_getfield(gL, -1, buf); // push command info table
	I_Assert(lua_istable(gL, -1));
	lua_remove(gL, -2); // pop COM_Command
	Z_Free(buf);

	lua_rawgeti(gL, -1, 2); // push flags from command info table
	if (lua_isboolean(gL, -1))
		flags = (lua_toboolean(gL, -1) ? COM_ADMIN : 0);
	else
		flags = (UINT16)lua_tointeger(gL, -1);
	lua_pop(gL, 1); // pop flags

	if (flags & COM_SPLITSCREEN) // splitscreen player command.
	{
		lpn = (flags & COM_SPLITSCREEN) >> COM_SSSHIFT;

		if (splitscreen < lpn)
		{
			lua_pop(gL, 2); // pop command info table and LUA_GetErrorMessage
			return; // can't execute splitscreen command without the player being in-game!
		}

		playernum = g_localplayers[lpn];
	}

	if (netgame && !( flags & COM_LOCAL ))/* don't send local commands */
	{ // Send the command through the network
		UINT8 argc;
		lua_pop(gL, 2); // pop command info table and LUA_GetErrorMessage

		if ((flags & COM_ADMIN) && !server && !IsPlayerAdmin(playernum)) // only server/admin can use this command.
		{
			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
			return;
		}

		if (COM_Argc() > UINT8_MAX)
			argc = UINT8_MAX;
		else
			argc = (UINT8)COM_Argc();
		if (argc == UINT8_MAX)
			len = UINT16_MAX;
		else
			len = (argc+1)*256;

		buf = malloc(len);
		p = buf;
		WRITEUINT8(p, argc);
		for (i = 0; i < argc; i++)
			WRITESTRINGN(p, COM_Argv(i), 255);

		SendNetXCmdForPlayer(lpn, XD_LUACMD, buf, p-buf);
		free(buf);
		return;
	}

	lua_rawgeti(gL, -1, 3); // push compatmode from command info table
	compat = lua_toboolean(gL, -1);
	lua_pop(gL, 1); // pop compatmode

	// Do the command locally, NetXCmds don't go through outside of GS_LEVEL || GS_INTERMISSION || GS_VOTING
	lua_rawgeti(gL, -1, 1); // push function from command info table
	I_Assert(lua_isfunction(gL, -1));
	lua_remove(gL, -2); // pop command info table

	if (!lua_checkstack(gL, COM_Argc() + 1))
	{
		CONS_Alert(CONS_WARNING, "lua command stack overflow (%d, need %s more)\n", lua_gettop(gL), sizeu1(COM_Argc() + 1));
		lua_pop(gL, 2); // pop function and LUA_GetErrorMessage
		return;
	}

	LUA_PushUserdata(gL, &players[playernum], META_PLAYER);
	for (i = 1; i < COM_Argc(); i++)
		lua_pushstring(gL, COM_Argv(i));
	LUA_Call(gL, (int)COM_Argc(), 0, 1, compat, true); // COM_Argc is 1-based, so this will cover the player we passed too.

	lua_pop(gL, 1); // Pop LUA_GetErrorMessage (lua_pcall doesn't pop it)
}

// Wrapper for COM_AddCommand
static int lib_comAddCommand(lua_State *L)
{
	int com_return = -1;
	const char *luaname = luaL_checkstring(L, 1);

	// must store in all lowercase
	char *name = Z_StrDup(luaname);
	strlwr(name);

	luaL_checktype(L, 2, LUA_TFUNCTION);
	//NOHOOK
	if (lua_gettop(L) >= 3)
	{ // For the third argument, only take a boolean or a number.
		lua_settop(L, 3);
		if (lua_type(L, 3) == LUA_TBOOLEAN)
		{
			CONS_Alert(CONS_WARNING,
					"Using a boolean for admin commands is "
					"deprecated and will be removed.\n"
					"Use \"COM_ADMIN\" instead.\n"
			);
		}
		else
			luaL_checktype(L, 3, LUA_TNUMBER);
	}
	else
	{ // No third argument? Default to 0.
		lua_settop(L, 2);
		lua_pushinteger(L, 0);
	}

	lua_getfield(L, LUA_REGISTRYINDEX, "COM_Command");
	I_Assert(lua_istable(L, -1));

	lua_createtable(L, 2, 0);
		lua_pushvalue(L, 2);
		lua_rawseti(L, -2, 1);

		lua_pushvalue(L, 3);
		lua_rawseti(L, -2, 2);

		lua_pushboolean(L, lua_compatmode);
		lua_rawseti(L, -2, 3);
	lua_setfield(L, -2, name);

	// Try to add the Lua command
	com_return = COM_AddLuaCommand(name);

	if (com_return < 0)
	{ // failed to add -- free the lowercased name and return error
		Z_Free(name);
		return luaL_error(L, "Couldn't add a new console command \"%s\"", luaname);
	}
	else if (com_return == 1)
	{ // command existed already -- free our name as the old string will continue to be used
		CONS_Printf("Replaced command \"%s\"\n", name);
		Z_Free(name);
	}
	else
	{ // new command was added -- do NOT free the string as it will forever be used by the console
		CONS_Printf("Added command \"%s\"\n", name);
	}
	return 0;
}

static int lib_comBufAddText(lua_State *L)
{
	int n = lua_gettop(L);  /* number of arguments */
	player_t *plr = NULL;
	if (n < 2)
		return luaL_error(L, "COM_BufAddText requires two arguments: player and text.");
	NOHUD
	lua_settop(L, 2);
	if (!lua_isnoneornil(L, 1))
		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
	if (plr && plr != &players[consoleplayer])
		return 0;
	COM_BufAddTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
	return 0;
}

static int lib_comBufInsertText(lua_State *L)
{
	int n = lua_gettop(L);  /* number of arguments */
	player_t *plr = NULL;
	if (n < 2)
		return luaL_error(L, "COM_BufInsertText requires two arguments: player and text.");
	NOHUD
	lua_settop(L, 2);
	if (!lua_isnoneornil(L, 1))
		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
	if (plr && plr != &players[consoleplayer])
		return 0;
	COM_BufInsertTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
	return 0;
}

void LUA_CVarChanged(void *cvar)
{
	this_cvar = cvar;
}

static void Lua_OnChange(void)
{
	boolean compat;

	lua_pushcfunction(gL, LUA_GetErrorMessage);
	lua_insert(gL, 1); // Because LUA_Call wants it at index 1.

	// From CV_OnChangeCompat registry field, get the function's compatmode setting.
	lua_getfield(gL, LUA_REGISTRYINDEX, "CV_OnChangeCompat");
	I_Assert(lua_istable(gL, -1));
	lua_pushlightuserdata(gL, this_cvar);
	lua_rawget(gL, -2);
	compat = luaL_checkboolean(gL, -1);
	lua_pop(gL, 2);

	// From CV_OnChange registry field, get the function for this cvar by name.
	lua_getfield(gL, LUA_REGISTRYINDEX, "CV_OnChange");
	I_Assert(lua_istable(gL, -1));
	lua_pushlightuserdata(gL, this_cvar);
	lua_rawget(gL, -2); // get function

	LUA_RawPushUserdata(gL, this_cvar);

	LUA_Call(gL, 1, 0, 1, compat, true); // call function(cvar)
	lua_pop(gL, 1); // pop CV_OnChange table
	lua_remove(gL, 1); // remove LUA_GetErrorMessage
}

static int lib_cvRegisterVar(lua_State *L)
{
	const char *k;
	lua_Integer i;
	consvar_t *cvar;
	luaL_checktype(L, 1, LUA_TTABLE);
	lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one.
	//NOHOOK
	cvar = ZZ_Calloc(sizeof(consvar_t));
	LUA_PushUserdata(L, cvar, META_CVAR);

#define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to " LUA_QL("CV_RegisterVar") " (%s)", e);
#define TYPEERROR(f, t) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t), luaL_typename(L, -1)))

	lua_pushnil(L);
	while (lua_next(L, 1)) {
		// stack: cvar table, cvar userdata, key/index, value
		//            1             2            3        4
		i = 0;
		k = NULL;
		if (lua_isnumber(L, 3))
			i = lua_tointeger(L, 3);
		else if (lua_isstring(L, 3))
			k = lua_tostring(L, 3);

		if (i == 1 || (k && fasticmp(k, "name"))) {
			if (!lua_isstring(L, 4))
				TYPEERROR("name", LUA_TSTRING)
			cvar->name = Z_StrDup(lua_tostring(L, 4));
		} else if (i == 2 || (k && fasticmp(k, "defaultvalue"))) {
			if (!lua_isstring(L, 4))
				TYPEERROR("defaultvalue", LUA_TSTRING)
			cvar->defaultvalue = Z_StrDup(lua_tostring(L, 4));
		} else if (i == 3 || (k && fasticmp(k, "flags"))) {
			if (!lua_isnumber(L, 4))
				TYPEERROR("flags", LUA_TNUMBER)
			cvar->flags = (INT32)lua_tointeger(L, 4);
		} else if (i == 4 || (k && fasticmp(k, "PossibleValue"))) {
			if (lua_islightuserdata(L, 4)) {
				CV_PossibleValue_t *pv = lua_touserdata(L, 4);
				if (pv == CV_OnOff || pv == CV_YesNo || pv == CV_Unsigned || pv == CV_Natural)
					cvar->PossibleValue = pv;
				else
					FIELDERROR("PossibleValue", "CV_PossibleValue_t expected, got unrecognised pointer")
			} else if (lua_istable(L, 4)) {
				// Accepts tables in the form of {MIN=0, MAX=9999} or {Red=0, Green=1, Blue=2}
				// and converts them to CV_PossibleValue_t {{0,"MIN"},{9999,"MAX"}} or {{0,"Red"},{1,"Green"},{2,"Blue"}}
				//
				// I don't really like the way this does it because a single PossibleValue table
				// being used for multiple cvars will be converted and stored multiple times.
				// So maybe instead it should be a seperate function which must be run beforehand or something.
				size_t count = 0;
				CV_PossibleValue_t *cvpv;

				lua_pushnil(L);
				while (lua_next(L, 4)) {
					count++;
					lua_pop(L, 1);
				}

				lua_getfield(L, LUA_REGISTRYINDEX, "CV_PossibleValue");
				I_Assert(lua_istable(L, 5));
				lua_pushlightuserdata(L, cvar);
				cvpv = lua_newuserdata(L, sizeof(CV_PossibleValue_t) * (count+1));
				lua_rawset(L, 5);
				lua_pop(L, 1); // pop CV_PossibleValue registry table

				i = 0;
				lua_pushnil(L);
				while (lua_next(L, 4)) {
					// stack: [...] PossibleValue table, index, value
					//                       4             5      6
					if (lua_type(L, 5) != LUA_TSTRING
					|| lua_type(L, 6) != LUA_TNUMBER)
						FIELDERROR("PossibleValue", "custom PossibleValue table requires a format of string=integer, i.e. {MIN=0, MAX=9999}");
					cvpv[i].strvalue = Z_StrDup(lua_tostring(L, 5));
					cvpv[i].value = (INT32)lua_tonumber(L, 6);
					i++;
					lua_pop(L, 1);
				}
				cvpv[i].value = 0;
				cvpv[i].strvalue = NULL;
				cvar->PossibleValue = cvpv;
			} else
				FIELDERROR("PossibleValue", va("%s or CV_PossibleValue_t expected, got %s", lua_typename(L, LUA_TTABLE), luaL_typename(L, -1)))
		} else if (cvar->flags & CV_CALL && (i == 5 || (k && fasticmp(k, "func")))) {
			if (!lua_isfunction(L, 4))
				TYPEERROR("func", LUA_TFUNCTION)
			lua_getfield(L, LUA_REGISTRYINDEX, "CV_OnChange");
			I_Assert(lua_istable(L, 5));
			lua_pushlightuserdata(L, cvar);
			lua_pushvalue(L, 4);
			lua_rawset(L, 5);
			lua_pop(L, 1);

			lua_getfield(L, LUA_REGISTRYINDEX, "CV_OnChangeCompat");
			I_Assert(lua_istable(L, 5));
			lua_pushlightuserdata(L, cvar);
			lua_pushboolean(L, lua_compatmode);
			lua_rawset(L, 5);
			lua_pop(L, 1);

			cvar->func = Lua_OnChange;
		}
		lua_pop(L, 1);
	}

#undef FIELDERROR
#undef TYPEERROR

	if (!cvar->name)
		return luaL_error(L, M_GetText("Variable has no name!\n"));
	if ((cvar->flags & CV_NOINIT) && !(cvar->flags & CV_CALL))
		return luaL_error(L, M_GetText("Variable %s has CV_NOINIT without CV_CALL\n"), cvar->name);
	if ((cvar->flags & CV_CALL) && !cvar->func)
		return luaL_error(L, M_GetText("Variable %s has CV_CALL without a function\n"), cvar->name);

	// actually time to register it to the console now! Finally!
	cvar->flags |= CV_MODIFIED;
	CV_RegisterVar(cvar);
	if (cvar->flags & CV_MODIFIED)
		return luaL_error(L, "failed to register cvar (probable conflict with internal variable/command names)");

	// return cvar userdata
	return 1;
}

static CV_PossibleValue_t fakenumlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, NULL}};
static consvar_t cv_fakenumlaps = CVAR_INIT("fakenumlaps", "3", CV_NOLUA, fakenumlaps_cons_t, NULL);

// For some reason I couldn't cherry pick this.
// Credits for this function go to james. All hail birb.		-Lat'

static int lib_cvFindVar(lua_State *L)
{
	const char *name = luaL_checkstring(L, 1);
	if (lua_compatmode)
	{
		// cv_numlaps -> numlaps (global, not cvar)
		// cv_basenumlaps -> cv_numlaps
		if (fastcmp(name, "numlaps"))
		{
			LUA_PushUserdata(L, &cv_fakenumlaps, META_CVAR);
			return 1;
		}
		else if (fastcmp(name, "basenumlaps"))
			name = "numlaps";
	}
	LUA_PushUserdata(L, CV_FindVar(name), META_CVAR);
	return 1;
}

static int CVarSetFunction
(
		lua_State *L,
		void (*Set)(consvar_t *, const char *),
		void (*SetValue)(consvar_t *, INT32)
){
	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);

	if (cvar->flags & CV_NOLUA)
		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);

	switch (lua_type(L, 2))
	{
		case LUA_TSTRING:
			(*Set)(cvar, lua_tostring(L, 2));
			break;
		case LUA_TNUMBER:
			(*SetValue)(cvar, (INT32)lua_tonumber(L, 2));
			break;
		default:
			return luaL_typerror(L, 1, "string or number");
	}

	return 0;
}

static int lib_cvSet(lua_State *L)
{
	return CVarSetFunction(L, CV_Set, CV_SetValue);
}

static int lib_cvStealthSet(lua_State *L)
{
	return CVarSetFunction(L, CV_StealthSet, CV_StealthSetValue);
}

static int lib_cvAddValue(lua_State *L)
{
	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);

	if (cvar->flags & CV_NOLUA)
		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);

	CV_AddValue(cvar, (INT32)luaL_checknumber(L, 2));

	return 0;
}

// CONS_Printf for a single player
// Use 'print' in baselib for a global message.
static int lib_consPrintf(lua_State *L)
{
	int n = lua_gettop(L);  /* number of arguments */
	int i;
	player_t *plr;
	if (n < 2)
		return luaL_error(L, "CONS_Printf requires at least two arguments: player and text.");
	//HUDSAFE

	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
	if (!plr)
		return LUA_ErrInvalid(L, "player_t");
	if (plr != &players[consoleplayer])
		return 0;
	lua_getglobal(L, "tostring");
	for (i=2; i<=n; i++) {
		const char *s;
		lua_pushvalue(L, -1);  /* function to be called */
		lua_pushvalue(L, i);   /* value to print */
		lua_call(L, 1, 1);
		s = lua_tostring(L, -1);  /* get result */
		if (s == NULL)
			return luaL_error(L, LUA_QL("tostring") " must return a string to "
													 LUA_QL("CONS_Printf"));
		if (i>2) CONS_Printf("\n");
		CONS_Printf("%s", s);
		lua_pop(L, 1);  /* pop result */
	}
	CONS_Printf("\n");
	return 0;
}

static luaL_Reg lib[] = {
	{"COM_AddCommand", lib_comAddCommand},
	{"COM_BufAddText", lib_comBufAddText},
	{"COM_BufInsertText", lib_comBufInsertText},
	{"CV_RegisterVar", lib_cvRegisterVar},
	{"CV_FindVar", lib_cvFindVar},
	{"CV_Set", lib_cvSet},
	{"CV_StealthSet", lib_cvStealthSet},
	{"CV_AddValue", lib_cvAddValue},
	{"CONS_Printf", lib_consPrintf},
	{NULL, NULL}
};

static int cvar_get(lua_State *L)
{
	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);
	const char *field = luaL_checkstring(L, 2);

	if(fastcmp(field,"name"))
		lua_pushstring(L, cvar->name);
	else if(fastcmp(field,"defaultvalue"))
		lua_pushstring(L, cvar->defaultvalue);
	else if(fastcmp(field,"flags"))
		lua_pushinteger(L, cvar->flags);
	else if(fastcmp(field,"value"))
		lua_pushinteger(L, cvar == &cv_fakenumlaps ? numlaps : cvar->value);
	else if(fastcmp(field,"string"))
		lua_pushstring(L, cvar == &cv_fakenumlaps ? va("%d", numlaps) : cvar->string);
	else if(fastcmp(field,"changed"))
		lua_pushboolean(L, cvar->changed);
	else if (devparm)
		return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS ".", lua_tostring(L, 2));
	else
		return 0;
	return 1;
}

int LUA_ConsoleLib(lua_State *L)
{
	// Metatable for consvar_t
	luaL_newmetatable(L, META_CVAR);
		lua_pushcfunction(L, cvar_get);
		lua_setfield(L, -2, "__index");
	lua_pop(L,1);

	// Set empty registry tables
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "COM_Command");
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "CV_Vars");
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "CV_PossibleValue");
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "CV_OnChange");
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "CV_OnChangeCompat");

	// Push opaque CV_PossibleValue pointers
	// Because I don't care enough to bother.
	lua_pushlightuserdata(L, CV_OnOff);
	lua_setglobal(L, "CV_OnOff");
	lua_pushlightuserdata(L, CV_YesNo);
	lua_setglobal(L, "CV_YesNo");
	lua_pushlightuserdata(L, CV_Unsigned);
	lua_setglobal(L, "CV_Unsigned");
	lua_pushlightuserdata(L, CV_Signed);
	lua_setglobal(L, "CV_Signed");
	lua_pushlightuserdata(L, CV_Natural);
	lua_setglobal(L, "CV_Natural");

	// Set global functions
	lua_pushvalue(L, LUA_GLOBALSINDEX);
	luaL_register(L, NULL, lib);
	return 0;
}
