feat(lua): implement resources

This commit is contained in:
maybegreat48
2025-06-29 13:16:33 -04:00
parent d14031fc6b
commit b4cfb3b94e
9 changed files with 486 additions and 36 deletions
+46 -2
View File
@@ -15,6 +15,22 @@ namespace YimMenu
m_Libraries.push_back(library);
}
int LuaManager::RegisterResourceTypeImpl(LuaResourceType* res_type)
{
m_ResourceTypes.push_back(res_type);
return m_ResourceTypes.size() - 1;
}
int LuaManager::GetNumResourceTypesImpl()
{
return m_ResourceTypes.size();
}
LuaResourceType* LuaManager::GetResourceTypeImpl(int index)
{
return m_ResourceTypes[index];
}
void LuaManager::LoadLibrariesImpl(lua_State* state)
{
for (auto library : m_Libraries)
@@ -115,7 +131,7 @@ namespace YimMenu
// 4) run tick coroutines
for (auto& script : m_LoadedScripts)
if (script->IsValid())
if (script->IsRunning())
script->Tick();
ScriptMgr::Yield();
@@ -160,7 +176,7 @@ namespace YimMenu
for (auto& script : m_LoadedScripts)
{
if (script->IsValid())
if (script->IsRunning())
{
result = (bool)(((int)result) & ((int)script->DispatchEvent(event, add_arguments_cb, handle_result)));
if (!result && handle_result)
@@ -170,4 +186,32 @@ namespace YimMenu
return result;
}
void LuaManager::ForAllResourcesOfTypeImpl(ForAllResourcesOfTypeCallback callback, int type)
{
auto _type = GetResourceType(type);
bool locked = false;
for (auto& script : m_LoadedScripts)
{
if (script->IsRunning())
{
auto& resources = script->GetAllResourcesOfType(type);
if (resources.size() > 0)
{
if (!locked)
{
_type->Lock();
locked = true;
}
for (auto& resource : resources)
{
callback(resource.get());
}
}
}
}
if (locked)
_type->Unlock();
}
}
+22
View File
@@ -1,6 +1,7 @@
#pragma once
#include "LuaScript.hpp"
#include "LuaLibrary.hpp"
#include "LuaResource.hpp"
namespace YimMenu
{
@@ -16,12 +17,14 @@ namespace YimMenu
using ForAllLoadedScriptsCallback = void(*)(std::shared_ptr<LuaScript>& script);
using ForAllUnloadedScriptsCallback = void(*)(UnloadedScript& script);
using ForAllResourcesOfTypeCallback = void(*)(LuaResource* resource);
private:
std::vector<std::shared_ptr<LuaScript>> m_LoadedScripts;
std::vector<UnloadedScript> m_UnloadedScripts;
std::chrono::system_clock::time_point m_LastRefreshedUnloadedScripts;
std::vector<LuaLibrary*> m_Libraries;
std::vector<LuaResourceType*> m_ResourceTypes;
std::queue<std::string> m_ScriptsToLoad;
std::mutex m_LoadMutex;
std::uint32_t m_MainThreadId;
@@ -31,6 +34,9 @@ namespace YimMenu
void AddUnloadedScript(std::string_view name, std::string_view path);
void RegisterLibraryImpl(LuaLibrary* library);
int RegisterResourceTypeImpl(LuaResourceType* res_type); // returns resource index
int GetNumResourceTypesImpl();
LuaResourceType* GetResourceTypeImpl(int index);
void LoadLibrariesImpl(lua_State* state);
void LoadScriptImpl(std::string path);
void RunScriptImpl();
@@ -39,6 +45,7 @@ namespace YimMenu
void ForAllLoadedScriptsImpl(ForAllLoadedScriptsCallback callback);
void ForAllUnloadedScriptsImpl(ForAllUnloadedScriptsCallback callback);
bool DispatchEventImpl(std::uint32_t event, const LuaScript::DispatchEventCallback& add_arguments_cb, bool handle_result = false);
void ForAllResourcesOfTypeImpl(ForAllResourcesOfTypeCallback callback, int type);
static LuaManager& GetInstance()
{
@@ -52,6 +59,21 @@ namespace YimMenu
GetInstance().RegisterLibraryImpl(library);
}
static int RegisterResourceType(LuaResourceType* res_type)
{
return GetInstance().RegisterResourceTypeImpl(res_type);
}
static int GetNumResourceTypes()
{
return GetInstance().GetNumResourceTypesImpl();
}
static LuaResourceType* GetResourceType(int index)
{
return GetInstance().GetResourceTypeImpl(index);
}
static void LoadLibraries(lua_State* state)
{
GetInstance().LoadLibrariesImpl(state);
+10
View File
@@ -0,0 +1,10 @@
#include "LuaResource.hpp"
#include "LuaManager.hpp"
namespace YimMenu
{
void LuaResourceType::Register()
{
m_Index = LuaManager::RegisterResourceType(this);
}
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
namespace YimMenu
{
class LuaResource
{
int m_Type;
public:
int GetType() const { return m_Type; }
void SetType(int type) { m_Type = type; }
virtual void OnEnable() {}; // called during resume and also immediately after AddResource
virtual void OnDisable() {}; // called during pause and before unload
};
class LuaResourceType
{
int m_Index{};
std::mutex m_Mutex{};
void Register();
public:
LuaResourceType()
{
Register();
}
void Lock()
{
m_Mutex.lock();
}
void Unlock()
{
m_Mutex.unlock();
}
int GetIndex() const
{
return m_Index;
}
};
}
+78
View File
@@ -109,10 +109,45 @@ namespace YimMenu
DeleteFiber(callback.m_Fiber);
}
void LuaScript::DisableResources()
{
for (int i = 0; i < m_Resources.size(); i++)
{
if (!m_Resources[i].size())
return;
auto type = LuaManager::GetResourceType(i);
type->Lock();
for (auto& resource : m_Resources[i])
resource->OnDisable();
type->Unlock();
}
}
void LuaScript::EnableResources()
{
for (int i = 0; i < m_Resources.size(); i++)
{
if (!m_Resources[i].size())
return;
auto type = LuaManager::GetResourceType(i);
type->Lock();
for (auto& resource : m_Resources[i])
resource->OnEnable();
type->Unlock();
}
}
LuaScript::LuaScript(std::string_view file_name) :
m_FileName(file_name),
m_ModuleName(std::filesystem::path(file_name).filename().string())
{
m_Resources.resize(LuaManager::GetNumResourceTypes());
m_State = luaL_newstate();
lua_pushlightuserdata(m_State, (void*)this);
@@ -143,6 +178,30 @@ namespace YimMenu
}
}
void LuaScript::Pause()
{
if (m_LoadState == LoadState::RUNNING)
{
DisableResources();
m_LoadState = LoadState::PAUSED;
}
}
void LuaScript::Resume()
{
if (m_LoadState == LoadState::PAUSED)
{
EnableResources();
m_LoadState = LoadState::RUNNING;
}
}
void LuaScript::MarkUnloaded()
{
DisableResources();
m_LoadState = LoadState::UNLOADED;
}
bool LuaScript::SafeToUnload()
{
for (auto& callback : m_ScriptCallbacks)
@@ -304,6 +363,25 @@ namespace YimMenu
return result;
}
void LuaScript::AddResource(std::shared_ptr<LuaResource>&& resource, int idx)
{
resource->SetType(idx); // TODO: this is a bad idea
auto type = LuaManager::GetResourceType(idx);
type->Lock();
m_Resources[resource->GetType()].push_back(std::move(resource));
type->Unlock();
}
int LuaScript::GetNumResourcesOfType(int type)
{
return m_Resources[type].size();
}
std::vector<std::shared_ptr<LuaResource>>& LuaScript::GetAllResourcesOfType(int idx)
{
return m_Resources[idx];
}
void LuaScript::ScriptCallback::SetTimeToResume(int millis)
{
+12 -30
View File
@@ -1,5 +1,6 @@
#pragma once
#include "lua.hpp"
#include "LuaResource.hpp"
namespace YimMenu
{
@@ -43,12 +44,14 @@ namespace YimMenu
bool m_RunningScriptCallbacks = false;
ScriptCallback* m_CurrentlyExecutingCallback = nullptr;
std::unordered_map<std::uint32_t, std::vector<int>> m_EventHandlers;
bool m_NativesLoaded = false; // TODO: move this to a LuaResource
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?
// 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);
int ResumeCoroutine(int n_args, int n_results, lua_State* coro_state);
void RemoveScriptCallback(ScriptCallback& callback);
void DisableResources();
void EnableResources();
public:
LuaScript(std::string_view file_name);
@@ -59,7 +62,7 @@ namespace YimMenu
return m_IsMalfunctioning;
}
bool IsValid() const
bool IsRunning() const
{
return m_LoadState == LoadState::RUNNING;
}
@@ -86,23 +89,11 @@ namespace YimMenu
m_LoadState = LoadState::WANT_RELOAD;
}
void Pause()
{
if (m_LoadState == LoadState::RUNNING)
m_LoadState = LoadState::PAUSED;
}
void Resume()
{
if (m_LoadState == LoadState::PAUSED)
m_LoadState = LoadState::RUNNING;
}
void Pause();
void Resume();
// Should only be called by LuaManager::RunScriptImpl
void MarkUnloaded()
{
m_LoadState = LoadState::UNLOADED;
}
void MarkUnloaded();
bool SafeToUnload();
@@ -135,18 +126,9 @@ namespace YimMenu
void AddEventHandler(std::uint32_t event, int handler);
bool DispatchEvent(std::uint32_t event, const DispatchEventCallback& add_arguments_cb, bool handle_result = false);
// ------- TODO: move these into a resource
bool AreNativesLoaded() const
{
return m_NativesLoaded;
}
void SetNativesLoaded()
{
m_NativesLoaded = true;
}
// --------------
// TODO: add RemoveResource
void AddResource(std::shared_ptr<LuaResource>&& resource, int idx);
int GetNumResourcesOfType(int type);
std::vector<std::shared_ptr<LuaResource>>& GetAllResourcesOfType(int idx);
};
}
+1 -1
View File
@@ -45,7 +45,7 @@ namespace YimMenu
{
if (!IsValid())
{
LOG(WARNING) << "IsValid() assertion failed for " << function_name;
LOG(WARNING) << "IsRunning() assertion failed for " << function_name;
}
}
+10 -3
View File
@@ -1,4 +1,5 @@
#include "core/scripting/LuaLibrary.hpp"
#include "core/scripting/LuaResource.hpp"
#include "core/scripting/LuaScript.hpp"
#include "core/scripting/LuaUtils.hpp"
#include "NativeDefs.hpp"
@@ -6,6 +7,12 @@
namespace YimMenu::Lua
{
class AreNativesLoadedResource : public LuaResource
{
};
const static LuaResourceType _AreNativesLoadedResource;
class Natives : LuaLibrary
{
using LuaLibrary::LuaLibrary;
@@ -14,7 +21,7 @@ namespace YimMenu::Lua
{
auto& script = LuaScript::GetScript(state);
if (script.AreNativesLoaded())
if (script.GetNumResourcesOfType(_AreNativesLoadedResource.GetIndex()) > 0)
luaL_error(state, "Natives have already been loaded");
for (int i = 0; i < g_NumLuaNativeDefs; i++)
@@ -28,14 +35,14 @@ namespace YimMenu::Lua
}
}
script.SetNativesLoaded();
script.AddResource(std::make_shared<AreNativesLoadedResource>(), _AreNativesLoadedResource.GetIndex());
return 0;
}
static int AreNativesLoaded(lua_State* state)
{
auto& script = LuaScript::GetScript(state);
lua_pushboolean(state, script.AreNativesLoaded());
lua_pushboolean(state, script.GetNumResourcesOfType(_AreNativesLoadedResource.GetIndex()) > 0);
return 1;
}
+264
View File
@@ -0,0 +1,264 @@
#include "core/scripting/LuaLibrary.hpp"
#include "core/scripting/LuaScript.hpp"
#include "core/scripting/LuaUtils.hpp"
#include "core/scripting/LatentFunction.hpp"
#include "game/gta/Ped.hpp"
#include "Entity.hpp"
namespace YimMenu::Lua
{
class Ped : LuaLibrary
{
public:
using LuaLibrary::LuaLibrary;
static int New(lua_State* state)
{
CreateObject<YimMenu::Ped>(state, luaL_checkinteger(state, 1));
return 1;
}
static int Create(lua_State* state)
{
CopyObject<YimMenu::Ped>(state, YimMenu::Ped::Create(GetHashArgument(state, 1), GetObject<rage::fvector3>(state, 2), lua_gettop(state) >= 3 ? luaL_checknumber(state, 3) : 0.0f));
return 1;
}
static int GetVehicle(lua_State* state)
{
CopyObject<YimMenu::Vehicle>(state, GetObject<YimMenu::Ped>(state, 1).GetVehicle());
return 1;
}
static int GetLastVehicle(lua_State* state)
{
CopyObject<YimMenu::Vehicle>(state, GetObject<YimMenu::Ped>(state, 1).GetLastVehicle());
return 1;
}
static int GetVehicleObjectId(lua_State* state)
{
lua_pushinteger(state, GetObject<YimMenu::Ped>(state, 1).GetVehicleObjectId());
return 1;
}
static int SetInVehicle(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetInVehicle(GetObject<YimMenu::Vehicle>(state, 2), lua_gettop(state) >= 3 ? luaL_checkinteger(state, 3) : 0);
return 0;
}
static int GetRagdoll(lua_State* state)
{
lua_pushboolean(state, GetObject<YimMenu::Ped>(state, 1).GetRagdoll());
return 1;
}
static int SetRagdoll(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetRagdoll(CheckBooleanSafe(state, 2));
return 0;
}
static int GetBonePosition(lua_State* state)
{
MoveObject<rage::fvector3>(state, GetObject<YimMenu::Ped>(state, 1).GetBonePosition(luaL_checkinteger(state, 2)));
return 0;
}
// GetConfigFlag
// SetConfigFlag
// SetCombatAttribute
static int IsEnemy(lua_State* state)
{
lua_pushboolean(state, GetObject<YimMenu::Ped>(state, 1).IsEnemy());
return 1;
}
static int GetAccuracy(lua_State* state)
{
lua_pushinteger(state, GetObject<YimMenu::Ped>(state, 1).GetAccuracy());
return 1;
}
static int SetAccuracy(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetAccuracy(luaL_checkinteger(state, 2));
return 0;
}
// GetPlayer
static int GiveWeapon(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).GiveWeapon(GetHashArgument(state, 2), lua_gettop(state) >= 3 ? CheckBooleanSafe(state, 3) : false);
return 0;
}
static int RemoveWeapon(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).RemoveWeapon(GetHashArgument(state, 2));
return 0;
}
static int GetCurrentWeapon(lua_State* state)
{
lua_pushinteger(state, GetObject<YimMenu::Ped>(state, 1).GetCurrentWeapon());
return 1;
}
static int HasWeapon(lua_State* state)
{
lua_pushboolean(state, GetObject<YimMenu::Ped>(state, 1).HasWeapon(GetHashArgument(state, 2)));
return 1;
}
static int SetInfiniteAmmo(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetInfiniteAmmo(CheckBooleanSafe(state, 2));
return 0;
}
static int SetInfiniteClip(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetInfiniteClip(CheckBooleanSafe(state, 2));
return 0;
}
static int SetMaxAmmoForWeapon(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetMaxAmmoForWeapon(GetHashArgument(state, 2));
return 0;
}
static int TeleportTo(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).TeleportTo(GetObject<rage::fvector3>(state, 2));
return 0;
}
static int GetArmour(lua_State* state)
{
lua_pushinteger(state, GetObject<YimMenu::Ped>(state, 1).GetArmour());
return 1;
}
static int SetArmour(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetArmour(luaL_checkinteger(state, 2));
return 0;
}
static int SetLeaderOfGroup(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetLeaderOfGroup(luaL_checkinteger(state, 2));
return 0;
}
static int AddToGroup(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).AddToGroup(luaL_checkinteger(state, 2));
return 0;
}
static int RemoveFromGroup(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).RemoveFromGroup();
return 0;
}
static int IsMemberOfGroup(lua_State* state)
{
lua_pushboolean(state, GetObject<YimMenu::Ped>(state, 1).IsMemberOfGroup(luaL_checkinteger(state, 2)));
return 1;
}
static int RandomizeOutfit(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).RandomizeOutfit();
return 0;
}
static int StartScenario(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).StartScenario(CheckStringSafe(state, 2), lua_gettop(state) >= 3 ? luaL_checkinteger(state, 3) : -1, lua_gettop(state) >= 4 ? CheckBooleanSafe(state, 4) : true);
return 0;
}
static int SetKeepTask(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetKeepTask(CheckBooleanSafe(state, 2));
return 0;
}
static int ClearDamage(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).ClearDamage();
return 0;
}
static int SetMaxTimeUnderwater(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetMaxTimeUnderwater(luaL_checkinteger(state, 2));
return 0;
}
static int SetAsCop(lua_State* state)
{
GetObject<YimMenu::Ped>(state, 1).SetAsCop();
return 0;
}
virtual void Register(lua_State* state) override
{
luaL_newmetatable(state, "Ped");
{
lua_newtable(state);
{
RegisterEntityMethods(state); // re-registering the entity methods under the vehicle table is more efficient than subclassing
SetFunction(state, GetVehicle, "get_vehicle");
SetFunction(state, GetLastVehicle, "get_last_vehicle");
SetFunction(state, GetVehicleObjectId, "get_vehicle_object_id");
SetFunction(state, SetInVehicle, "set_in_vehicle");
SetFunction(state, GetRagdoll, "get_ragdoll");
SetFunction(state, SetRagdoll, "set_ragdoll");
SetFunction(state, GetBonePosition, "get_bone_position");
SetFunction(state, IsEnemy, "is_enemy");
SetFunction(state, GetAccuracy, "get_accuracy");
SetFunction(state, SetAccuracy, "set_accuracy");
SetFunction(state, GiveWeapon, "give_weapon");
SetFunction(state, RemoveWeapon, "remove_weapon");
SetFunction(state, GetCurrentWeapon, "get_current_weapon");
SetFunction(state, HasWeapon, "has_weapon");
SetFunction(state, SetInfiniteAmmo, "set_infinite_ammo");
SetFunction(state, SetInfiniteClip, "set_infinite_clip");
SetFunction(state, SetMaxAmmoForWeapon, "set_max_ammo_for_weapon");
SetFunction(state, TeleportTo, "teleport_to");
SetFunction(state, GetArmour, "get_armour");
SetFunction(state, SetArmour, "set_armour");
SetFunction(state, SetLeaderOfGroup, "set_leader_of_group");
SetFunction(state, AddToGroup, "add_to_group");
SetFunction(state, RemoveFromGroup, "remove_from_group");
SetFunction(state, IsMemberOfGroup, "is_member_of_group");
SetFunction(state, RandomizeOutfit, "randomize_outfit");
SetFunction(state, StartScenario, "start_scenario");
SetFunction(state, SetKeepTask, "set_keep_task");
SetFunction(state, ClearDamage, "clear_damage");
SetFunction(state, SetMaxTimeUnderwater, "set_max_time_underwater");
SetFunction(state, SetAsCop, "set_as_cop");
}
lua_setfield(state, -2, "__index"); // prototype
}
Metatable<YimMenu::Ped>::Register(state);
Metatable<YimMenu::Entity>::AddSubclass<YimMenu::Ped>();
lua_newtable(state);
SetConstructor<New>(state);
SetFunction(state, LatentFunction<Create>, "create");
lua_setglobal(state, "Ped");
}
};
Ped _Ped;
}