From f0c3de69765841d322b15d4487881c84fb2302ed Mon Sep 17 00:00:00 2001 From: Jaime Ita Passos Date: Mon, 14 Nov 2022 02:18:24 -0300 Subject: [PATCH] Add gamepad library and hooks for Lua scripting --- src/g_game.c | 11 ++ src/g_game.h | 3 + src/lua_baselib.c | 1 + src/lua_hook.h | 6 + src/lua_hooklib.c | 23 +++ src/lua_inputlib.c | 372 ++++++++++++++++++++++++++++++++++++++++++++ src/lua_libs.h | 1 + src/lua_script.c | 18 +++ src/lua_script.h | 1 + src/sdl/i_gamepad.c | 8 +- 10 files changed, 442 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 911ba19fa..c4767bad9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1808,8 +1808,15 @@ INT32 G_GetGamepadDeviceIndex(INT32 player) return cv_usegamepad[player].value; } +void G_OnGamepadConnect(UINT8 which) +{ + LUA_HookGamepadEvent(which, HOOK(GamepadAdded)); +} + void G_OnGamepadDisconnect(UINT8 which) { + LUA_HookGamepadEvent(which, HOOK(GamepadRemoved)); + if (!cv_gamepad_autopause.value) return; @@ -2320,6 +2327,10 @@ boolean G_LuaResponder(event_t *ev) cancelled = LUA_HookKey(ev, HOOK(KeyUp)); LUA_InvalidateUserdata(ev); } + else if (ev->type == ev_gamepad_down) + cancelled = LUA_HookGamepadButton(ev, HOOK(GamepadButtonDown)); + else if (ev->type == ev_gamepad_up) + cancelled = LUA_HookGamepadButton(ev, HOOK(GamepadButtonUp)); return cancelled; } diff --git a/src/g_game.h b/src/g_game.h index 6c24054a0..e798176af 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -133,6 +133,9 @@ INT32 G_GetGamepadDeviceIndex(INT32 player); // returns a player's gamepad index INT16 G_GetGamepadForPlayer(player_t *player); +// called when a player's gamepad is connected +void G_OnGamepadConnect(UINT8 which); + // called when a player's gamepad is disconnected void G_OnGamepadDisconnect(UINT8 which); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 29adb478a..6212a77f0 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -220,6 +220,7 @@ static const struct { {META_LUABANKS, "luabanks[]"}, {META_KEYEVENT, "keyevent_t"}, + {META_GAMEPAD, "gamepad_t"}, {META_MOUSE, "mouse_t"}, {NULL, NULL} }; diff --git a/src/lua_hook.h b/src/lua_hook.h index 5a14294c3..37a38e056 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -74,6 +74,10 @@ automatically. X (PlayerCanEnterSpinGaps),\ X (KeyDown),\ X (KeyUp),\ + X (GamepadButtonDown),\ + X (GamepadButtonUp),\ + X (GamepadAdded),\ + X (GamepadRemoved),\ #define STRING_HOOK_LIST(X) \ X (BotAI),/* B_BuildTailsTiccmd by skin name */\ @@ -125,6 +129,8 @@ void LUA_HookBool(boolean value, int hook); int LUA_HookPlayer(player_t *, int hook); int LUA_HookTiccmd(player_t *, ticcmd_t *, int hook); int LUA_HookKey(event_t *event, int hook); // Hooks for key events +int LUA_HookGamepadButton(event_t *event, int hook); +void LUA_HookGamepadEvent(UINT8 which, int hook); void LUA_HookThinkFrame(void); int LUA_HookMobjLineCollide(mobj_t *, line_t *); diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 0b24b7b53..6506d3dc6 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -14,6 +14,7 @@ #include "doomstat.h" #include "p_mobj.h" #include "g_game.h" +#include "g_input.h" #include "r_skins.h" #include "b_bot.h" #include "z_zone.h" @@ -641,6 +642,28 @@ int LUA_HookKey(event_t *event, int hook_type) return hook.status; } +int LUA_HookGamepadButton(event_t *event, int hook_type) +{ + Hook_State hook; + if (prepare_hook(&hook, false, hook_type)) + { + LUA_PushUserdata(gL, &gamepads[event->which], META_GAMEPAD); + lua_pushstring(gL, gamepad_button_names[event->key]); + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +void LUA_HookGamepadEvent(UINT8 which, int hook_type) +{ + Hook_State hook; + if (prepare_hook(&hook, 0, hook_type)) + { + LUA_PushUserdata(gL, &gamepads[which], META_GAMEPAD); + call_hooks(&hook, 0, res_none); + } +} + void LUA_HookHUD(int hook_type, huddrawlist_h list) { const hook_t * map = &hudHookIds[hook_type]; diff --git a/src/lua_inputlib.c b/src/lua_inputlib.c index 88efc3490..e4962a0fb 100644 --- a/src/lua_inputlib.c +++ b/src/lua_inputlib.c @@ -129,6 +129,21 @@ static int lib_getCursorPosition(lua_State *L) 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}, @@ -143,6 +158,7 @@ static luaL_Reg lib[] = { {"getMouseGrab", lib_getMouseGrab}, {"setMouseGrab", lib_setMouseGrab}, {"getCursorPosition", lib_getCursorPosition}, + {"getPlayerGamepad", lib_getPlayerGamepad}, {NULL, NULL} }; @@ -198,6 +214,341 @@ static int keyevent_get(lua_State *L) 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 // /////////// @@ -258,6 +609,27 @@ int LUA_InputLib(lua_State *L) 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"); diff --git a/src/lua_libs.h b/src/lua_libs.h index b4a891edb..68e976234 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -91,6 +91,7 @@ extern boolean mousegrabbedbylua; #define META_LUABANKS "LUABANKS[]*" #define META_KEYEVENT "KEYEVENT_T*" +#define META_GAMEPAD "GAMEPAD_T*" #define META_MOUSE "MOUSE_T*" boolean luaL_checkboolean(lua_State *L, int narg); diff --git a/src/lua_script.c b/src/lua_script.c index 4d4071545..87b0cbfba 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -949,6 +949,7 @@ enum ARCH_MAPHEADER, ARCH_SKINCOLOR, ARCH_MOUSE, + ARCH_GAMEPAD, ARCH_TEND=0xFF, }; @@ -976,6 +977,7 @@ static const struct { {META_SLOPE, ARCH_SLOPE}, {META_MAPHEADER, ARCH_MAPHEADER}, {META_SKINCOLOR, ARCH_SKINCOLOR}, + {META_GAMEPAD, ARCH_GAMEPAD}, {META_MOUSE, ARCH_MOUSE}, {NULL, ARCH_NULL} }; @@ -1291,6 +1293,13 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex) WRITEUINT16(save_p, info - skincolors); break; } + case ARCH_GAMEPAD: + { + gamepad_t *gamepad = *((gamepad_t **)lua_touserdata(gL, myindex)); + WRITEUINT8(save_p, ARCH_GAMEPAD); + WRITEUINT8(save_p, gamepad->num); + break; + } case ARCH_MOUSE: { mouse_t *m = *((mouse_t **)lua_touserdata(gL, myindex)); @@ -1541,6 +1550,15 @@ static UINT8 UnArchiveValue(int TABLESINDEX) case ARCH_SKINCOLOR: LUA_PushUserdata(gL, &skincolors[READUINT16(save_p)], META_SKINCOLOR); break; + case ARCH_GAMEPAD: + { + UINT8 which = READUINT8(save_p); + if (which < NUM_GAMEPADS) + LUA_PushUserdata(gL, &gamepads[which], META_GAMEPAD); + else // Wait, what? + lua_pushnil(gL); + break; + } case ARCH_MOUSE: LUA_PushUserdata(gL, READUINT16(save_p) == 1 ? &mouse : &mouse2, META_MOUSE); break; diff --git a/src/lua_script.h b/src/lua_script.h index e586b04a8..080427039 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -30,6 +30,7 @@ // TODO add some distinction between fixed numbers and integer numbers // for at least the purpose of printing and maybe math. #define luaL_checkfixed(L, i) luaL_checkinteger(L, i) +#define luaL_optfixed(L, i, o) luaL_optinteger(L, i, o) #define lua_pushfixed(L, f) lua_pushinteger(L, f) // angle_t casting diff --git a/src/sdl/i_gamepad.c b/src/sdl/i_gamepad.c index c3150ac6e..8da58f03a 100644 --- a/src/sdl/i_gamepad.c +++ b/src/sdl/i_gamepad.c @@ -233,7 +233,7 @@ static boolean Controller_OpenDevice(UINT8 which, INT32 devindex) CONS_Debug(DBG_GAMELOGIC, M_GetText(" Type: %s\n"), G_GamepadTypeToString(controller->info->type)); // Change the ring LEDs on Xbox 360 controllers - // TODO: Doesn't seem to work? + // FIXME: Doesn't seem to work? SDL_GameControllerSetPlayerIndex(controller->dev, which); // Check if rumble is supported @@ -248,7 +248,11 @@ static boolean Controller_OpenDevice(UINT8 which, INT32 devindex) CONS_Debug(DBG_GAMELOGIC, M_GetText(" Rumble supported: No\n"));; } - controller->info->connected = true; + if (!controller->info->connected) + { + controller->info->connected = true; + G_OnGamepadConnect(which); + } } return controller->started;