diff --git a/src/core/scripting/LatentFunction.hpp b/src/core/scripting/LatentFunction.hpp index 769e634..8aa1505 100644 --- a/src/core/scripting/LatentFunction.hpp +++ b/src/core/scripting/LatentFunction.hpp @@ -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 int LatentFunction(lua_State* state) { diff --git a/src/core/scripting/LuaManager.cpp b/src/core/scripting/LuaManager.cpp index 9b6a855..74e603a 100644 --- a/src/core/scripting/LuaManager.cpp +++ b/src/core/scripting/LuaManager.cpp @@ -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; + } } \ No newline at end of file diff --git a/src/core/scripting/LuaManager.hpp b/src/core/scripting/LuaManager.hpp index 619eb15..5cf5072 100644 --- a/src/core/scripting/LuaManager.hpp +++ b/src/core/scripting/LuaManager.hpp @@ -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); + } }; } \ No newline at end of file diff --git a/src/core/scripting/LuaScript.cpp b/src/core/scripting/LuaScript.cpp index b442540..a479b1a 100644 --- a/src/core/scripting/LuaScript.cpp +++ b/src/core/scripting/LuaScript.cpp @@ -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) { diff --git a/src/core/scripting/LuaScript.hpp b/src/core/scripting/LuaScript.hpp index b5b1741..fcafa96 100644 --- a/src/core/scripting/LuaScript.hpp +++ b/src/core/scripting/LuaScript.hpp @@ -30,6 +30,8 @@ namespace YimMenu void SetTimeToResume(int millis); }; + using DispatchEventCallback = std::function; + private: LoadState m_LoadState = LoadState::EMPTY; lua_State* m_State = nullptr; @@ -40,6 +42,7 @@ namespace YimMenu std::vector m_QueuedScriptCallbacks; bool m_RunningScriptCallbacks = false; ScriptCallback* m_CurrentlyExecutingCallback = nullptr; + std::unordered_map> 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); }; } \ No newline at end of file diff --git a/src/core/scripting/libraries/Script.cpp b/src/core/scripting/libraries/Script.cpp index f26bb39..c24a928 100644 --- a/src/core/scripting/libraries/Script.cpp +++ b/src/core/scripting/libraries/Script.cpp @@ -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"); } }; diff --git a/src/game/frontend/submenus/Settings/LuaScripts.cpp b/src/game/frontend/submenus/Settings/LuaScripts.cpp index 5a9b270..51dfeee 100644 --- a/src/game/frontend/submenus/Settings/LuaScripts.cpp +++ b/src/game/frontend/submenus/Settings/LuaScripts.cpp @@ -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(); diff --git a/src/game/hooks/Network/HandleScriptedGameEvent.cpp b/src/game/hooks/Network/HandleScriptedGameEvent.cpp index 4ba2623..10456d0 100644 --- a/src/game/hooks/Network/HandleScriptedGameEvent.cpp +++ b/src/game/hooks/Network/HandleScriptedGameEvent.cpp @@ -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(event.m_Args); switch (static_cast(script_event->GetEventIndex()))