Rewrite of how lua hooks chat commands

This commit is contained in:
MysterD 2022-02-03 19:50:27 -08:00
parent 4bf4c476e6
commit 0935eed3b7
9 changed files with 214 additions and 35 deletions

View file

@ -532,6 +532,7 @@
<ClCompile Include="..\src\pc\network\discord\user.c" />
<ClCompile Include="..\src\pc\network\network.c" />
<ClCompile Include="..\src\pc\network\network_player.c" />
<ClCompile Include="..\src\pc\network\network_utils.c" />
<ClCompile Include="..\src\pc\network\packets\packet.c" />
<ClCompile Include="..\src\pc\network\packets\packet_area.c" />
<ClCompile Include="..\src\pc\network\packets\packet_area_request.c" />
@ -993,6 +994,7 @@
<ClInclude Include="..\src\pc\network\discord\user.h" />
<ClInclude Include="..\src\pc\network\network.h" />
<ClInclude Include="..\src\pc\network\network_player.h" />
<ClInclude Include="..\src\pc\network\network_utils.h" />
<ClInclude Include="..\src\pc\network\packets\packet.h" />
<ClInclude Include="..\src\pc\network\reservation_area.h" />
<ClInclude Include="..\src\pc\network\socket\socket.h" />

View file

@ -4878,6 +4878,9 @@
<ClCompile Include="..\src\pc\djui\djui_panel_controls_extra.c">
<Filter>Source Files\src\pc\djui\panel</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\network_utils.c">
<Filter>Source Files\src\pc\network</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\actors\common0.h">
@ -6034,5 +6037,8 @@
<ClInclude Include="..\src\pc\djui\djui_panel_controls_extra.h">
<Filter>Source Files\src\pc\djui\panel</Filter>
</ClInclude>
<ClInclude Include="..\src\pc\network\network_utils.h">
<Filter>Source Files\src\pc\network</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -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 |
<br />

View file

@ -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 |
<br />
@ -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:](#)

View file

@ -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)

View file

@ -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);

View file

@ -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"

View file

@ -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);
}

View file

@ -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