mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2024-11-29 07:23:01 +00:00
Added hook_on_sync_table_change() to Lua API
This commit is contained in:
parent
d57606bfa3
commit
ab1e85994e
4 changed files with 282 additions and 130 deletions
|
@ -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:](#)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue