From ab1e85994e506c400c068f042f757e929470f8ac Mon Sep 17 00:00:00 2001 From: MysterD Date: Fri, 4 Feb 2022 00:15:14 -0800 Subject: [PATCH] Added hook_on_sync_table_change() to Lua API --- docs/lua/hooks.md | 79 ++++++++++++----- mods/hide-and-seek.lua | 157 +++++++++++++++++----------------- src/pc/lua/smlua_hooks.c | 71 +++++++++++++-- src/pc/lua/smlua_sync_table.c | 105 +++++++++++++++++------ 4 files changed, 282 insertions(+), 130 deletions(-) diff --git a/docs/lua/hooks.md b/docs/lua/hooks.md index c0cb5f41..85cb941f 100644 --- a/docs/lua/hooks.md +++ b/docs/lua/hooks.md @@ -5,7 +5,43 @@ Hooks are a way for SM64 to trigger Lua code, whereas the functions listed in [f
-## [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:](#) + +
+ +## [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 | | :--- | :---------- | :--------- | | 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_DISCONNECTED | Called when a player disconnects | [MarioState](structs.md#MarioState) disconnector | -
- -## [hook_event](#hook_event) - -The lua functions sent to `hook_event()` will be automatically called by SM64 when certain events occur. - ### Parameters | Field | Type | @@ -114,32 +144,39 @@ 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. +
+ +## [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 | Field | Type | | ----- | ---- | -| command | string | -| description | string | +| syncTable | SyncTable | +| field | value | +| tag | value | | 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 +function on_testing_field_changed(tag, oldVal, newVal) + print('testingField changed:', tag, ',', oldVal, '->', newVal) 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:](#) diff --git a/mods/hide-and-seek.lua b/mods/hide-and-seek.lua index 3d001f27..363bcfc2 100644 --- a/mods/hide-and-seek.lua +++ b/mods/hide-and-seek.lua @@ -2,27 +2,19 @@ -- 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 --- 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 gGlobalSyncTable.hideAndSeek = true -- keep track of round info for popup -sCachedRoundNumber = 0 -sCachedRoundEnded = true gGlobalSyncTable.roundNumber = 0 gGlobalSyncTable.roundEnded = true sRoundEndedTimer = 0 +sRoundIntermissionTime = 5 * 30 -- five seconds -- server keeps track of last player turned seeker sLastSeekerIndex = 0 --- keep track of distance moved recently +-- keep track of distance moved recently (camping detection) sLastPos = {} sLastPos.x = 0 sLastPos.y = 0 @@ -54,7 +46,7 @@ function server_update(m) return end - -- the following is round-ended code + -- check to see if the round should end if not gGlobalSyncTable.roundEnded then if not hasHider or not hasSeeker then gGlobalSyncTable.roundEnded = true @@ -66,7 +58,7 @@ function server_update(m) -- if round was over for 5 seconds sRoundEndedTimer = sRoundEndedTimer + 1 - if sRoundEndedTimer >= 30 * 5 then + if sRoundEndedTimer >= sRoundIntermissionTime then -- reset seekers if not hasHider then for i=0,(MAX_PLAYERS-1) do @@ -95,6 +87,36 @@ function server_update(m) 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() -- check gamemode enabled state if not gGlobalSyncTable.hideAndSeek then @@ -106,60 +128,10 @@ function update() server_update(gMarioStates[0]) end - -- inform players when a new round has begun - 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 - + -- check if local player is camping camping_detection(gMarioStates[0]) 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) -- check gamemode enabled state if not gGlobalSyncTable.hideAndSeek then @@ -169,9 +141,9 @@ function mario_update(m) -- this code runs for all players local s = gPlayerSyncTable[m.playerIndex] - -- only run certain code for the local player - if m.playerIndex == 0 then - mario_local_update(m) + -- if the local player died, make them a seeker + if m.playerIndex == 0 and m.health <= 0x110 then + s.seeking = true end -- display all seekers as metal @@ -179,18 +151,6 @@ function mario_update(m) m.marioBodyState.modelState = MODEL_STATE_METAL m.health = 0x880 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 function mario_before_phys_step(m) @@ -271,6 +231,40 @@ function on_hide_and_seek_command(msg) return false 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 -- ----------- @@ -282,3 +276,10 @@ hook_event(HOOK_ON_PVP_ATTACK, on_pvp_attack) 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) + +-- 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 diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index 74e319e7..5b29c690 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -48,7 +48,7 @@ void smlua_call_event_hooks(enum LuaHookedEventType hookType) { // call the callback 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; } } @@ -70,7 +70,7 @@ void smlua_call_event_hooks_mario_param(enum LuaHookedEventType hookType, struct // call the callback 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; } } @@ -98,7 +98,7 @@ void smlua_call_event_hooks_mario_params(enum LuaHookedEventType hookType, struc // call the callback 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; } } @@ -120,7 +120,7 @@ void smlua_call_event_hooks_network_player_param(enum LuaHookedEventType hookTyp // call the callback 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; } } @@ -182,10 +182,10 @@ bool smlua_call_action_hook(struct MarioState* m, s32* returnValue) { lua_pushinteger(L, m->playerIndex); lua_gettable(L, -2); lua_remove(L, -2); - + // call the callback 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; } @@ -280,7 +280,7 @@ bool smlua_call_chat_command_hook(char* command) { // call the callback 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; } @@ -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 // ////////// @@ -344,4 +400,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); + smlua_bind_function(L, "hook_on_sync_table_change", smlua_hook_on_sync_table_change); } diff --git a/src/pc/lua/smlua_sync_table.c b/src/pc/lua/smlua_sync_table.c index 3c0623b1..9dda39f7 100644 --- a/src/pc/lua/smlua_sync_table.c +++ b/src/pc/lua/smlua_sync_table.c @@ -16,6 +16,7 @@ static void smlua_sync_table_create(u16 modRemoteIndex, enum LuaSyncTableType ls smlua_push_integer_field(t, "_type", lst); smlua_push_table_field(t, "_seq"); smlua_push_table_field(t, "_table"); + smlua_push_table_field(t, "_hook_on_changed"); // set parent lua_pushstring(L, "_parent"); @@ -112,7 +113,36 @@ static bool smlua_sync_table_unwind(int syncTableIndex, int keyIndex) { 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_State* L = gLuaState; @@ -120,21 +150,23 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al int keyIndex = stackIndex + 2; int valueIndex = stackIndex + 3; + bool ret = false; + // get modRemoteIndex u16 modRemoteIndex = smlua_get_integer_field(syncTableIndex, "_remoteIndex"); if (!gSmLuaConvertSuccess) { LOG_LUA("Error: tried to alter sync table with an invalid modRemoteIndex: %u", modRemoteIndex); - return; + return false; } // get key struct LSTNetworkType lntKey = smlua_to_lnt(L, keyIndex); if (!gSmLuaConvertSuccess) { LOG_LUA("Error: tried to alter sync table with an invalid key"); - return; + return false; } lntKey = lntKey; - + //////////////// // prev value // @@ -144,12 +176,12 @@ static void smlua_sync_table_send_field(u8 toLocalIndex, int stackIndex, bool al lua_pushvalue(L, keyIndex); lua_rawget(L, -2); int prevValueType = lua_type(L, -1); - lua_pop(L, 1); // pop prev value - lua_pop(L, 1); // pop _table + lua_remove(L, -2); // pop _table + int prevValueIndex = lua_gettop(L); if (prevValueType == LUA_TTABLE) { 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 (prevValueType != LUA_TNIL) { 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)) { LOG_LUA("Error: tried to generate a sync table with a non-empty table"); - return; + goto CLEANUP_STACK; } // 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_pop(L, 1); // pop _table - LUA_STACK_CHECK_END(); - return; + ret = true; + goto CLEANUP_STACK; } struct LSTNetworkType lntValue = smlua_to_lnt(L, valueIndex); if (!gSmLuaConvertSuccess) { LOG_LUA("Error: tried to alter sync table with an invalid value"); - return; + goto CLEANUP_STACK; } // set value lua_getfield(L, syncTableIndex, "_table"); - lua_pushvalue(L, -3); - lua_pushvalue(L, -3); + lua_pushvalue(L, keyIndex); + lua_pushvalue(L, valueIndex); lua_settable(L, -3); 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 if (!smlua_sync_table_unwind(syncTableIndex, keyIndex)) { LOG_LUA("Error: failed to unwind sync table for sending over the network"); - return; + goto CLEANUP_STACK; } // 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); } + + /////////////// + // call hook // + /////////////// + + smlua_sync_table_call_hook(syncTableIndex, keyIndex, prevValueIndex, valueIndex); + + +CLEANUP_STACK: + lua_remove(L, prevValueIndex); // pop prevValue 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; } - smlua_sync_table_send_field(0, 0, true); + lua_pushboolean(L, smlua_sync_table_send_field(0, 0, true)); 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_getfield(L, LUA_REGISTRYINDEX, entry->path); // get the file's "global" table + lua_remove(L, -2); // remove global table int fileGlobalIndex = lua_gettop(L); // push global sync table u16 syncTableSize = 1; smlua_push_lnt(&lntKeys[lntKeyCount - 1]); lua_gettable(L, fileGlobalIndex); - int syncTableIndex = lua_gettop(L); if (lua_type(L, -1) != LUA_TTABLE) { LOG_ERROR("Received sync table field packet with an invalid table"); return; } + lua_remove(L, fileGlobalIndex); // pop file's "global" table + int syncTableIndex = lua_gettop(L); for (int i = lntKeyCount - 2; i >= 1; i--) { // 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); lua_pop(L, 1); // pop seq 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; } @@ -355,17 +398,31 @@ void smlua_set_sync_table_field_from_network(u64 seq, u16 modRemoteIndex, u16 ln // get internal table lua_pushstring(L, "_table"); 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 smlua_push_lnt(&lntKeys[0]); 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, syncTableSize); // pop sync table - lua_pop(L, 1); // pop file's "global" table - lua_pop(L, 1); // pop global table LUA_STACK_CHECK_END(); }