feat(lua): some more changes (incomplete)

This commit is contained in:
maybegreat48 committed 2025-07-01 15:58:54 -04:00
1 parent 1fcdc0f028
commit b5a0bbbb3f
11 files changed
+407 -16

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
#include "LuaConfig.hpp"
#include "core/filemgr/FileMgr.hpp"
namespace YimMenu
{
LuaConfig::LuaConfig(std::string_view script_name) :
m_ConfigFile(FileMgr::GetProjectFile(std::string("./scripts/config/") + script_name.data() + ".json")),
m_Dirty(false)
{
}
void LuaConfig::Load()
{
if (m_ConfigFile.Exists())
{
try
{
std::ifstream stream(m_ConfigFile.Path());
nlohmann::json json;
stream >> json;
stream.close();
for (auto& [key, value] : json["item_states"].items())
{
auto as_uint = std::stol(key.c_str());
m_ItemStates.emplace(as_uint, value);
}
}
catch (const std::exception& e)
{
LOGF(FATAL, "Error loading config file {}: {}", m_ConfigFile.Path().filename().string(), e.what());
std::filesystem::remove(m_ConfigFile.Path()); // TODO: this is probably a bad idea
}
}
}
void LuaConfig::Update()
{
if (!m_Dirty)
return;
try
{
std::lock_guard lock(m_Mutex);
nlohmann::json json;
json["item_states"] = nlohmann::json::object();
for (auto& [key, value] : m_ItemStates)
{
json[std::to_string(key)] = value;
}
std::ofstream stream(m_ConfigFile.Path());
stream << json;
stream.close();
}
catch (const std::exception& e)
{
LOGF(FATAL, "Error saving config file {}: {}", m_ConfigFile.Path().filename().string(), e.what());
}
}
std::optional<nlohmann::json> LuaConfig::GetItemState(std::uint32_t hash)
{
// no need to lock when we're just reading from the array
if (auto it = m_ItemStates.find(hash); it != m_ItemStates.end())
return it->second;
else
return std::nullopt;
}
void LuaConfig::SetItemState(std::uint32_t hash, nlohmann::json value)
{
std::lock_guard lock(m_Mutex);
if (auto it = m_ItemStates.find(hash); it != m_ItemStates.end())
it->second = value;
else
m_ItemStates.emplace(hash, value);
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "core/filemgr/File.hpp"
namespace YimMenu
{
class LuaConfig
{
bool m_Dirty;
File m_ConfigFile;
std::mutex m_Mutex;
std::unordered_map<std::uint32_t, nlohmann::json> m_ItemStates;
public:
LuaConfig(std::string_view script_name);
// call when a script is loaded
void Load();
// should be called every now and then (10s?) and before unload
void Update();
std::optional<nlohmann::json> GetItemState(std::uint32_t hash);
void SetItemState(std::uint32_t hash, nlohmann::json value);
};
}
+10 -4
View File
@@ -2,6 +2,8 @@
#include "core/filemgr/FileMgr.hpp"
#include "core/backend/ScriptMgr.hpp"
#include "core/frontend/Notifications.hpp"
#include "types/script/scrThread.hpp"
#include "core/util/Joaat.hpp"
namespace YimMenu
{
@@ -129,10 +131,14 @@ namespace YimMenu
}
}
// 4) run tick coroutines
for (auto& script : m_LoadedScripts)
if (script->IsRunning())
script->Tick();
// don't run stuff while we're starting up
if (rage::scrThread::GetRunningThread()->m_ScriptHash != "startup"_J)
{
// 4) run tick coroutines
for (auto& script : m_LoadedScripts)
if (script->IsRunning())
script->Tick();
}
ScriptMgr::Yield();
}
+11 -2
View File
@@ -145,7 +145,8 @@ namespace YimMenu
LuaScript::LuaScript(std::string_view file_name) :
m_FileName(file_name),
m_ModuleName(std::filesystem::path(file_name).filename().string())
m_ModuleName(std::filesystem::path(file_name).filename().string()),
m_Config(std::filesystem::path(file_name).stem().string())
{
m_Resources.resize(LuaManager::GetNumResourceTypes());
m_State = luaL_newstate();
@@ -219,8 +220,16 @@ namespace YimMenu
return *script;
}
void LuaScript::AddScriptCallback(int coro_handle)
void LuaScript::AddScriptCallback(int func_handle)
{
lua_rawgeti(m_State, LUA_REGISTRYINDEX, func_handle);
lua_State* coro_state = lua_newthread(m_State);
lua_pushvalue(m_State, 1); // xmove can only move from top of stack, so we have to push the function again even if it's already in the stack
lua_xmove(m_State, coro_state, 1);
auto coro_handle = luaL_ref(m_State, LUA_REGISTRYINDEX);
ScriptCallback callback;
callback.m_Coroutine = coro_handle;
callback.m_LastYieldFromCode = false;
+10 -2
View File
@@ -1,6 +1,8 @@
#pragma once
#include "lua.hpp"
#include "LuaResource.hpp"
#include "LuaConfig.hpp"
#include "LuaUserInterface.hpp"
namespace YimMenu
{
@@ -45,6 +47,8 @@ namespace YimMenu
ScriptCallback* m_CurrentlyExecutingCallback = nullptr;
std::unordered_map<std::uint32_t, std::vector<int>> m_EventHandlers;
std::vector<std::vector<std::shared_ptr<LuaResource>>> m_Resources; // yes, it's a shared pointer stored in a vector of resources stored in a vector of resource types TODO: can we just use raw pointers or even store the resource directly in that array?
LuaConfig m_Config;
LuaUserInterface m_Interface;
// 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);
@@ -110,8 +114,7 @@ namespace YimMenu
// we're guaranteed to have a LuaScript for each lua_State, so we can return it as a reference
static LuaScript& GetScript(lua_State* state);
void AddScriptCallback(int coro_handle);
void AddScriptCallback(int func_handle);
// must be called from a coroutine
void Yield(lua_State* state, int millis = 0, bool from_code = true);
@@ -130,5 +133,10 @@ namespace YimMenu
void AddResource(std::shared_ptr<LuaResource>&& resource, int idx);
int GetNumResourcesOfType(int type);
std::vector<std::shared_ptr<LuaResource>>& GetAllResourcesOfType(int idx);
LuaUserInterface& GetUserInterface()
{
return m_Interface;
}
};
}
+9
View File
@@ -0,0 +1,9 @@
#include "LuaUserInterface.hpp"
namespace YimMenu::Lua
{
UIElementBase::UIElementBase(LuaUserInterface& interface) :
m_Interface(interface)
{
}
}
+122
View File
@@ -0,0 +1,122 @@
#pragma once
/*
something like
menu.set_menu_name("Test Interface Menu")
root = menu.get_root()
root:add_button("testbtn", "Test Button", function()
notify.info("Test", "Hello")
end)
root:add_looped_checkbox("testbox", "Test Checkbox", function()
-- on tick
end, function()
-- on enable
end, function()
-- on disable
end, [should_serialize=true])
x = root:add_group("Name", 3)
y = root:add_tab_group
*/
namespace YimMenu
{
class LuaUserInterface;
}
namespace YimMenu::Lua
{
class UIRoot;
class UIElementBase
{
LuaUserInterface& m_Interface;
public:
UIElementBase(LuaUserInterface& _interface);
virtual void Draw() = 0;
};
class UIElementGroup : public UIElementBase
{
std::string m_Name;
int m_NumElems; // num elements per row
//std::vector<std::unique_ptr<UIElement>> m_Elements;
public:
UIElementGroup(LuaUserInterface& _interface, std::string_view name, int num_elems = 7);
virtual void Draw();
};
class UIElement : public UIElementBase
{
std::string m_CmdString;
std::uint32_t m_CmdHash;
std::string m_Label;
public:
virtual void Draw() override = 0;
};
class UIElementWithState : public UIElement
{
bool m_ShouldSerialize;
public:
virtual void Draw() = 0;
};
class UIElementButton : public UIElement
{
int m_Function;
public:
virtual void Draw() = 0;
};
class UIButtonCheckbox : public UIElementWithState
{
bool m_Value;
int m_OnChange;
public:
virtual void Draw() = 0;
};
class UIButtonIntSlider : public UIElementWithState
{
int m_Value;
int m_OnChange;
public:
virtual void Draw() = 0;
};
class UIRoot
{
};
}
namespace YimMenu
{
class LuaUserInterface
{
std::mutex m_TickFunctionsLock;
std::unordered_set<int> m_TickFunctions;
std::deque<int> m_ThrottledCoroutines;
std::chrono::system_clock::time_point m_LastThrotlledCoroutinePush;
std::unordered_map<std::uint32_t, Lua::UIElement*> m_ElementsById;
public:
void AddTickFunction(int func);
void RemoveTickFunction(int func);
void QueueCoroutine(int coro, bool immediate = false);
// must be called from the main thread
void Tick();
// must be called from the DX thread
void Draw();
};
}
+2 -2
View File
@@ -146,10 +146,10 @@ namespace YimMenu::Lua
}
// luaL_checkstring automatically converts ints into strings, which we don't want
inline const char* CheckStringSafe(lua_State* state, int index)
inline const char* CheckStringSafe(lua_State* state, int index, size_t* size = nullptr)
{
luaL_checktype(state, index, LUA_TSTRING);
return lua_tostring(state, index);
return lua_tolstring(state, index, size);
}
// lua doesn't even offer a check function for bools
+56
View File
@@ -0,0 +1,56 @@
#include "core/scripting/LuaLibrary.hpp"
#include "core/scripting/LuaScript.hpp"
#include "core/scripting/LuaUtils.hpp"
#include "core/frontend/Notifications.hpp"
namespace YimMenu::Lua
{
class Notify : LuaLibrary
{
using LuaLibrary::LuaLibrary;
static int Success(lua_State* state)
{
auto title = CheckStringSafe(state, 1);
auto message = CheckStringSafe(state, 2);
Notifications::Show(title, message, NotificationType::Success);
return 0;
}
static int Info(lua_State* state)
{
auto title = CheckStringSafe(state, 1);
auto message = CheckStringSafe(state, 2);
Notifications::Show(title, message, NotificationType::Info);
return 0;
}
static int Warn(lua_State* state)
{
auto title = CheckStringSafe(state, 1);
auto message = CheckStringSafe(state, 2);
Notifications::Show(title, message, NotificationType::Warning);
return 0;
}
static int Error(lua_State* state)
{
auto title = CheckStringSafe(state, 1);
auto message = CheckStringSafe(state, 2);
Notifications::Show(title, message, NotificationType::Error);
return 0;
}
virtual void Register(lua_State* state) override
{
lua_newtable(state);
SetFunction(state, Success, "success");
SetFunction(state, Info, "info");
SetFunction(state, Warn, "warn");
SetFunction(state, Error, "error");
lua_setglobal(state, "notify");
}
};
Notify _Notify;
}
+3 -6
View File
@@ -22,12 +22,9 @@ namespace YimMenu::Lua
luaL_checktype(state, 1, LUA_TFUNCTION); // will throw error if a1 isn't a function. not sure what happens if you don't pass any parameters
lua_State* coro_state = lua_newthread(state);
lua_pushvalue(state, 1); // xmove can only move from top of stack, so we have to push the function again even if it's already in the stack
lua_xmove(state, coro_state, 1);
auto coro_handle = luaL_ref(state, LUA_REGISTRYINDEX);
script.AddScriptCallback(coro_handle);
auto func_handle = luaL_ref(state, LUA_REGISTRYINDEX);
script.AddScriptCallback(func_handle);
luaL_unref(state, LUA_REGISTRYINDEX, func_handle);
return 0;
}
+76
View File
@@ -0,0 +1,76 @@
#include "core/scripting/LuaLibrary.hpp"
#include "core/scripting/LuaScript.hpp"
#include "core/scripting/LuaUtils.hpp"
#include "game/gta/Natives.hpp"
#include "game/backend/Self.hpp"
namespace YimMenu::Lua
{
class Network : LuaLibrary
{
using LuaLibrary::LuaLibrary;
// trigger_script_event(2000, -1, "if", 2, 2.5)
static int TriggerScriptEvent(lua_State* state)
{
auto hash = GetHashArgument(state, 1);
int bits = luaL_checkinteger(state, 2);
size_t param_length;
auto param_str = CheckStringSafe(state, 3, &param_length);
int64_t tse_args[40];
tse_args[0] = hash;
tse_args[1] = Self::GetPlayer().GetId();
tse_args[2] = bits;
if (param_length >= 37)
luaL_error(state, "too many arguments");
for (int i = 0; i < param_length; i++)
{
switch (param_str[i])
{
case 'i': // integer
{
int as_int = luaL_checkinteger(state, 4 + i);
tse_args[3 + i] = *(int64_t*)&as_int;
break;
}
case 'f': // float
{
float as_float = luaL_checknumber(state, 4 + i);
tse_args[3 + i] = *(int64_t*)&as_float;
break;
}
case 'l': // long
{
tse_args[3 + i] = luaL_checkinteger(state, 4 + i);
break;
}
case 'h': // hash
{
std::uint32_t as_uint = GetHashArgument(state, 4 + i);
tse_args[3 + i] = *(int64_t*)&as_uint;
break;
}
default:
{
luaL_error(state, "unknown format specifier: '%c'", param_str[i]);
}
// TODO: add text label stuff
}
}
SCRIPT::_SEND_TU_SCRIPT_EVENT_NEW(1, tse_args, 3 + param_length, bits, hash);
return 0;
}
virtual void Register(lua_State* state) override
{
lua_newtable(state);
SetFunction(state, TriggerScriptEvent, "trigger_script_event");
lua_setglobal(state, "network");
}
};
Network _Network;
}