From 0935eed3b7e7175b3ec343437c3e9cf4ceebab96 Mon Sep 17 00:00:00 2001 From: MysterD Date: Thu, 3 Feb 2022 19:50:27 -0800 Subject: [PATCH] Rewrite of how lua hooks chat commands --- build-windows-visual-studio/sm64ex.vcxproj | 2 + .../sm64ex.vcxproj.filters | 6 + docs/lua/constants.md | 3 +- docs/lua/hooks.md | 31 +++- mods/hide-and-seek.lua | 46 ++++++ src/pc/djui/djui_chat_box.c | 3 +- src/pc/lua/smlua_constants_autogen.c | 3 +- src/pc/lua/smlua_hooks.c | 149 +++++++++++++++--- src/pc/lua/smlua_hooks.h | 6 +- 9 files changed, 214 insertions(+), 35 deletions(-) diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index 77db1e7ef..6ff0acf03 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -532,6 +532,7 @@ + @@ -993,6 +994,7 @@ + diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters index b8e39ffc0..b79a4bffe 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj.filters +++ b/build-windows-visual-studio/sm64ex.vcxproj.filters @@ -4878,6 +4878,9 @@ Source Files\src\pc\djui\panel + + Source Files\src\pc\network + @@ -6034,5 +6037,8 @@ Source Files\src\pc\djui\panel + + Source Files\src\pc\network + \ No newline at end of file diff --git a/docs/lua/constants.md b/docs/lua/constants.md index 8a342e486..607a74b4c 100644 --- a/docs/lua/constants.md +++ b/docs/lua/constants.md @@ -1390,8 +1390,7 @@ | HOOK_ON_PVP_ATTACK | 5 | | HOOK_ON_PLAYER_CONNECTED | 6 | | HOOK_ON_PLAYER_DISCONNECTED | 7 | -| HOOK_ON_CHAT_COMMAND | 8 | -| HOOK_MAX | 9 | +| HOOK_MAX | 8 |
diff --git a/docs/lua/hooks.md b/docs/lua/hooks.md index 5d81bccaa..c0cb5f416 100644 --- a/docs/lua/hooks.md +++ b/docs/lua/hooks.md @@ -16,7 +16,6 @@ Hooks are a way for SM64 to trigger Lua code, whereas the functions listed in [f | HOOK_ON_PVP_ATTACK | Called when one player attacks another | [MarioState](structs.md#MarioState) attacker, [MarioState](structs.md#MarioState) victim | | HOOK_ON_PLAYER_CONNECTED | Called when a player connects | [MarioState](structs.md#MarioState) connector | | HOOK_ON_PLAYER_DISCONNECTED | Called when a player disconnects | [MarioState](structs.md#MarioState) disconnector | -| HOOK_ON_CHAT_COMMAND | Called when a player types a chat message starting with '/' | `string` message -> `return` `true` on valid command |
@@ -114,3 +113,33 @@ hook_mario_action(ACT_WALL_SLIDE, act_wall_slide) ``` [:arrow_up_small:](#) + +## [hook_chat_command](#hook_chat_command) +`hook_chat_command()` allows Lua mods to react and respond to chat commands. Chat commands start with the `/` character. The function the mod passes to the hook should return `true` when the command was valid and `false` otherwise. + +### Parameters + +| Field | Type | +| ----- | ---- | +| command | string | +| description | string | +| func | Lua Function | + +### Lua Example + +```lua +function on_test_command(msg) + if msg == 'on' then + djui_chat_message_create('Test: enabled') + return true + elseif msg == 'off' then + djui_chat_message_create('Test: disabled') + return true + end + return false +end + +hook_chat_command('test', "[on|off] turn test on or off", on_hide_and_seek_command) +``` + +[:arrow_up_small:](#) diff --git a/mods/hide-and-seek.lua b/mods/hide-and-seek.lua index 12277d08d..cfa33feb2 100644 --- a/mods/hide-and-seek.lua +++ b/mods/hide-and-seek.lua @@ -9,6 +9,9 @@ for i=0,(MAX_PLAYERS-1) do sCachedState[i].seeking = false end +-- globally sync enabled state +gGlobalSyncTable.hideAndSeek = true + -- keep track of round numbers for popup sCachedRoundNumber = 0 gGlobalSyncTable.roundNumber = 0 @@ -32,8 +35,10 @@ function server_update(m) local hasSeeker = false local hasHider = false local activePlayers = {} + local connectedCount = 0 for i=0,(MAX_PLAYERS-1) do if gNetworkPlayers[i].connected then + connectedCount = connectedCount + 1 table.insert(activePlayers, gPlayerSyncTable[i]) if gPlayerSyncTable[i].seeking then hasSeeker = true @@ -43,6 +48,12 @@ function server_update(m) end end + -- only change state if there are 2+ players + if connectedCount < 2 then + sStaleTimer = 0 + return + end + -- increment stale timer if not hasHider or not hasSeeker then sStaleTimer = sStaleTimer + 1 @@ -80,6 +91,11 @@ function server_update(m) end function update() + -- check gamemode enabled state + if not gGlobalSyncTable.hideAndSeek then + return + end + -- only allow the server to figure out the seeker if network_is_server() then server_update(gMarioStates[0]) @@ -133,6 +149,11 @@ function mario_local_update(m) end function mario_update(m) + -- check gamemode enabled state + if not gGlobalSyncTable.hideAndSeek then + return + end + -- this code runs for all players local s = gPlayerSyncTable[m.playerIndex] @@ -161,6 +182,11 @@ function mario_update(m) end function mario_before_phys_step(m) + -- check gamemode enabled state + if not gGlobalSyncTable.hideAndSeek then + return + end + local s = gPlayerSyncTable[m.playerIndex] -- only make seekers faster @@ -190,6 +216,11 @@ function mario_before_phys_step(m) end function on_pvp_attack(attacker, victim) + -- check gamemode enabled state + if not gGlobalSyncTable.hideAndSeek then + return + end + -- this code runs when a player attacks another player local sAttacker = gPlayerSyncTable[attacker.playerIndex] local sVictim = gPlayerSyncTable[victim.playerIndex] @@ -211,6 +242,19 @@ function on_player_connected(m) s.seeking = false end +function on_hide_and_seek_command(msg) + if msg == 'on' then + djui_chat_message_create('Hide-and-seek mod: enabled') + gGlobalSyncTable.hideAndSeek = true + return true + elseif msg == 'off' then + djui_chat_message_create('Hide-and-seek mod: disabled') + gGlobalSyncTable.hideAndSeek = false + return true + end + return false +end + ----------- -- hooks -- ----------- @@ -220,3 +264,5 @@ hook_event(HOOK_MARIO_UPDATE, mario_update) hook_event(HOOK_BEFORE_PHYS_STEP, mario_before_phys_step) hook_event(HOOK_ON_PVP_ATTACK, on_pvp_attack) hook_event(HOOK_ON_PLAYER_CONNECTED, on_player_connected) + +hook_chat_command('hide', "[on|off] turn hide-and-seek on or off", on_hide_and_seek_command) diff --git a/src/pc/djui/djui_chat_box.c b/src/pc/djui/djui_chat_box.c index 81cb2b13c..9dee5b94a 100644 --- a/src/pc/djui/djui_chat_box.c +++ b/src/pc/djui/djui_chat_box.c @@ -33,8 +33,9 @@ static void djui_chat_box_input_enter(struct DjuiInputbox* chatInput) { if (strlen(chatInput->buffer) != 0) { if (chatInput->buffer[0] == '/') { - if (!smlua_call_event_hook_on_chat_command(chatInput->buffer)) { + if (!smlua_call_chat_command_hook(chatInput->buffer)) { djui_chat_message_create("Unrecognized chat command."); + smlua_display_chat_commands(); } } else { djui_chat_message_create_from(gNetworkPlayerLocal->globalIndex, chatInput->buffer); diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index 9c95a2eb8..c512a13cf 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -1439,8 +1439,7 @@ char gSmluaConstants[] = "" "HOOK_ON_PVP_ATTACK = 5\n" "HOOK_ON_PLAYER_CONNECTED = 6\n" "HOOK_ON_PLAYER_DISCONNECTED = 7\n" -"HOOK_ON_CHAT_COMMAND = 8\n" -"HOOK_MAX = 9\n" +"HOOK_MAX = 8\n" "SPTASK_STATE_NOT_STARTED = 0\n" "SPTASK_STATE_RUNNING = 1\n" "SPTASK_STATE_INTERRUPTED = 2\n" diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index 3fb4eafaf..74e319e7c 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -1,4 +1,5 @@ #include "smlua.h" +#include "pc/djui/djui_chat_message.h" #define MAX_HOOKED_REFERENCES 64 @@ -125,31 +126,6 @@ void smlua_call_event_hooks_network_player_param(enum LuaHookedEventType hookTyp } } -bool smlua_call_event_hook_on_chat_command(char* message) { - lua_State* L = gLuaState; - if (L == NULL) { return false; } - bool ret = false; - - struct LuaHookedEvent* hook = &sHookedEvents[HOOK_ON_CHAT_COMMAND]; - for (int i = 0; i < hook->count; i++) { - // push the callback onto the stack - lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); - - // push message - lua_pushstring(L, message); - - // call the callback - if (0 != lua_pcall(L, 1, 1, 0)) { - LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); - continue; - } - - ret = ret || smlua_to_boolean(L, -1); - } - - return ret; -} - //////////////////// // hooked actions // //////////////////// @@ -172,6 +148,11 @@ int smlua_hook_mario_action(lua_State* L) { } lua_Integer action = smlua_to_integer(L, -2); + if (action == 0 || gSmLuaConvertSuccess) { + LOG_LUA("Hook Action: tried to hook invalid action"); + return 0; + } + int ref = luaL_ref(L, LUA_REGISTRYINDEX); if (ref == -1) { @@ -221,6 +202,111 @@ bool smlua_call_action_hook(struct MarioState* m, s32* returnValue) { return false; } + ///////////////////////// + // hooked chat command // +///////////////////////// + +struct LuaHookedChatCommand { + char* command; + char* description; + int reference; +}; + +#define MAX_HOOKED_CHAT_COMMANDS 64 + +static struct LuaHookedChatCommand sHookedChatCommands[MAX_HOOKED_CHAT_COMMANDS] = { 0 }; +static int sHookedChatCommandsCount = 0; + +int smlua_hook_chat_command(lua_State* L) { + if (L == NULL) { return 0; } + if (sHookedChatCommandsCount >= MAX_HOOKED_CHAT_COMMANDS) { + LOG_LUA("Hooked chat command exceeded maximum references!"); + return 0; + } + + const char* command = smlua_to_string(L, 1); + if (command == NULL || strlen(command) == 0 || !gSmLuaConvertSuccess) { + LOG_LUA("Hook chat command: tried to hook invalid command"); + return 0; + } + + const char* description = smlua_to_string(L, 2); + if (description == NULL || strlen(description) == 0 || !gSmLuaConvertSuccess) { + LOG_LUA("Hook chat command: tried to hook invalid description"); + return 0; + } + + int ref = luaL_ref(L, LUA_REGISTRYINDEX); + if (ref == -1) { + LOG_LUA("Hook chat command: tried to hook undefined function '%s'", command); + return 0; + } + + struct LuaHookedChatCommand* hooked = &sHookedChatCommands[sHookedChatCommandsCount]; + hooked->command = strdup(command); + hooked->description = strdup(description); + hooked->reference = ref; + if (!gSmLuaConvertSuccess) { return 0; } + + sHookedChatCommandsCount++; + return 1; +} + +bool smlua_call_chat_command_hook(char* command) { + lua_State* L = gLuaState; + if (L == NULL) { return false; } + for (int i = 0; i < sHookedChatCommandsCount; i++) { + struct LuaHookedChatCommand* hook = &sHookedChatCommands[i]; + size_t commandLength = strlen(hook->command); + for (size_t j = 0; j < commandLength; j++) { + if (hook->command[j] != command[j + 1]) { + goto NEXT_HOOK; + } + } + + char* params = &command[commandLength + 1]; + if (*params != '\0' && *params != ' ') { + goto NEXT_HOOK; + } + if (*params == ' ') { + params++; + } + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference); + + // push parameter + lua_pushstring(L, params); + + // call the callback + if (0 != lua_pcall(L, 1, 1, 0)) { + LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); + continue; + } + + // output the return value + bool returnValue = smlua_to_boolean(L, -1); + lua_pop(L, 1); + + if (!gSmLuaConvertSuccess) { return false; } + + return returnValue; + +NEXT_HOOK:; + } + + return false; +} + +void smlua_display_chat_commands(void) { + for (int i = 0; i < sHookedChatCommandsCount; i++) { + struct LuaHookedChatCommand* hook = &sHookedChatCommands[i]; + char msg[256] = { 0 }; + snprintf(msg, 256, "/%s %s", hook->command, hook->description); + djui_chat_message_create(msg); + } +} + ////////// // misc // ////////// @@ -238,6 +324,17 @@ static void smlua_clear_hooks(void) { sHookedMarioActions[i].reference = 0; } sHookedMarioActionsCount = 0; + + for (int i = 0; i < sHookedChatCommandsCount; i++) { + if (sHookedChatCommands[i].command != NULL) { free(sHookedChatCommands[i].command); } + sHookedChatCommands[i].command = NULL; + + if (sHookedChatCommands[i].description != NULL) { free(sHookedChatCommands[i].description); } + sHookedChatCommands[i].description = NULL; + + sHookedChatCommands[i].reference = 0; + } + sHookedChatCommandsCount = 0; } void smlua_bind_hooks(void) { @@ -246,5 +343,5 @@ void smlua_bind_hooks(void) { smlua_bind_function(L, "hook_event", smlua_hook_event); smlua_bind_function(L, "hook_mario_action", smlua_hook_mario_action); - + smlua_bind_function(L, "hook_chat_command", smlua_hook_chat_command); } diff --git a/src/pc/lua/smlua_hooks.h b/src/pc/lua/smlua_hooks.h index 855852c62..0d459f7d8 100644 --- a/src/pc/lua/smlua_hooks.h +++ b/src/pc/lua/smlua_hooks.h @@ -12,7 +12,6 @@ enum LuaHookedEventType { HOOK_ON_PVP_ATTACK, HOOK_ON_PLAYER_CONNECTED, HOOK_ON_PLAYER_DISCONNECTED, - HOOK_ON_CHAT_COMMAND, HOOK_MAX, }; @@ -25,17 +24,18 @@ static char* LuaHookedEventTypeName[] = { "HOOK_ON_PVP_ATTACK", "HOOK_ON_PLAYER_CONNECTED", "HOOK_ON_PLAYER_DISCONNECTED", - "HOOK_ON_CHAT_COMMAND", "HOOK_MAX" }; void smlua_call_event_hooks(enum LuaHookedEventType hookType); void smlua_call_event_hooks_mario_param(enum LuaHookedEventType hookType, struct MarioState* m); void smlua_call_event_hooks_mario_params(enum LuaHookedEventType hookType, struct MarioState* m1, struct MarioState* m2); -bool smlua_call_event_hook_on_chat_command(char* message); bool smlua_call_action_hook(struct MarioState* m, s32* returnValue); +bool smlua_call_chat_command_hook(char* command); +void smlua_display_chat_commands(void); + void smlua_bind_hooks(void); #endif \ No newline at end of file