Added hook_on_sync_table_change() to Lua API

This commit is contained in:
MysterD 2022-02-04 00:15:14 -08:00
parent d57606bfa3
commit ab1e85994e
4 changed files with 282 additions and 130 deletions

View file

@ -5,7 +5,43 @@ Hooks are a way for SM64 to trigger Lua code, whereas the functions listed in [f
<br /> <br />
## [Hook Event Types](#Hook-Event-Types) ## [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:](#)
<br />
## [hook_event](#hook_event)
The lua functions sent to `hook_event()` will be automatically called by SM64 when certain events occur.
### [Hook Event Types](#Hook-Event-Types)
| Type | Description | Parameters | | Type | Description | Parameters |
| :--- | :---------- | :--------- | | :--- | :---------- | :--------- |
| HOOK_UPDATE | Called once per frame | None | | HOOK_UPDATE | Called once per frame | None |
@ -17,12 +53,6 @@ Hooks are a way for SM64 to trigger Lua code, whereas the functions listed in [f
| HOOK_ON_PLAYER_CONNECTED | Called when a player connects | [MarioState](structs.md#MarioState) connector | | 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_PLAYER_DISCONNECTED | Called when a player disconnects | [MarioState](structs.md#MarioState) disconnector |
<br />
## [hook_event](#hook_event)
The lua functions sent to `hook_event()` will be automatically called by SM64 when certain events occur.
### Parameters ### Parameters
| Field | Type | | Field | Type |
@ -114,32 +144,39 @@ hook_mario_action(ACT_WALL_SLIDE, act_wall_slide)
[:arrow_up_small:](#) [:arrow_up_small:](#)
## [hook_chat_command](#hook_chat_command) <br />
`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.
## [hook_on_sync_table_change](#hook_on_sync_table_change)
`hook_on_sync_table_change()` allows Lua mods to react to sync table changes.
- `syncTable` parameter must be a sync table, e.g. [gGlobalSyncTable](globals.md#gGlobalSyncTable), [gPlayerSyncTable[]](globals.md#gPlayerSyncTable), or one of their child tables.
- `field` parameter must be one of the fields in the `SyncTable`.
- `tag` parameter can be any type, and is automatically passed to the callback.
- `func` parameter must be a function with three parameters: `tag`, `oldVal`, and `newVal`.
- `tag` will be the same `tag` passed into `hook_on_sync_table_change()`.
- `oldVal` will be the value before it was set.
- `newVal` will be the value that it was set to.
### Parameters ### Parameters
| Field | Type | | Field | Type |
| ----- | ---- | | ----- | ---- |
| command | string | | syncTable | SyncTable |
| description | string | | field | value |
| tag | value |
| func | Lua Function | | func | Lua Function |
### Lua Example ### Lua Example
```lua ```lua
function on_test_command(msg) function on_testing_field_changed(tag, oldVal, newVal)
if msg == 'on' then print('testingField changed:', tag, ',', oldVal, '->', newVal)
djui_chat_message_create('Test: enabled')
return true
elseif msg == 'off' then
djui_chat_message_create('Test: disabled')
return true
end
return false
end end
hook_chat_command('test', "[on|off] turn test on or off", on_hide_and_seek_command) hook_on_sync_table_change(gGlobalSyncTable, 'testingField', 'tag', on_testing_field_changed)
-- now when testingField is set, either locally or over the network, on_testing_field_changed() will be called
gGlobalSyncTable.testingField = 'hello'
``` ```
[:arrow_up_small:](#) [:arrow_up_small:](#)

View file

@ -2,27 +2,19 @@
-- incompatible: gamemode -- incompatible: gamemode
-- description: A simple manhunt gamemode for Co-op.\n\nThe game is split into two teams:\n\nHiders and Seekers. The goal is for all\n\Hiders to be converted into a Seeker within a certain timeframe.\n\nAll Seekers appear as a metal character, and are given boosted speed\n\and jump height.\n\nHiders are given no enhancements, and\n\become a Seeker upon dying.\n\nEnjoy! :D\n\nConcept by: Super Keeberghrh -- description: A simple manhunt gamemode for Co-op.\n\nThe game is split into two teams:\n\nHiders and Seekers. The goal is for all\n\Hiders to be converted into a Seeker within a certain timeframe.\n\nAll Seekers appear as a metal character, and are given boosted speed\n\and jump height.\n\nHiders are given no enhancements, and\n\become a Seeker upon dying.\n\nEnjoy! :D\n\nConcept by: Super Keeberghrh
-- cache old seeking state for sounds and popups
sCachedState = {}
for i=0,(MAX_PLAYERS-1) do
sCachedState[i] = {}
sCachedState[i].seeking = false
end
-- globally sync enabled state -- globally sync enabled state
gGlobalSyncTable.hideAndSeek = true gGlobalSyncTable.hideAndSeek = true
-- keep track of round info for popup -- keep track of round info for popup
sCachedRoundNumber = 0
sCachedRoundEnded = true
gGlobalSyncTable.roundNumber = 0 gGlobalSyncTable.roundNumber = 0
gGlobalSyncTable.roundEnded = true gGlobalSyncTable.roundEnded = true
sRoundEndedTimer = 0 sRoundEndedTimer = 0
sRoundIntermissionTime = 5 * 30 -- five seconds
-- server keeps track of last player turned seeker -- server keeps track of last player turned seeker
sLastSeekerIndex = 0 sLastSeekerIndex = 0
-- keep track of distance moved recently -- keep track of distance moved recently (camping detection)
sLastPos = {} sLastPos = {}
sLastPos.x = 0 sLastPos.x = 0
sLastPos.y = 0 sLastPos.y = 0
@ -54,7 +46,7 @@ function server_update(m)
return return
end end
-- the following is round-ended code -- check to see if the round should end
if not gGlobalSyncTable.roundEnded then if not gGlobalSyncTable.roundEnded then
if not hasHider or not hasSeeker then if not hasHider or not hasSeeker then
gGlobalSyncTable.roundEnded = true gGlobalSyncTable.roundEnded = true
@ -66,7 +58,7 @@ function server_update(m)
-- if round was over for 5 seconds -- if round was over for 5 seconds
sRoundEndedTimer = sRoundEndedTimer + 1 sRoundEndedTimer = sRoundEndedTimer + 1
if sRoundEndedTimer >= 30 * 5 then if sRoundEndedTimer >= sRoundIntermissionTime then
-- reset seekers -- reset seekers
if not hasHider then if not hasHider then
for i=0,(MAX_PLAYERS-1) do for i=0,(MAX_PLAYERS-1) do
@ -95,6 +87,36 @@ function server_update(m)
end end
end end
function camping_detection(m)
-- this code only runs for the local player
local s = gPlayerSyncTable[m.playerIndex]
-- track how far the local player has moved recently
sDistanceMoved = sDistanceMoved - 0.25 + vec3f_dist(sLastPos, m.pos) * 0.02
vec3f_copy(sLastPos, m.pos)
-- clamp between 0 to 100
if sDistanceMoved < 0 then sDistanceMoved = 0 end
if sDistanceMoved > 100 then sDistanceMoved = 100 end
-- if player hasn't moved enough, start a timer
if sDistanceMoved < 10 and not s.seeking then
sDistanceTimer = sDistanceTimer + 1
end
-- if the player has moved enough, reset the timer
if sDistanceMoved > 20 then
sDistanceTimer = 0
end
-- inform the player that they need to move, or make them a seeker
if sDistanceTimer == 30 * 1 then
djui_popup_create('\\#ff4040\\Keep moving!', 3)
elseif sDistanceTimer > 30 * 6 then
s.seeking = true
end
end
function update() function update()
-- check gamemode enabled state -- check gamemode enabled state
if not gGlobalSyncTable.hideAndSeek then if not gGlobalSyncTable.hideAndSeek then
@ -106,60 +128,10 @@ function update()
server_update(gMarioStates[0]) server_update(gMarioStates[0])
end end
-- inform players when a new round has begun -- check if local player is camping
if sCachedRoundNumber < gGlobalSyncTable.roundNumber then
sCachedRoundNumber = gGlobalSyncTable.roundNumber
djui_popup_create('\\#a0ffa0\\a new round has begun', 2)
sDistanceMoved = 100
sDistanceTimer = 0
play_character_sound(gMarioStates[0], CHAR_SOUND_HERE_WE_GO)
end
-- inform players when a round has ended
if gGlobalSyncTable.roundEnded and not sCachedRoundEnded then
sCachedRoundNumber = gGlobalSyncTable.roundNumber
djui_popup_create('\\#a0a0ff\\the round has ended', 2)
end
sCachedRoundEnded = gGlobalSyncTable.roundEnded
camping_detection(gMarioStates[0]) camping_detection(gMarioStates[0])
end end
function camping_detection(m)
-- this code only runs for the local player
local s = gPlayerSyncTable[m.playerIndex]
-- become a seeker if you stop moving
sDistanceMoved = sDistanceMoved - 0.25 + vec3f_dist(sLastPos, m.pos) * 0.02
vec3f_copy(sLastPos, m.pos)
if sDistanceMoved > 100 then
sDistanceMoved = 100
end
if sDistanceMoved < 0 then
sDistanceMoved = 0
end
if sDistanceMoved < 10 and not s.seeking then
sDistanceTimer = sDistanceTimer + 1
if sDistanceTimer == 30 * 1 then
djui_popup_create('\\#ff4040\\Keep moving!', 3)
elseif sDistanceTimer > 30 * 6 then
s.seeking = true
end
end
if sDistanceMoved > 20 then
sDistanceTimer = 0
end
end
function mario_local_update(m)
-- this code only runs for the local player
local s = gPlayerSyncTable[m.playerIndex]
if m.health <= 0x110 then
s.seeking = true
end
end
function mario_update(m) function mario_update(m)
-- check gamemode enabled state -- check gamemode enabled state
if not gGlobalSyncTable.hideAndSeek then if not gGlobalSyncTable.hideAndSeek then
@ -169,9 +141,9 @@ function mario_update(m)
-- this code runs for all players -- this code runs for all players
local s = gPlayerSyncTable[m.playerIndex] local s = gPlayerSyncTable[m.playerIndex]
-- only run certain code for the local player -- if the local player died, make them a seeker
if m.playerIndex == 0 then if m.playerIndex == 0 and m.health <= 0x110 then
mario_local_update(m) s.seeking = true
end end
-- display all seekers as metal -- display all seekers as metal
@ -179,18 +151,6 @@ function mario_update(m)
m.marioBodyState.modelState = MODEL_STATE_METAL m.marioBodyState.modelState = MODEL_STATE_METAL
m.health = 0x880 m.health = 0x880
end end
-- play sound and create popup if seeker state changed
local c = sCachedState[m.playerIndex]
local np = gNetworkPlayers[m.playerIndex]
if s.seeking and not c.seeking then
play_sound(SOUND_OBJ_BOWSER_LAUGH, m.marioObj.header.gfx.cameraToObject)
playerColor = network_get_player_text_color_string(m.playerIndex)
djui_popup_create(playerColor .. np.name .. '\\#ffa0a0\\ is now a seeker', 2)
sLastSeekerIndex = m.playerIndex
end
c.seeking = s.seeking
end end
function mario_before_phys_step(m) function mario_before_phys_step(m)
@ -271,6 +231,40 @@ function on_hide_and_seek_command(msg)
return false return false
end end
-----------------------
-- network callbacks --
-----------------------
function on_round_number_changed(tag, oldVal, newVal)
-- inform players when a new round has begun
if oldVal < newVal then
djui_popup_create('\\#a0ffa0\\a new round has begun', 2)
sDistanceMoved = 100
sDistanceTimer = 0
play_character_sound(gMarioStates[0], CHAR_SOUND_HERE_WE_GO)
end
end
function on_round_ended_changed(tag, oldVal, newVal)
-- inform players when a round has ended
if newVal and not oldVal then
djui_popup_create('\\#a0a0ff\\the round has ended', 2)
end
end
function on_seeking_changed(tag, oldVal, newVal)
local m = gMarioStates[tag]
local np = gNetworkPlayers[tag]
-- play sound and create popup if became a seeker
if newVal and not oldVal then
play_sound(SOUND_OBJ_BOWSER_LAUGH, m.marioObj.header.gfx.cameraToObject)
playerColor = network_get_player_text_color_string(m.playerIndex)
djui_popup_create(playerColor .. np.name .. '\\#ffa0a0\\ is now a seeker', 2)
sLastSeekerIndex = m.playerIndex
end
end
----------- -----------
-- hooks -- -- hooks --
----------- -----------
@ -282,3 +276,10 @@ hook_event(HOOK_ON_PVP_ATTACK, on_pvp_attack)
hook_event(HOOK_ON_PLAYER_CONNECTED, on_player_connected) hook_event(HOOK_ON_PLAYER_CONNECTED, on_player_connected)
hook_chat_command('hide-and-seek', "[on|off] turn hide-and-seek on or off", on_hide_and_seek_command) hook_chat_command('hide-and-seek', "[on|off] turn hide-and-seek on or off", on_hide_and_seek_command)
-- call functions when certain sync table values change
hook_on_sync_table_change(gGlobalSyncTable, 'roundNumber', 0, on_round_number_changed)
hook_on_sync_table_change(gGlobalSyncTable, 'roundEnded', 0, on_round_ended_changed)
for i=0,(MAX_PLAYERS-1) do
hook_on_sync_table_change(gPlayerSyncTable[i], 'seeking', i, on_seeking_changed)
end

View file

@ -48,7 +48,7 @@ void smlua_call_event_hooks(enum LuaHookedEventType hookType) {
// call the callback // call the callback
if (0 != lua_pcall(L, 0, 0, 0)) { if (0 != lua_pcall(L, 0, 0, 0)) {
LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); LOG_LUA("Failed to call the event_hook callback: %u, %s", hookType, lua_tostring(L, -1));
continue; continue;
} }
} }
@ -70,7 +70,7 @@ void smlua_call_event_hooks_mario_param(enum LuaHookedEventType hookType, struct
// call the callback // call the callback
if (0 != lua_pcall(L, 1, 0, 0)) { if (0 != lua_pcall(L, 1, 0, 0)) {
LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); LOG_LUA("Failed to call the callback: %u, %s", hookType, lua_tostring(L, -1));
continue; continue;
} }
} }
@ -98,7 +98,7 @@ void smlua_call_event_hooks_mario_params(enum LuaHookedEventType hookType, struc
// call the callback // call the callback
if (0 != lua_pcall(L, 2, 0, 0)) { if (0 != lua_pcall(L, 2, 0, 0)) {
LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); LOG_LUA("Failed to call the callback: %u, %s", hookType, lua_tostring(L, -1));
continue; continue;
} }
} }
@ -120,7 +120,7 @@ void smlua_call_event_hooks_network_player_param(enum LuaHookedEventType hookTyp
// call the callback // call the callback
if (0 != lua_pcall(L, 1, 0, 0)) { if (0 != lua_pcall(L, 1, 0, 0)) {
LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); LOG_LUA("Failed to call the callback: %u, %s", hookType, lua_tostring(L, -1));
continue; continue;
} }
} }
@ -185,7 +185,7 @@ bool smlua_call_action_hook(struct MarioState* m, s32* returnValue) {
// call the callback // call the callback
if (0 != lua_pcall(L, 1, 1, 0)) { if (0 != lua_pcall(L, 1, 1, 0)) {
LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); LOG_LUA("Failed to call the action callback: %u, %s", m->action, lua_tostring(L, -1));
continue; continue;
} }
@ -280,7 +280,7 @@ bool smlua_call_chat_command_hook(char* command) {
// call the callback // call the callback
if (0 != lua_pcall(L, 1, 1, 0)) { if (0 != lua_pcall(L, 1, 1, 0)) {
LOG_LUA("Failed to call the callback: %s", lua_tostring(L, -1)); LOG_LUA("Failed to call the chat command callback: %s, %s", command, lua_tostring(L, -1));
continue; continue;
} }
@ -307,6 +307,62 @@ void smlua_display_chat_commands(void) {
} }
} }
//////////////////////////////
// hooked sync table change //
//////////////////////////////
int smlua_hook_on_sync_table_change(lua_State* L) {
LUA_STACK_CHECK_BEGIN();
if (L == NULL) { return 0; }
if(!smlua_functions_valid_param_count(L, 4)) { return 0; }
int syncTableIndex = 1;
int keyIndex = 2;
int tagIndex = 3;
int funcIndex = 4;
if (lua_type(L, syncTableIndex) != LUA_TTABLE) {
LOG_LUA("Tried to attach a non-table to hook_on_sync_table_change: %d", lua_type(L, syncTableIndex));
return 0;
}
if (lua_type(L, funcIndex) != LUA_TFUNCTION) {
LOG_LUA("Tried to attach a non-function to hook_on_sync_table_change: %d", lua_type(L, funcIndex));
return 0;
}
// set hook's table
lua_newtable(L);
int valTableIndex = lua_gettop(L);
lua_pushstring(L, "_func");
lua_pushvalue(L, funcIndex);
lua_settable(L, valTableIndex);
lua_pushstring(L, "_tag");
lua_pushvalue(L, tagIndex);
lua_settable(L, valTableIndex);
// get _hook_on_changed
lua_pushstring(L, "_hook_on_changed");
lua_rawget(L, syncTableIndex);
int hookOnChangedIndex = lua_gettop(L);
// attach
lua_pushvalue(L, keyIndex);
lua_pushvalue(L, valTableIndex);
lua_settable(L, hookOnChangedIndex);
// clean up
lua_remove(L, hookOnChangedIndex);
lua_remove(L, valTableIndex);
LUA_STACK_CHECK_END();
return 1;
}
////////// //////////
// misc // // misc //
////////// //////////
@ -344,4 +400,5 @@ void smlua_bind_hooks(void) {
smlua_bind_function(L, "hook_event", smlua_hook_event); 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_mario_action", smlua_hook_mario_action);
smlua_bind_function(L, "hook_chat_command", smlua_hook_chat_command); smlua_bind_function(L, "hook_chat_command", smlua_hook_chat_command);
smlua_bind_function(L, "hook_on_sync_table_change", smlua_hook_on_sync_table_change);
} }

View file

@ -16,6 +16,7 @@ static void smlua_sync_table_create(u16 modRemoteIndex, enum LuaSyncTableType ls
smlua_push_integer_field(t, "_type", lst); smlua_push_integer_field(t, "_type", lst);
smlua_push_table_field(t, "_seq"); smlua_push_table_field(t, "_seq");
smlua_push_table_field(t, "_table"); smlua_push_table_field(t, "_table");
smlua_push_table_field(t, "_hook_on_changed");
// set parent // set parent
lua_pushstring(L, "_parent"); lua_pushstring(L, "_parent");
@ -112,7 +113,36 @@ static bool smlua_sync_table_unwind(int syncTableIndex, int keyIndex) {
return true; return true;
} }
static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool alterSeq) { static void smlua_sync_table_call_hook(int syncTableIndex, int keyIndex, int prevValueIndex, int valueIndex) {
LUA_STACK_CHECK_BEGIN();
lua_State* L = gLuaState;
// get hook table
lua_pushstring(L, "_hook_on_changed"); lua_rawget(L, syncTableIndex);
lua_pushvalue(L, keyIndex); lua_gettable(L, -2);
lua_remove(L, -2); // pop _hook_on_changed
int hookTableIndex = lua_gettop(L);
if (lua_type(L, hookTableIndex) == LUA_TTABLE) {
// push hook func
lua_pushstring(L, "_func"); lua_gettable(L, hookTableIndex);
// push hook params
lua_pushstring(L, "_tag"); lua_gettable(L, hookTableIndex);
lua_pushvalue(L, prevValueIndex);
lua_pushvalue(L, valueIndex);
// call hook
if (0 != lua_pcall(L, 3, 0, 0)) {
LOG_LUA("Failed to call the hook_on_changed callback: %s", lua_tostring(L, -1));
}
}
lua_pop(L, 1); // pop _hook_on_changed's value
LUA_STACK_CHECK_END();
}
static bool smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool alterSeq) {
LUA_STACK_CHECK_BEGIN(); LUA_STACK_CHECK_BEGIN();
lua_State* L = gLuaState; lua_State* L = gLuaState;
@ -120,18 +150,20 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al
int keyIndex = stackIndex + 2; int keyIndex = stackIndex + 2;
int valueIndex = stackIndex + 3; int valueIndex = stackIndex + 3;
bool ret = false;
// get modRemoteIndex // get modRemoteIndex
u16 modRemoteIndex = smlua_get_integer_field(syncTableIndex, "_remoteIndex"); u16 modRemoteIndex = smlua_get_integer_field(syncTableIndex, "_remoteIndex");
if (!gSmLuaConvertSuccess) { if (!gSmLuaConvertSuccess) {
LOG_LUA("Error: tried to alter sync table with an invalid modRemoteIndex: %u", modRemoteIndex); LOG_LUA("Error: tried to alter sync table with an invalid modRemoteIndex: %u", modRemoteIndex);
return; return false;
} }
// get key // get key
struct LSTNetworkType lntKey = smlua_to_lnt(L, keyIndex); struct LSTNetworkType lntKey = smlua_to_lnt(L, keyIndex);
if (!gSmLuaConvertSuccess) { if (!gSmLuaConvertSuccess) {
LOG_LUA("Error: tried to alter sync table with an invalid key"); LOG_LUA("Error: tried to alter sync table with an invalid key");
return; return false;
} }
lntKey = lntKey; lntKey = lntKey;
@ -144,12 +176,12 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al
lua_pushvalue(L, keyIndex); lua_pushvalue(L, keyIndex);
lua_rawget(L, -2); lua_rawget(L, -2);
int prevValueType = lua_type(L, -1); int prevValueType = lua_type(L, -1);
lua_pop(L, 1); // pop prev value lua_remove(L, -2); // pop _table
lua_pop(L, 1); // pop _table int prevValueIndex = lua_gettop(L);
if (prevValueType == LUA_TTABLE) { if (prevValueType == LUA_TTABLE) {
LOG_LUA("Error: tried to assign on top of sync table"); LOG_LUA("Error: tried to assign on top of sync table");
return; goto CLEANUP_STACK;
} }
/////////// ///////////
@ -161,12 +193,12 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al
if (valueType == LUA_TTABLE) { if (valueType == LUA_TTABLE) {
if (prevValueType != LUA_TNIL) { if (prevValueType != LUA_TNIL) {
LOG_LUA("Error: tried to set a sync table field to a different sync table"); LOG_LUA("Error: tried to set a sync table field to a different sync table");
return; goto CLEANUP_STACK;
} }
if (!smlua_is_table_empty(valueIndex)) { if (!smlua_is_table_empty(valueIndex)) {
LOG_LUA("Error: tried to generate a sync table with a non-empty table"); LOG_LUA("Error: tried to generate a sync table with a non-empty table");
return; goto CLEANUP_STACK;
} }
// create sync table // create sync table
@ -179,19 +211,19 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al
lua_settable(L, -3); lua_settable(L, -3);
lua_pop(L, 1); // pop _table lua_pop(L, 1); // pop _table
LUA_STACK_CHECK_END(); ret = true;
return; goto CLEANUP_STACK;
} }
struct LSTNetworkType lntValue = smlua_to_lnt(L, valueIndex); struct LSTNetworkType lntValue = smlua_to_lnt(L, valueIndex);
if (!gSmLuaConvertSuccess) { if (!gSmLuaConvertSuccess) {
LOG_LUA("Error: tried to alter sync table with an invalid value"); LOG_LUA("Error: tried to alter sync table with an invalid value");
return; goto CLEANUP_STACK;
} }
// set value // set value
lua_getfield(L, syncTableIndex, "_table"); lua_getfield(L, syncTableIndex, "_table");
lua_pushvalue(L, -3); lua_pushvalue(L, keyIndex);
lua_pushvalue(L, -3); lua_pushvalue(L, valueIndex);
lua_settable(L, -3); lua_settable(L, -3);
lua_pop(L, 1); // pop _table lua_pop(L, 1); // pop _table
@ -224,7 +256,7 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al
// unwind key + parent tables // unwind key + parent tables
if (!smlua_sync_table_unwind(syncTableIndex, keyIndex)) { if (!smlua_sync_table_unwind(syncTableIndex, keyIndex)) {
LOG_LUA("Error: failed to unwind sync table for sending over the network"); LOG_LUA("Error: failed to unwind sync table for sending over the network");
return; goto CLEANUP_STACK;
} }
// send over the network // send over the network
@ -232,12 +264,23 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al
network_send_lua_sync_table(toLocalIndex, seq, modRemoteIndex, sUnwoundLntsCount, sUnwoundLnts, &lntValue); network_send_lua_sync_table(toLocalIndex, seq, modRemoteIndex, sUnwoundLntsCount, sUnwoundLnts, &lntValue);
} }
///////////////
// call hook //
///////////////
smlua_sync_table_call_hook(syncTableIndex, keyIndex, prevValueIndex, valueIndex);
CLEANUP_STACK:
lua_remove(L, prevValueIndex); // pop prevValue
LUA_STACK_CHECK_END(); LUA_STACK_CHECK_END();
return ret;
} }
static int smlua__set_sync_table_field(UNUSED lua_State* L) { static int smlua__set_sync_table_field(lua_State* L) {
if (!smlua_functions_valid_param_count(L, 3)) { return 0; } if (!smlua_functions_valid_param_count(L, 3)) { return 0; }
smlua_sync_table_send_field(0, 0, true); lua_pushboolean(L, smlua_sync_table_send_field(0, 0, true));
return 1; return 1;
} }
@ -277,17 +320,19 @@ void smlua_set_sync_table_field_from_network(u64 seq, u16 modRemoteIndex, u16 ln
lua_getglobal(L, "_G"); // get global table lua_getglobal(L, "_G"); // get global table
lua_getfield(L, LUA_REGISTRYINDEX, entry->path); // get the file's "global" table lua_getfield(L, LUA_REGISTRYINDEX, entry->path); // get the file's "global" table
lua_remove(L, -2); // remove global table
int fileGlobalIndex = lua_gettop(L); int fileGlobalIndex = lua_gettop(L);
// push global sync table // push global sync table
u16 syncTableSize = 1; u16 syncTableSize = 1;
smlua_push_lnt(&lntKeys[lntKeyCount - 1]); smlua_push_lnt(&lntKeys[lntKeyCount - 1]);
lua_gettable(L, fileGlobalIndex); lua_gettable(L, fileGlobalIndex);
int syncTableIndex = lua_gettop(L);
if (lua_type(L, -1) != LUA_TTABLE) { if (lua_type(L, -1) != LUA_TTABLE) {
LOG_ERROR("Received sync table field packet with an invalid table"); LOG_ERROR("Received sync table field packet with an invalid table");
return; return;
} }
lua_remove(L, fileGlobalIndex); // pop file's "global" table
int syncTableIndex = lua_gettop(L);
for (int i = lntKeyCount - 2; i >= 1; i--) { for (int i = lntKeyCount - 2; i >= 1; i--) {
// get child sync table // get child sync table
@ -341,8 +386,6 @@ void smlua_set_sync_table_field_from_network(u64 seq, u16 modRemoteIndex, u16 ln
LOG_INFO("Received outdated sync table field packet: %llu <= %llu", seq, readSeq); LOG_INFO("Received outdated sync table field packet: %llu <= %llu", seq, readSeq);
lua_pop(L, 1); // pop seq table lua_pop(L, 1); // pop seq table
lua_pop(L, syncTableSize); // pop sync table lua_pop(L, syncTableSize); // pop sync table
lua_pop(L, 1); // pop file's "global" table
lua_pop(L, 1); // pop global table
return; return;
} }
@ -355,17 +398,31 @@ void smlua_set_sync_table_field_from_network(u64 seq, u16 modRemoteIndex, u16 ln
// get internal table // get internal table
lua_pushstring(L, "_table"); lua_pushstring(L, "_table");
lua_rawget(L, -2); lua_rawget(L, -2);
int t = lua_gettop(L); int internalTableIndex = lua_gettop(L);
// get prevValue
smlua_push_lnt(&lntKeys[0]);
lua_rawget(L, internalTableIndex);
int prevValueIndex = lua_gettop(L);
// set key/value // set key/value
smlua_push_lnt(&lntKeys[0]); smlua_push_lnt(&lntKeys[0]);
smlua_push_lnt(lntValue); smlua_push_lnt(lntValue);
lua_rawset(L, t); lua_rawset(L, internalTableIndex);
// call hook
smlua_push_lnt(&lntKeys[0]);
int keyIndex = lua_gettop(L);
smlua_push_lnt(lntValue);
int valueIndex = lua_gettop(L);
smlua_sync_table_call_hook(syncTableIndex, keyIndex, prevValueIndex, valueIndex);
lua_pop(L, 1); // pop value
lua_pop(L, 1); // pop key
// cleanup
lua_pop(L, 1); // pop prevValue
lua_pop(L, 1); // pop internal table lua_pop(L, 1); // pop internal table
lua_pop(L, syncTableSize); // pop sync table lua_pop(L, syncTableSize); // pop sync table
lua_pop(L, 1); // pop file's "global" table
lua_pop(L, 1); // pop global table
LUA_STACK_CHECK_END(); LUA_STACK_CHECK_END();
} }