diff --git a/docs/lua/hooks.md b/docs/lua/hooks.md
index c0cb5f416..85cb941f5 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 3d001f274..363bcfc24 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 74e319e7c..5b29c690a 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 3c0623b15..9dda39f79 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();
}