Skip to content
Snippets Groups Projects
Select Git revision
  • next default protected
  • wad-start-and-end-from-srb2classic
  • fix-emerald-hunt
  • woof-midi-port
  • fix-1425
  • expose-path-traverse
  • can-hurt-self-score-fix
  • post-mobj-thinker
  • hms-useragent
  • gitlab-ci
  • nightshoopsanity
  • fullscreen-toggle
  • next-test
  • bbox
  • cmake-enable-cxx
  • master
  • more-use-afters-frees
  • cmake-clang-tidy
  • cmake-valgrind
  • font_drawer
  • SRB2_release_2.2.10
  • SRB2_release_2.2.9
  • 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
40 results

lua_inputlib.c

Blame
  • Forked from STJr / SRB2
    3200 commits behind the upstream repository.
    lua_inputlib.c 15.95 KiB
    // SONIC ROBO BLAST 2
    //-----------------------------------------------------------------------------
    // Copyright (C) 2021-2022 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_inputlib.c
    /// \brief input library for Lua scripting
    
    #include "doomdef.h"
    #include "fastcmp.h"
    #include "g_input.h"
    #include "g_game.h"
    #include "hu_stuff.h"
    #include "i_system.h"
    #include "i_gamepad.h"
    
    #include "lua_script.h"
    #include "lua_libs.h"
    
    boolean mousegrabbedbylua = true;
    
    ///////////////
    // FUNCTIONS //
    ///////////////
    
    static int lib_gameControlDown(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	if (i < 0 || i >= NUM_GAMECONTROLS)
    		return luaL_error(L, "GC_* constant %d out of range (0 - %d)", i, NUM_GAMECONTROLS-1);
    	lua_pushinteger(L, G_PlayerInputDown(0, i));
    	return 1;
    }
    
    static int lib_gameControl2Down(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	if (i < 0 || i >= NUM_GAMECONTROLS)
    		return luaL_error(L, "GC_* constant %d out of range (0 - %d)", i, NUM_GAMECONTROLS-1);
    	lua_pushinteger(L, G_PlayerInputDown(1, i));
    	return 1;
    }
    
    static int lib_gameControlToKeyNum(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	if (i < 0 || i >= NUM_GAMECONTROLS)
    		return luaL_error(L, "GC_* constant %d out of range (0 - %d)", i, NUM_GAMECONTROLS-1);
    	lua_pushinteger(L, gamecontrol[i][0]);
    	lua_pushinteger(L, gamecontrol[i][1]);
    	return 2;
    }
    
    static int lib_gameControl2ToKeyNum(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	if (i < 0 || i >= NUM_GAMECONTROLS)
    		return luaL_error(L, "GC_* constant %d out of range (0 - %d)", i, NUM_GAMECONTROLS-1);
    	lua_pushinteger(L, gamecontrolbis[i][0]);
    	lua_pushinteger(L, gamecontrolbis[i][1]);
    	return 2;
    }
    
    static int lib_joyAxis(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	lua_pushinteger(L, G_JoyAxis(0, i) / 32);
    	return 1;
    }
    
    static int lib_joy2Axis(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	lua_pushinteger(L, G_JoyAxis(1, i) / 32);
    	return 1;
    }
    
    static int lib_keyNumToName(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	lua_pushstring(L, G_KeyNumToName(i));
    	return 1;
    }
    
    static int lib_keyNameToNum(lua_State *L)
    {
    	const char *str = luaL_checkstring(L, 1);
    	lua_pushinteger(L, G_KeyNameToNum(str));
    	return 1;
    }
    
    static int lib_keyNumPrintable(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	lua_pushboolean(L, i >= 32 && i <= 127);
    	return 1;
    }
    
    static int lib_shiftKeyNum(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 1);
    	if (i >= 32 && i <= 127)
    		lua_pushinteger(L, shiftxform[i]);
    	return 1;
    }
    
    static int lib_getMouseGrab(lua_State *L)
    {
    	lua_pushboolean(L, mousegrabbedbylua);
    	return 1;
    }
    
    static int lib_setMouseGrab(lua_State *L)
    {
    	mousegrabbedbylua = luaL_checkboolean(L, 1);
    	I_UpdateMouseGrab();
    	return 0;
    }
    
    static int lib_getCursorPosition(lua_State *L)
    {
    	int x, y;
    	I_GetCursorPosition(&x, &y);
    	lua_pushinteger(L, x);
    	lua_pushinteger(L, y);
    	return 2;
    }
    
    static int lib_getPlayerGamepad(lua_State *L)
    {
    	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
    	if (!player)
    		return LUA_ErrInvalid(L, "player_t");
    
    	INT16 which = G_GetGamepadForPlayer(player);
    	if (which >= 0)
    		LUA_PushUserdata(L, &gamepads[which], META_GAMEPAD);
    	else
    		lua_pushnil(L);
    
    	return 1;
    }
    
    static luaL_Reg lib[] = {
    	{"gameControlDown", lib_gameControlDown},
    	{"gameControl2Down", lib_gameControl2Down},
    	{"gameControlToKeyNum", lib_gameControlToKeyNum},
    	{"gameControl2ToKeyNum", lib_gameControl2ToKeyNum},
    	{"joyAxis", lib_joyAxis},
    	{"joy2Axis", lib_joy2Axis},
    	{"keyNumToName", lib_keyNumToName},
    	{"keyNameToNum", lib_keyNameToNum},
    	{"keyNumPrintable", lib_keyNumPrintable},
    	{"shiftKeyNum", lib_shiftKeyNum},
    	{"getMouseGrab", lib_getMouseGrab},
    	{"setMouseGrab", lib_setMouseGrab},
    	{"getCursorPosition", lib_getCursorPosition},
    	{"getPlayerGamepad", lib_getPlayerGamepad},
    	{NULL, NULL}
    };
    
    ///////////////////
    // gamekeydown[] //
    ///////////////////
    
    static int lib_getGameKeyDown(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 2);
    	if (i < 0 || i >= NUMINPUTS)
    		return luaL_error(L, "gamekeydown[] index %d out of range (0 - %d)", i, NUMINPUTS-1);
    	lua_pushboolean(L, gamekeydown[i]);
    	return 1;
    }
    
    static int lib_setGameKeyDown(lua_State *L)
    {
    	int i = luaL_checkinteger(L, 2);
    	boolean j = luaL_checkboolean(L, 3);
    	if (i < 0 || i >= NUMINPUTS)
    		return luaL_error(L, "gamekeydown[] index %d out of range (0 - %d)", i, NUMINPUTS-1);
    	gamekeydown[i] = j;
    	return 0;
    }
    
    static int lib_lenGameKeyDown(lua_State *L)
    {
    	lua_pushinteger(L, NUMINPUTS);
    	return 1;
    }
    
    ///////////////
    // KEY EVENT //
    ///////////////
    
    static int keyevent_get(lua_State *L)
    {
    	event_t *event = *((event_t **)luaL_checkudata(L, 1, META_KEYEVENT));
    	const char *field = luaL_checkstring(L, 2);
    
    	I_Assert(event != NULL);
    
    	if (fastcmp(field,"name"))
    		lua_pushstring(L, G_KeyNumToName(event->key));
    	else if (fastcmp(field,"num"))
    		lua_pushinteger(L, event->key);
    	else if (fastcmp(field,"repeated"))
    		lua_pushboolean(L, event->repeated);
    	else
    		return luaL_error(L, "keyevent_t has no field named %s", field);
    
    	return 1;
    }
    
    /////////////
    // GAMEPAD //
    /////////////
    
    enum gamepad_leftright_e {
    	gamepad_opt_left,
    	gamepad_opt_right
    };
    
    static const char *const gamepad_leftright_opt[] = {
    	"left",
    	"right",
    	NULL};
    
    // Buttons
    static int gamepad_isButtonDown(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	gamepad_button_e button = luaL_checkoption(L, 2, NULL, gamepad_button_names);
    	lua_pushboolean(L, gamepad->buttons[button] == 1);
    	return 1;
    }
    
    // Axes
    static int gamepad_getAxis(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	gamepad_axis_e axis = luaL_checkoption(L, 2, NULL, gamepad_axis_names);
    	boolean applyDeadzone = luaL_opt(L, luaL_checkboolean, 3, true);
    	lua_pushfixed(L, G_GetAdjustedGamepadAxis(gamepad->num, axis, applyDeadzone));
    	return 1;
    }
    
    // Sticks
    static int gamepad_getStick(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	enum gamepad_leftright_e stick = luaL_checkoption(L, 2, NULL, gamepad_leftright_opt);
    	boolean applyDeadzone = luaL_opt(L, luaL_checkboolean, 3, true);
    
    	switch (stick)
    	{
    	case gamepad_opt_left:
    		lua_pushfixed(L, G_GetAdjustedGamepadAxis(gamepad->num, GAMEPAD_AXIS_LEFTX, applyDeadzone));
    		lua_pushfixed(L, G_GetAdjustedGamepadAxis(gamepad->num, GAMEPAD_AXIS_LEFTY, applyDeadzone));
    		break;
    	case gamepad_opt_right:
    		lua_pushfixed(L, G_GetAdjustedGamepadAxis(gamepad->num, GAMEPAD_AXIS_RIGHTX, applyDeadzone));
    		lua_pushfixed(L, G_GetAdjustedGamepadAxis(gamepad->num, GAMEPAD_AXIS_RIGHTY, applyDeadzone));
    		break;
    	}
    
    	return 2;
    }
    
    // Triggers
    static int gamepad_getTrigger(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	enum gamepad_leftright_e stick = luaL_checkoption(L, 2, NULL, gamepad_leftright_opt);
    	boolean applyDeadzone = luaL_opt(L, luaL_checkboolean, 3, true);
    	gamepad_axis_e axis = 0;
    
    	switch (stick)
    	{
    	case gamepad_opt_left:
    		axis = GAMEPAD_AXIS_TRIGGERLEFT;
    		break;
    	case gamepad_opt_right:
    		axis = GAMEPAD_AXIS_TRIGGERRIGHT;
    		break;
    	}
    
    	lua_pushfixed(L, G_GetAdjustedGamepadAxis(gamepad->num, axis, applyDeadzone));
    	return 1;
    }
    
    // Button and axis names
    static int gamepad_getButtonName(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	gamepad_button_e button = luaL_checkoption(L, 2, NULL, gamepad_button_names);
    	lua_pushstring(L, G_GetGamepadButtonString(gamepad->type, button, GAMEPAD_STRING_DEFAULT));
    	return 1;
    }
    
    static int gamepad_getAxisName(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	gamepad_axis_e axis = luaL_checkoption(L, 2, NULL, gamepad_axis_names);
    	lua_pushstring(L, G_GetGamepadAxisString(gamepad->type, axis, GAMEPAD_STRING_DEFAULT, false));
    	return 1;
    }
    
    static int gamepad_getTriggerName(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	enum gamepad_leftright_e stick = luaL_checkoption(L, 2, NULL, gamepad_leftright_opt);
    	gamepad_axis_e axis = 0;
    
    	switch (stick)
    	{
    	case gamepad_opt_left:
    		axis = GAMEPAD_AXIS_TRIGGERLEFT;
    		break;
    	case gamepad_opt_right:
    		axis = GAMEPAD_AXIS_TRIGGERRIGHT;
    		break;
    	}
    
    	lua_pushstring(L, G_GetGamepadAxisString(gamepad->type, axis, GAMEPAD_STRING_DEFAULT, false));
    	return 1;
    }
    
    // Rumble
    static int gamepad_doRumble(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	fixed_t large_magnitude = luaL_checkfixed(L, 2);
    	fixed_t small_magnitude = luaL_optfixed(L, 3, large_magnitude);
    	tic_t duration = luaL_optinteger(L, 4, 0);
    
    #define CHECK_MAGNITUDE(which) \
    	if (which##_magnitude < 0 || which##_magnitude > FRACUNIT) \
    		return luaL_error(L, va(#which " motor frequency %f out of range (minimum is 0.0, maximum is 1.0)", \
    			FixedToFloat(which##_magnitude)))
    
    	CHECK_MAGNITUDE(large);
    	CHECK_MAGNITUDE(small);
    
    #undef CHECK_MAGNITUDE
    
    	lua_pushboolean(L, G_RumbleGamepad(gamepad->num, large_magnitude, small_magnitude, duration));
    	return 1;
    }
    
    static int gamepad_stopRumble(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	G_StopGamepadRumble(gamepad->num);
    	return 0;
    }
    
    // Accessing gamepad userdata
    enum gamepad_opt_e {
    	gamepad_opt_connected,
    	gamepad_opt_type,
    	gamepad_opt_isXbox,
    	gamepad_opt_isPlayStation,
    	gamepad_opt_isNintendoSwitch,
    	gamepad_opt_isJoyCon,
    	gamepad_opt_hasRumble,
    	gamepad_opt_isRumbling,
    	gamepad_opt_isRumblePaused,
    	gamepad_opt_largeMotorFrequency,
    	gamepad_opt_smallMotorFrequency,
    	gamepad_opt_isButtonDown,
    	gamepad_opt_getAxis,
    	gamepad_opt_getStick,
    	gamepad_opt_getTrigger,
    	gamepad_opt_getButtonName,
    	gamepad_opt_getAxisName,
    	gamepad_opt_getTriggerName,
    	gamepad_opt_rumble,
    	gamepad_opt_stopRumble
    };
    
    static const char *const gamepad_opt[] = {
    	"connected",
    	"type",
    	"isXbox",
    	"isPlayStation",
    	"isNintendoSwitch",
    	"isJoyCon",
    	"hasRumble",
    	"isRumbling",
    	"isRumblePaused",
    	"largeMotorFrequency",
    	"smallMotorFrequency",
    	"isButtonDown",
    	"getAxis",
    	"getStick",
    	"getTrigger",
    	"getButtonName",
    	"getAxisName",
    	"getTriggerName",
    	"rumble",
    	"stopRumble",
    	NULL};
    
    static int (*gamepad_fn_list[9])(lua_State *L) = {
    	gamepad_isButtonDown,
    	gamepad_getAxis,
    	gamepad_getStick,
    	gamepad_getTrigger,
    	gamepad_getButtonName,
    	gamepad_getAxisName,
    	gamepad_getTriggerName,
    	gamepad_doRumble,
    	gamepad_stopRumble
    };
    
    static int gamepad_get(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	enum gamepad_opt_e field = luaL_checkoption(L, 2, NULL, gamepad_opt);
    
    	switch (field)
    	{
    	case gamepad_opt_connected:
    		lua_pushboolean(L, gamepad->connected);
    		break;
    	case gamepad_opt_type:
    		lua_pushstring(L, G_GamepadTypeToString(gamepad->type));
    		break;
    	case gamepad_opt_isXbox:
    		lua_pushboolean(L, G_GamepadTypeIsXbox(gamepad->type));
    		break;
    	case gamepad_opt_isPlayStation:
    		lua_pushboolean(L, G_GamepadTypeIsPlayStation(gamepad->type));
    		break;
    	case gamepad_opt_isNintendoSwitch:
    		lua_pushboolean(L, G_GamepadTypeIsNintendoSwitch(gamepad->type));
    		break;
    	case gamepad_opt_isJoyCon:
    		// No, this does not include the grip.
    		lua_pushboolean(L, G_GamepadTypeIsJoyCon(gamepad->type));
    		break;
    	case gamepad_opt_hasRumble:
    		lua_pushboolean(L, G_RumbleSupported(gamepad->num));
    		break;
    	case gamepad_opt_isRumbling:
    		lua_pushboolean(L, gamepad->rumble.active);
    		break;
    	case gamepad_opt_isRumblePaused:
    		lua_pushboolean(L, G_GetGamepadRumblePaused(gamepad->num));
    		break;
    	case gamepad_opt_largeMotorFrequency:
    		lua_pushfixed(L, G_GetLargeMotorFreq(gamepad->num));
    		break;
    	case gamepad_opt_smallMotorFrequency:
    		lua_pushfixed(L, G_GetSmallMotorFreq(gamepad->num));
    		break;
    	case gamepad_opt_isButtonDown:
    	case gamepad_opt_getAxis:
    	case gamepad_opt_getStick:
    	case gamepad_opt_getTrigger:
    	case gamepad_opt_getButtonName:
    	case gamepad_opt_getAxisName:
    	case gamepad_opt_getTriggerName:
    	case gamepad_opt_rumble:
    	case gamepad_opt_stopRumble:
    		lua_pushcfunction(L, gamepad_fn_list[field - gamepad_opt_isButtonDown]);
    		break;
    	}
    	return 1;
    }
    
    static int gamepad_set(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	enum gamepad_opt_e field = luaL_checkoption(L, 2, NULL, gamepad_opt);
    
    	switch (field)
    	{
    	case gamepad_opt_isRumblePaused:
    		G_SetGamepadRumblePaused(gamepad->num, luaL_checkboolean(L, 3));
    		break;
    	case gamepad_opt_largeMotorFrequency:
    		G_SetLargeMotorFreq(gamepad->num, luaL_checkfixed(L, 3));
    		break;
    	case gamepad_opt_smallMotorFrequency:
    		G_SetSmallMotorFreq(gamepad->num, luaL_checkfixed(L, 3));
    		break;
    	default:
    		return luaL_error(L, LUA_QL("gamepad") " field " LUA_QS " should not be set directly.", gamepad_opt[field]);
    	}
    	return 1;
    }
    
    static int gamepad_num(lua_State *L)
    {
    	gamepad_t *gamepad = *((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD));
    	lua_pushinteger(L, gamepad->num + 1);
    	return 1;
    }
    
    static int lib_iterateGamepads(lua_State *L)
    {
    	INT32 i = -1;
    	if (lua_gettop(L) < 2)
    	{
    		lua_pushcfunction(L, lib_iterateGamepads);
    		return 1;
    	}
    	lua_settop(L, 2);
    	lua_remove(L, 1); // State is unused
    	if (!lua_isnil(L, 1))
    		i = (INT32)(*((gamepad_t **)luaL_checkudata(L, 1, META_GAMEPAD)) - gamepads);
    	for (i++; i < NUM_GAMEPADS; i++)
    	{
    		if (!gamepads[i].connected)
    			continue;
    		LUA_PushUserdata(L, &gamepads[i], META_GAMEPAD);
    		return 1;
    	}
    	return 0;
    }
    
    static int lib_getGamepad(lua_State *L)
    {
    	if (lua_type(L, 2) == LUA_TNUMBER)
    	{
    		lua_Integer i = luaL_checkinteger(L, 2);
    		if (i < 1 || i > NUM_GAMEPADS)
    			return luaL_error(L, "gamepads[] index %d out of range (1 - %d)", i, NUM_GAMEPADS);
    		LUA_PushUserdata(L, &gamepads[i - 1], META_GAMEPAD);
    		return 1;
    	}
    
    	if (fastcmp(luaL_checkstring(L, 2), "iterate"))
    	{
    		lua_pushcfunction(L, lib_iterateGamepads);
    		return 1;
    	}
    
    	return 0;
    }
    
    static int lib_lenGamepad(lua_State *L)
    {
    	lua_pushinteger(L, NUM_GAMEPADS);
    	return 1;
    }
    
    ///////////
    // MOUSE //
    ///////////
    
    static int mouse_get(lua_State *L)
    {
    	mouse_t *m = *((mouse_t **)luaL_checkudata(L, 1, META_MOUSE));
    	const char *field = luaL_checkstring(L, 2);
    
    	I_Assert(m != NULL);
    
    	if (fastcmp(field,"dx"))
    		lua_pushinteger(L, m->dx);
    	else if (fastcmp(field,"dy"))
    		lua_pushinteger(L, m->dy);
    	else if (fastcmp(field,"mlookdy"))
    		lua_pushinteger(L, m->mlookdy);
    	else if (fastcmp(field,"rdx"))
    		lua_pushinteger(L, m->rdx);
    	else if (fastcmp(field,"rdy"))
    		lua_pushinteger(L, m->rdy);
    	else if (fastcmp(field,"buttons"))
    		lua_pushinteger(L, m->buttons);
    	else
    		return luaL_error(L, "mouse_t has no field named %s", field);
    
    	return 1;
    }
    
    // #mouse -> 1 or 2
    static int mouse_num(lua_State *L)
    {
    	mouse_t *m = *((mouse_t **)luaL_checkudata(L, 1, META_MOUSE));
    
    	I_Assert(m != NULL);
    
    	lua_pushinteger(L, m == &mouse ? 1 : 2);
    	return 1;
    }
    
    int LUA_InputLib(lua_State *L)
    {
    	lua_newuserdata(L, 0);
    		lua_createtable(L, 0, 2);
    			lua_pushcfunction(L, lib_getGameKeyDown);
    			lua_setfield(L, -2, "__index");
    
    			lua_pushcfunction(L, lib_setGameKeyDown);
    			lua_setfield(L, -2, "__newindex");
    
    			lua_pushcfunction(L, lib_lenGameKeyDown);
    			lua_setfield(L, -2, "__len");
    		lua_setmetatable(L, -2);
    	lua_setglobal(L, "gamekeydown");
    
    	luaL_newmetatable(L, META_KEYEVENT);
    		lua_pushcfunction(L, keyevent_get);
    		lua_setfield(L, -2, "__index");
    	lua_pop(L, 1);
    
    	luaL_newmetatable(L, META_GAMEPAD);
    		lua_pushcfunction(L, gamepad_get);
    		lua_setfield(L, -2, "__index");
    
    		lua_pushcfunction(L, gamepad_set);
    		lua_setfield(L, -2, "__newindex");
    
    		lua_pushcfunction(L, gamepad_num);
    		lua_setfield(L, -2, "__len");
    	lua_pop(L, 1);
    
    	lua_newuserdata(L, 0);
    		lua_createtable(L, 0, 2);
    			lua_pushcfunction(L, lib_getGamepad);
    			lua_setfield(L, -2, "__index");
    
    			lua_pushcfunction(L, lib_lenGamepad);
    			lua_setfield(L, -2, "__len");
    		lua_setmetatable(L, -2);
    	lua_setglobal(L, "gamepads");
    
    	luaL_newmetatable(L, META_MOUSE);
    		lua_pushcfunction(L, mouse_get);
    		lua_setfield(L, -2, "__index");
    
    		lua_pushcfunction(L, mouse_num);
    		lua_setfield(L, -2, "__len");
    	lua_pop(L, 1);
    
    	luaL_register(L, "input", lib);
    	return 0;
    }