feat(lua): implement events

This commit is contained in:
maybegreat48
2025-06-26 14:44:15 -04:00
parent c9e4e74acf
commit 1370ac1cac
8 changed files with 122 additions and 4 deletions
+1
View File
@@ -5,6 +5,7 @@ namespace YimMenu::Lua
{
int LatentFunctionInternal(lua_State* state, lua_CFunction func);
// a latent function is a function that can yield
template<lua_CFunction function>
int LatentFunction(lua_State* state)
{
+24 -1
View File
@@ -95,10 +95,16 @@ namespace YimMenu
continue;
for (auto& script : m_LoadedScripts)
{
if (std::filesystem::equivalent(script->GetPath(), entry.path().string()))
continue;
{
// continue;
goto next;
}
}
AddUnloadedScript(entry.path().filename().string(), entry.path().string());
next:
}
m_LastRefreshedUnloadedScripts = std::chrono::system_clock::now();
}
@@ -144,4 +150,21 @@ namespace YimMenu
for (auto& script : m_UnloadedScripts)
callback(script);
}
bool LuaManager::DispatchEventImpl(std::uint32_t event, const LuaScript::DispatchEventCallback& add_arguments_cb, bool handle_result)
{
auto result = true;
for (auto& script : m_LoadedScripts)
{
if (script->IsValid())
{
result = (bool)(((int)result) & ((int)script->DispatchEvent(event, add_arguments_cb, handle_result)));
if (!result && handle_result)
return false;
}
}
return result;
}
}
+8
View File
@@ -42,6 +42,7 @@ namespace YimMenu
void SetRunningCoroutineImpl(lua_State* script);
void ForAllLoadedScriptsImpl(ForAllLoadedScriptsCallback callback);
void ForAllUnloadedScriptsImpl(ForAllUnloadedScriptsCallback callback);
bool DispatchEventImpl(std::uint32_t event, const LuaScript::DispatchEventCallback& add_arguments_cb, bool handle_result = false);
static LuaManager& GetInstance()
{
@@ -95,5 +96,12 @@ namespace YimMenu
{
GetInstance().ForAllUnloadedScriptsImpl(callback);
}
// if handle_result is true, the event will be blocked when a callback returns false
// events MUST be dispatched from the main thread
static bool DispatchEvent(std::uint32_t event, const LuaScript::DispatchEventCallback& add_arguments_cb, bool handle_result = false)
{
return GetInstance().DispatchEventImpl(event, add_arguments_cb, handle_result);
}
};
}
+43 -2
View File
@@ -119,8 +119,9 @@ namespace YimMenu
lua_setfield(m_State, LUA_REGISTRYINDEX, "context");
LuaManager::LoadLibraries(m_State);
if (luaL_loadfilex(m_State, file_name.data(), "t") != LUA_OK) // don't load binary chunks
auto result = luaL_loadfilex(m_State, file_name.data(), "t");
if (result != LUA_OK) // don't load binary chunks
{
auto error = lua_tostring(m_State, -1);
LOGF(FATAL, "{}: {}", m_ModuleName, error);
@@ -263,6 +264,46 @@ namespace YimMenu
std::ranges::move(m_QueuedScriptCallbacks, std::back_inserter(m_ScriptCallbacks));
m_QueuedScriptCallbacks.clear();
}
void LuaScript::AddEventHandler(std::uint32_t event, int handler)
{
if (auto it = m_EventHandlers.find(event); it != m_EventHandlers.end())
it->second.push_back(handler);
else
m_EventHandlers.emplace(event, std::vector{handler});
}
bool LuaScript::DispatchEvent(std::uint32_t event, const DispatchEventCallback& add_arguments_cb, bool handle_result)
{
bool result = true;
if (auto it = m_EventHandlers.find(event); it != m_EventHandlers.end())
{
for (auto& handler : it->second)
{
lua_rawgeti(m_State, LUA_REGISTRYINDEX, handler);
auto num_args = add_arguments_cb(m_State);
if (CallFunction(num_args, 1))
{
if (!lua_isnoneornil(m_State, -1))
{
if (lua_toboolean(m_State, -1) == false)
{
result = false;
}
}
lua_pop(m_State, 1);
}
if (!result && handle_result)
return false;
}
}
return result;
}
void LuaScript::ScriptCallback::SetTimeToResume(int millis)
{
+6
View File
@@ -30,6 +30,8 @@ namespace YimMenu
void SetTimeToResume(int millis);
};
using DispatchEventCallback = std::function<int(lua_State* state)>;
private:
LoadState m_LoadState = LoadState::EMPTY;
lua_State* m_State = nullptr;
@@ -40,6 +42,7 @@ namespace YimMenu
std::vector<ScriptCallback> m_QueuedScriptCallbacks;
bool m_RunningScriptCallbacks = false;
ScriptCallback* m_CurrentlyExecutingCallback = nullptr;
std::unordered_map<std::uint32_t, std::vector<int>> m_EventHandlers;
// Calls the function at the top of stack. If this returns false the stack would have nothing on it
bool CallFunction(int n_args, int n_results, lua_State* override_state = nullptr);
@@ -127,5 +130,8 @@ namespace YimMenu
{
return m_CurrentlyExecutingCallback;
}
void AddEventHandler(std::uint32_t event, int handler);
bool DispatchEvent(std::uint32_t event, const DispatchEventCallback& add_arguments_cb, bool handle_result = false);
};
}
+15
View File
@@ -1,5 +1,6 @@
#include "core/scripting/LuaLibrary.hpp"
#include "core/scripting/LuaScript.hpp"
#include "core/util/Joaat.hpp"
namespace YimMenu::Lua
{
@@ -50,6 +51,17 @@ namespace YimMenu::Lua
return 1;
}
static int RegisterEventHandler(lua_State* state)
{
auto event_name = luaL_checkstring(state, 1);
luaL_checktype(state, 2, LUA_TFUNCTION);
lua_pushvalue(state, 2);
int handler = luaL_ref(state, LUA_REGISTRYINDEX);
LuaScript::GetScript(state).AddEventHandler(Joaat(event_name), handler);
return 0;
}
virtual void Register(lua_State* state) override
{
lua_newtable(state);
@@ -63,6 +75,9 @@ namespace YimMenu::Lua
lua_pushcfunction(state, IsInsideCallback);
lua_setfield(state, -2, "is_inside_callback");
lua_pushcfunction(state, RegisterEventHandler);
lua_setfield(state, -2, "register_event_handler");
lua_setglobal(state, "script");
}
};
@@ -59,7 +59,7 @@ namespace YimMenu::Submenus
ImGui::Text("%s", selectedScript->GetName().data());
bool paused = selectedScript->GetLoadState() == LuaScript::LoadState::PAUSED;
if (ImGui::Button(paused ? "Pause" : "Resume"))
if (ImGui::Button(paused ? "Resume" : "Pause"))
{
if (paused)
selectedScript->Resume();
@@ -5,11 +5,35 @@
#include "types/script/globals/GPBD_FM_3.hpp"
#include "types/script/globals/GlobalPlayerBD.hpp"
#include "types/script/ScriptEvent.hpp"
#include "core/scripting/LuaManager.hpp"
#include "core/util/Joaat.hpp"
namespace YimMenu::Hooks
{
static bool CheckLuaScripts(Player player, CScriptedGameEvent& event)
{
return LuaManager::DispatchEvent("menu.script_event_received"_J, [player, &event](lua_State* state)
{
// TODO: pass a Player instance
lua_pushinteger(state, player.GetId());
lua_newtable(state);
auto length = event.m_ArgsSize / 8;
for (int i = 0; i < length; i++)
{
lua_pushinteger(state, i == 0 ? (ptrdiff_t)(int)event.m_Args[i] : event.m_Args[i]);
lua_rawseti(state, -2, i + 1);
}
return 2;
}, true);
}
bool Network::HandleScriptedGameEvent(Player player, CScriptedGameEvent& event)
{
if (!CheckLuaScripts(player, event))
return false;
SCRIPT_EVENT* script_event = reinterpret_cast<SCRIPT_EVENT*>(event.m_Args);
switch (static_cast<ScriptEventIndex>(script_event->GetEventIndex()))