diff --git a/fxmanifest.lua b/fxmanifest.lua
index a33a834..9c0f698 100644
--- a/fxmanifest.lua
+++ b/fxmanifest.lua
@@ -1,14 +1,14 @@
-name "Jim_Bridge"
-author "Jimathy"
-version "2.0"
-description "Framework Bridge By Jimathy"
-fx_version "cerulean"
-game "gta5"
-lua54 'yes'
-
-files {
- 'starter.lua',
- 'shared/*.lua',
- 'shared/make/*.lua',
- 'shared/scaleforms/*.lua',
-}
+name "Jim_Bridge"
+author "Jimathy"
+version "2.0"
+description "Framework Bridge By Jimathy"
+fx_version "cerulean"
+game "gta5"
+lua54 'yes'
+
+files {
+ 'starter.lua',
+ 'shared/*.lua',
+ 'shared/make/*.lua',
+ 'shared/scaleforms/*.lua',
+}
diff --git a/shared/_eventDebug.lua b/shared/_eventDebug.lua
index 5814f35..524ab20 100644
--- a/shared/_eventDebug.lua
+++ b/shared/_eventDebug.lua
@@ -1,149 +1,149 @@
--- IN NO WAY PERFECT -- ** Experimental debugging
-function toggleDebug()
- Config.System.Debug = not Config.System.Debug
- print("Debug Prints = "..tostring(Config.System.Debug))
-end
-exports("toggleDebug", toggleDebug)
-
-function getDebug() return Config.System.Debug end
-exports("getDebug", getDebug)
-
-local origRegisterNetEvent = RegisterNetEvent
-local origTriggerEvent = TriggerEvent
-local origTriggerServerEvent = TriggerServerEvent
-local origTriggerClientEvent = TriggerClientEvent
-local origExecuteCommand = ExecuteCommand
-local origRegisterCommand = RegisterCommand
-local origPairs = pairs
-local origiPairs = ipairs
-
-function getDebugInfo(info)
- local info = info
- local level = 2
- if info and info.short_src:match("scheduler.lua") then
- repeat
- info = debug.getinfo(level, "nSl")
- level += 1
- local found = false
- for _, v in pairs({
- "deffered.lua",
- "scheduler.lua",
- "_eventDebug.lua",
- "targets.lua",
- "init.lua",
- "MySQL.lua",
- "helpers.lua",
- }) do
- if info and info.short_src:match(v) then
- found = true
- end
- end
- until not info or (info.short_src and found == false)
- end
-
- return " ^7[^3"..(info and info.short_src:match("^.+/(.+)$") or "unknown").."^7:^3"..(info and info.currentline or "unknown").."^7]"
-end
-
---This is just a for debugging, not important, just announces which events are being registered triggered when these functions are used
-function RegisterNetEvent(name, funct)
- if Config.System.EventDebug then
- if name:find("__ox_cb_") then
- print("^6Bridge^7: ^2Registered ^3"..(isServer() and "Server" or "Client").." ^2Callback^7: ^6"..name:gsub("__ox_cb_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- else
- print("^6Bridge^7: ^2Registering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- end
- end
- origRegisterNetEvent(name, funct)
-end
-
-function TriggerEvent(name, ...)
- local data = {...}
- if Config.System.EventDebug then
- if name:find("__cfx_export") then
- print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Export^7: ^6"..name:gsub("__cfx_export_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- else
- print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- end
- for i, value in ipairs(data) do
- if value then
- local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
- print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
- end
- end end
- origTriggerEvent(name, ...)
-end
-
-function TriggerServerEvent(name, ...) -- Client side, trigger a server event
- local data = {...}
- if Config.System.EventDebug then
- if name:find("__ox_cb") then
- print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Server ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- else
- print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Server ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- end
- for i, value in ipairs(data) do
- if value then
- local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
- print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
- end
- end end
- origTriggerServerEvent(name, ...)
-end
-
-function TriggerClientEvent(name, ...) -- Server side, trigger a client event
- local data = {...}
- if Config.System.EventDebug then
- if name:find("__ox_cb") then
- print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Client ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- else
- print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Client ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- end
- for i, value in ipairs(data) do
- if value then
- local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
- print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
- end
- end end
- origTriggerClientEvent(name, ...)
-end
-
-function RegisterCommand(command, funct, restrict)
- if Config.System.EventDebug then
- print("^6Bridge^7: ^2Registering ^2Command^7: /"..command.." ^7| ^4Funct^7: "..tostring(funct):gsub("function: ", "").." ^7| ^4Admin^7: "..(restict and "true" or "false")..getDebugInfo(debug.getinfo(2, "nSl")))
- end
- origRegisterCommand(command, funct, restrict)
-end
-
-function ExecuteCommand(comm) -- Client side, execute /command
- if Config.System.EventDebug then
- print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3ExecuteCommand^7: /"..comm..getDebugInfo(debug.getinfo(2, "nSl")))
- end
- origExecuteCommand(comm)
-end
-
-function pairs(tbl)
- if not tbl then
- print("^1Error^7: ^1nil ^2for ^3pairs^7(), ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- return origPairs({})
- end
- return origPairs(tbl)
-end
-
-function ipairs(tbl)
- local tbl = tbl
- if not tbl then
- if Config.System.EventDebug then
- print("^1Error^7: ^3iPairs^7() ^1nil ^2recieved^7, ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
- end
- tbl = {}
- end
- return pairsByKeys(tbl) -- change to pairsByKeys for less errors
-end
-
---[[
-local origPrint = print
-function print(...)
- origPrint(getDebugInfo(debug.getinfo(2, "nSl"))..":")
- origPrint(...)
-end
+-- IN NO WAY PERFECT -- ** Experimental debugging
+function toggleDebug()
+ Config.System.Debug = not Config.System.Debug
+ print("Debug Prints = "..tostring(Config.System.Debug))
+end
+exports("toggleDebug", toggleDebug)
+
+function getDebug() return Config.System.Debug end
+exports("getDebug", getDebug)
+
+local origRegisterNetEvent = RegisterNetEvent
+local origTriggerEvent = TriggerEvent
+local origTriggerServerEvent = TriggerServerEvent
+local origTriggerClientEvent = TriggerClientEvent
+local origExecuteCommand = ExecuteCommand
+local origRegisterCommand = RegisterCommand
+local origPairs = pairs
+local origiPairs = ipairs
+
+function getDebugInfo(info)
+ local info = info
+ local level = 2
+ if info and info.short_src:match("scheduler.lua") then
+ repeat
+ info = debug.getinfo(level, "nSl")
+ level += 1
+ local found = false
+ for _, v in pairs({
+ "deffered.lua",
+ "scheduler.lua",
+ "_eventDebug.lua",
+ "targets.lua",
+ "init.lua",
+ "MySQL.lua",
+ "helpers.lua",
+ }) do
+ if info and info.short_src:match(v) then
+ found = true
+ end
+ end
+ until not info or (info.short_src and found == false)
+ end
+
+ return " ^7[^3"..(info and info.short_src:match("^.+/(.+)$") or "unknown").."^7:^3"..(info and info.currentline or "unknown").."^7]"
+end
+
+--This is just a for debugging, not important, just announces which events are being registered triggered when these functions are used
+function RegisterNetEvent(name, funct)
+ if Config.System.EventDebug then
+ if name:find("__ox_cb_") then
+ print("^6Bridge^7: ^2Registered ^3"..(isServer() and "Server" or "Client").." ^2Callback^7: ^6"..name:gsub("__ox_cb_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ else
+ print("^6Bridge^7: ^2Registering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+ end
+ origRegisterNetEvent(name, funct)
+end
+
+function TriggerEvent(name, ...)
+ local data = {...}
+ if Config.System.EventDebug then
+ if name:find("__cfx_export") then
+ print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Export^7: ^6"..name:gsub("__cfx_export_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ else
+ print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+ for i, value in ipairs(data) do
+ if value then
+ local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
+ print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
+ end
+ end end
+ origTriggerEvent(name, ...)
+end
+
+function TriggerServerEvent(name, ...) -- Client side, trigger a server event
+ local data = {...}
+ if Config.System.EventDebug then
+ if name:find("__ox_cb") then
+ print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Server ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ else
+ print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Server ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+ for i, value in ipairs(data) do
+ if value then
+ local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
+ print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
+ end
+ end end
+ origTriggerServerEvent(name, ...)
+end
+
+function TriggerClientEvent(name, ...) -- Server side, trigger a client event
+ local data = {...}
+ if Config.System.EventDebug then
+ if name:find("__ox_cb") then
+ print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Client ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ else
+ print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Client ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+ for i, value in ipairs(data) do
+ if value then
+ local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
+ print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
+ end
+ end end
+ origTriggerClientEvent(name, ...)
+end
+
+function RegisterCommand(command, funct, restrict)
+ if Config.System.EventDebug then
+ print("^6Bridge^7: ^2Registering ^2Command^7: /"..command.." ^7| ^4Funct^7: "..tostring(funct):gsub("function: ", "").." ^7| ^4Admin^7: "..(restict and "true" or "false")..getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+ origRegisterCommand(command, funct, restrict)
+end
+
+function ExecuteCommand(comm) -- Client side, execute /command
+ if Config.System.EventDebug then
+ print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3ExecuteCommand^7: /"..comm..getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+ origExecuteCommand(comm)
+end
+
+function pairs(tbl)
+ if not tbl then
+ print("^1Error^7: ^1nil ^2for ^3pairs^7(), ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ return origPairs({})
+ end
+ return origPairs(tbl)
+end
+
+function ipairs(tbl)
+ local tbl = tbl
+ if not tbl then
+ if Config.System.EventDebug then
+ print("^1Error^7: ^3iPairs^7() ^1nil ^2recieved^7, ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+ tbl = {}
+ end
+ return pairsByKeys(tbl) -- change to pairsByKeys for less errors
+end
+
+--[[
+local origPrint = print
+function print(...)
+ origPrint(getDebugInfo(debug.getinfo(2, "nSl"))..":")
+ origPrint(...)
+end
]]
\ No newline at end of file
diff --git a/shared/_loaders.lua b/shared/_loaders.lua
index bae08cb..78f87b4 100644
--- a/shared/_loaders.lua
+++ b/shared/_loaders.lua
@@ -1,110 +1,121 @@
---- Executes a function when the player character is loaded into the game.
----
---- This function sets up event listeners for player loading based on the game framework detected (e.g., QB, ESX, OX).
----
---- If `onStart` is `true`, it will also attempt to execute the function on resource start after ensuring the player is logged in. (Helpful for debugging)
----
---- @param func function The function to execute when the player is loaded.
---- @param onStart boolean (optional) If `true`, the function will also execute on resource start. Default is `false`.
----
---- @usage
---- ```lua
---- onPlayerLoaded(function()
---- -- Your code here
---- end, true)
---- ```
-function onPlayerLoaded(func, onStart)
- local onPlayerName = ""
- local loaded = false
- if onStart then
- onResourceStart(function()
- if not LocalPlayer.state.isLoggedIn then
- Wait(3000)
- if not LocalPlayer.state.isLoggedIn then -- If the player is not logged in after waiting, skip execution
- return
- end
- end
- loaded = true -- Mark as already loaded
- debugPrint("^6Bridge^7: ^2Loading ^3onResourceStart^7() ^2through ^3onPlayerLoaded^7()")
- Wait(2000)
- func()
- end, true)
- end
- if not loaded then
- local tempFunc = function()
- debugPrint("^6Bridge^7: ^2Executing ^3onPlayerLoaded^7()")
- func()
- end
- if isStarted(QBExport) or isStarted(QBXExport) then onPlayerName = QBExport
- AddEventHandler('QBCore:Client:OnPlayerLoaded', tempFunc)
- elseif isStarted(ESXExport) then onPlayerName = ESXExport
- AddEventHandler('esx:playerLoaded', tempFunc)
- elseif isStarted(OXCoreExport) then onPlayerName = OXCoreExport
- AddEventHandler('ox:playerLoaded', tempFunc)
- end
- if onPlayerName ~= "" then
- debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^2 with ^7"..onPlayerName)
- else
- print("^4ERROR^7: ^2No Core detected for onPlayerLoaded ^7- ^2Check ^3exports^1.^2lua^7")
- end
- end
-end
-
---- Executes a function when the resource starts.
----
---- This function wraps the `onResourceStart` event, allowing you to execute code when the resource starts.
----
---- @param func function The function to execute on resource start.
---- @param thisScript boolean (optional) If `true`, only runs the function when this resource starts. Default is `true`.
----
---- @usage
---- ```lua
---- onResourceStart(function()
---- -- Your code here
---- end, true)
---- ```
-function onResourceStart(func, thisScript)
- debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^2")
- AddEventHandler('onResourceStart', function(resourceName)
- if getScript() == resourceName and (thisScript or true) then
- func()
- end
- end)
-end
-
---- Executes a function when the resource stops.
----
---- This function wraps the `onResourceStop` event, allowing you to execute code when the resource stops.
----
---- @param func function The function to execute on resource stop.
---- @param thisScript boolean (optional) If `true`, only runs the function when this resource stops. Default is `true`.
----
---- @usage
---- ```lua
---- onResourceStop(function()
---- -- Cleanup code here
---- end, true)
---- ```
-function onResourceStop(func, thisScript)
- debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^2")
- AddEventHandler('onResourceStop', function(resourceName)
- if getScript() == resourceName and (thisScript or true) then
- func()
- end
- end)
-end
-
---- Waits until the player is logged in before continuing execution.
----
---- This function blocks execution until `LocalPlayer.state.isLoggedIn` is `true`.
----
----@usage
---- ```lua
---- waitForLogin()
---- ```
-function waitForLogin()
- while not LocalPlayer.state.isLoggedIn do
- debugPrint("Waiting")
- Wait(100)
- end
+--- Executes a function when the player character is loaded into the game.
+---
+--- This function sets up event listeners for player loading based on the game framework detected (e.g., QB, ESX, OX).
+---
+--- If `onStart` is `true`, it will also attempt to execute the function on resource start after ensuring the player is logged in. (Helpful for debugging)
+---
+--- @param func function The function to execute when the player is loaded.
+--- @param onStart boolean (optional) If `true`, the function will also execute on resource start. Default is `false`.
+---
+--- @usage
+--- ```lua
+--- onPlayerLoaded(function()
+--- -- Your code here
+--- end, true)
+--- ```
+function onPlayerLoaded(func, onStart)
+ local onPlayerName = ""
+ local loaded = false
+ if onStart then
+ onResourceStart(function()
+ if not LocalPlayer.state.isLoggedIn then
+ Wait(3000)
+ if not LocalPlayer.state.isLoggedIn then -- If the player is not logged in after waiting, skip execution
+ return
+ end
+ end
+ loaded = true -- Mark as already loaded
+ debugPrint("^6Bridge^7: ^2Loading ^3onResourceStart^7() ^2through ^3onPlayerLoaded^7()")
+ Wait(2000)
+ func()
+ end, true)
+ end
+ if not loaded then
+ local tempFunc = function()
+ debugPrint("^6Bridge^7: ^2Executing ^3onPlayerLoaded^7()")
+ func()
+ end
+ if isStarted(QBExport) or isStarted(QBXExport) then onPlayerName = QBExport
+ AddEventHandler('QBCore:Client:OnPlayerLoaded', tempFunc)
+ elseif isStarted(ESXExport) then onPlayerName = ESXExport
+ AddEventHandler('esx:playerLoaded', tempFunc)
+ elseif isStarted(OXCoreExport) then onPlayerName = OXCoreExport
+ AddEventHandler('ox:playerLoaded', tempFunc)
+ end
+ if onPlayerName ~= "" then
+ debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^2 with ^7"..onPlayerName)
+ else
+ print("^4ERROR^7: ^2No Core detected for onPlayerLoaded ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+ end
+end
+
+--trying to add unload functions for when players switch ped
+function onPlayerUnload(func)
+ AddEventHandler('QBCore:Client:OnPlayerUnload', function()
+ func()
+ end)
+ AddEventHandler('ox:playerLogout', function()
+ func()
+ end)
+end
+
+
+--- Executes a function when the resource starts.
+---
+--- This function wraps the `onResourceStart` event, allowing you to execute code when the resource starts.
+---
+--- @param func function The function to execute on resource start.
+--- @param thisScript boolean (optional) If `true`, only runs the function when this resource starts. Default is `true`.
+---
+--- @usage
+--- ```lua
+--- onResourceStart(function()
+--- -- Your code here
+--- end, true)
+--- ```
+function onResourceStart(func, thisScript)
+ debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^2")
+ AddEventHandler('onResourceStart', function(resourceName)
+ if getScript() == resourceName and (thisScript or true) then
+ func()
+ end
+ end)
+end
+
+--- Executes a function when the resource stops.
+---
+--- This function wraps the `onResourceStop` event, allowing you to execute code when the resource stops.
+---
+--- @param func function The function to execute on resource stop.
+--- @param thisScript boolean (optional) If `true`, only runs the function when this resource stops. Default is `true`.
+---
+--- @usage
+--- ```lua
+--- onResourceStop(function()
+--- -- Cleanup code here
+--- end, true)
+--- ```
+function onResourceStop(func, thisScript)
+ debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^2")
+ AddEventHandler('onResourceStop', function(resourceName)
+ if getScript() == resourceName and (thisScript or true) then
+ func()
+ end
+ end)
+end
+
+--- Waits until the player is logged in before continuing execution.
+---
+--- This function blocks execution until `LocalPlayer.state.isLoggedIn` is `true`.
+---
+---@usage
+--- ```lua
+--- waitForLogin()
+--- ```
+function waitForLogin()
+ while not LocalPlayer.state.isLoggedIn do
+ debugPrint("Waiting")
+ Wait(100)
+ end
end
\ No newline at end of file
diff --git a/shared/callback.lua b/shared/callback.lua
index d1c8740..e2b9cbf 100644
--- a/shared/callback.lua
+++ b/shared/callback.lua
@@ -1,68 +1,68 @@
---- Registers a callback function with the appropriate framework.
----
---- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
---- It adapts the callback function to match the expected signature for the framework.
----
----@param callbackName string The name of the callback to register.
----@param funct function The function to be called when the callback is triggered.
----
----@usage
---- ```lua
---- createCallback('myCallback', function(source, ...)
---- -- Your callback code here
---- end)
---- ```
-function createCallback(callbackName, funct)
- if isStarted(OXLibExport) then
- lib.callback.register(callbackName, funct)
- else
- local adaptedFunction = function(source, cb, ...)
- local result = funct(source, ...)
- cb(result)
- end
-
- if isStarted(QBExport) then
- Core = Core or exports[QBExport]:GetCoreObject()
- Core.Functions.CreateCallback(callbackName, adaptedFunction)
- elseif isStarted(ESXExport) then
- ESX.RegisterServerCallback(callbackName, adaptedFunction)
- else
- print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to register callback with", callbackName)
- end
- end
-end
-
---- Triggers a server callback and returns the result.
----
---- This function triggers a server callback using the appropriate framework's method and awaits the result.
----
----@param callbackName string The name of the callback to trigger.
----@param ... any Additional arguments to pass to the callback.
----
----@return any any The result returned by the callback function.
----
----@usage
---- ```lua
---- local result = triggerCallback('myCallback', arg1, arg2)
---- ```
-function triggerCallback(callbackName, ...)
- local result = nil
- if isStarted(OXLibExport) then
- result = lib.callback.await(callbackName, false, ...)
- elseif isStarted(QBExport) then
- local p = promise.new()
- Core.Functions.TriggerCallback(callbackName, function(cbResult)
- p:resolve(cbResult)
- end, ...)
- result = Citizen.Await(p)
- elseif isStarted(ESXExport) then
- local p = promise.new()
- ESX.TriggerServerCallback(callbackName, function(cbResult)
- p:resolve(cbResult)
- end, ...)
- result = Citizen.Await(p)
- else
- print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with", callbackName)
- end
- return result
+--- Registers a callback function with the appropriate framework.
+---
+--- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
+--- It adapts the callback function to match the expected signature for the framework.
+---
+---@param callbackName string The name of the callback to register.
+---@param funct function The function to be called when the callback is triggered.
+---
+---@usage
+--- ```lua
+--- createCallback('myCallback', function(source, ...)
+--- -- Your callback code here
+--- end)
+--- ```
+function createCallback(callbackName, funct)
+ if isStarted(OXLibExport) then
+ lib.callback.register(callbackName, funct)
+ else
+ local adaptedFunction = function(source, cb, ...)
+ local result = funct(source, ...)
+ cb(result)
+ end
+
+ if isStarted(QBExport) then
+ Core = Core or exports[QBExport]:GetCoreObject()
+ Core.Functions.CreateCallback(callbackName, adaptedFunction)
+ elseif isStarted(ESXExport) then
+ ESX.RegisterServerCallback(callbackName, adaptedFunction)
+ else
+ print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to register callback with", callbackName)
+ end
+ end
+end
+
+--- Triggers a server callback and returns the result.
+---
+--- This function triggers a server callback using the appropriate framework's method and awaits the result.
+---
+---@param callbackName string The name of the callback to trigger.
+---@param ... any Additional arguments to pass to the callback.
+---
+---@return any any The result returned by the callback function.
+---
+---@usage
+--- ```lua
+--- local result = triggerCallback('myCallback', arg1, arg2)
+--- ```
+function triggerCallback(callbackName, ...)
+ local result = nil
+ if isStarted(OXLibExport) then
+ result = lib.callback.await(callbackName, false, ...)
+ elseif isStarted(QBExport) then
+ local p = promise.new()
+ Core.Functions.TriggerCallback(callbackName, function(cbResult)
+ p:resolve(cbResult)
+ end, ...)
+ result = Citizen.Await(p)
+ elseif isStarted(ESXExport) then
+ local p = promise.new()
+ ESX.TriggerServerCallback(callbackName, function(cbResult)
+ p:resolve(cbResult)
+ end, ...)
+ result = Citizen.Await(p)
+ else
+ print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with", callbackName)
+ end
+ return result
end
\ No newline at end of file
diff --git a/shared/contextmenus.lua b/shared/contextmenus.lua
index f16d350..82e64af 100644
--- a/shared/contextmenus.lua
+++ b/shared/contextmenus.lua
@@ -1,290 +1,290 @@
---- Opens a menu using the configured menu system.
----
---- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
----
----@param Menu table A table containing the menu options to display.
---- Each menu item can include:
---- - **header** (`string`): The text to display for the menu item.
---- - **txt** (`string`, optional): Additional text or description.
---- - **icon** (`string`, optional): Icon to display with the menu item.
---- - **onSelect** (`function`, optional): Function to execute when the menu item is selected.
---- - **arrow** (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
---- - **params** (`table`, optional): Additional parameters, such as events and arguments.
---- - **isMenuHeader** (`boolean`, optional): Marks the item as a header.
---- - **disabled** (`boolean`, optional): Disables the menu item if `true`.
----
----@param data table A table containing configuration data for the menu.
---- - **header** (`string`): The header/title of the menu.
---- - **headertxt** (`string`, optional): Additional header text.
---- - **onBack** (`function`, optional): Function to call when the "Return" option is selected.
---- - **onExit** (`function`, optional): Function to call when the menu is exited.
---- - **onSelected** (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
---- - **canClose** (`boolean`, optional): Whether the menu can be closed by the user.
----
----@usage
---- ```lua
---- openMenu({
---- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
---- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
---- }, {
---- header = "Main Menu",
---- headertxt = "Select an option",
---- onBack = function() print("Return selected") end,
---- onExit = function() print("Menu closed") end,
---- canClose = true,
---- })
---- ```
-function openMenu(Menu, data)
- if Config.System.Menu == "jim" then
- if data.onBack then
- table.insert(Menu, 1, {
- icon = "fas fa-circle-arrow-left",
- title = "Return",
- onSelect = data.onBack,
- })
- end
- exports["jim-nui"]:openMenu({
- title = data.header..(data.headertxt and " -- "..data.headertxt or ""),
- canClose = data.canClose and data.canClose or nil,
- onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
- onExit = data.onExit and data.onExit or nil,
- options = Menu,
- })
-
- elseif Config.System.Menu == "ox" then
- local index = nil
- if data.onBack and not data.onSelected then
- table.insert(Menu, 1, {
- icon = "fas fa-circle-arrow-left",
- title = "Return",
- onSelect = data.onBack,
- label = "Return",
- })
- end
- for k in pairs(Menu) do
- if data.onSelected and Menu[k].arrow then
- Menu[k].icon = "fas fa-angle-right"
- end
- if not Menu[k].title then
- if Menu[k].header ~= nil and Menu[k].header ~= "" then
- Menu[k].title = Menu[k].header
- Menu[k].label = Menu[k].header
- if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
- else
- Menu[k].title = Menu[k].txt
- Menu[k].label = Menu[k].txt
- end
- end
- if Menu[k].params then
- Menu[k].event = Menu[k].params.event
- Menu[k].args = Menu[k].params.args or {}
- end
- if Menu[k].isMenuHeader then
- Menu[k].disabled = true
- end
- end
- local menuID = 'Menu'
- (data.onSelected and lib.registerMenu or lib.registerContext)({
- id = menuID,
- title = data.header..br..br..(data.headertxt and data.headertxt or ""),
- position = 'top-right',
- options = Menu,
- canClose = data.canClose and data.canClose or nil,
- onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
- onExit = data.onExit and data.onExit or nil,
- onSelected = data.onSelected and (function(selected) index = selected end) or nil,
- }, data.onSelected and (function(x, y, args)
- if Menu[x].refresh then
- if Menu[x].onSelect then
- Menu[x].onSelect()
- end
- lib.showMenu(menuID, index)
- else
- if Menu[x].onSelect then
- Menu[x].onSelect()
- else
- lib.showMenu(menuID, index)
- end
- end
- end) or nil)
- if data.onSelected then
- lib.showMenu(menuID, 1)
- else
- lib.showContext(menuID)
- end
-
- elseif Config.System.Menu == "qb" then
- if data.onBack then
- table.insert(Menu, 1, {
- icon = "fas fa-circle-arrow-left",
- header = " ",
- txt = "Return",
- params = {
- isAction = true,
- event = data.onBack,
- },
- })
- elseif data.canClose then
- table.insert(Menu, 1, {
- icon = "fas fa-circle-xmark",
- header = " ",
- txt = "Close",
- params = {
- isAction = true,
- event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
- },
- })
- end
- if data.header ~= nil then
- local tempMenu = {}
- for k, v in pairs(Menu) do tempMenu[k + 1] = v end
- tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
- Menu = tempMenu
- end
- for k in pairs(Menu) do
- if not Menu[k].params or not Menu[k].params.event then
- if Menu[k].onSelect then
- Menu[k].params = {
- isAction = true,
- event = Menu[k].onSelect,
- }
- else
- Menu[k].params = {
- isAction = true,
- event = function() end,
- }
- end
- end
- if not Menu[k].header then Menu[k].header = " " end
- if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
- end
- exports[QBMenuExport]:openMenu(Menu)
-
- elseif Config.System.Menu == "gta" then
- WarMenu.CreateMenu(tostring(Menu),
- data.header,
- data.headertxt or " ",
- {
- titleColor = { 222, 255, 255 },
- maxOptionCountOnScreen = 15,
- width = 0.25,
- x = 0.7,
- })
- if WarMenu.IsAnyMenuOpened() then return end
- WarMenu.OpenMenu(tostring(Menu))
- CreateThread(function()
- local close = true
- while true do
- if WarMenu.Begin(tostring(Menu)) then
- if data.onBack then
- if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
- WarMenu.CloseMenu()
- Wait(10)
- data.onBack()
- end
- end
- for k in pairs(Menu) do
- local pressed = WarMenu.Button(Menu[k].header)
- if not Menu[k].header then
- Menu[k].header = Menu[k].txt
- Menu[k].txt = nil
- end
- if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
- if Menu[k].disabled or Menu[k].isMenuHeader then
- WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
- else
- WarMenu.ToolTip(
- (Menu[k].blip and "~BLIP_".."8".."~ " or "")..
- Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
- true)
- end
- end
- if pressed and not Menu[k].isMenuHeader then
- WarMenu.CloseMenu()
- close = false
- Menu[k].onSelect()
- end
- end
- WarMenu.End()
- else
- return
- end
- if not WarMenu.IsAnyMenuOpened() and close then
- stopTempCam(cam)
- if data.onExit then data.onExit() end
- end
- Wait(0)
- end
- end)
-
- elseif Config.System.Menu == "esx" then
- for k in pairs(Menu) do
- Menu[k].label = Menu[k].header
- Menu[k].name = "button"..k
- end
- if data.canClose then
- table.insert(Menu, 1, {
- icon = "fas fa-circle-xmark",
- label = "Close",
- name = "close",
- onSelect = data.onExit,
- })
- end
- if data.onBack then
- table.insert(Menu, 1, {
- icon = "fas fa-circle-arrow-left",
- label = "Return",
- name = "return",
- onSelect = data.onBack,
- })
- end
-
- ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
- title = data.header,
- align = 'top-right',
- elements = Menu,
- },
- function(menuData, menu)
- for k in pairs(Menu) do
- if menuData.current.name == Menu[k].name then
- menu.close()
- Wait(10)
- Menu[k].onSelect()
- end
- end
- end,
- function(data, menu)
- menu.close()
- end)
- end
-end
-
---- A line break constant used for formatting menu headers.
-br = (Config.System.Menu == "ox" or Config.System.Menu == "gta") and "\n" or "
"
-
---- Checks if the menu system is classified as 'ox' or 'gta'.
----
---- This function is used to decide how to make line breaks in menu headers.
----
---- @return boolean Returns `true` if the menu system is 'ox' or 'gta'; otherwise, `false`.
----
---- @usage
---- ```lua
---- if isOx() then
---- -- Use specific formatting
---- end
---- ```
-function isOx() return (Config.System.Menu == "ox" or Config.System.Menu == "gta") end
-
-
---- Checks if any WarMenu menu is currently open.
----
---- @return boolean Returns `true` if a WarMenu menu is open; otherwise, `false`.
----
---- @usage
---- ```lua
---- if isWarMenuOpen() then
---- -- Do something
---- end
---- ```
+--- Opens a menu using the configured menu system.
+---
+--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
+---
+---@param Menu table A table containing the menu options to display.
+--- Each menu item can include:
+--- - **header** (`string`): The text to display for the menu item.
+--- - **txt** (`string`, optional): Additional text or description.
+--- - **icon** (`string`, optional): Icon to display with the menu item.
+--- - **onSelect** (`function`, optional): Function to execute when the menu item is selected.
+--- - **arrow** (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
+--- - **params** (`table`, optional): Additional parameters, such as events and arguments.
+--- - **isMenuHeader** (`boolean`, optional): Marks the item as a header.
+--- - **disabled** (`boolean`, optional): Disables the menu item if `true`.
+---
+---@param data table A table containing configuration data for the menu.
+--- - **header** (`string`): The header/title of the menu.
+--- - **headertxt** (`string`, optional): Additional header text.
+--- - **onBack** (`function`, optional): Function to call when the "Return" option is selected.
+--- - **onExit** (`function`, optional): Function to call when the menu is exited.
+--- - **onSelected** (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
+--- - **canClose** (`boolean`, optional): Whether the menu can be closed by the user.
+---
+---@usage
+--- ```lua
+--- openMenu({
+--- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
+--- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
+--- }, {
+--- header = "Main Menu",
+--- headertxt = "Select an option",
+--- onBack = function() print("Return selected") end,
+--- onExit = function() print("Menu closed") end,
+--- canClose = true,
+--- })
+--- ```
+function openMenu(Menu, data)
+ if Config.System.Menu == "jim" then
+ if data.onBack then
+ table.insert(Menu, 1, {
+ icon = "fas fa-circle-arrow-left",
+ title = "Return",
+ onSelect = data.onBack,
+ })
+ end
+ exports["jim-nui"]:openMenu({
+ title = data.header..(data.headertxt and " -- "..data.headertxt or ""),
+ canClose = data.canClose and data.canClose or nil,
+ onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
+ onExit = data.onExit and data.onExit or nil,
+ options = Menu,
+ })
+
+ elseif Config.System.Menu == "ox" then
+ local index = nil
+ if data.onBack and not data.onSelected then
+ table.insert(Menu, 1, {
+ icon = "fas fa-circle-arrow-left",
+ title = "Return",
+ onSelect = data.onBack,
+ label = "Return",
+ })
+ end
+ for k in pairs(Menu) do
+ if data.onSelected and Menu[k].arrow then
+ Menu[k].icon = "fas fa-angle-right"
+ end
+ if not Menu[k].title then
+ if Menu[k].header ~= nil and Menu[k].header ~= "" then
+ Menu[k].title = Menu[k].header
+ Menu[k].label = Menu[k].header
+ if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
+ else
+ Menu[k].title = Menu[k].txt
+ Menu[k].label = Menu[k].txt
+ end
+ end
+ if Menu[k].params then
+ Menu[k].event = Menu[k].params.event
+ Menu[k].args = Menu[k].params.args or {}
+ end
+ if Menu[k].isMenuHeader then
+ Menu[k].disabled = true
+ end
+ end
+ local menuID = 'Menu'
+ (data.onSelected and lib.registerMenu or lib.registerContext)({
+ id = menuID,
+ title = data.header..br..br..(data.headertxt and data.headertxt or ""),
+ position = 'top-right',
+ options = Menu,
+ canClose = data.canClose and data.canClose or nil,
+ onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
+ onExit = data.onExit and data.onExit or nil,
+ onSelected = data.onSelected and (function(selected) index = selected end) or nil,
+ }, data.onSelected and (function(x, y, args)
+ if Menu[x].refresh then
+ if Menu[x].onSelect then
+ Menu[x].onSelect()
+ end
+ lib.showMenu(menuID, index)
+ else
+ if Menu[x].onSelect then
+ Menu[x].onSelect()
+ else
+ lib.showMenu(menuID, index)
+ end
+ end
+ end) or nil)
+ if data.onSelected then
+ lib.showMenu(menuID, 1)
+ else
+ lib.showContext(menuID)
+ end
+
+ elseif Config.System.Menu == "qb" then
+ if data.onBack then
+ table.insert(Menu, 1, {
+ icon = "fas fa-circle-arrow-left",
+ header = " ",
+ txt = "Return",
+ params = {
+ isAction = true,
+ event = data.onBack,
+ },
+ })
+ elseif data.canClose then
+ table.insert(Menu, 1, {
+ icon = "fas fa-circle-xmark",
+ header = " ",
+ txt = "Close",
+ params = {
+ isAction = true,
+ event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
+ },
+ })
+ end
+ if data.header ~= nil then
+ local tempMenu = {}
+ for k, v in pairs(Menu) do tempMenu[k + 1] = v end
+ tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
+ Menu = tempMenu
+ end
+ for k in pairs(Menu) do
+ if not Menu[k].params or not Menu[k].params.event then
+ if Menu[k].onSelect then
+ Menu[k].params = {
+ isAction = true,
+ event = Menu[k].onSelect,
+ }
+ else
+ Menu[k].params = {
+ isAction = true,
+ event = function() end,
+ }
+ end
+ end
+ if not Menu[k].header then Menu[k].header = " " end
+ if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
+ end
+ exports[QBMenuExport]:openMenu(Menu)
+
+ elseif Config.System.Menu == "gta" then
+ WarMenu.CreateMenu(tostring(Menu),
+ data.header,
+ data.headertxt or " ",
+ {
+ titleColor = { 222, 255, 255 },
+ maxOptionCountOnScreen = 15,
+ width = 0.25,
+ x = 0.7,
+ })
+ if WarMenu.IsAnyMenuOpened() then return end
+ WarMenu.OpenMenu(tostring(Menu))
+ CreateThread(function()
+ local close = true
+ while true do
+ if WarMenu.Begin(tostring(Menu)) then
+ if data.onBack then
+ if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
+ WarMenu.CloseMenu()
+ Wait(10)
+ data.onBack()
+ end
+ end
+ for k in pairs(Menu) do
+ local pressed = WarMenu.Button(Menu[k].header)
+ if not Menu[k].header then
+ Menu[k].header = Menu[k].txt
+ Menu[k].txt = nil
+ end
+ if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
+ if Menu[k].disabled or Menu[k].isMenuHeader then
+ WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
+ else
+ WarMenu.ToolTip(
+ (Menu[k].blip and "~BLIP_".."8".."~ " or "")..
+ Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
+ true)
+ end
+ end
+ if pressed and not Menu[k].isMenuHeader then
+ WarMenu.CloseMenu()
+ close = false
+ Menu[k].onSelect()
+ end
+ end
+ WarMenu.End()
+ else
+ return
+ end
+ if not WarMenu.IsAnyMenuOpened() and close then
+ stopTempCam(cam)
+ if data.onExit then data.onExit() end
+ end
+ Wait(0)
+ end
+ end)
+
+ elseif Config.System.Menu == "esx" then
+ for k in pairs(Menu) do
+ Menu[k].label = Menu[k].header
+ Menu[k].name = "button"..k
+ end
+ if data.canClose then
+ table.insert(Menu, 1, {
+ icon = "fas fa-circle-xmark",
+ label = "Close",
+ name = "close",
+ onSelect = data.onExit,
+ })
+ end
+ if data.onBack then
+ table.insert(Menu, 1, {
+ icon = "fas fa-circle-arrow-left",
+ label = "Return",
+ name = "return",
+ onSelect = data.onBack,
+ })
+ end
+
+ ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
+ title = data.header,
+ align = 'top-right',
+ elements = Menu,
+ },
+ function(menuData, menu)
+ for k in pairs(Menu) do
+ if menuData.current.name == Menu[k].name then
+ menu.close()
+ Wait(10)
+ Menu[k].onSelect()
+ end
+ end
+ end,
+ function(data, menu)
+ menu.close()
+ end)
+ end
+end
+
+--- A line break constant used for formatting menu headers.
+br = (Config.System.Menu == "ox" or Config.System.Menu == "gta") and "\n" or "
"
+
+--- Checks if the menu system is classified as 'ox' or 'gta'.
+---
+--- This function is used to decide how to make line breaks in menu headers.
+---
+--- @return boolean Returns `true` if the menu system is 'ox' or 'gta'; otherwise, `false`.
+---
+--- @usage
+--- ```lua
+--- if isOx() then
+--- -- Use specific formatting
+--- end
+--- ```
+function isOx() return (Config.System.Menu == "ox" or Config.System.Menu == "gta") end
+
+
+--- Checks if any WarMenu menu is currently open.
+---
+--- @return boolean Returns `true` if a WarMenu menu is open; otherwise, `false`.
+---
+--- @usage
+--- ```lua
+--- if isWarMenuOpen() then
+--- -- Do something
+--- end
+--- ```
function isWarMenuOpen() if Config.System.Menu == "gta" then return WarMenu.IsAnyMenuOpened() else return false end end
\ No newline at end of file
diff --git a/shared/coreloader.lua b/shared/coreloader.lua
index 3a70f26..174e5bd 100644
--- a/shared/coreloader.lua
+++ b/shared/coreloader.lua
@@ -1,170 +1,197 @@
--- Create empty Variables --
-Items, Vehicles, Jobs, Gangs, Core, ESX = {}, nil, nil, nil, nil, nil
-
--- Correct QB inventory export (if needed) from 'qb-inventory' to 'ps-inventory' or 'lj-inventory' --
-Exports.QBInv = (isStarted("ps-inventory") and "ps-inventory") or (isStarted("lj-inventory") and "lj-inventory") or Exports.QBInv
-
--- Create simple variables based on the corresponding framework exports --
-OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport = Exports.OXLibExport or "", Exports.QBXExport or "", Exports.QBExport or "", Exports.ESXExport or "", Exports.OXCoreExport or ""
-
--- Create simple variables based on the corresponding inventory names --
-OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv = Exports.OXInv or "", Exports.QBInv or "", Exports.PSInv or "", Exports.QSInv or "", Exports.CoreInv or "", Exports.CodeMInv or "", Exports.OrigenInv or ""
-
--- QB-Menu export name grabbed from exports.lua --
-QBMenuExport = Exports.QBMenuExport or ""
-
--- Target exports based on what is loaded --
-QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
-
--- If Debug mode is on in the loading script, print the list of found exports --
--- Some may "lie", 'ox_target' attempts to use 'qb-target' exports and this print will say its loaded (which is technically true) --
-for _, v in pairs(Exports) do
- if isStarted(v) then debugPrint("^6Bridge^7: '^3"..v.."^7' ^2export found ^7") end
-end
-
-local itemResource, jobResource, vehResource = "", "", ""
-
--- Load item lists --
--- Complies the items from ox_inventory, qb-core or esx into 'Items' and loads them in a layout similar to qb-core's Shared items.lua --
--- For example this makes it so instead of QBCore.Shared.Items[item], you can load 'Item[item]' in the script --
-if isStarted(OXInv) then itemResource = OXInv
- Items = exports[OXInv]:Items()
- for k, v in pairs(Items) do
- if v.client and v.client.image then
- Items[k].image = (v.client.image):gsub("nui://"..OXInv.."/web/images/", "")
- else
- Items[k].image = k..".png"
- end
- Items[k].hunger = v.client and v.client.hunger or nil
- Items[k].thirst = v.client and v.client.thirst or nil
- end
-
-elseif isStarted(QBExport) then itemResource = QBExport
- Core = Core or exports[QBExport]:GetCoreObject()
- Items = Core and Core.Shared.Items or nil
- if isStarted(QBExport) and not isStarted(QBXExport) then
- RegisterNetEvent('QBCore:Client:UpdateObject', function()
- Core = Core or exports[QBExport]:GetCoreObject()
- Items = Core and Core.Shared.Items or nil
- end)
- end
-
-elseif isStarted(ESXExport) then itemResource = ESXExport
- ESX = exports[ESXExport]:getSharedObject()
- Items = ESX and ESX.Items or nil
-end
--- If it fails to load items, then it will print the error below --
--- If it loads them and debug is on, print how many items and where from --
-if not Items then
- print("^4ERROR^7: ^2No Core Items detected ^7- ^2Check ^3exports^1.^2lua^7")
-else
- debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource)
-end
-
--- Load Vehicles --
--- Complies the vehicles from the detected frameworks into a table in the style of qb-cores shared vehicles.lua --
--- For example, instead of using QBCore.Shared.Vehicles[vehicle] you can load 'Vehicles[vehicle]' in the script --
-if isStarted(QBXExport) or isStarted(QBExport) then
- Core = Core or exports[QBExport]:GetCoreObject()
- Vehicles = Core and Core.Shared.Vehicles
- if isStarted(QBExport) and not isStarted(QBXExport) then
- RegisterNetEvent('QBCore:Client:UpdateObject', function()
- Core = Core or exports[QBExport]:GetCoreObject()
- Vehicles = Core and Core.Shared.Vehicles
- end)
- end
- vehResource = QBExport
-elseif isStarted(OXCoreExport) then
- Vehicles = {}
- for k, v in pairs(Ox.GetVehicleData()) do
- Vehicles[k] = { model = k, price = v.price, name = v.name, brand = v.make }
- end
- vehResource = OXCoreExport
-elseif isStarted(ESXExport) then
- -- print("^6Bridge^7: ^2Loading ^3Vehicles^2 from ^7"..ESXExport)
- -- print("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..ESXExport)
- CreateThread(function()
- if isServer() then
- createCallback(getScript()..":getVehiclesPrices", function(source)
- Vehicles = MySQL.query.await('SELECT model, price, name FROM vehicles')
- vehResource = ESXExport
- return Vehicles
- end)
- end
- if not isServer() then
- local TempVehicles = triggerCallback(getScript()..":getVehiclesPrices")
- for _, v in pairs(TempVehicles) do
- Vehicles = Vehicles or {}
- Vehicles[v.model] = { model = v.model, price = v.price, name = v.name, brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper) }
- end
- end
- end)
-end
-if vehResource == nil then
- print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3exports^1.^2lua^7")
-else
- debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..vehResource)
-end
-
--- Load Jobs --
--- Attempts to load the details of jobs and gangs and compile into tables --
--- For example, instead of using QBCore.Shared.Jobs[job] you can load 'Jobs[job]' in the script --
-if isStarted(QBXExport) then jobResource = QBXExport
- Core = Core or exports[QBExport]:GetCoreObject()
- Jobs, Gangs = exports[QBXExport]:GetJobs(), exports[QBXExport]:GetGangs()
-
-elseif isStarted(OXCoreExport) then jobResource = OXExport
- CreateThread(function()
- if isServer() then
- createCallback(getScript()..":getOxGroups", function(source)
- Jobs = MySQL.query.await('SELECT * FROM `ox_groups`') return Jobs
- end)
- else
- local TempJobs = triggerCallback(getScript()..":getOxGroups")
- Jobs = TempJobs or {}
- for k, v in pairs(TempJobs) do
- local grades = {}
- for i = 1, #v.grades do grades[i] = { name = v.grades[i], isboss = (i == #v.grades)} end
- Jobs[v.name] = { label = v.label, grades = grades }
- end
- Gangs = Jobs
- end
- end)
-
-elseif isStarted(QBExport) then jobResource = QBExport
- Core = Core or exports[QBExport]:GetCoreObject()
- Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
- if isStarted(QBExport) and not isStarted(QBXExport) then
- RegisterNetEvent('QBCore:Client:UpdateObject', function()
- Core = exports[QBExport]:GetCoreObject()
- Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
- end)
- end
-
-elseif isStarted(ESXExport) then
- --print("^6Bridge^7: ^2Loading ^3Jobs^7/^3Gangs^2 from ^7"..ESXExport)
- ESX = exports[ESXExport]:getSharedObject()
- if isServer() then
- Jobs = ESX.GetJobs()
- for k, v in pairs(Jobs) do
- local count = countTable(Jobs[k].grades)-1
- Jobs[k].grades[tostring(count)].isBoss = true
- end
- Gangs = Jobs
- end
- CreateThread(function()
- while not ESX do Wait(0) end
- if isServer() then
- createCallback(getScript()..":getJobs", function(source)
- return Jobs
- end)
- end
- if not isServer() then
- Jobs = triggerCallback(getScript()..":getJobs")
- Gangs = Jobs
- end
- end)
-end
-if not isStarted(ESXExport) and Jobs then
- debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Jobs).." ^3Jobs^2 from ^7"..jobResource, "^6Bridge^7: ^2Loading ^6"..countTable(Gangs).." ^3Gangs^2 from ^7"..jobResource)
+-- Create empty Variables --
+Items, Vehicles, Jobs, Gangs, Core, ESX = {}, nil, nil, nil, nil, nil
+
+-- Correct QB inventory export (if needed) from 'qb-inventory' to 'ps-inventory' or 'lj-inventory' --
+Exports.QBInv = (isStarted("ps-inventory") and "ps-inventory") or (isStarted("lj-inventory") and "lj-inventory") or Exports.QBInv
+
+-- Create simple variables based on the corresponding framework exports --
+OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport = Exports.OXLibExport or "", Exports.QBXExport or "", Exports.QBExport or "", Exports.ESXExport or "", Exports.OXCoreExport or ""
+
+-- Create simple variables based on the corresponding inventory names --
+OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv = Exports.OXInv or "", Exports.QBInv or "", Exports.PSInv or "", Exports.QSInv or "", Exports.CoreInv or "", Exports.CodeMInv or "", Exports.OrigenInv or ""
+
+-- QB-Menu export name grabbed from exports.lua --
+QBMenuExport = Exports.QBMenuExport or ""
+
+-- Target exports based on what is loaded --
+QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
+
+-- If Debug mode is on in the loading script, print the list of found exports --
+-- Some may "lie", 'ox_target' attempts to use 'qb-target' exports and this print will say its loaded (which is technically true) --
+for _, v in pairs(Exports) do
+ if isStarted(v) then debugPrint("^6Bridge^7: '^3"..v.."^7' ^2export found ^7") end
+end
+
+local itemResource, jobResource, vehResource = "", "", ""
+
+-- Load item lists --
+-- Complies the items from ox_inventory, qb-core or esx into 'Items' and loads them in a layout similar to qb-core's Shared items.lua --
+-- For example this makes it so instead of QBCore.Shared.Items[item], you can load 'Item[item]' in the script --
+if isStarted(OXInv) then
+ itemResource = OXInv
+ Items = exports[OXInv]:Items()
+ for k, v in pairs(Items) do
+ if v.client and v.client.image then
+ Items[k].image = (v.client.image):gsub("nui://"..OXInv.."/web/images/", "")
+ else
+ Items[k].image = k..".png"
+ end
+ Items[k].hunger = v.client and v.client.hunger or nil
+ Items[k].thirst = v.client and v.client.thirst or nil
+ end
+
+elseif isStarted(QBExport) then
+ itemResource = QBExport
+ Core = Core or exports[QBExport]:GetCoreObject()
+ Items = Core and Core.Shared.Items or nil
+ if isStarted(QBExport) and not isStarted(QBXExport) then
+ RegisterNetEvent('QBCore:Client:UpdateObject', function()
+ Core = Core or exports[QBExport]:GetCoreObject()
+ Items = Core and Core.Shared.Items or nil
+ end)
+ end
+
+elseif isStarted(ESXExport) then
+ itemResource = ESXExport
+ ESX = exports[ESXExport]:getSharedObject()
+ --Items = ESX and ESX.Items or nil
+ while ESX == nil do
+ print("Waiting for ESX")
+ Wait(0)
+ end
+ if isServer() then
+ Items = ESX.GetItems()
+ debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource)
+ end
+ CreateThread(function()
+ while not ESX do Wait(0) end
+ if isServer() then
+ createCallback(getScript()..":getItems", function(source)
+ return Items
+ end)
+ end
+ if not isServer() then
+ Items = triggerCallback(getScript()..":getItems")
+ debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource)
+ end
+ end)
+
+
+end
+-- If it fails to load items, then it will print the error below --
+-- If it loads them and debug is on, print how many items and where from --
+if not isStarted(ESXExport) then
+ if not Items then
+ print("^4ERROR^7: ^2No Core Items detected ^7- ^2Check ^3exports^1.^2lua^7")
+ else
+ debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource)
+ end
+end
+
+-- Load Vehicles --
+-- Complies the vehicles from the detected frameworks into a table in the style of qb-cores shared vehicles.lua --
+-- For example, instead of using QBCore.Shared.Vehicles[vehicle] you can load 'Vehicles[vehicle]' in the script --
+if isStarted(QBXExport) or isStarted(QBExport) then
+ Core = Core or exports[QBExport]:GetCoreObject()
+ Vehicles = Core and Core.Shared.Vehicles
+ if isStarted(QBExport) and not isStarted(QBXExport) then
+ RegisterNetEvent('QBCore:Client:UpdateObject', function()
+ Core = Core or exports[QBExport]:GetCoreObject()
+ Vehicles = Core and Core.Shared.Vehicles
+ end)
+ end
+ vehResource = QBExport
+elseif isStarted(OXCoreExport) then
+ Vehicles = {}
+ for k, v in pairs(Ox.GetVehicleData()) do
+ Vehicles[k] = { model = k, price = v.price, name = v.name, brand = v.make }
+ end
+ vehResource = OXCoreExport
+elseif isStarted(ESXExport) then
+ -- print("^6Bridge^7: ^2Loading ^3Vehicles^2 from ^7"..ESXExport)
+ -- print("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..ESXExport)
+ CreateThread(function()
+ if isServer() then
+ createCallback(getScript()..":getVehiclesPrices", function(source)
+ Vehicles = MySQL.query.await('SELECT model, price, name FROM vehicles')
+ vehResource = ESXExport
+ return Vehicles
+ end)
+ end
+ if not isServer() then
+ local TempVehicles = triggerCallback(getScript()..":getVehiclesPrices")
+ for _, v in pairs(TempVehicles) do
+ Vehicles = Vehicles or {}
+ Vehicles[v.model] = { model = v.model, price = v.price, name = v.name, brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper) }
+ end
+ end
+ end)
+end
+if vehResource == nil then
+ print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3exports^1.^2lua^7")
+else
+ debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..vehResource)
+end
+
+-- Load Jobs --
+-- Attempts to load the details of jobs and gangs and compile into tables --
+-- For example, instead of using QBCore.Shared.Jobs[job] you can load 'Jobs[job]' in the script --
+if isStarted(QBXExport) then jobResource = QBXExport
+ Core = Core or exports[QBExport]:GetCoreObject()
+ Jobs, Gangs = exports[QBXExport]:GetJobs(), exports[QBXExport]:GetGangs()
+
+elseif isStarted(OXCoreExport) then jobResource = OXExport
+ CreateThread(function()
+ if isServer() then
+ createCallback(getScript()..":getOxGroups", function(source)
+ Jobs = MySQL.query.await('SELECT * FROM `ox_groups`') return Jobs
+ end)
+ else
+ local TempJobs = triggerCallback(getScript()..":getOxGroups")
+ Jobs = TempJobs or {}
+ for k, v in pairs(TempJobs) do
+ local grades = {}
+ for i = 1, #v.grades do grades[i] = { name = v.grades[i], isboss = (i == #v.grades)} end
+ Jobs[v.name] = { label = v.label, grades = grades }
+ end
+ Gangs = Jobs
+ end
+ end)
+
+elseif isStarted(QBExport) then jobResource = QBExport
+ Core = Core or exports[QBExport]:GetCoreObject()
+ Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
+ if isStarted(QBExport) and not isStarted(QBXExport) then
+ RegisterNetEvent('QBCore:Client:UpdateObject', function()
+ Core = exports[QBExport]:GetCoreObject()
+ Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
+ end)
+ end
+
+elseif isStarted(ESXExport) then
+ --print("^6Bridge^7: ^2Loading ^3Jobs^7/^3Gangs^2 from ^7"..ESXExport)
+ ESX = exports[ESXExport]:getSharedObject()
+ if isServer() then
+ Jobs = ESX.GetJobs()
+ for k, v in pairs(Jobs) do
+ local count = countTable(Jobs[k].grades)-1
+ Jobs[k].grades[tostring(count)].isBoss = true
+ end
+ Gangs = Jobs
+ end
+ CreateThread(function()
+ while not ESX do Wait(0) end
+ if isServer() then
+ createCallback(getScript()..":getJobs", function(source)
+ return Jobs
+ end)
+ end
+ if not isServer() then
+ Jobs = triggerCallback(getScript()..":getJobs")
+ Gangs = Jobs
+ end
+ end)
+end
+if not isStarted(ESXExport) and Jobs then
+ debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Jobs).." ^3Jobs^2 from ^7"..jobResource, "^6Bridge^7: ^2Loading ^6"..countTable(Gangs).." ^3Gangs^2 from ^7"..jobResource)
end
\ No newline at end of file
diff --git a/shared/crafting.lua b/shared/crafting.lua
index 45711f6..71cc8e1 100644
--- a/shared/crafting.lua
+++ b/shared/crafting.lua
@@ -1,481 +1,481 @@
-local CraftLock = false
-
---- Opens a crafting menu based on the provided data.
----
---- This function checks job requirements, prepares the menu options, and opens the crafting menu.
---- It handles item availability, crafting recipes, and displays appropriate icons and labels.
----
----@param data table A table containing crafting menu data.
---- - **craftable** (`table`): The crafting options and settings.
---- - **Header** (`string`): The header/title of the crafting menu.
---- - **Recipes** (`table`): A list of crafting recipes.
---- - **coords** (`vector3`): The coordinates where the crafting menu is being opened.
---- - **stashTable** (`string` or `table`, optional): The stash name(s) to check for item availability.
---- - **stashName** (`string` or `table`, optional): Alias for `stashTable`.
---- - **job** (`string` or `table`, optional): Job(s) required to access the crafting menu.
---- - **gang** (`string` or `table`, optional): Gang(s) required to access the crafting menu.
---- - **onBack** (`function`, optional): Function to call when returning from the menu.
----
----@usage
---- ```lua
---- craftingMenu({
---- craftable = {
---- Header = "Weapon Crafting",
---- Recipes = {
---- [1] = {
---- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
---- amount = 1,
---- },
---- -- More recipes...
---- },
---- Anims = {
---- animDict = "amb@prop_human_parking_meter@male@idle_a",
---- anim = "idle_a",
---- },
---- },
---- coords = vector3(100.0, 200.0, 300.0),
---- stashTable = "crafting_stash",
---- job = "mechanic", -- Optional
---- onBack = function() print("Returning to previous menu") end,
---- })
---- ```
-function craftingMenu(data)
- if CraftLock then return end
- if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
- if Config.System.Menu == "jim" then
- triggerNotify(nil, "Thinking", "info")
- else
- openMenu({ { header = "Thinking...", icon = "fas fa-hourglass-end", isMenuHeader = true } }, { header = "Crafting Menu" } )
- end
- if data.stashTable then data.stashName = data.stashTable end
- local Menu, hasjob = {}, false
- local Recipes = data.craftable.Recipes
- local tempCarryTable = {}
- for i = 1, #Recipes do
- for k in pairs(Recipes[i]) do
- if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
- tempCarryTable[k] = Recipes[i].amount or 1
- end
- end
- end
-
- local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
- for i = 1, #Recipes do
- if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
- for k, v in pairs(Recipes[i]) do
- if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
- if Recipes[i].job then
- for l, b in pairs(Recipes[i].job) do
- hasjob = hasJob(l, nil, b)
- if hasjob == true then break end
- end
- else hasjob = true end
- local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or nil)
- if hasjob then
- local itemTable = {}
- local metaTable = {}
- for l, b in pairs(Recipes[i][tostring(k)]) do
- settext = settext..(settext ~= "" and br or "")..(Items[l] and Items[l].label or "error - "..l)..(b > 1 and " x"..b or "")
- metaTable[Items[l] and Items[l].label or "error - "..l] = b
- itemTable[l] = b
- Wait(0)
- end
- while not canCarryTable do Wait(0) end
- disable = not checkHasItem(data.stashName, itemTable)
- setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - " .. tostring(k)) .. (Recipes[i]["amount"] > 1 and " x" .. Recipes[i]["amount"] or "")
- if not disable then
- if not canCarryTable[k] then setheader = setheader .. " 📦"
- else setheader = setheader .. " ✔️" end
- elseif not canCarryTable[k] then setheader = setheader .. " 📦" end
- Menu[#Menu + 1] = {
- arrow = not disable and canCarryTable[k],
- disable = isStarted(QBMenuExport) and disable and not canCarryTable[k],
- icon = invImg((metadata and metadata.image) or tostring(k)),
- image = invImg((metadata and metadata.image) or tostring(k)),
- header = setheader..((disable or not canCarryTable[k]) and " ❌" or ""),
- txt = isStarted(QBMenuExport) and settext or nil,
- --metadata = debugMode and Recipes[i]["metadata"] or nil,
- metadata = metaTable,
- onSelect = ((not disable and canCarryTable[k]) and (function()
- local transdata = { item = k, craft = data.craftable.Recipes[i], craftable = data.craftable, coords = data.coords, stashName = data.stashName, onBack = data.onBack, metadata = Recipes[i]["metadata"] }
- if Config.Crafting.MultiCraft then multiCraft(transdata) else makeItem(transdata) end
- end) or nil),
- }
- end
- end
- Wait(0)
- end
- end
- openMenu(Menu, { header = data.craftable.Header, onBack = data.onBack or nil, canClose = true, onExit = function() end, })
- lookEnt(data.coords)
-end
-
---- Opens a menu for selecting the quantity to craft.
----
---- This function presents the player with options to craft multiple quantities of an item, based on `Config.Crafting.MultiCraftAmounts`.
----
----@param data table A table containing crafting data.
---- - **item** (`string`): The item to craft.
---- - **craft** (`table`): The crafting recipe for the item.
---- - **craftable** (`table`): The crafting options and settings.
---- - **coords** (`vector3`): The coordinates where the crafting is taking place.
---- - **stashName** (`string` or `table`, optional): The stash name(s) to check for item availability.
---- - **onBack** (`function`, optional): Function to call when returning from the menu.
---- - **metadata** (`table`, optional): Metadata for the crafted item.
----
----@usage
---- ```lua
---- multiCraft({
---- item = "weapon_pistol",
---- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
---- craftable = craftingOptions,
---- coords = vector3(100.0, 200.0, 300.0),
---- stashName = "crafting_stash",
---- onBack = function() craftingMenu(data) end,
---- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
---- })
---- ```
-function multiCraft(data)
- local Menu = {}
- local success = Config.Crafting.MultiCraftAmounts
- local metadata = data.metadata or nil
- Menu[#Menu+1] = {
- isMenuHeader = true,
- icon = invImg(metadata and metadata.image or data.item),
- header = metadata and metadata.label or Items[data.item].label,
- }
- for k in pairsByKeys(success) do
- local settext = ""
- local itemTable = {}
- for l, b in pairs(data.craft[data.item]) do
- itemTable[l] = (b * k)
- settext = settext..(settext ~= "" and br or "")..Items[l].label..(b*k > 1 and "- x"..b*k or "")
- Wait(0)
- end
- local disable, stashname = checkHasItem(data.stashName, itemTable)
- Menu[#Menu + 1] = {
- isMenuHeader = not disable,
- arrow = disable,
- header = "Craft - x"..k * data.craft.amount,
- txt = settext,
- onSelect = function ()
- makeItem({item = data.item, craft = data.craft, craftable = data.craftable, amount = k, coords = data.coords, stashName = stashname, stashTable = data.stashName, onBack = data.onBack, metadata = data.metadata })
- end,
- }
- end
- openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end, })
-end
-
---- Initiates the crafting process for a specified item.
----
---- This function handles the crafting animation, progress bar, item removal, and item creation.
----
----@param data table A table containing crafting data.
---- - **item** (`string`): The item to craft.
---- - **craft** (`table`): The crafting recipe for the item.
---- - **craftable** (`table`): The crafting options and settings.
---- - **amount** (`number`, optional): The quantity to craft. Default is `1`.
---- - **coords** (`vector3`): The coordinates where the crafting is taking place.
---- - **stashName** (`string` or `table`, optional): The stash name(s) to remove items from.
---- - **stashTable** (`string` or `table`, optional): Alias for `stashName`.
---- - **onBack** (`function`, optional): Function to call when returning from the menu.
---- - **metadata** (`table`, optional): Metadata for the crafted item.
----
----@usage
---- ```lua
---- makeItem({
---- item = "weapon_pistol",
---- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
---- craftable = craftingOptions,
---- amount = 2,
---- coords = vector3(100.0, 200.0, 300.0),
---- stashName = "crafting_stash",
---- onBack = function() craftingMenu(data) end,
---- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
---- })
---- ```
-function makeItem(data)
- if CraftLock then return end
- CraftLock = true
- if data.stashTable then data.stashName = data.stashTable end
- local bartime = data.craftable.progressBar and data.craftable.progressBar.time or 5000
- local bartext = (data.craftable.progressBar and data.craftable.progressBar.label) or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"]) or "Making a"
- local animDict = data.craftable.Anims and data.craftable.Anims.animDict or "amb@prop_human_parking_meter@male@idle_a"
- local anim = data.craftable.Anims and data.craftable.Anims.anim or "idle_a"
- local amount = data.amount and (data.amount ~= 1) and data.amount or 1
- local metadata = data.metadata or nil
- local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
-
- local crafted, crafting = true, true
- local cam = createTempCam(PlayerPedId(), data.coords)
- startTempCam(cam)
-
- for i = 1, amount do
- for k, v in pairs(data.craft) do
- if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
- if type(v) == "table" then
- for l, b in pairs(v) do
- if crafting and progressBar({
- label = "Using "..b.." "..Items[l].label,
- time = 1000,
- cancel = true,
- dict = 'pickup_object',
- anim = "putdown_low",
- flag = 48,
- icon = l,
- }) then
- TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", Items[l], "use", b) -- Show item box for each item
- else
- crafted, crafting = false, false
- break
- end
- Wait(200)
- end
- if crafted then
- local craftProp = nil
- if prop then
- local model, pos, rot, bone = prop.model, prop.pos, prop.rot, prop.bone
- craftProp = makeProp({ prop = model, coords = vec4(0, 0, 0, 0), true, true })
- AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), bone), pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, true, true, false, true, 1, true)
- end
- if crafting and progressBar({
- label = bartext..((metadata and metadata.label) or Items[data.item].label),
- time = bartime,
- cancel = true,
- dict = animDict,
- anim = anim,
- flag = 49,
- icon = data.item,
- }) then
- TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata)
- else
- crafting = false
- break
- end
- if craftProp then destroyProp(craftProp) end
- end
- end
- end
- end
- Wait(500)
- end
- stopTempCam()
- CraftLock = false
- lockInv(false)
- craftingMenu(data)
- ClearPedTasks(PlayerPedId())
-end
-
---- Server event handler for giving the crafted item to the player.
----
---- This event is triggered when the crafting process is completed successfully.
----
---- @param ItemMake string The item being crafted.
---- @param craftable table The crafting recipe and details.
---- @param stashName string|table The stash name(s) to remove items from.
---- @param metadata table (optional) Metadata for the crafted item.
-RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata)
- local src, amount, stashItems = source, craftable and craftable.amount or 1, nil
- if stashName then
- local itemRemove = {}
- if type(stashName) == "table" then
- for _, name in pairs(stashName) do
- stashItems = getStash(name)
- for k, v in pairs(craftable[ItemMake] or {}) do
- for _, b in pairs(stashItems or {}) do
- if k == b.name then itemRemove[k] = v end
- end
- end
- end
- else
- stashItems = getStash(stashName)
- for k, v in pairs(craftable[ItemMake] or {}) do
- for _, b in pairs(stashItems or {}) do
- if k == b.name then itemRemove[k] = v end
- end
- end
- end
- stashRemoveItem(stashItems, stashName, itemRemove)
- else
- if craftable then
- for k, v in pairs(craftable[ItemMake] or {}) do
- TriggerEvent(getScript()..":server:toggleItem", false, tostring(k), v, src)
- end
- end
- end
- TriggerEvent(getScript()..":server:toggleItem", true, ItemMake, amount, src, metadata)
- --if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
-end)
-
---- Opens a selling menu based on the provided data.
----
---- This function checks available items to sell, prepares the menu options, and opens the selling menu.
----
----@param data table A table containing selling menu data.
---- - **sellTable** (`table`): The selling options and settings.
---- - **Items** (`table`): A list of items that can be sold with their prices.
---- - **Header** (`string`, optional): The header/title of the selling menu.
---- - **ped** (`number`, optional): The ped entity involved in the selling interaction.
---- - **onBack** (`function`, optional): Function to call when returning from the menu.
----
----@usage
---- ```lua
---- sellMenu({
---- sellTable = {
---- Header = "Sell Items",
---- Items = {
---- ["gold_ring"] = 100,
---- ["diamond"] = 500,
---- },
---- },
---- ped = pedEntity,
---- onBack = function() print("Returning to previous menu") end,
---- })
---- ```
-function sellMenu(data)
- local origData = data
- local Menu = {}
- if data.sellTable.Items then
- local itemList = {}
- for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
- local _, hasTable = hasItem(itemList)
- for k, v in pairsByKeys(data.sellTable.Items) do
- Menu[#Menu +1] = {
- isMenuHeader = not hasTable[k].hasItem,
- icon = invImg(k),
- header = Items[k].label.. (hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
- txt = Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"],
- onSelect = function()
- sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
- end,
- }
- end
- else
- for k, v in pairsByKeys(data.sellTable) do
- if type(v) == "table" then
- Menu[#Menu +1] = {
- arrow = true,
- header = k,
- txt = "Amount of items: "..countTable(v.Items),
- onSelect = function()
- v.onBack = function() sellMenu(origData) end
- v.sellTable = data.sellTable[k]
- sellMenu(v)
- end,
- }
- end
- end
- end
- openMenu(Menu, { header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items), headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items) or "", canClose = true, onBack = data.onBack })
-end
-
---- Handles the selling animation and item transaction.
----
---- This function plays the selling animation, removes the item from the player's inventory, and gives the player money.
----
----@param data table A table containing selling data.
---- - **item** (`string`): The item to sell.
---- - **price** (`number`): The price per item.
---- - **ped** (`number`, optional): The ped entity involved in the selling interaction.
---- - **onBack** (`function`, optional): Function to call when returning from the menu.
----
----@usage
---- ```lua
---- sellAnim({
---- item = "gold_ring",
---- price = 100,
---- ped = pedEntity,
---- onBack = function() sellMenu(data) end,
---- })
---- ```
-function sellAnim(data)
- if not hasItem(data.item, 1) then
- triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error")
- return
- end
- for k, v in pairs(GetGamePool('CObject')) do
- for _, model in pairs({`p_cs_clipboard`}) do
- if GetEntityModel(v) == model then
- if IsEntityAttachedToEntity(data.ped, v) then
- DeleteObject(v) DetachEntity(v, 0, 0) SetEntityAsMissionEntity(v, true, true)
- Wait(100) DeleteEntity(v)
- end
- end
- end
- end
- TriggerServerEvent(getScript().."Sellitems", data)
- lookEnt(data.ped)
- local dict = "mp_common"
- playAnim(dict, "givetake2_a", 0.3, 2)
- playAnim(dict, "givetake2_b", 0.3, 2, data.ped)
- Wait(2000)
- StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5)
- StopAnimTask(data.ped, dict, "givetake2_b", 0.5)
- if data.onBack then data.onBack() end
-end
-
---- Server event handler for processing the item sale.
----
---- This event removes the sold item from the player's inventory and adds money to their account.
----
----@param data table The data containing item and price information.
-RegisterNetEvent(getScript().."Sellitems", function(data)
- local src = source
- local hasItems, hasTable = hasItem(data.item, 1, src)
- if hasItems then
- TriggerEvent(getScript()..":server:toggleItem", false, data.item, hasTable[data.item].count, src)
- TriggerEvent(getScript()..":server:FundPlayer", (hasTable[data.item].count * data.price), "cash", src)
- else
- triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src)
- end
-end)
-
---- Opens a shop interface for the player.
----
---- This function checks job requirements and opens the shop using the appropriate inventory system.
----
----@param data table A table containing shop data.
---- - **shop** (`string`): The shop identifier.
---- - **items** (`table`): The items available in the shop.
---- - **coords** (`vector3`): The coordinates where the shop interaction is happening.
---- - **job** (`string` or `table`, optional): Job(s) required to access the shop.
---- - **gang** (`string` or `table`, optional): Gang(s) required to access the shop.
----
----@usage
---- ```lua
---- openShop({
---- shop = "weapon_shop",
---- items = weaponShopItems,
---- coords = vector3(100.0, 200.0, 300.0),
---- job = "police",
---- })
---- ```
-function openShop(data)
- if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
- if isStarted(OXInv) then
- exports[OXInv]:openInventory('shop', { type = data.shop })
- elseif isStarted(QBInv) then
- if QBInvNew then
- TriggerServerEvent(getScript()..':server:OpenShopNewQB', data.shop) -- i hate qb-inv
- else
- TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
- end
- else
- TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
- end
- lookEnt(data.coords)
-end
-
---- Server event handler for opening a new QB inventory shop.
----
---- This event is triggered when using the new QB inventory system.
----
----@param data table The shop data to open.
-RegisterNetEvent(getScript()..':server:OpenShopNewQB', function(data)
- exports[QBInv]:OpenShop(source, data)
-end)
-
---- Server-side callback registration for checking if the player can carry items.
-if isServer() then
- createCallback(getScript()..':server:canCarry', function(source, itemTable) local result = canCarry(itemTable, source) return result end)
+local CraftLock = false
+
+--- Opens a crafting menu based on the provided data.
+---
+--- This function checks job requirements, prepares the menu options, and opens the crafting menu.
+--- It handles item availability, crafting recipes, and displays appropriate icons and labels.
+---
+---@param data table A table containing crafting menu data.
+--- - **craftable** (`table`): The crafting options and settings.
+--- - **Header** (`string`): The header/title of the crafting menu.
+--- - **Recipes** (`table`): A list of crafting recipes.
+--- - **coords** (`vector3`): The coordinates where the crafting menu is being opened.
+--- - **stashTable** (`string` or `table`, optional): The stash name(s) to check for item availability.
+--- - **stashName** (`string` or `table`, optional): Alias for `stashTable`.
+--- - **job** (`string` or `table`, optional): Job(s) required to access the crafting menu.
+--- - **gang** (`string` or `table`, optional): Gang(s) required to access the crafting menu.
+--- - **onBack** (`function`, optional): Function to call when returning from the menu.
+---
+---@usage
+--- ```lua
+--- craftingMenu({
+--- craftable = {
+--- Header = "Weapon Crafting",
+--- Recipes = {
+--- [1] = {
+--- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
+--- amount = 1,
+--- },
+--- -- More recipes...
+--- },
+--- Anims = {
+--- animDict = "amb@prop_human_parking_meter@male@idle_a",
+--- anim = "idle_a",
+--- },
+--- },
+--- coords = vector3(100.0, 200.0, 300.0),
+--- stashTable = "crafting_stash",
+--- job = "mechanic", -- Optional
+--- onBack = function() print("Returning to previous menu") end,
+--- })
+--- ```
+function craftingMenu(data)
+ if CraftLock then return end
+ if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
+ if Config.System.Menu == "jim" then
+ triggerNotify(nil, "Thinking", "info")
+ else
+ openMenu({ { header = "Thinking...", icon = "fas fa-hourglass-end", isMenuHeader = true } }, { header = "Crafting Menu" } )
+ end
+ if data.stashTable then data.stashName = data.stashTable end
+ local Menu, hasjob = {}, false
+ local Recipes = data.craftable.Recipes
+ local tempCarryTable = {}
+ for i = 1, #Recipes do
+ for k in pairs(Recipes[i]) do
+ if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
+ tempCarryTable[k] = Recipes[i].amount or 1
+ end
+ end
+ end
+
+ local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
+ for i = 1, #Recipes do
+ if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
+ for k, v in pairs(Recipes[i]) do
+ if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
+ if Recipes[i].job then
+ for l, b in pairs(Recipes[i].job) do
+ hasjob = hasJob(l, nil, b)
+ if hasjob == true then break end
+ end
+ else hasjob = true end
+ local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or nil)
+ if hasjob then
+ local itemTable = {}
+ local metaTable = {}
+ for l, b in pairs(Recipes[i][tostring(k)]) do
+ settext = settext..(settext ~= "" and br or "")..(Items[l] and Items[l].label or "error - "..l)..(b > 1 and " x"..b or "")
+ metaTable[Items[l] and Items[l].label or "error - "..l] = b
+ itemTable[l] = b
+ Wait(0)
+ end
+ while not canCarryTable do Wait(0) end
+ disable = not checkHasItem(data.stashName, itemTable)
+ setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - " .. tostring(k)) .. (Recipes[i]["amount"] > 1 and " x" .. Recipes[i]["amount"] or "")
+ if not disable then
+ if not canCarryTable[k] then setheader = setheader .. " 📦"
+ else setheader = setheader .. " ✔️" end
+ elseif not canCarryTable[k] then setheader = setheader .. " 📦" end
+ Menu[#Menu + 1] = {
+ arrow = not disable and canCarryTable[k],
+ disable = isStarted(QBMenuExport) and disable and not canCarryTable[k],
+ icon = invImg((metadata and metadata.image) or tostring(k)),
+ image = invImg((metadata and metadata.image) or tostring(k)),
+ header = setheader..((disable or not canCarryTable[k]) and " ❌" or ""),
+ txt = isStarted(QBMenuExport) and settext or nil,
+ --metadata = debugMode and Recipes[i]["metadata"] or nil,
+ metadata = metaTable,
+ onSelect = ((not disable and canCarryTable[k]) and (function()
+ local transdata = { item = k, craft = data.craftable.Recipes[i], craftable = data.craftable, coords = data.coords, stashName = data.stashName, onBack = data.onBack, metadata = Recipes[i]["metadata"] }
+ if Config.Crafting.MultiCraft then multiCraft(transdata) else makeItem(transdata) end
+ end) or nil),
+ }
+ end
+ end
+ Wait(0)
+ end
+ end
+ openMenu(Menu, { header = data.craftable.Header, onBack = data.onBack or nil, canClose = true, onExit = function() end, })
+ lookEnt(data.coords)
+end
+
+--- Opens a menu for selecting the quantity to craft.
+---
+--- This function presents the player with options to craft multiple quantities of an item, based on `Config.Crafting.MultiCraftAmounts`.
+---
+---@param data table A table containing crafting data.
+--- - **item** (`string`): The item to craft.
+--- - **craft** (`table`): The crafting recipe for the item.
+--- - **craftable** (`table`): The crafting options and settings.
+--- - **coords** (`vector3`): The coordinates where the crafting is taking place.
+--- - **stashName** (`string` or `table`, optional): The stash name(s) to check for item availability.
+--- - **onBack** (`function`, optional): Function to call when returning from the menu.
+--- - **metadata** (`table`, optional): Metadata for the crafted item.
+---
+---@usage
+--- ```lua
+--- multiCraft({
+--- item = "weapon_pistol",
+--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
+--- craftable = craftingOptions,
+--- coords = vector3(100.0, 200.0, 300.0),
+--- stashName = "crafting_stash",
+--- onBack = function() craftingMenu(data) end,
+--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
+--- })
+--- ```
+function multiCraft(data)
+ local Menu = {}
+ local success = Config.Crafting.MultiCraftAmounts
+ local metadata = data.metadata or nil
+ Menu[#Menu+1] = {
+ isMenuHeader = true,
+ icon = invImg(metadata and metadata.image or data.item),
+ header = metadata and metadata.label or Items[data.item].label,
+ }
+ for k in pairsByKeys(success) do
+ local settext = ""
+ local itemTable = {}
+ for l, b in pairs(data.craft[data.item]) do
+ itemTable[l] = (b * k)
+ settext = settext..(settext ~= "" and br or "")..Items[l].label..(b*k > 1 and "- x"..b*k or "")
+ Wait(0)
+ end
+ local disable, stashname = checkHasItem(data.stashName, itemTable)
+ Menu[#Menu + 1] = {
+ isMenuHeader = not disable,
+ arrow = disable,
+ header = "Craft - x"..k * data.craft.amount,
+ txt = settext,
+ onSelect = function ()
+ makeItem({item = data.item, craft = data.craft, craftable = data.craftable, amount = k, coords = data.coords, stashName = stashname, stashTable = data.stashName, onBack = data.onBack, metadata = data.metadata })
+ end,
+ }
+ end
+ openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end, })
+end
+
+--- Initiates the crafting process for a specified item.
+---
+--- This function handles the crafting animation, progress bar, item removal, and item creation.
+---
+---@param data table A table containing crafting data.
+--- - **item** (`string`): The item to craft.
+--- - **craft** (`table`): The crafting recipe for the item.
+--- - **craftable** (`table`): The crafting options and settings.
+--- - **amount** (`number`, optional): The quantity to craft. Default is `1`.
+--- - **coords** (`vector3`): The coordinates where the crafting is taking place.
+--- - **stashName** (`string` or `table`, optional): The stash name(s) to remove items from.
+--- - **stashTable** (`string` or `table`, optional): Alias for `stashName`.
+--- - **onBack** (`function`, optional): Function to call when returning from the menu.
+--- - **metadata** (`table`, optional): Metadata for the crafted item.
+---
+---@usage
+--- ```lua
+--- makeItem({
+--- item = "weapon_pistol",
+--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
+--- craftable = craftingOptions,
+--- amount = 2,
+--- coords = vector3(100.0, 200.0, 300.0),
+--- stashName = "crafting_stash",
+--- onBack = function() craftingMenu(data) end,
+--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
+--- })
+--- ```
+function makeItem(data)
+ if CraftLock then return end
+ CraftLock = true
+ if data.stashTable then data.stashName = data.stashTable end
+ local bartime = data.craftable.progressBar and data.craftable.progressBar.time or 5000
+ local bartext = (data.craftable.progressBar and data.craftable.progressBar.label) or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"]) or "Making a"
+ local animDict = data.craftable.Anims and data.craftable.Anims.animDict or "amb@prop_human_parking_meter@male@idle_a"
+ local anim = data.craftable.Anims and data.craftable.Anims.anim or "idle_a"
+ local amount = data.amount and (data.amount ~= 1) and data.amount or 1
+ local metadata = data.metadata or nil
+ local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
+
+ local crafted, crafting = true, true
+ local cam = createTempCam(PlayerPedId(), data.coords)
+ startTempCam(cam)
+
+ for i = 1, amount do
+ for k, v in pairs(data.craft) do
+ if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
+ if type(v) == "table" then
+ for l, b in pairs(v) do
+ if crafting and progressBar({
+ label = "Using "..b.." "..Items[l].label,
+ time = 1000,
+ cancel = true,
+ dict = 'pickup_object',
+ anim = "putdown_low",
+ flag = 48,
+ icon = l,
+ }) then
+ TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", Items[l], "use", b) -- Show item box for each item
+ else
+ crafted, crafting = false, false
+ break
+ end
+ Wait(200)
+ end
+ if crafted then
+ local craftProp = nil
+ if prop then
+ local model, pos, rot, bone = prop.model, prop.pos, prop.rot, prop.bone
+ craftProp = makeProp({ prop = model, coords = vec4(0, 0, 0, 0), true, true })
+ AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), bone), pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, true, true, false, true, 1, true)
+ end
+ if crafting and progressBar({
+ label = bartext..((metadata and metadata.label) or Items[data.item].label),
+ time = bartime,
+ cancel = true,
+ dict = animDict,
+ anim = anim,
+ flag = 49,
+ icon = data.item,
+ }) then
+ TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata)
+ else
+ crafting = false
+ break
+ end
+ if craftProp then destroyProp(craftProp) end
+ end
+ end
+ end
+ end
+ Wait(500)
+ end
+ stopTempCam()
+ CraftLock = false
+ lockInv(false)
+ craftingMenu(data)
+ ClearPedTasks(PlayerPedId())
+end
+
+--- Server event handler for giving the crafted item to the player.
+---
+--- This event is triggered when the crafting process is completed successfully.
+---
+--- @param ItemMake string The item being crafted.
+--- @param craftable table The crafting recipe and details.
+--- @param stashName string|table The stash name(s) to remove items from.
+--- @param metadata table (optional) Metadata for the crafted item.
+RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata)
+ local src, amount, stashItems = source, craftable and craftable.amount or 1, nil
+ if stashName then
+ local itemRemove = {}
+ if type(stashName) == "table" then
+ for _, name in pairs(stashName) do
+ stashItems = getStash(name)
+ for k, v in pairs(craftable[ItemMake] or {}) do
+ for _, b in pairs(stashItems or {}) do
+ if k == b.name then itemRemove[k] = v end
+ end
+ end
+ end
+ else
+ stashItems = getStash(stashName)
+ for k, v in pairs(craftable[ItemMake] or {}) do
+ for _, b in pairs(stashItems or {}) do
+ if k == b.name then itemRemove[k] = v end
+ end
+ end
+ end
+ stashRemoveItem(stashItems, stashName, itemRemove)
+ else
+ if craftable then
+ for k, v in pairs(craftable[ItemMake] or {}) do
+ TriggerEvent(getScript()..":server:toggleItem", false, tostring(k), v, src)
+ end
+ end
+ end
+ TriggerEvent(getScript()..":server:toggleItem", true, ItemMake, amount, src, metadata)
+ --if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
+end)
+
+--- Opens a selling menu based on the provided data.
+---
+--- This function checks available items to sell, prepares the menu options, and opens the selling menu.
+---
+---@param data table A table containing selling menu data.
+--- - **sellTable** (`table`): The selling options and settings.
+--- - **Items** (`table`): A list of items that can be sold with their prices.
+--- - **Header** (`string`, optional): The header/title of the selling menu.
+--- - **ped** (`number`, optional): The ped entity involved in the selling interaction.
+--- - **onBack** (`function`, optional): Function to call when returning from the menu.
+---
+---@usage
+--- ```lua
+--- sellMenu({
+--- sellTable = {
+--- Header = "Sell Items",
+--- Items = {
+--- ["gold_ring"] = 100,
+--- ["diamond"] = 500,
+--- },
+--- },
+--- ped = pedEntity,
+--- onBack = function() print("Returning to previous menu") end,
+--- })
+--- ```
+function sellMenu(data)
+ local origData = data
+ local Menu = {}
+ if data.sellTable.Items then
+ local itemList = {}
+ for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
+ local _, hasTable = hasItem(itemList)
+ for k, v in pairsByKeys(data.sellTable.Items) do
+ Menu[#Menu +1] = {
+ isMenuHeader = not hasTable[k].hasItem,
+ icon = invImg(k),
+ header = Items[k].label.. (hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
+ txt = Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"],
+ onSelect = function()
+ sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
+ end,
+ }
+ end
+ else
+ for k, v in pairsByKeys(data.sellTable) do
+ if type(v) == "table" then
+ Menu[#Menu +1] = {
+ arrow = true,
+ header = k,
+ txt = "Amount of items: "..countTable(v.Items),
+ onSelect = function()
+ v.onBack = function() sellMenu(origData) end
+ v.sellTable = data.sellTable[k]
+ sellMenu(v)
+ end,
+ }
+ end
+ end
+ end
+ openMenu(Menu, { header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items), headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items) or "", canClose = true, onBack = data.onBack })
+end
+
+--- Handles the selling animation and item transaction.
+---
+--- This function plays the selling animation, removes the item from the player's inventory, and gives the player money.
+---
+---@param data table A table containing selling data.
+--- - **item** (`string`): The item to sell.
+--- - **price** (`number`): The price per item.
+--- - **ped** (`number`, optional): The ped entity involved in the selling interaction.
+--- - **onBack** (`function`, optional): Function to call when returning from the menu.
+---
+---@usage
+--- ```lua
+--- sellAnim({
+--- item = "gold_ring",
+--- price = 100,
+--- ped = pedEntity,
+--- onBack = function() sellMenu(data) end,
+--- })
+--- ```
+function sellAnim(data)
+ if not hasItem(data.item, 1) then
+ triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error")
+ return
+ end
+ for k, v in pairs(GetGamePool('CObject')) do
+ for _, model in pairs({`p_cs_clipboard`}) do
+ if GetEntityModel(v) == model then
+ if IsEntityAttachedToEntity(data.ped, v) then
+ DeleteObject(v) DetachEntity(v, 0, 0) SetEntityAsMissionEntity(v, true, true)
+ Wait(100) DeleteEntity(v)
+ end
+ end
+ end
+ end
+ TriggerServerEvent(getScript().."Sellitems", data)
+ lookEnt(data.ped)
+ local dict = "mp_common"
+ playAnim(dict, "givetake2_a", 0.3, 2)
+ playAnim(dict, "givetake2_b", 0.3, 2, data.ped)
+ Wait(2000)
+ StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5)
+ StopAnimTask(data.ped, dict, "givetake2_b", 0.5)
+ if data.onBack then data.onBack() end
+end
+
+--- Server event handler for processing the item sale.
+---
+--- This event removes the sold item from the player's inventory and adds money to their account.
+---
+---@param data table The data containing item and price information.
+RegisterNetEvent(getScript().."Sellitems", function(data)
+ local src = source
+ local hasItems, hasTable = hasItem(data.item, 1, src)
+ if hasItems then
+ TriggerEvent(getScript()..":server:toggleItem", false, data.item, hasTable[data.item].count, src)
+ TriggerEvent(getScript()..":server:FundPlayer", (hasTable[data.item].count * data.price), "cash", src)
+ else
+ triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src)
+ end
+end)
+
+--- Opens a shop interface for the player.
+---
+--- This function checks job requirements and opens the shop using the appropriate inventory system.
+---
+---@param data table A table containing shop data.
+--- - **shop** (`string`): The shop identifier.
+--- - **items** (`table`): The items available in the shop.
+--- - **coords** (`vector3`): The coordinates where the shop interaction is happening.
+--- - **job** (`string` or `table`, optional): Job(s) required to access the shop.
+--- - **gang** (`string` or `table`, optional): Gang(s) required to access the shop.
+---
+---@usage
+--- ```lua
+--- openShop({
+--- shop = "weapon_shop",
+--- items = weaponShopItems,
+--- coords = vector3(100.0, 200.0, 300.0),
+--- job = "police",
+--- })
+--- ```
+function openShop(data)
+ if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
+ if isStarted(OXInv) then
+ exports[OXInv]:openInventory('shop', { type = data.shop })
+ elseif isStarted(QBInv) then
+ if QBInvNew then
+ TriggerServerEvent(getScript()..':server:OpenShopNewQB', data.shop) -- i hate qb-inv
+ else
+ TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
+ end
+ else
+ TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
+ end
+ lookEnt(data.coords)
+end
+
+--- Server event handler for opening a new QB inventory shop.
+---
+--- This event is triggered when using the new QB inventory system.
+---
+---@param data table The shop data to open.
+RegisterNetEvent(getScript()..':server:OpenShopNewQB', function(data)
+ exports[QBInv]:OpenShop(source, data)
+end)
+
+--- Server-side callback registration for checking if the player can carry items.
+if isServer() then
+ createCallback(getScript()..':server:canCarry', function(source, itemTable) local result = canCarry(itemTable, source) return result end)
end
\ No newline at end of file
diff --git a/shared/drawText.lua b/shared/drawText.lua
index 35b748f..a72420e 100644
--- a/shared/drawText.lua
+++ b/shared/drawText.lua
@@ -1,61 +1,61 @@
-local radarTable = {}
-
---- Displays text on the screen using the configured draw text system.
----
---- This function handles displaying text with optional images or icons using different frameworks like 'qb', 'ox', 'gta', and 'esx'.
----
----@param image string|nil An optional image or icon to display with the text. Can be a URL, path, or a reference to an icon.
----@param input table A table of strings, each representing a line of text to display.
----@param style string|nil An optional style code for default GTA popups (e.g., '~g~' for green text).
----@param oxStyleTable table|nil An optional table specifying style parameters for the 'ox' draw text system.
----
----@usage
---- ```lua
---- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
---- ```
-function drawText(image, input, style, oxStyleTable) local text = ""
- if Config.System.drawText == "qb" then
- for i = 1, #input do
- text = text..input[i]..""..(input[i+1] ~= nil and "
" or "") end
- local text = text:gsub("%:", ":")
- if image then
- text = '
'..text
- end
- exports[QBExport]:DrawText(text, 'left')
-
- elseif Config.System.drawText == "ox" then
- for k, v in pairs(input) do
- input[k] = v.." \n"
- end
- lib.showTextUI(table.concat(input), { icon = (image and radarTable[image] or image) or nil, position = 'left-center', style = oxStyleTable})
-
- elseif Config.System.drawText == "gta" then
- for i = 1, #input do if input[i] ~= "" then text = text..input[i].."\n~s~" end end
- if image then text = "~BLIP_"..image.."~ "..text end
-
- DisplayHelpMsg(text:gsub("%:", ":~"..(style or "g").."~"))
- elseif Config.System.drawText == "esx" then
- for i = 1, #input do
- text = text..input[i]..""..(input[i+1] ~= nil and "
" or "") end
- local text = text:gsub("%:", ":")
- if image then
- text = '
'..text
- end
- ESX.TextUI(text, nil)
- end
-end
-
---- Hides any text currently being displayed on the screen.
----
---- This function clears the text displayed by the `drawText` function, using the appropriate method based on the configured draw text system.
-function hideText()
- if Config.System.drawText == "qb" then
- exports[QBExport]:HideText()
- elseif Config.System.drawText == "ox" then
- lib.hideTextUI()
- elseif Config.System.drawText == "gta" then
- ClearAllHelpMessages()
- elseif Config.System.drawText == "esx" then
- ESX.HideUI()
- end
+local radarTable = {}
+
+--- Displays text on the screen using the configured draw text system.
+---
+--- This function handles displaying text with optional images or icons using different frameworks like 'qb', 'ox', 'gta', and 'esx'.
+---
+---@param image string|nil An optional image or icon to display with the text. Can be a URL, path, or a reference to an icon.
+---@param input table A table of strings, each representing a line of text to display.
+---@param style string|nil An optional style code for default GTA popups (e.g., '~g~' for green text).
+---@param oxStyleTable table|nil An optional table specifying style parameters for the 'ox' draw text system.
+---
+---@usage
+--- ```lua
+--- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
+--- ```
+function drawText(image, input, style, oxStyleTable) local text = ""
+ if Config.System.drawText == "qb" then
+ for i = 1, #input do
+ text = text..input[i]..""..(input[i+1] ~= nil and "
" or "") end
+ local text = text:gsub("%:", ":")
+ if image then
+ text = '
'..text
+ end
+ exports[QBExport]:DrawText(text, 'left')
+
+ elseif Config.System.drawText == "ox" then
+ for k, v in pairs(input) do
+ input[k] = v.." \n"
+ end
+ lib.showTextUI(table.concat(input), { icon = (image and radarTable[image] or image) or nil, position = 'left-center', style = oxStyleTable})
+
+ elseif Config.System.drawText == "gta" then
+ for i = 1, #input do if input[i] ~= "" then text = text..input[i].."\n~s~" end end
+ if image then text = "~BLIP_"..image.."~ "..text end
+
+ DisplayHelpMsg(text:gsub("%:", ":~"..(style or "g").."~"))
+ elseif Config.System.drawText == "esx" then
+ for i = 1, #input do
+ text = text..input[i]..""..(input[i+1] ~= nil and "
" or "") end
+ local text = text:gsub("%:", ":")
+ if image then
+ text = '
'..text
+ end
+ ESX.TextUI(text, nil)
+ end
+end
+
+--- Hides any text currently being displayed on the screen.
+---
+--- This function clears the text displayed by the `drawText` function, using the appropriate method based on the configured draw text system.
+function hideText()
+ if Config.System.drawText == "qb" then
+ exports[QBExport]:HideText()
+ elseif Config.System.drawText == "ox" then
+ lib.hideTextUI()
+ elseif Config.System.drawText == "gta" then
+ ClearAllHelpMessages()
+ elseif Config.System.drawText == "esx" then
+ ESX.HideUI()
+ end
end
\ No newline at end of file
diff --git a/shared/duifunctions.lua b/shared/duifunctions.lua
index f77276b..178c189 100644
--- a/shared/duifunctions.lua
+++ b/shared/duifunctions.lua
@@ -1,122 +1,122 @@
--- DUI STUFF -- * Experimental * --
-
-scriptTxd = not isServer() and CreateRuntimeTxd(getScript()..'scriptTxd') or nil
-customDUIList = {}
-
--- DUI CLIENT
-function createDui(name, http, size, txd)
- --print(name, http, size, txd)
- if not customDUIList[name] then
- local newTxt = CreateDui(http, math.floor(size.x), math.floor(size.y))
- while not GetDuiHandle(newTxt) do Wait(0) end
- CreateRuntimeTextureFromDuiHandle(txd, name, GetDuiHandle(newTxt))
- customDUIList[name] = newTxt
- SetDuiUrl(customDUIList[name], http)
- else
- SetDuiUrl(customDUIList[name], http)
- end
-end
-
-function DuiSelect(data)
- local image = ""
- for k, v in pairs(duiList[data.name]) do
- if v.tex.texn == data.texn then
- if duiList[data.name][k] then
- image = "- Current Image -
"..
- "
"..
- "Size: ["..math.floor(data.size.x)..", "..math.floor(data.size.y).."]
"
- end
- end
- end
- local dialog = exports['qb-input']:ShowInput({
- header = image..Loc[Config.Lan].menu["dui_new"],
- submitText = Loc[Config.Lan].menu["dui_change"],
- inputs = { { type = 'text', isRequired = true, name = 'url', text = Loc[Config.Lan].menu["dui_url"] } } })
- if dialog then
- if not dialog.url then return end
- data.url = dialog.url
- --Scan the link to see if it has an image extention otherwise, stop here.
- local searchList = { "png", "jpg", "jpeg", "gif", "webp", "bmp" }
- --Scan the link for certain terms that will flag it and refuse to show it
- local banList = { "porn" } -- I dunno, let me know what links people manage to find
- local searchFound = false
- for k, v in pairs(searchList) do
- if string.find(tostring(data.url), tostring(v))then
- searchFound = true
- end
- end
- for k, v in pairs(banList) do
- if string.find(tostring(data.url), tostring(v)) then
- searchFound = false print("BANNED WORD: "..v)
- end
- end
- if searchFound then
- TriggerServerEvent(getScript()..":Server:ChangeDUI", data)
- end
- end
-end
-
-RegisterNetEvent(getScript()..":Client:ChangeDUI", function(data)
- debugPrint("^6Bridge^7: ^2Recieving new DUI ^7- ^6"..data.url.."^7")
- if tostring(data.url) ~= "-" then
- createDui(data.texn, tostring(data.url), data.size, scriptTxd)
- AddReplaceTexture(tostring(data.texd), tostring(data.texn), getScript()..'scriptTxd', tostring(data.texn))
- end
-end)
-
-RegisterNetEvent(getScript()..":Client:ClearDUI", function(data)
- if customDUIList[tostring(data.texn)] then
- RemoveReplaceTexture(tostring(data.texd), tostring(data.texn))
- if IsDuiAvailable(customDUIList[tostring(data.texn)]) then
- SetDuiUrl(customDUIList[data.name], nil)
- end
- end
-end)
-
--- DUI SERVER
-RegisterNetEvent(getScript()..":Server:ChangeDUI", function(data)
- -- if no url given, "reset" it back to preset
- if not data.url then
- for k, v in pairs(duiList[data.name]) do
- if v.tex.texn == data.texn then
- debugPrint("^6Bridge^7: ^2Preset^7: ^6"..tostring(duiList[data.name][k].preset).."^7")
- data.url = duiList[data.name][k].preset
- end
- end
- end
- -- if it has a url, update server DUI list and send to players
- for k, v in pairs(duiList[data.name]) do
- if v.tex.texn == data.texn then
- duiList[data.name][k].url = data.url
- end
- end
- debugPrint("^6Bridge^7: ^3DUI^2 Sending new DUI to all players^7 - ^6"..data.url.."^7")
- TriggerClientEvent(getScript()..":Client:ChangeDUI", -1, data)
-end)
-
-RegisterNetEvent(getScript()..":Server:ClearDUI", function(data)
- if data.url == "-" then
- for k, v in pairs(duiList[data.name]) do
- if v.tex.texn == data.texn then
- duiList[data.name][k].url = "-"
- end
- end
- end
- -- Clear the DUI from loading
- TriggerClientEvent(getScript()..":Client:ClearDUI", -1, data)
- --duiList[tostring(data.tex)].url = ""
-end)
-
-AddEventHandler('onResourceStop', function(r) if r ~= getScript() then return end
- for k, v in pairs(duiList or {}) do
- for i = 1, #v do
- RemoveReplaceTexture(tostring(v[i].tex.texd), tostring(v[i].tex.texn))
- end
- end
-end)
-
-if isServer() then
- createCallback(getScript()..":Server:duiList", function(source)
- return duiList
- end)
+-- DUI STUFF -- * Experimental * --
+
+scriptTxd = not isServer() and CreateRuntimeTxd(getScript()..'scriptTxd') or nil
+customDUIList = {}
+
+-- DUI CLIENT
+function createDui(name, http, size, txd)
+ --print(name, http, size, txd)
+ if not customDUIList[name] then
+ local newTxt = CreateDui(http, math.floor(size.x), math.floor(size.y))
+ while not GetDuiHandle(newTxt) do Wait(0) end
+ CreateRuntimeTextureFromDuiHandle(txd, name, GetDuiHandle(newTxt))
+ customDUIList[name] = newTxt
+ SetDuiUrl(customDUIList[name], http)
+ else
+ SetDuiUrl(customDUIList[name], http)
+ end
+end
+
+function DuiSelect(data)
+ local image = ""
+ for k, v in pairs(duiList[data.name]) do
+ if v.tex.texn == data.texn then
+ if duiList[data.name][k] then
+ image = "- Current Image -
"..
+ "
"..
+ "Size: ["..math.floor(data.size.x)..", "..math.floor(data.size.y).."]
"
+ end
+ end
+ end
+ local dialog = exports['qb-input']:ShowInput({
+ header = image..Loc[Config.Lan].menu["dui_new"],
+ submitText = Loc[Config.Lan].menu["dui_change"],
+ inputs = { { type = 'text', isRequired = true, name = 'url', text = Loc[Config.Lan].menu["dui_url"] } } })
+ if dialog then
+ if not dialog.url then return end
+ data.url = dialog.url
+ --Scan the link to see if it has an image extention otherwise, stop here.
+ local searchList = { "png", "jpg", "jpeg", "gif", "webp", "bmp" }
+ --Scan the link for certain terms that will flag it and refuse to show it
+ local banList = { "porn" } -- I dunno, let me know what links people manage to find
+ local searchFound = false
+ for k, v in pairs(searchList) do
+ if string.find(tostring(data.url), tostring(v))then
+ searchFound = true
+ end
+ end
+ for k, v in pairs(banList) do
+ if string.find(tostring(data.url), tostring(v)) then
+ searchFound = false print("BANNED WORD: "..v)
+ end
+ end
+ if searchFound then
+ TriggerServerEvent(getScript()..":Server:ChangeDUI", data)
+ end
+ end
+end
+
+RegisterNetEvent(getScript()..":Client:ChangeDUI", function(data)
+ debugPrint("^6Bridge^7: ^2Recieving new DUI ^7- ^6"..data.url.."^7")
+ if tostring(data.url) ~= "-" then
+ createDui(data.texn, tostring(data.url), data.size, scriptTxd)
+ AddReplaceTexture(tostring(data.texd), tostring(data.texn), getScript()..'scriptTxd', tostring(data.texn))
+ end
+end)
+
+RegisterNetEvent(getScript()..":Client:ClearDUI", function(data)
+ if customDUIList[tostring(data.texn)] then
+ RemoveReplaceTexture(tostring(data.texd), tostring(data.texn))
+ if IsDuiAvailable(customDUIList[tostring(data.texn)]) then
+ SetDuiUrl(customDUIList[data.name], nil)
+ end
+ end
+end)
+
+-- DUI SERVER
+RegisterNetEvent(getScript()..":Server:ChangeDUI", function(data)
+ -- if no url given, "reset" it back to preset
+ if not data.url then
+ for k, v in pairs(duiList[data.name]) do
+ if v.tex.texn == data.texn then
+ debugPrint("^6Bridge^7: ^2Preset^7: ^6"..tostring(duiList[data.name][k].preset).."^7")
+ data.url = duiList[data.name][k].preset
+ end
+ end
+ end
+ -- if it has a url, update server DUI list and send to players
+ for k, v in pairs(duiList[data.name]) do
+ if v.tex.texn == data.texn then
+ duiList[data.name][k].url = data.url
+ end
+ end
+ debugPrint("^6Bridge^7: ^3DUI^2 Sending new DUI to all players^7 - ^6"..data.url.."^7")
+ TriggerClientEvent(getScript()..":Client:ChangeDUI", -1, data)
+end)
+
+RegisterNetEvent(getScript()..":Server:ClearDUI", function(data)
+ if data.url == "-" then
+ for k, v in pairs(duiList[data.name]) do
+ if v.tex.texn == data.texn then
+ duiList[data.name][k].url = "-"
+ end
+ end
+ end
+ -- Clear the DUI from loading
+ TriggerClientEvent(getScript()..":Client:ClearDUI", -1, data)
+ --duiList[tostring(data.tex)].url = ""
+end)
+
+AddEventHandler('onResourceStop', function(r) if r ~= getScript() then return end
+ for k, v in pairs(duiList or {}) do
+ for i = 1, #v do
+ RemoveReplaceTexture(tostring(v[i].tex.texd), tostring(v[i].tex.texn))
+ end
+ end
+end)
+
+if isServer() then
+ createCallback(getScript()..":Server:duiList", function(source)
+ return duiList
+ end)
end
\ No newline at end of file
diff --git a/shared/effects.lua b/shared/effects.lua
index d6608cb..353aef1 100644
--- a/shared/effects.lua
+++ b/shared/effects.lua
@@ -1,190 +1,190 @@
---Screen Effects
-local alienEffect = false
-function AlienEffect()
- if alienEffect then return else alienEffect = true end
- debugPrint("^5Debug^7: ^3AlienEffect^7() ^2activated")
- AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
- Wait(math.random(5000, 8000))
- local Ped = PlayerPedId()
- local animDict = "MOVE_M@DRUNK@VERYDRUNK"
- loadAnimDict(animDict)
- SetPedCanRagdoll(Ped, true)
- ShakeGameplayCam('DRUNK_SHAKE', 2.80)
- SetTimecycleModifier("Drunk")
- SetPedMovementClipset(Ped, animDict, 1)
- SetPedMotionBlur(Ped, true)
- SetPedIsDrunk(Ped, true)
- Wait(1500)
- SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
- Wait(13500)
- SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
- Wait(120500)
- ClearTimecycleModifier()
- ResetScenarioTypesEnabled()
- ResetPedMovementClipset(Ped, 0)
- SetPedIsDrunk(Ped, false)
- SetPedMotionBlur(Ped, false)
- AnimpostfxStopAll()
- ShakeGameplayCam('DRUNK_SHAKE', 0.0)
- AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
- Wait(math.random(45000, 60000))
- AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
- AnimpostfxStop("DrugsMichaelAliensFightIn")
- AnimpostfxStop("DrugsMichaelAliensFight")
- AnimpostfxStop("DrugsMichaelAliensFightOut")
- alienEffect = false
- debugPrint("^5Debug^7: ^3AlienEffect^7() ^2stopped")
-end
-local weedEffect = false
-function WeedEffect()
- if weedEffect then return else weedEffect = true end
- debugPrint("^5Debug^7: ^3WeedEffect^7() ^2activated")
- AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
- Wait(math.random(3000, 20000))
- AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
- Wait(math.random(15000, 20000))
- AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
- AnimpostfxStop("DrugsMichaelAliensFightIn")
- AnimpostfxStop("DrugsMichaelAliensFight")
- AnimpostfxStop("DrugsMichaelAliensFightOut")
- weedEffect = false
- debugPrint("^5Debug^7: ^3WeedEffect^7() ^2stopped")
-end
-local trevorEffect = false
-function TrevorEffect()
- if trevorEffect then return else trevorEffect = true end
- debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2activated")
- AnimpostfxPlay("DrugsTrevorClownsFightIn", 3.0, 0)
- Wait(3000)
- AnimpostfxPlay("DrugsTrevorClownsFight", 3.0, 0)
- Wait(30000)
- AnimpostfxPlay("DrugsTrevorClownsFightOut", 3.0, 0)
- AnimpostfxStop("DrugsTrevorClownsFight")
- AnimpostfxStop("DrugsTrevorClownsFightIn")
- AnimpostfxStop("DrugsTrevorClownsFightOut")
- trevorEffect = false
- debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2stopped")
-end
-local turboEffect = false
-function TurboEffect()
- if turboEffect then return else turboEffect = true end
- debugPrint("^5Debug^7: ^3TurboEffect^7() ^2activated")
- AnimpostfxPlay('RaceTurbo', 0, true)
- SetTimecycleModifier('rply_motionblur')
- ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
- Wait(30000)
- StopGameplayCamShaking(true)
- SetTransitionTimecycleModifier('default', 0.35)
- Wait(1000)
- ClearTimecycleModifier()
- AnimpostfxStop('RaceTurbo')
- turboEffect = false
- debugPrint("^5Debug^7: ^3TurboEffect^7() ^2stopped")
-end
-local rampageEffect = false
-function RampageEffect()
- if rampageEffect then return else rampageEffect = true end
- debugPrint("^5Debug^7: ^3RampageEffect^7() ^2activated")
- AnimpostfxPlay('Rampage', 0, true)
- SetTimecycleModifier('rply_motionblur')
- ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
- Wait(30000)
- StopGameplayCamShaking(true)
- SetTransitionTimecycleModifier('default', 0.35)
- Wait(1000)
- ClearTimecycleModifier()
- AnimpostfxStop('Rampage')
- rampageEffect = false
- debugPrint("^5Debug^7: ^3RampageEffect^7() ^2stopped")
-end
-local focusEffect = false
-function FocusEffect()
- if focusEffect then return else focusEffect = true end
- debugPrint("^5Debug^7: ^3FocusEffect^7() ^2activated")
- Wait(1000)
- AnimpostfxPlay('FocusIn', 0, true)
- Wait(30000)
- AnimpostfxStop('FocusIn')
- focusEffect = false
- debugPrint("^5Debug^7: ^3FocusEffect^7() ^2stopped")
-end
-local nightVisionEffect = false
-function NightVisionEffect()
- if nightVisionEffect then return else nightVisionEffect = true end
- debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2activated")
- SetNightvision(true)
- Wait(math.random(3000, 4000)) -- FEEL FREE TO CHANGE THIS
- SetNightvision(false)
- SetSeethrough(false)
- nightVisionEffect = false
- debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2stopped")
-end
-local thermalEffect = false
-function ThermalEffect()
- if thermalEffect then return else thermalEffect = true end
- debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2activated")
- SetNightvision(true)
- SetSeethrough(true)
- Wait(math.random(2000, 3000)) -- FEEL FREE TO CHANGE THIS
- SetNightvision(false)
- SetSeethrough(false)
- thermalEffect = false
- debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2stopped")
-end
-
---Built-in Buff effects
-local healEffect = false
-function HealEffect(data)
- if healEffect then return end
- debugPrint("^5Debug^7: ^3HealEffect^7() ^2activated")
- healEffect = true
- local count = (data[1] / 1000)
- while count > 0 do
- Wait(1000)
- count -= 1
- SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + data[2])
- end
- healEffect = false
- debugPrint("^5Debug^7: ^3HealEffect^7() ^2stopped")
-end
-
-local staminaEffect = false
-function StaminaEffect(data)
- if staminaEffect then return end
- debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2activated")
- staminaEffect = true
- local startStamina = (data[1] / 1000)
- SetRunSprintMultiplierForPlayer(PlayerId(), 1.49)
- while startStamina > 0 do
- Wait(1000)
- if math.random(5, 100) < 10 then RestorePlayerStamina(PlayerId(), data[2]) end
- startStamina -= 1
- if math.random(5, 100) < 51 then end
- end
- startStamina = 0
- SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
- staminaEffect = false
- debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2stopped")
-end
-
-function StopEffects() -- Used to clear up any effects stuck on screen
- debugPrint("^5Bridge^7: ^2All screen effects stopped")
- ShakeGameplayCam('DRUNK_SHAKE', 0.0)
- SetPedToRagdoll(PlayerPedId(), 5000, 1000, 1, 0, 0, 0)
- ClearTimecycleModifier()
- ResetScenarioTypesEnabled()
- ResetPedMovementClipset(PlayerPedId(), 0)
- SetPedIsDrunk(PlayerPedId(), false)
- SetPedMotionBlur(PlayerPedId(), false)
- SetNightvision(false)
- SetSeethrough(false)
- AnimpostfxStop("DrugsMichaelAliensFightIn")
- AnimpostfxStop("DrugsMichaelAliensFight")
- AnimpostfxStop("DrugsMichaelAliensFightOut")
- AnimpostfxStop("DrugsTrevorClownsFight")
- AnimpostfxStop("DrugsTrevorClownsFightIn")
- AnimpostfxStop("DrugsTrevorClownsFightOut")
- AnimpostfxStop('RaceTurbo')
- AnimpostfxStop('FocusIn')
- AnimpostfxStop('Rampage')
+--Screen Effects
+local alienEffect = false
+function AlienEffect()
+ if alienEffect then return else alienEffect = true end
+ debugPrint("^5Debug^7: ^3AlienEffect^7() ^2activated")
+ AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
+ Wait(math.random(5000, 8000))
+ local Ped = PlayerPedId()
+ local animDict = "MOVE_M@DRUNK@VERYDRUNK"
+ loadAnimDict(animDict)
+ SetPedCanRagdoll(Ped, true)
+ ShakeGameplayCam('DRUNK_SHAKE', 2.80)
+ SetTimecycleModifier("Drunk")
+ SetPedMovementClipset(Ped, animDict, 1)
+ SetPedMotionBlur(Ped, true)
+ SetPedIsDrunk(Ped, true)
+ Wait(1500)
+ SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
+ Wait(13500)
+ SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
+ Wait(120500)
+ ClearTimecycleModifier()
+ ResetScenarioTypesEnabled()
+ ResetPedMovementClipset(Ped, 0)
+ SetPedIsDrunk(Ped, false)
+ SetPedMotionBlur(Ped, false)
+ AnimpostfxStopAll()
+ ShakeGameplayCam('DRUNK_SHAKE', 0.0)
+ AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
+ Wait(math.random(45000, 60000))
+ AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
+ AnimpostfxStop("DrugsMichaelAliensFightIn")
+ AnimpostfxStop("DrugsMichaelAliensFight")
+ AnimpostfxStop("DrugsMichaelAliensFightOut")
+ alienEffect = false
+ debugPrint("^5Debug^7: ^3AlienEffect^7() ^2stopped")
+end
+local weedEffect = false
+function WeedEffect()
+ if weedEffect then return else weedEffect = true end
+ debugPrint("^5Debug^7: ^3WeedEffect^7() ^2activated")
+ AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
+ Wait(math.random(3000, 20000))
+ AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
+ Wait(math.random(15000, 20000))
+ AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
+ AnimpostfxStop("DrugsMichaelAliensFightIn")
+ AnimpostfxStop("DrugsMichaelAliensFight")
+ AnimpostfxStop("DrugsMichaelAliensFightOut")
+ weedEffect = false
+ debugPrint("^5Debug^7: ^3WeedEffect^7() ^2stopped")
+end
+local trevorEffect = false
+function TrevorEffect()
+ if trevorEffect then return else trevorEffect = true end
+ debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2activated")
+ AnimpostfxPlay("DrugsTrevorClownsFightIn", 3.0, 0)
+ Wait(3000)
+ AnimpostfxPlay("DrugsTrevorClownsFight", 3.0, 0)
+ Wait(30000)
+ AnimpostfxPlay("DrugsTrevorClownsFightOut", 3.0, 0)
+ AnimpostfxStop("DrugsTrevorClownsFight")
+ AnimpostfxStop("DrugsTrevorClownsFightIn")
+ AnimpostfxStop("DrugsTrevorClownsFightOut")
+ trevorEffect = false
+ debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2stopped")
+end
+local turboEffect = false
+function TurboEffect()
+ if turboEffect then return else turboEffect = true end
+ debugPrint("^5Debug^7: ^3TurboEffect^7() ^2activated")
+ AnimpostfxPlay('RaceTurbo', 0, true)
+ SetTimecycleModifier('rply_motionblur')
+ ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
+ Wait(30000)
+ StopGameplayCamShaking(true)
+ SetTransitionTimecycleModifier('default', 0.35)
+ Wait(1000)
+ ClearTimecycleModifier()
+ AnimpostfxStop('RaceTurbo')
+ turboEffect = false
+ debugPrint("^5Debug^7: ^3TurboEffect^7() ^2stopped")
+end
+local rampageEffect = false
+function RampageEffect()
+ if rampageEffect then return else rampageEffect = true end
+ debugPrint("^5Debug^7: ^3RampageEffect^7() ^2activated")
+ AnimpostfxPlay('Rampage', 0, true)
+ SetTimecycleModifier('rply_motionblur')
+ ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
+ Wait(30000)
+ StopGameplayCamShaking(true)
+ SetTransitionTimecycleModifier('default', 0.35)
+ Wait(1000)
+ ClearTimecycleModifier()
+ AnimpostfxStop('Rampage')
+ rampageEffect = false
+ debugPrint("^5Debug^7: ^3RampageEffect^7() ^2stopped")
+end
+local focusEffect = false
+function FocusEffect()
+ if focusEffect then return else focusEffect = true end
+ debugPrint("^5Debug^7: ^3FocusEffect^7() ^2activated")
+ Wait(1000)
+ AnimpostfxPlay('FocusIn', 0, true)
+ Wait(30000)
+ AnimpostfxStop('FocusIn')
+ focusEffect = false
+ debugPrint("^5Debug^7: ^3FocusEffect^7() ^2stopped")
+end
+local nightVisionEffect = false
+function NightVisionEffect()
+ if nightVisionEffect then return else nightVisionEffect = true end
+ debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2activated")
+ SetNightvision(true)
+ Wait(math.random(3000, 4000)) -- FEEL FREE TO CHANGE THIS
+ SetNightvision(false)
+ SetSeethrough(false)
+ nightVisionEffect = false
+ debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2stopped")
+end
+local thermalEffect = false
+function ThermalEffect()
+ if thermalEffect then return else thermalEffect = true end
+ debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2activated")
+ SetNightvision(true)
+ SetSeethrough(true)
+ Wait(math.random(2000, 3000)) -- FEEL FREE TO CHANGE THIS
+ SetNightvision(false)
+ SetSeethrough(false)
+ thermalEffect = false
+ debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2stopped")
+end
+
+--Built-in Buff effects
+local healEffect = false
+function HealEffect(data)
+ if healEffect then return end
+ debugPrint("^5Debug^7: ^3HealEffect^7() ^2activated")
+ healEffect = true
+ local count = (data[1] / 1000)
+ while count > 0 do
+ Wait(1000)
+ count -= 1
+ SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + data[2])
+ end
+ healEffect = false
+ debugPrint("^5Debug^7: ^3HealEffect^7() ^2stopped")
+end
+
+local staminaEffect = false
+function StaminaEffect(data)
+ if staminaEffect then return end
+ debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2activated")
+ staminaEffect = true
+ local startStamina = (data[1] / 1000)
+ SetRunSprintMultiplierForPlayer(PlayerId(), 1.49)
+ while startStamina > 0 do
+ Wait(1000)
+ if math.random(5, 100) < 10 then RestorePlayerStamina(PlayerId(), data[2]) end
+ startStamina -= 1
+ if math.random(5, 100) < 51 then end
+ end
+ startStamina = 0
+ SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
+ staminaEffect = false
+ debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2stopped")
+end
+
+function StopEffects() -- Used to clear up any effects stuck on screen
+ debugPrint("^5Bridge^7: ^2All screen effects stopped")
+ ShakeGameplayCam('DRUNK_SHAKE', 0.0)
+ SetPedToRagdoll(PlayerPedId(), 5000, 1000, 1, 0, 0, 0)
+ ClearTimecycleModifier()
+ ResetScenarioTypesEnabled()
+ ResetPedMovementClipset(PlayerPedId(), 0)
+ SetPedIsDrunk(PlayerPedId(), false)
+ SetPedMotionBlur(PlayerPedId(), false)
+ SetNightvision(false)
+ SetSeethrough(false)
+ AnimpostfxStop("DrugsMichaelAliensFightIn")
+ AnimpostfxStop("DrugsMichaelAliensFight")
+ AnimpostfxStop("DrugsMichaelAliensFightOut")
+ AnimpostfxStop("DrugsTrevorClownsFight")
+ AnimpostfxStop("DrugsTrevorClownsFightIn")
+ AnimpostfxStop("DrugsTrevorClownsFightOut")
+ AnimpostfxStop('RaceTurbo')
+ AnimpostfxStop('FocusIn')
+ AnimpostfxStop('Rampage')
end
\ No newline at end of file
diff --git a/shared/helpers.lua b/shared/helpers.lua
index 70e3871..38a8c54 100644
--- a/shared/helpers.lua
+++ b/shared/helpers.lua
@@ -1,920 +1,938 @@
---- Utility Functions for Resource Management and Debugging
----
---- This script provides a set of utility functions for managing resources, debugging, and handling various common tasks within the game environment.
---- It includes functions for checking resource states, generating unique keys, formatting numbers and coordinates, handling JSON data, and more.
-
---[[ Resource and Environment Checks ]]--
-
---- Checks if a specific resource is started.
----
----@param script string The name of the resource to check.
----@return boolean `true` if the resource state contains "start", otherwise `false`.
----
----@usage
---- ```lua
---- if isStarted("myResource") then
---- print("Resource is running")
---- end
---- ```
-function isStarted(script)
- return GetResourceState(script):find("start")
-end
-
-local scriptName = nil
-
---- Retrieves the current resource name.
----
---- Caches the resource name after the first call for efficiency.
----
---- @return string scriptName The name of the current resource.
----
---- @usage
---- ```lua
---- local currentScript = getScript()
---- print("Current script:", currentScript)
---- ```
-function getScript()
- if not scriptName then scriptName = GetCurrentResourceName() end
- return scriptName
-end
-
---- Determines if the current execution context is the server.
----
---- Very helpful for shared files complaining about client functions running on server or vice versa
----
---- @return boolean Returns `true` if running on the server, otherwise `false`.
----
----@usage
---- ```lua
---- if isServer() then
---- -- Server-specific code
---- else
---- -- Client-specific code
---- end
---- ```
-function isServer()
- return IsDuplicityVersion()
-end
-
---[[ Debugging Functions ]]--
-
---- Prints debug messages if debugging mode is enabled.
----
---- Concatenates all arguments and prints them along with debug information.
----
---- @param ... any Multiple arguments to be concatenated and printed.
----
---- @usage
---- ```lua
---- debugPrint("Player has joined:", playerName)
---- ```
-function debugPrint(...)
- if debugMode then
- local args = {...}
- local output = table.concat(args, " ") -- Concatenate all arguments with a space
- print(output, getDebugInfo(debug.getinfo(2, "nSl")))
- end
-end
-
---- Prints event-related debug messages if event debugging is enabled.
----
---- @param ... any Multiple arguments to be printed.
----
---- @usage
---- ```lua
---- eventPrint("Event triggered:", eventName)
---- ```
-function eventPrint(...)
- if Config.System.EventDebug then
- print(...)
- end
-end
-
--- Function to recursively colorize the JSON data
-function getSortedKeys(tbl)
- local keys = {}
- for k in pairs(tbl) do keys[#keys + 1] = k end
- table.sort(keys, function(a, b)
- local numA, numB = tonumber(a), tonumber(b)
- if numA and numB then return numA < numB
- else return tostring(a) < tostring(b) end
- end)
- return keys
-end
-
---- Recursively colorizes a table for debug printing.
----
---- @param tbl table The table to colorize.
---- @return table colourizedTable The colorized table.
----
---- @usage
---- ```lua
---- local colorizedData = colorizeTable(myTable)
---- jsonPrint(colorizedData)
---- ```
-function colorizeTable(tbl)
- local newData, sortedKeys = {}, getSortedKeys(tbl)
- for _, k in ipairs(sortedKeys) do
- local v = tbl[k]
- newData["^6"..tostring(k).."^7"] = ((type(v) == "table") and colorizeTable(v)) or (type(v):find("vector") and formatCoord(v)) or "^2"..tostring(v).."^7"
- end
- return newData
-end
-
---- Encodes a table into an ordered JSON string with indentation.
----
---- @param data table The table to encode.
---- @param indent string The string used for indentation (e.g., " ").
---- @param level number The current indentation level.
---- @return string The formatted JSON string.
----
---- @usage
---- ```lua
---- local jsonString = encodeOrderedJSON(myTable, " ", 0)
---- print(jsonString)
---- ```
-function encodeOrderedJSON(data, indent, level)
- local jsonParts, prefix, sortedKeys = {"{"}, string.rep(indent, level), getSortedKeys(data)
- for i, k in ipairs(sortedKeys) do
- jsonParts[#jsonParts + 1] = (i > 1 and ",\n" or "\n")..prefix..indent..json.encode(k)..": "
- jsonParts[#jsonParts + 1] = (type(data[k]) == "table") and encodeOrderedJSON(data[k], indent, level + 1) or json.encode(data[k])
- end
- jsonParts[#jsonParts + 1] = "\n"..prefix.."}"
- return table.concat(jsonParts)
-end
-
---- Prints a table as a colorized and ordered JSON string if debugging mode is enabled.
----
---- @param data table The table to print.
----
---- @usage
---- ```lua
---- jsonPrint(myTable)
---- ```
-function jsonPrint(data)
- if debugMode then
- print(encodeOrderedJSON(colorizeTable(data), " ", 0), getDebugInfo(debug.getinfo(2, "nSl")))
- end
-end
-
---- Retrieves the current time formatted for debug prints.
----
---- @return string string The formatted time string, e.g., "^7(14:23:45)".
----
---- @usage
---- ```lua
---- local currentTime = GetPrintTime()
---- debugPrint("Current Time:", currentTime)
---- ```
-function GetPrintTime()
- if isServer() then
- local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
- return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
- else
- local _, _, _, hour, min, sec = GetLocalTime()
- return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
- end
-end
-
---- Generates a unique 3-character alphanumeric key.
----
---- @return string GeneratedString A randomly generated 3-character string.
----
---- @usage
---- ```lua
---- local uniqueKey = keyGen()
---- print("Generated Key:", uniqueKey)
---- ```
-function keyGen()
- local charset = {
- "q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m",
- "Q","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M",
- "1","2","3","4","5","6","7","8","9","0"
- }
- local GeneratedID = ""
- for i = 1, 3 do GeneratedID = GeneratedID..charset[math.random(1, #charset)] end
- return GeneratedID
-end
-
---- Formats a number with commas as thousand separators.
----
---- @param amount number The number to format.
---- @return string commaValue The formatted number string with commas.
----
---- @usage
---- ```lua
---- local formattedNumber = cv(1000000) -- "1,000,000"
---- print(formattedNumber)
---- ``
-function cv(amount)
- local formatted = tostring(amount or "0")
- while true do formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') if (k==0) then break end Wait(0) end
- return formatted
-end
-
---- Formats a coordinate vector for debug printing.
----
---- @param coord table A vector3 or vector4 table with `x`, `y`, `z`, and optional `w` components.
---- @return string The formatted coordinate string with color codes.
----
---- @usage
---- ```lua
---- local formattedCoord = formatCoord(vector3(100.0, 200.0, 300.0))
---- debugPrint("Player Position:", formattedCoord)
---- ```
-function formatCoord(coord)
- local vecType = type(coord):gsub("tor", "")
- local components = {
- [1] = coord.x and ("^6"..string.format("%.1f", coord.x)) or "",
- [2] = coord.y and ("^7, ^6"..string.format("%.1f", coord.y)) or "",
- [3] = coord.z and ("^7, ^6"..string.format("%.1f", coord.z)) or "",
- [4] = coord.w and ("^7, ^6"..string.format("%.1f", coord.w)) or "",
- }
- return "^5"..vecType.."^7("..components[1]..components[2]..components[3]..components[4].."^7)"
-end
-
---- Calculates the center point of a list of zones (coordinates).
----
---- @param table table A table of vector3 coordinates.
---- @return vector3 vector3 The center coordinate.
----
---- @usage
---- ```lua
---- local center = getCenterOfZones({vector3(100, 200, 300), vector3(110, 210, 310)})
---- print("Center of Zones:", center)
---- ```
-function getCenterOfZones(table)
- local totalX, totalY, totalZ = 0, 0, 0
-
- for _, coord in ipairs(table) do
- totalX = totalX + coord.x
- totalY = totalY + coord.y
- totalZ = totalZ + coord.z
- end
-
- local count = #table
- return vector3(totalX / count, totalY / count, totalZ / count)
-end
-
---- Counts the number of keys in a table.
----
---- @param table table The table to count keys in.
---- @return number number The number of keys in the table.
----
---- @usage
---- ```lua
---- local count = countTable(myTable)
---- print("Number of keys:", count)
---- ```
-function countTable(table) local i = 0 for keys in pairs(table) do i += 1 end return i end
-
-
---- Returns an iterator that iterates over a table's keys in sorted order.
----
---- @param t table The table to iterate over.
---- @return function function An iterator function.
----
---- @usage
---- ```lua
---- for k, v in pairsByKeys(myTable) do
---- print(k, v)
---- end
---- ```
-function pairsByKeys(t)
- local t = t
- if not t then
- print("^1Error^7: ^3Nil ^2table recieved for ^3pairsByKeys^7(), ^2setting to ^7{} ^2to prevent break^7")
- t = {}
- end
- local a = {} for n in pairs(t) do a[#a+1] = n end table.sort(a) local i = 0 local iter = function() i += 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter
-end
-
---- Creates a new table with consecutive numerical indices sorted by the `id` field.
----
---- @param originalTable table The original table with entries containing an `id` field.
---- @return table The new table with sorted entries and consecutive `id` values.
----
---- @usage
---- local sortedTable = createConsecutiveTable(originalTable)
---- for i, entry in ipairs(sortedTable) do
---- print(i, entry)
---- end
-function createConsecutiveTable(originalTable)
- local sortedEntries = {}
- for _, entry in pairs(originalTable) do
- table.insert(sortedEntries, entry)
- end
- table.sort(sortedEntries, function(a, b)
- return a.id < b.id
- end)
- local newTable = {}
- for newIndex, entry in ipairs(sortedEntries) do
- entry.id = newIndex
- newTable[newIndex] = entry
- end
- return newTable
-end
-
---[[ Drawing Functions ]]--
-
---- Draws 3D text at specified coordinates.
----
---- @param coord table A vector3 table with `x`, `y`, and `z` coordinates.
---- @param text string The text to display.
---- @param highlight boolean (optional) Whether to highlight certain parts of the text.
----
---- @usage
---- ```lua
---- CreateThread(function()
---- while true do
---- DrawText3D(vector3(100, 200, 300), "Hello World", true)
---- Wait(0)
---- end
---- end)
---- ```
-function DrawText3D(coord, text, highlight)
- SetTextScale(0.30, 0.30)
- SetTextFont(0)
- SetTextProportional(1)
- SetTextColour(255, 255, 255, 215)
- SetTextEntry("STRING")
- SetTextCentre(true)
- local totalLength = string.len(text)
- local textMaxLength = textMaxLength or 99 -- max 99
- local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text
- AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text)
- SetDrawOrigin(coord.x, coord.y, coord.z, 0)
- DrawText(0.0, 0.0)
- local count, length = GetLineCountAndMaxLength(text)
-
- local padding = 0.005
- local heightFactor = (count / 43) + padding
- local weightFactor = (length / 150) + padding
-
- local height = (heightFactor / 2) - padding / 1
- local width = (weightFactor / 2) - padding / 1
-
- DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
- ClearDrawOrigin()
-end
-
---- Displays a help message on the screen.
----
---- @param text string The text to display as a help message.
----
---- @usage
---- DisplayHelpMsg("Press E to interact")
-function DisplayHelpMsg(text)
- BeginTextCommandDisplayHelp("STRING")
- AddTextComponentScaleform(text)
- EndTextCommandDisplayHelp(0, true, false, -1)
-end
-
---- Displays a "Saving/Loading" spinner with a custom message.
----
---- @param text string The message to display alongside the spinner.
----
---- @usage
---- ```lua
---- displaySpinner("Saving data...")
---- ```
-function displaySpinner(text)
- BeginTextCommandBusyspinnerOn('STRING')
- AddTextComponentSubstringPlayerName(text)
- EndTextCommandBusyspinnerOn(4)
-end
-
---- Stops the "Saving/Loading" spinner.
----
---- This function is client-side only.
----
---- @usage
---- ```lua
---- stopSpinner()
---- ```
-function stopSpinner()
- if not isServer() then
- BusyspinnerOff()
- end
-end
-
---- Concatenates a table of strings into a single string separated by newlines.
----
---- @param tbl table A table containing string elements.
---- @return string string The concatenated string with newline separators.
----
---- @usage
---- ```lua
---- local combinedText = concatenateText({"Line 1", "Line 2", "Line 3"})
---- print(combinedText)
---- ```
-function concatenateText(tbl)
- local result = ""
- for i = 1, #tbl do
- result = result..tbl[i]
- if i < #tbl then
- result = result.."\n" -- Add newline only if it's not the last element
- end
- end
- return result
-end
-
---- Converts rotation to a direction vector.
----
---- @param rot vector3 A vector3 containing rotation values
---- @return vector3 vector3 A vector3 representing the direction.
----
---- @usage
---- ```lua
---- local direction = RotationToDirection({ z = 90 })
---- print(direction)
---- ```
-function RotationToDirection(rot)
- local adjust = (math.pi / 180)
- return vec3(-math.sin(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)), math.cos(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)), math.sin(adjust * rot.x))
-end
-
---- Creates a simple text-based progress bar.
----
---- @param percentage number The completion percentage (0-100).
---- @return string string A string representing the progress bar, e.g., "█████░░░░░".
----
---- @usage
---- ```lua
---- local bar = basicBar(50) -- "█████░░░░░"
---- print(bar)
---- ```
-function basicBar(percentage)
- local percentage = math.ceil(percentage)
- local totalBlocks = 10
- local filledBlocks = math.floor((percentage / 100) * totalBlocks)
- local emptyBlocks = totalBlocks - filledBlocks
-
- local bar = string.rep("█", filledBlocks)..string.rep("░", emptyBlocks)
- return bar
-end
-
---- Normalizes a 3D vector.
----
---- @param vec vector3 A vector3 table with `x`, `y`, and `z` components.
---- @return vector3 vector3 The normalized vector3.
----
---- @usage
---- ```lua
---- local normalizedVec = normalizeVector(vector3(1, 2, 3))
---- print(normalizedVec)
---- ```
-function normalizeVector(vec)
- local length = math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z)
- if length ~= 0 then
- return vec3(vec.x / length, vec.y / length, vec.z / length)
- else
- return vec3(0, 0, 0)
- end
-end
-
---- Draws a line between two coordinates for debugging purposes.
----
---- @param startCoords vector3 A vector3 table representing the start point.
---- @param endCoords vector3 A vector3 table representing the end point.
---- @param col vector4 A table with `x`, `y`, `z`, `w` representing the color and opacity.
----
---- @usage
---- ```lua
---- drawLine(vector3(100, 200, 300), vector3(150, 250, 350), vector4(255, 0, 0, 255))
---- ```
-function drawLine(startCoords, endCoords, col)
- if debugMode then
- CreateThread(function()
- local showCount = 1000
- while showCount >= 0 do
- DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w)
- showCount -= 10
- Wait(0)
- end
- end)
- end
-end
-
---- Draws a sphere at specified coordinates for debugging purposes.
----
---- @param coords vector3 A vector3 table representing the center of the sphere.
---- @param col vector4 A table with `x`, `y`, `z`, `w` representing the color and opacity.
----
---- @usage
---- ```lua
---- drawSphere(vector3(100, 200, 300), vector4(0, 255, 0, 255))
---- ```
-function drawSphere(coords, col)
- if debugMode then
- CreateThread(function()
- local showCount = 1000
- while showCount >= 0 do
- DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w)
- showCount -= 1
- Wait(10)
- end
- end)
- end
-end
-
---- Performs a raycast between two coordinates and returns the result.
----
---- @param startCoords table A vector3 table representing the start point.
---- @param endCoords table A vector3 table representing the end point.
---- @param entity number|nil The entity to ignore during the raycast.
---- @param flags number|nil Raycast flags to customize the raycast behavior. Defaults to `4294967295`.
---- @return multiple multiple Returns multiple values from `GetShapeTestResultIncludingMaterial`.
----
---- @usage
---- ```lua
---- local hit, hitPos, material = PerformRaycast(startVec, endVec, playerPed, 1)
---- if hit == 1 then
---- print("Hit at position:", hitPos)
---- print("Material:", material)
---- end
---- ```
-function PerformRaycast(startCoords, endCoords, entity, flags)
- drawLine(startCoords, endCoords, vec4(0, 0, 255, 255))
- local val1, val2, val3, val4, val5, val6 = GetShapeTestResult(StartExpensiveSynchronousShapeTestLosProbe(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, flags or 4294967295, entity, 0))
- if val2 then
- --drawSphere(val3, vec4(255, 0, 255, 0.5))
- end
- return val1, val2, val3, val4, val5, val6
-end
-
---- Adjusts the Z-coordinate of a position to align with the ground.
----
---- @param coords vector4 A vector3 or vector4 table with `x`, `y`, `z`, and optional `w` components.
---- @return vector3|vector4 vector adjusted coordinate with the Z value set to the ground level.
----
---- @usage
---- ```lua
---- local groundCoords = adjustForGround(playerCoords)
---- print("Ground Position:", groundCoords)
---- ```
-function adjustForGround(coords)
- local coords = coords
- local foundGround, zPos = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0)
-
- if foundGround then
- if coords.w then
- coords = vec4(coords.x, coords.y, zPos, coords.w)
- else
- coords = vec3(coords.x, coords.y, zPos)
- end
- --debugPrint("^6Bridge^7: Adjusting for ground pos ", coords.z, zPos)
-
- return coords
- else
- return coords
- end
-end
-
---- Ensures that a network vehicle exists by verifying its network ID.
----
---- @param vehNetID number The network ID of the vehicle.
---- @return number number The vehicle entity if it exists, otherwise `0`.
----
---- @usage
---- ```lua
---- local vehicle = ensureNetToVeh(netID)
---- if vehicle ~= 0 then
---- print("Vehicle exists:", vehicle)
---- end
---- ```
-function ensureNetToVeh(vehNetID)
- debugPrint("^6Bridge^7: ^3ensureNetToVeh^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..vehNetID.."^7)")
- local timeout = 100
- while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do
- timeout -= 1
- Wait(10)
- end
- if not NetworkDoesNetworkIdExist(vehNetID) then return 0 end
- timeout = 100
- local vehicle = NetToVeh(vehNetID)
- while not DoesEntityExist(vehicle) and vehicle ~= 0 and timeout > 0 do
- timeout -= 1
- Wait(10)
- end
- if not DoesEntityExist(vehicle) then return 0 end
- return vehicle
-end
-
---- Ensures that a network entity exists by verifying its network ID.
----
---- @param entNetID number The network ID of the entity.
---- @return number The entity if it exists, otherwise `0`.
----
---- @usage
---- local entity = ensureNetToEnt(netID)
---- if entity ~= 0 then
---- print("Entity exists:", entity)
---- end
-function ensureNetToEnt(entNetID)
- debugPrint("^6Bridge^7: ^3ensureNetToEnt^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..entNetID.."^7)")
- local timeout = 100
- while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do
- timeout -= 1
- Wait(10)
- end
- if not NetworkDoesNetworkIdExist(entNetID) then return 0 end
- timeout = 100
- local entity = NetworkGetEntityFromNetworkId(entNetID)
- while not DoesEntityExist(entity) and entity ~= 0 and timeout > 0 do
- timeout -= 1
- Wait(10)
- end
- if not DoesEntityExist(entity) then return 0 end
- return entity
-end
-
---[[ Material Definitions ]]--
-
---- A table mapping material names to their corresponding hash values.
----
---- Used for identifying materials based on hash codes.
-local materials = {
- none = -1,
- Unknown = -1775485061,
- concrete = 1187676648,
- concrete_pothole = 359120722,
- concrete_dusty = -1084640111,
- tarmac = 282940568,
- tarmac_painted = -1301352528,
- tarmac_pothole = 1886546517,
- rumble_strip = -250168275,
- breeze_block = -954112554,
- rock = -840216541,
- rock_mossy = -124769592,
- stone = 765206029,
- cobblestone = 576169331,
- brick = 1639053622,
- marble = 1945073303,
- paving_slab = 1907048430,
- sandstone_solid = 592446772,
- sandstone_brittle = 1913209870,
- sand_loose = -1595148316,
- sand_compact = 510490462,
- sand_wet = 909950165,
- sand_track = -1907520769,
- sand_underwater = -1136057692,
- sand_dry_deep = 509508168,
- sand_wet_deep = 1288448767,
- ice = -786060715,
- ice_tarmac = -1931024423,
- snow_loose = -1937569590,
- snow_compact = -878560889,
- snow_deep = 1619704960,
- snow_tarmac = 1550304810,
- gravel_small = 951832588,
- gravel_large = 2128369009,
- gravel_deep = -356706482,
- gravel_train_track = 1925605558,
- dirt_track = -1885547121,
- mud_hard = -1942898710,
- mud_pothole = 312396330,
- mud_soft = 1635937914,
- mud_underwater = -273490167,
- mud_deep = 1109728704,
- marsh = 223086562,
- marsh_deep = 1584636462,
- soil = -700658213,
- clay_hard = 1144315879,
- clay_soft = 560985072,
- grass_long = -461750719,
- grass = 1333033863,
- grass_short = -1286696947,
- hay = -1833527165,
- bushes = 581794674,
- twigs = -913351839,
- leaves = -2041329971,
- woodchips = -309121453,
- tree_bark = -1915425863,
- metal_solid_small = -1447280105,
- metal_solid_medium = -365631240,
- metal_solid_large = 752131025,
- metal_hollow_small = 15972667,
- metal_hollow_medium = 1849540536,
- metal_hollow_large = -583213831,
- metal_chainlink_small = 762193613,
- metal_chainlink_large = 125958708,
- metal_corrugated_iron = 834144982,
- metal_grille = -426118011,
- metal_railing = 2100727187,
- metal_duct = 1761524221,
- metal_garage_door = -231260695,
- metal_manhole = -754997699,
- wood_solid_small = -399872228,
- wood_solid_medium = 555004797,
- wood_solid_large = 815762359,
- wood_solid_polished = 126470059,
- wood_floor_dusty = -749452322,
- wood_hollow_small = 1993976879,
- wood_hollow_medium = -365476163,
- wood_hollow_large = -925419289,
- wood_chipboard = 1176309403,
- wood_old_creaky = 722686013,
- wood_high_density = -1742843392,
- wood_lattice = 2011204130,
- ceramic = -1186320715,
- roof_tile = 1755188853,
- roof_felt = -1417164731,
- fibreglass = 1354180827,
- tarpaulin = -642658848,
- plastic = -2073312001,
- plastic_hollow = 627123000,
- plastic_high_density = -1625995479,
- plastic_clear = -1859721013,
- plastic_hollow_clear = 772722531,
- plastic_high_density_clear = -1338473170,
- fibreglass_hollow = -766055098,
- rubber = -145735917,
- rubber_hollow = -783934672,
- linoleum = 289630530,
- laminate = 1845676458,
- carpet_solid = 669292054,
- carpet_solid_dusty = 158576196,
- carpet_floorboard = -1396484943,
- cloth = 122789469,
- plaster_solid = -574122433,
- plaster_brittle = -251888898,
- cardboard_sheet = 236511221,
- cardboard_box = -1409054440,
- paper = 474149820,
- foam = 808719444,
- feather_pillow = 1341866303,
- polystyrene = -1756927331,
- leather = -570470900,
- tvscreen = 1429989756,
- slatted_blinds = 673696729,
- glass_shoot_through = 937503243,
- glass_bulletproof = 244521486,
- glass_opaque = 1500272081,
- perspex = -1619794068,
- car_metal = -93061983,
- car_plastic = 2137197282,
- car_softtop = -979647862,
- car_softtop_clear = 2130571536,
- car_glass_weak = 1247281098,
- car_glass_medium = 602884284,
- car_glass_strong = 1070994698,
- car_glass_bulletproof = -1721915930,
- car_glass_opaque = 513061559,
- water = 435688960,
- blood = 5236042,
- oil = -634481305,
- petrol = -1634184340,
- fresh_meat = 868733839,
- dried_meat = -1445160429,
- emissive_glass = 1501078253,
- emissive_plastic = 1059629996,
- vfx_metal_electrified = -309134265,
- vfx_metal_water_tower = 611561919,
- vfx_metal_steam = -691277294,
- vfx_metal_flame = 332778253,
- phys_no_friction = 1666473731,
- phys_golf_ball = -1693813558,
- phys_tennis_ball = -256704763,
- phys_caster = -235302683,
- phys_caster_rusty = 2016463089,
- phys_car_void = 1345867677,
- phys_ped_capsule = -291631035,
- phys_electric_fence = -1170043733,
- phys_electric_metal = -2013761145,
- phys_barbed_wire = -1543323456,
- phys_pooltable_surface = 605776921,
- phys_pooltable_cushion = 972939963,
- phys_pooltable_ball = -748341562,
- buttocks = 483400232,
- thigh_left = -460535871,
- shin_left = 652772852,
- foot_left = 1926285543,
- thigh_right = -236981255,
- shin_right = -446036155,
- foot_right = -1369136684,
- spine0 = -1922286884,
- spine1 = -1140112869,
- spine2 = 1457572381,
- spine3 = 32752644,
- clavicle_left = -1469616465,
- upper_arm_left = -510342358,
- lower_arm_left = 1045062756,
- hand_left = 113101985,
- clavicle_right = -1557288998,
- upper_arm_right = 1501153539,
- lower_arm_right = 1777921590,
- hand_right = 2000961972,
- neck = 1718294164,
- head = -735392753,
- animal_default = 286224918,
- car_engine = -1916939624,
- puddle = 999829011,
- concrete_pavement = 2015599386,
- brick_pavement = -1147361576,
- phys_dynamic_cover_bound = -2047468855,
- vfx_wood_beer_barrel = 998201806,
- wood_high_friction = -2140087047,
- rock_noinst = 127813971,
- bushes_noinst = 1441114862,
- metal_solid_road_surface = -729112334,
- stunt_ramp_surface = -2088174996,
- temp_01 = 746881105,
- temp_02 = -1977970111,
- temp_03 = 1911121241,
- temp_04 = 1923995104,
- temp_05 = -1393662448,
- temp_06 = 1061250033,
- temp_07 = -1765523682,
- temp_08 = 1343679702,
- temp_09 = 1026054937,
- temp_10 = 63305994,
- temp_11 = 47470226,
- temp_12 = 702596674,
- temp_13 = -1637485913,
- temp_14 = -645955574,
- temp_15 = -1583997931,
- temp_16 = -1512735273,
- temp_17 = 1011960114,
- temp_18 = 1354993138,
- temp_19 = -801804446,
- temp_20 = -2052880405,
- temp_21 = -1037756060,
- temp_22 = -620388353,
- temp_23 = 465002639,
- temp_24 = 1963820161,
- temp_25 = 1952288305,
- temp_26 = -1116253098,
- temp_27 = 889255498,
- temp_28 = -1179674098,
- temp_29 = 1078418101,
- temp_30 = 13626292
-}
-
---- Retrieves the ground material at a specified position.
----
---- This function performs a raycast downwards from the given coordinates to determine the material type of the ground.
----
---- @param coords vector3 The coordinates from which to perform the raycast.
---- @return number|nil number The material hash if found; otherwise, `nil`.
---- @return string string The name of the material.
----
---- @usage
---- ```lua
---- local materialHash, materialName = GetGroundMaterialAtPosition(vector3(100, 200, 300))
---- print("Ground material:", materialName)
---- ```
-function GetGroundMaterialAtPosition(coords)
- local endX, endY, endZ = coords.x, coords.y, coords.z - 1.0
-
- local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endX, endY, endZ, 1.0, 1, playerPed, 7)
- local _, hit, _, _, materialHash, _ = GetShapeTestResultIncludingMaterial(rayHandle)
- local materialName = "Unknown"
- for k, v in pairs(materials) do
- if v == materialHash then
- materialName = k
- break
- end
- end
- if hit then return materialHash, materialName
- else return nil, materialName end
-end
-
---- Retrieves the dimensions of a prop/model.
----
---- This function loads the specified model and returns its width, depth, and height based on its bounding box.
----
---- @param model string The name or hash of the model.
---- @return number number The width of the prop.
---- @return number number The depth of the prop.
---- @return number number The height of the prop.
----
---- @usage
---- ```lua
---- local width, depth, height = GetPropDimensions("prop_barrel_01a")
---- print("Dimensions:", width, depth, height)
---- ```
-function GetPropDimensions(model)
- loadModel(model)
- local minDim, maxDim = GetModelDimensions(model)
- local width, depth, height = maxDim.x - minDim.x, maxDim.y - minDim.y, maxDim.z - minDim.z
-
- return width, depth, height
-end
-
---- Retrieves the forward direction vector of an entity based on its heading.
----
---- This function calculates the forward direction vector using the entity's heading angle.
----
---- @param entity number The entity whose forward vector is to be calculated.
---- @return vector3 vector3 The forward direction vector.
----
---- @usage
---- ```lua
---- local forwardVec = GetEntityForwardVector(playerPed)
---- print("Forward Vector:", forwardVec)
---- ```
-function GetEntityForwardVector(entity)
- local heading = math.rad(GetEntityHeading(entity) + 90)
- return vec3(math.cos(heading), math.sin(heading), 0.0)
+--- Utility Functions for Resource Management and Debugging
+---
+--- This script provides a set of utility functions for managing resources, debugging, and handling various common tasks within the game environment.
+--- It includes functions for checking resource states, generating unique keys, formatting numbers and coordinates, handling JSON data, and more.
+
+--[[ Resource and Environment Checks ]]--
+
+--- Checks if a specific resource is started.
+---
+---@param script string The name of the resource to check.
+---@return boolean `true` if the resource state contains "start", otherwise `false`.
+---
+---@usage
+--- ```lua
+--- if isStarted("myResource") then
+--- print("Resource is running")
+--- end
+--- ```
+function isStarted(script)
+ return GetResourceState(script):find("start")
+end
+
+local scriptName = nil
+
+--- Retrieves the current resource name.
+---
+--- Caches the resource name after the first call for efficiency.
+---
+--- @return string scriptName The name of the current resource.
+---
+--- @usage
+--- ```lua
+--- local currentScript = getScript()
+--- print("Current script:", currentScript)
+--- ```
+function getScript()
+ if not scriptName then scriptName = GetCurrentResourceName() end
+ return scriptName
+end
+
+--- Determines if the current execution context is the server.
+---
+--- Very helpful for shared files complaining about client functions running on server or vice versa
+---
+--- @return boolean Returns `true` if running on the server, otherwise `false`.
+---
+---@usage
+--- ```lua
+--- if isServer() then
+--- -- Server-specific code
+--- else
+--- -- Client-specific code
+--- end
+--- ```
+function isServer()
+ return IsDuplicityVersion()
+end
+
+--[[ Debugging Functions ]]--
+
+--- Prints debug messages if debugging mode is enabled.
+---
+--- Concatenates all arguments and prints them along with debug information.
+---
+--- @param ... any Multiple arguments to be concatenated and printed.
+---
+--- @usage
+--- ```lua
+--- debugPrint("Player has joined:", playerName)
+--- ```
+function debugPrint(...)
+ if debugMode then
+ local args = {...}
+ local output = table.concat(args, " ") -- Concatenate all arguments with a space
+ print(output, getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+end
+
+--- Prints event-related debug messages if event debugging is enabled.
+---
+--- @param ... any Multiple arguments to be printed.
+---
+--- @usage
+--- ```lua
+--- eventPrint("Event triggered:", eventName)
+--- ```
+function eventPrint(...)
+ if Config.System.EventDebug then
+ print(...)
+ end
+end
+
+-- Function to recursively colorize the JSON data
+function getSortedKeys(tbl)
+ local keys = {}
+ for k in pairs(tbl) do keys[#keys + 1] = k end
+ table.sort(keys, function(a, b)
+ local numA, numB = tonumber(a), tonumber(b)
+ if numA and numB then return numA < numB
+ else return tostring(a) < tostring(b) end
+ end)
+ return keys
+end
+
+--- Recursively colorizes a table for debug printing.
+---
+--- @param tbl table The table to colorize.
+--- @return table colourizedTable The colorized table.
+---
+--- @usage
+--- ```lua
+--- local colorizedData = colorizeTable(myTable)
+--- jsonPrint(colorizedData)
+--- ```
+function colorizeTable(tbl)
+ local newData, sortedKeys = {}, getSortedKeys(tbl)
+ for _, k in ipairs(sortedKeys) do
+ local v = tbl[k]
+ newData["^6"..tostring(k).."^7"] = ((type(v) == "table") and colorizeTable(v)) or (type(v):find("vector") and formatCoord(v)) or "^2"..tostring(v).."^7"
+ end
+ return newData
+end
+
+--- Encodes a table into an ordered JSON string with indentation.
+---
+--- @param data table The table to encode.
+--- @param indent string The string used for indentation (e.g., " ").
+--- @param level number The current indentation level.
+--- @return string The formatted JSON string.
+---
+--- @usage
+--- ```lua
+--- local jsonString = encodeOrderedJSON(myTable, " ", 0)
+--- print(jsonString)
+--- ```
+function encodeOrderedJSON(data, indent, level)
+ local jsonParts, prefix, sortedKeys = {"{"}, string.rep(indent, level), getSortedKeys(data)
+ for i, k in ipairs(sortedKeys) do
+ jsonParts[#jsonParts + 1] = (i > 1 and ",\n" or "\n")..prefix..indent..json.encode(k)..": "
+ jsonParts[#jsonParts + 1] = (type(data[k]) == "table") and encodeOrderedJSON(data[k], indent, level + 1) or json.encode(data[k])
+ end
+ jsonParts[#jsonParts + 1] = "\n"..prefix.."}"
+ return table.concat(jsonParts)
+end
+
+--- Prints a table as a colorized and ordered JSON string if debugging mode is enabled.
+---
+--- @param data table The table to print.
+---
+--- @usage
+--- ```lua
+--- jsonPrint(myTable)
+--- ```
+function jsonPrint(data)
+ if debugMode then
+ print(encodeOrderedJSON(colorizeTable(data), " ", 0), getDebugInfo(debug.getinfo(2, "nSl")))
+ end
+end
+
+--- Retrieves the current time formatted for debug prints.
+---
+--- @return string string The formatted time string, e.g., "^7(14:23:45)".
+---
+--- @usage
+--- ```lua
+--- local currentTime = GetPrintTime()
+--- debugPrint("Current Time:", currentTime)
+--- ```
+function GetPrintTime()
+ if isServer() then
+ local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
+ return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
+ else
+ local _, _, _, hour, min, sec = GetLocalTime()
+ return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
+ end
+end
+
+--- Generates a unique 3-character alphanumeric key.
+---
+--- @return string GeneratedString A randomly generated 3-character string.
+---
+--- @usage
+--- ```lua
+--- local uniqueKey = keyGen()
+--- print("Generated Key:", uniqueKey)
+--- ```
+function keyGen()
+ local charset = {
+ "q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m",
+ "Q","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M",
+ "1","2","3","4","5","6","7","8","9","0"
+ }
+ local GeneratedID = ""
+ for i = 1, 3 do GeneratedID = GeneratedID..charset[math.random(1, #charset)] end
+ return GeneratedID
+end
+
+--- Formats a number with commas as thousand separators.
+---
+--- @param amount number The number to format.
+--- @return string commaValue The formatted number string with commas.
+---
+--- @usage
+--- ```lua
+--- local formattedNumber = cv(1000000) -- "1,000,000"
+--- print(formattedNumber)
+--- ``
+function cv(amount)
+ local formatted = tostring(amount or "0")
+ while true do formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') if (k==0) then break end Wait(0) end
+ return formatted
+end
+
+--- Formats a coordinate vector for debug printing.
+---
+--- @param coord table A vector3 or vector4 table with `x`, `y`, `z`, and optional `w` components.
+--- @return string The formatted coordinate string with color codes.
+---
+--- @usage
+--- ```lua
+--- local formattedCoord = formatCoord(vector3(100.0, 200.0, 300.0))
+--- debugPrint("Player Position:", formattedCoord)
+--- ```
+function formatCoord(coord)
+ local vecType = type(coord):gsub("tor", "")
+ local components = {
+ [1] = coord.x and ("^6"..string.format("%.1f", coord.x)) or "",
+ [2] = coord.y and ("^7, ^6"..string.format("%.1f", coord.y)) or "",
+ [3] = coord.z and ("^7, ^6"..string.format("%.1f", coord.z)) or "",
+ [4] = coord.w and ("^7, ^6"..string.format("%.1f", coord.w)) or "",
+ }
+ return "^5"..vecType.."^7("..components[1]..components[2]..components[3]..components[4].."^7)"
+end
+
+--- Calculates the center point of a list of zones (coordinates).
+---
+--- @param table table A table of vector3 coordinates.
+--- @return vector3 vector3 The center coordinate.
+---
+--- @usage
+--- ```lua
+--- local center = getCenterOfZones({vector3(100, 200, 300), vector3(110, 210, 310)})
+--- print("Center of Zones:", center)
+--- ```
+function getCenterOfZones(table)
+ local totalX, totalY, totalZ = 0, 0, 0
+
+ for _, coord in ipairs(table) do
+ totalX = totalX + coord.x
+ totalY = totalY + coord.y
+ totalZ = totalZ + coord.z
+ end
+
+ local count = #table
+ return vector3(totalX / count, totalY / count, totalZ / count)
+end
+
+--- Counts the number of keys in a table.
+---
+--- @param table table The table to count keys in.
+--- @return number number The number of keys in the table.
+---
+--- @usage
+--- ```lua
+--- local count = countTable(myTable)
+--- print("Number of keys:", count)
+--- ```
+function countTable(table) local i = 0 for keys in pairs(table) do i += 1 end return i end
+
+
+--- Returns an iterator that iterates over a table's keys in sorted order.
+---
+--- @param t table The table to iterate over.
+--- @return function function An iterator function.
+---
+--- @usage
+--- ```lua
+--- for k, v in pairsByKeys(myTable) do
+--- print(k, v)
+--- end
+--- ```
+function pairsByKeys(t)
+ local t = t
+ if not t then
+ print("^1Error^7: ^3Nil ^2table recieved for ^3pairsByKeys^7(), ^2setting to ^7{} ^2to prevent break^7")
+ t = {}
+ end
+ local a = {} for n in pairs(t) do a[#a+1] = n end table.sort(a) local i = 0 local iter = function() i += 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter
+end
+
+--- Creates a new table with consecutive numerical indices sorted by the `id` field.
+---
+--- @param originalTable table The original table with entries containing an `id` field.
+--- @return table The new table with sorted entries and consecutive `id` values.
+---
+--- @usage
+--- local sortedTable = createConsecutiveTable(originalTable)
+--- for i, entry in ipairs(sortedTable) do
+--- print(i, entry)
+--- end
+function createConsecutiveTable(originalTable)
+ local sortedEntries = {}
+ for _, entry in pairs(originalTable) do
+ table.insert(sortedEntries, entry)
+ end
+ table.sort(sortedEntries, function(a, b)
+ return a.id < b.id
+ end)
+ local newTable = {}
+ for newIndex, entry in ipairs(sortedEntries) do
+ entry.id = newIndex
+ newTable[newIndex] = entry
+ end
+ return newTable
+end
+
+--[[ Drawing Functions ]]--
+
+--- Draws 3D text at specified coordinates.
+---
+--- @param coord table A vector3 table with `x`, `y`, and `z` coordinates.
+--- @param text string The text to display.
+--- @param highlight boolean (optional) Whether to highlight certain parts of the text.
+---
+--- @usage
+--- ```lua
+--- CreateThread(function()
+--- while true do
+--- DrawText3D(vector3(100, 200, 300), "Hello World", true)
+--- Wait(0)
+--- end
+--- end)
+--- ```
+function DrawText3D(coord, text, highlight)
+ SetTextScale(0.30, 0.30)
+ SetTextFont(0)
+ SetTextProportional(1)
+ SetTextColour(255, 255, 255, 215)
+ SetTextEntry("STRING")
+ SetTextCentre(true)
+ local totalLength = string.len(text)
+ local textMaxLength = textMaxLength or 99 -- max 99
+ local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text
+ AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text)
+ SetDrawOrigin(coord.x, coord.y, coord.z, 0)
+ DrawText(0.0, 0.0)
+ local count, length = GetLineCountAndMaxLength(text)
+
+ local padding = 0.005
+ local heightFactor = (count / 43) + padding
+ local weightFactor = (length / 150) + padding
+
+ local height = (heightFactor / 2) - padding / 1
+ local width = (weightFactor / 2) - padding / 1
+
+ DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
+ ClearDrawOrigin()
+end
+
+-- readd missing function for drawtext
+function GetLineCountAndMaxLength(text)
+ local lineCount = 0
+ local maxLength = 0
+ for line in text:gmatch("[^\n]+") do
+ lineCount = lineCount + 1
+ local lineLength = string.len(line)
+ if lineLength > maxLength then
+ maxLength = lineLength
+ end
+ end
+ -- If there are no newline characters (or text is empty), treat it as a single line.
+ if lineCount == 0 then
+ lineCount = 1
+ end
+ return lineCount, maxLength
+end
+
+--- Displays a help message on the screen.
+---
+--- @param text string The text to display as a help message.
+---
+--- @usage
+--- DisplayHelpMsg("Press E to interact")
+function DisplayHelpMsg(text)
+ BeginTextCommandDisplayHelp("STRING")
+ AddTextComponentScaleform(text)
+ EndTextCommandDisplayHelp(0, true, false, -1)
+end
+
+--- Displays a "Saving/Loading" spinner with a custom message.
+---
+--- @param text string The message to display alongside the spinner.
+---
+--- @usage
+--- ```lua
+--- displaySpinner("Saving data...")
+--- ```
+function displaySpinner(text)
+ BeginTextCommandBusyspinnerOn('STRING')
+ AddTextComponentSubstringPlayerName(text)
+ EndTextCommandBusyspinnerOn(4)
+end
+
+--- Stops the "Saving/Loading" spinner.
+---
+--- This function is client-side only.
+---
+--- @usage
+--- ```lua
+--- stopSpinner()
+--- ```
+function stopSpinner()
+ if not isServer() then
+ BusyspinnerOff()
+ end
+end
+
+--- Concatenates a table of strings into a single string separated by newlines.
+---
+--- @param tbl table A table containing string elements.
+--- @return string string The concatenated string with newline separators.
+---
+--- @usage
+--- ```lua
+--- local combinedText = concatenateText({"Line 1", "Line 2", "Line 3"})
+--- print(combinedText)
+--- ```
+function concatenateText(tbl)
+ local result = ""
+ for i = 1, #tbl do
+ result = result..tbl[i]
+ if i < #tbl then
+ result = result.."\n" -- Add newline only if it's not the last element
+ end
+ end
+ return result
+end
+
+--- Converts rotation to a direction vector.
+---
+--- @param rot vector3 A vector3 containing rotation values
+--- @return vector3 vector3 A vector3 representing the direction.
+---
+--- @usage
+--- ```lua
+--- local direction = RotationToDirection({ z = 90 })
+--- print(direction)
+--- ```
+function RotationToDirection(rot)
+ local adjust = (math.pi / 180)
+ return vec3(-math.sin(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)), math.cos(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)), math.sin(adjust * rot.x))
+end
+
+--- Creates a simple text-based progress bar.
+---
+--- @param percentage number The completion percentage (0-100).
+--- @return string string A string representing the progress bar, e.g., "█████░░░░░".
+---
+--- @usage
+--- ```lua
+--- local bar = basicBar(50) -- "█████░░░░░"
+--- print(bar)
+--- ```
+function basicBar(percentage)
+ local percentage = math.ceil(percentage)
+ local totalBlocks = 10
+ local filledBlocks = math.floor((percentage / 100) * totalBlocks)
+ local emptyBlocks = totalBlocks - filledBlocks
+
+ local bar = string.rep("█", filledBlocks)..string.rep("░", emptyBlocks)
+ return bar
+end
+
+--- Normalizes a 3D vector.
+---
+--- @param vec vector3 A vector3 table with `x`, `y`, and `z` components.
+--- @return vector3 vector3 The normalized vector3.
+---
+--- @usage
+--- ```lua
+--- local normalizedVec = normalizeVector(vector3(1, 2, 3))
+--- print(normalizedVec)
+--- ```
+function normalizeVector(vec)
+ local length = math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z)
+ if length ~= 0 then
+ return vec3(vec.x / length, vec.y / length, vec.z / length)
+ else
+ return vec3(0, 0, 0)
+ end
+end
+
+--- Draws a line between two coordinates for debugging purposes.
+---
+--- @param startCoords vector3 A vector3 table representing the start point.
+--- @param endCoords vector3 A vector3 table representing the end point.
+--- @param col vector4 A table with `x`, `y`, `z`, `w` representing the color and opacity.
+---
+--- @usage
+--- ```lua
+--- drawLine(vector3(100, 200, 300), vector3(150, 250, 350), vector4(255, 0, 0, 255))
+--- ```
+function drawLine(startCoords, endCoords, col)
+ if debugMode then
+ CreateThread(function()
+ local showCount = 1000
+ while showCount >= 0 do
+ DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w)
+ showCount -= 10
+ Wait(0)
+ end
+ end)
+ end
+end
+
+--- Draws a sphere at specified coordinates for debugging purposes.
+---
+--- @param coords vector3 A vector3 table representing the center of the sphere.
+--- @param col vector4 A table with `x`, `y`, `z`, `w` representing the color and opacity.
+---
+--- @usage
+--- ```lua
+--- drawSphere(vector3(100, 200, 300), vector4(0, 255, 0, 255))
+--- ```
+function drawSphere(coords, col)
+ if debugMode then
+ CreateThread(function()
+ local showCount = 1000
+ while showCount >= 0 do
+ DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w)
+ showCount -= 1
+ Wait(10)
+ end
+ end)
+ end
+end
+
+--- Performs a raycast between two coordinates and returns the result.
+---
+--- @param startCoords table A vector3 table representing the start point.
+--- @param endCoords table A vector3 table representing the end point.
+--- @param entity number|nil The entity to ignore during the raycast.
+--- @param flags number|nil Raycast flags to customize the raycast behavior. Defaults to `4294967295`.
+--- @return multiple multiple Returns multiple values from `GetShapeTestResultIncludingMaterial`.
+---
+--- @usage
+--- ```lua
+--- local hit, hitPos, material = PerformRaycast(startVec, endVec, playerPed, 1)
+--- if hit == 1 then
+--- print("Hit at position:", hitPos)
+--- print("Material:", material)
+--- end
+--- ```
+function PerformRaycast(startCoords, endCoords, entity, flags)
+ drawLine(startCoords, endCoords, vec4(0, 0, 255, 255))
+ local val1, val2, val3, val4, val5, val6 = GetShapeTestResult(StartExpensiveSynchronousShapeTestLosProbe(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, flags or 4294967295, entity, 0))
+ if val2 then
+ --drawSphere(val3, vec4(255, 0, 255, 0.5))
+ end
+ return val1, val2, val3, val4, val5, val6
+end
+
+--- Adjusts the Z-coordinate of a position to align with the ground.
+---
+--- @param coords vector4 A vector3 or vector4 table with `x`, `y`, `z`, and optional `w` components.
+--- @return vector3|vector4 vector adjusted coordinate with the Z value set to the ground level.
+---
+--- @usage
+--- ```lua
+--- local groundCoords = adjustForGround(playerCoords)
+--- print("Ground Position:", groundCoords)
+--- ```
+function adjustForGround(coords)
+ local coords = coords
+ local foundGround, zPos = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0)
+
+ if foundGround then
+ if coords.w then
+ coords = vec4(coords.x, coords.y, zPos, coords.w)
+ else
+ coords = vec3(coords.x, coords.y, zPos)
+ end
+ --debugPrint("^6Bridge^7: Adjusting for ground pos ", coords.z, zPos)
+
+ return coords
+ else
+ return coords
+ end
+end
+
+--- Ensures that a network vehicle exists by verifying its network ID.
+---
+--- @param vehNetID number The network ID of the vehicle.
+--- @return number number The vehicle entity if it exists, otherwise `0`.
+---
+--- @usage
+--- ```lua
+--- local vehicle = ensureNetToVeh(netID)
+--- if vehicle ~= 0 then
+--- print("Vehicle exists:", vehicle)
+--- end
+--- ```
+function ensureNetToVeh(vehNetID)
+ debugPrint("^6Bridge^7: ^3ensureNetToVeh^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..vehNetID.."^7)")
+ local timeout = 100
+ while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do
+ timeout -= 1
+ Wait(10)
+ end
+ if not NetworkDoesNetworkIdExist(vehNetID) then return 0 end
+ timeout = 100
+ local vehicle = NetToVeh(vehNetID)
+ while not DoesEntityExist(vehicle) and vehicle ~= 0 and timeout > 0 do
+ timeout -= 1
+ Wait(10)
+ end
+ if not DoesEntityExist(vehicle) then return 0 end
+ return vehicle
+end
+
+--- Ensures that a network entity exists by verifying its network ID.
+---
+--- @param entNetID number The network ID of the entity.
+--- @return number The entity if it exists, otherwise `0`.
+---
+--- @usage
+--- local entity = ensureNetToEnt(netID)
+--- if entity ~= 0 then
+--- print("Entity exists:", entity)
+--- end
+function ensureNetToEnt(entNetID)
+ debugPrint("^6Bridge^7: ^3ensureNetToEnt^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..entNetID.."^7)")
+ local timeout = 100
+ while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do
+ timeout -= 1
+ Wait(10)
+ end
+ if not NetworkDoesNetworkIdExist(entNetID) then return 0 end
+ timeout = 100
+ local entity = NetworkGetEntityFromNetworkId(entNetID)
+ while not DoesEntityExist(entity) and entity ~= 0 and timeout > 0 do
+ timeout -= 1
+ Wait(10)
+ end
+ if not DoesEntityExist(entity) then return 0 end
+ return entity
+end
+
+--[[ Material Definitions ]]--
+
+--- A table mapping material names to their corresponding hash values.
+---
+--- Used for identifying materials based on hash codes.
+local materials = {
+ none = -1,
+ Unknown = -1775485061,
+ concrete = 1187676648,
+ concrete_pothole = 359120722,
+ concrete_dusty = -1084640111,
+ tarmac = 282940568,
+ tarmac_painted = -1301352528,
+ tarmac_pothole = 1886546517,
+ rumble_strip = -250168275,
+ breeze_block = -954112554,
+ rock = -840216541,
+ rock_mossy = -124769592,
+ stone = 765206029,
+ cobblestone = 576169331,
+ brick = 1639053622,
+ marble = 1945073303,
+ paving_slab = 1907048430,
+ sandstone_solid = 592446772,
+ sandstone_brittle = 1913209870,
+ sand_loose = -1595148316,
+ sand_compact = 510490462,
+ sand_wet = 909950165,
+ sand_track = -1907520769,
+ sand_underwater = -1136057692,
+ sand_dry_deep = 509508168,
+ sand_wet_deep = 1288448767,
+ ice = -786060715,
+ ice_tarmac = -1931024423,
+ snow_loose = -1937569590,
+ snow_compact = -878560889,
+ snow_deep = 1619704960,
+ snow_tarmac = 1550304810,
+ gravel_small = 951832588,
+ gravel_large = 2128369009,
+ gravel_deep = -356706482,
+ gravel_train_track = 1925605558,
+ dirt_track = -1885547121,
+ mud_hard = -1942898710,
+ mud_pothole = 312396330,
+ mud_soft = 1635937914,
+ mud_underwater = -273490167,
+ mud_deep = 1109728704,
+ marsh = 223086562,
+ marsh_deep = 1584636462,
+ soil = -700658213,
+ clay_hard = 1144315879,
+ clay_soft = 560985072,
+ grass_long = -461750719,
+ grass = 1333033863,
+ grass_short = -1286696947,
+ hay = -1833527165,
+ bushes = 581794674,
+ twigs = -913351839,
+ leaves = -2041329971,
+ woodchips = -309121453,
+ tree_bark = -1915425863,
+ metal_solid_small = -1447280105,
+ metal_solid_medium = -365631240,
+ metal_solid_large = 752131025,
+ metal_hollow_small = 15972667,
+ metal_hollow_medium = 1849540536,
+ metal_hollow_large = -583213831,
+ metal_chainlink_small = 762193613,
+ metal_chainlink_large = 125958708,
+ metal_corrugated_iron = 834144982,
+ metal_grille = -426118011,
+ metal_railing = 2100727187,
+ metal_duct = 1761524221,
+ metal_garage_door = -231260695,
+ metal_manhole = -754997699,
+ wood_solid_small = -399872228,
+ wood_solid_medium = 555004797,
+ wood_solid_large = 815762359,
+ wood_solid_polished = 126470059,
+ wood_floor_dusty = -749452322,
+ wood_hollow_small = 1993976879,
+ wood_hollow_medium = -365476163,
+ wood_hollow_large = -925419289,
+ wood_chipboard = 1176309403,
+ wood_old_creaky = 722686013,
+ wood_high_density = -1742843392,
+ wood_lattice = 2011204130,
+ ceramic = -1186320715,
+ roof_tile = 1755188853,
+ roof_felt = -1417164731,
+ fibreglass = 1354180827,
+ tarpaulin = -642658848,
+ plastic = -2073312001,
+ plastic_hollow = 627123000,
+ plastic_high_density = -1625995479,
+ plastic_clear = -1859721013,
+ plastic_hollow_clear = 772722531,
+ plastic_high_density_clear = -1338473170,
+ fibreglass_hollow = -766055098,
+ rubber = -145735917,
+ rubber_hollow = -783934672,
+ linoleum = 289630530,
+ laminate = 1845676458,
+ carpet_solid = 669292054,
+ carpet_solid_dusty = 158576196,
+ carpet_floorboard = -1396484943,
+ cloth = 122789469,
+ plaster_solid = -574122433,
+ plaster_brittle = -251888898,
+ cardboard_sheet = 236511221,
+ cardboard_box = -1409054440,
+ paper = 474149820,
+ foam = 808719444,
+ feather_pillow = 1341866303,
+ polystyrene = -1756927331,
+ leather = -570470900,
+ tvscreen = 1429989756,
+ slatted_blinds = 673696729,
+ glass_shoot_through = 937503243,
+ glass_bulletproof = 244521486,
+ glass_opaque = 1500272081,
+ perspex = -1619794068,
+ car_metal = -93061983,
+ car_plastic = 2137197282,
+ car_softtop = -979647862,
+ car_softtop_clear = 2130571536,
+ car_glass_weak = 1247281098,
+ car_glass_medium = 602884284,
+ car_glass_strong = 1070994698,
+ car_glass_bulletproof = -1721915930,
+ car_glass_opaque = 513061559,
+ water = 435688960,
+ blood = 5236042,
+ oil = -634481305,
+ petrol = -1634184340,
+ fresh_meat = 868733839,
+ dried_meat = -1445160429,
+ emissive_glass = 1501078253,
+ emissive_plastic = 1059629996,
+ vfx_metal_electrified = -309134265,
+ vfx_metal_water_tower = 611561919,
+ vfx_metal_steam = -691277294,
+ vfx_metal_flame = 332778253,
+ phys_no_friction = 1666473731,
+ phys_golf_ball = -1693813558,
+ phys_tennis_ball = -256704763,
+ phys_caster = -235302683,
+ phys_caster_rusty = 2016463089,
+ phys_car_void = 1345867677,
+ phys_ped_capsule = -291631035,
+ phys_electric_fence = -1170043733,
+ phys_electric_metal = -2013761145,
+ phys_barbed_wire = -1543323456,
+ phys_pooltable_surface = 605776921,
+ phys_pooltable_cushion = 972939963,
+ phys_pooltable_ball = -748341562,
+ buttocks = 483400232,
+ thigh_left = -460535871,
+ shin_left = 652772852,
+ foot_left = 1926285543,
+ thigh_right = -236981255,
+ shin_right = -446036155,
+ foot_right = -1369136684,
+ spine0 = -1922286884,
+ spine1 = -1140112869,
+ spine2 = 1457572381,
+ spine3 = 32752644,
+ clavicle_left = -1469616465,
+ upper_arm_left = -510342358,
+ lower_arm_left = 1045062756,
+ hand_left = 113101985,
+ clavicle_right = -1557288998,
+ upper_arm_right = 1501153539,
+ lower_arm_right = 1777921590,
+ hand_right = 2000961972,
+ neck = 1718294164,
+ head = -735392753,
+ animal_default = 286224918,
+ car_engine = -1916939624,
+ puddle = 999829011,
+ concrete_pavement = 2015599386,
+ brick_pavement = -1147361576,
+ phys_dynamic_cover_bound = -2047468855,
+ vfx_wood_beer_barrel = 998201806,
+ wood_high_friction = -2140087047,
+ rock_noinst = 127813971,
+ bushes_noinst = 1441114862,
+ metal_solid_road_surface = -729112334,
+ stunt_ramp_surface = -2088174996,
+ temp_01 = 746881105,
+ temp_02 = -1977970111,
+ temp_03 = 1911121241,
+ temp_04 = 1923995104,
+ temp_05 = -1393662448,
+ temp_06 = 1061250033,
+ temp_07 = -1765523682,
+ temp_08 = 1343679702,
+ temp_09 = 1026054937,
+ temp_10 = 63305994,
+ temp_11 = 47470226,
+ temp_12 = 702596674,
+ temp_13 = -1637485913,
+ temp_14 = -645955574,
+ temp_15 = -1583997931,
+ temp_16 = -1512735273,
+ temp_17 = 1011960114,
+ temp_18 = 1354993138,
+ temp_19 = -801804446,
+ temp_20 = -2052880405,
+ temp_21 = -1037756060,
+ temp_22 = -620388353,
+ temp_23 = 465002639,
+ temp_24 = 1963820161,
+ temp_25 = 1952288305,
+ temp_26 = -1116253098,
+ temp_27 = 889255498,
+ temp_28 = -1179674098,
+ temp_29 = 1078418101,
+ temp_30 = 13626292
+}
+
+--- Retrieves the ground material at a specified position.
+---
+--- This function performs a raycast downwards from the given coordinates to determine the material type of the ground.
+---
+--- @param coords vector3 The coordinates from which to perform the raycast.
+--- @return number|nil number The material hash if found; otherwise, `nil`.
+--- @return string string The name of the material.
+---
+--- @usage
+--- ```lua
+--- local materialHash, materialName = GetGroundMaterialAtPosition(vector3(100, 200, 300))
+--- print("Ground material:", materialName)
+--- ```
+function GetGroundMaterialAtPosition(coords)
+ local endX, endY, endZ = coords.x, coords.y, coords.z - 1.0
+
+ local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endX, endY, endZ, 1.0, 1, playerPed, 7)
+ local _, hit, _, _, materialHash, _ = GetShapeTestResultIncludingMaterial(rayHandle)
+ local materialName = "Unknown"
+ for k, v in pairs(materials) do
+ if v == materialHash then
+ materialName = k
+ break
+ end
+ end
+ if hit then return materialHash, materialName
+ else return nil, materialName end
+end
+
+--- Retrieves the dimensions of a prop/model.
+---
+--- This function loads the specified model and returns its width, depth, and height based on its bounding box.
+---
+--- @param model string The name or hash of the model.
+--- @return number number The width of the prop.
+--- @return number number The depth of the prop.
+--- @return number number The height of the prop.
+---
+--- @usage
+--- ```lua
+--- local width, depth, height = GetPropDimensions("prop_barrel_01a")
+--- print("Dimensions:", width, depth, height)
+--- ```
+function GetPropDimensions(model)
+ loadModel(model)
+ local minDim, maxDim = GetModelDimensions(model)
+ local width, depth, height = maxDim.x - minDim.x, maxDim.y - minDim.y, maxDim.z - minDim.z
+
+ return width, depth, height
+end
+
+--- Retrieves the forward direction vector of an entity based on its heading.
+---
+--- This function calculates the forward direction vector using the entity's heading angle.
+---
+--- @param entity number The entity whose forward vector is to be calculated.
+--- @return vector3 vector3 The forward direction vector.
+---
+--- @usage
+--- ```lua
+--- local forwardVec = GetEntityForwardVector(playerPed)
+--- print("Forward Vector:", forwardVec)
+--- ```
+function GetEntityForwardVector(entity)
+ local heading = math.rad(GetEntityHeading(entity) + 90)
+ return vec3(math.cos(heading), math.sin(heading), 0.0)
end
\ No newline at end of file
diff --git a/shared/input.lua b/shared/input.lua
index 2f65af1..dd3145b 100644
--- a/shared/input.lua
+++ b/shared/input.lua
@@ -1,156 +1,191 @@
--- INPUT --
--- Multiscript input script function to create simple input text boxes --
-
---- Creates a simple input dialog compatible with multiple menu systems.
----
---- This function generates input dialogs for different frameworks (OX, QB, GTA) based on the configuration.
---- It supports various input types such as radio buttons, numbers, text, and select dropdowns.
----
----@param title string The title/header of the input dialog.
----@param opts table A table containing input options. Each option should have a `type` and other relevant fields based on the type.
---- - **type** (`string`): The type of input. Supported types: "radio", "number", "text", "select".
---- - **label** (`string`, optional): The label for the input (used for "radio" and "select" types in OX).
---- - **text** (`string`, optional): The text prompt for the input.
---- - **name** (`string`): The identifier name for the input.
---- - **isRequired** (`boolean`, optional): Whether the input is required.
---- - **default** (`any`, optional): The default value for the input.
---- - **options** (`table`, optional): A table of options for "radio" and "select" types.
---- - **min** (`number`, optional): The minimum value (used for "select" type).
---- - **max** (`number`, optional): The maximum value (used for "number" and "select" types).
---- - **txt** (`string`, optional): Additional text or description for the input.
----
----@return table|nil table Returns the user's input as a table if the dialog is submitted, otherwise returns `nil`.
----
----@usage
---- ```lua
---- local userInput = createInput("Enter Details", {
---- { type = "text", text = "Name", name = "playerName", isRequired = true },
---- { type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
---- { type = "radio", label = "Gender", name = "playerGender", options = {
---- { text = "Male", value = "male" },
---- { text = "Female", value = "female" },
---- { text = "Other", value = "other" },
---- }},
---- })
---- ```
-function createInput(title, opts)
- local dialog = nil
- local options = {}
-
- if Config.System.Menu == "ox" then
- for i = 1, #opts do
- if opts[i].type == "radio" then
- -- Convert radio options to select type for OX
- for k in pairs(opts[i].options) do
- opts[i].options[k].label = opts[i].options[k].text
- end
- options[i] = {
- type = "select",
- isRequired = opts[i].isRequired,
- label = opts[i].label or opts[i].text,
- name = opts[i].name,
- default = opts[i].default or opts[i].options[1].value,
- options = opts[i].options,
- }
- end
- if opts[i].type == "number" then
- options[i] = {
- type = "number",
- label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
- isRequired = opts[i].isRequired,
- name = opts[i].name,
- options = opts[i].options,
- }
- end
- if opts[i].type == "text" then
- options[i] = {
- type = "input",
- label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
- default = opts[i].default,
- isRequired = opts[i].isRequired,
- }
- end
- if opts[i].type == "select" then
- options[i] = {
- type = "select",
- label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
- isRequired = opts[i].isRequired,
- name = opts[i].name,
- options = opts[i].options,
- min = opts[i].min,
- max = opts[i].max,
- default = opts[i].default,
- }
- end
- end
- dialog = exports[OXLibExport]:inputDialog(title, options)
- return dialog
- end
-
- if Config.System.Menu == "qb" then
- dialog = exports['qb-input']:ShowInput({ header = title, submitText = "Accept", inputs = opts })
- return dialog
- end
-
- if Config.System.Menu == "gta" then
- WarMenu.CreateMenu(tostring(opts),
- title,
- " ",
- {
- titleColor = { 222, 255, 255 },
- maxOptionCountOnScreen = 15,
- width = 0.25,
- x = 0.7,
- })
- if WarMenu.IsAnyMenuOpened() then return end
- WarMenu.OpenMenu(tostring(opts))
-
- local close = true
- local _comboBoxItems = {}
- local _comboBoxIndex = { 1, 1 }
-
- while true do
- if WarMenu.Begin(tostring(opts)) then
- for i = 1, #opts do
- if opts[i].type == "radio" then
- for k in pairs(opts[i].options) do
- if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
- _comboBoxItems[i][k] = opts[i].options[k].text
- end
- local _, comboBoxIndex = WarMenu.ComboBox(opts[i].label, _comboBoxItems[i], _comboBoxIndex[i])
- if _comboBoxIndex[i] ~= comboBoxIndex then
- _comboBoxIndex[i] = comboBoxIndex
- end
- end
- if opts[i].type == "number" then
- for b = 1, opts[i].max do
- if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
- _comboBoxItems[i][b] = b
- end
- local _, comboBoxIndex = WarMenu.ComboBox(opts[i].text, _comboBoxItems[i], _comboBoxIndex[i])
- if _comboBoxIndex[i] ~= comboBoxIndex then
- _comboBoxIndex[i] = comboBoxIndex
- end
- end
- end
- local pressed = WarMenu.Button("Pay")
- if pressed then
- WarMenu.CloseMenu()
- close = false
- local result = {}
- for i = 1, #_comboBoxIndex do
- result[i] = _comboBoxItems[i][_comboBoxIndex[i]]
- end
- return result
- end
- WarMenu.End()
- else
- return
- end
- if not WarMenu.IsAnyMenuOpened() and close then
- if data.onExit then data.onExit() end
- end
- Wait(0)
- end
- end
+-- INPUT --
+-- Multiscript input script function to create simple input text boxes --
+
+--- Creates a simple input dialog compatible with multiple menu systems.
+---
+--- This function generates input dialogs for different frameworks (OX, QB, GTA) based on the configuration.
+--- It supports various input types such as radio buttons, numbers, text, and select dropdowns.
+---
+---@param title string The title/header of the input dialog.
+---@param opts table A table containing input options. Each option should have a `type` and other relevant fields based on the type.
+--- - **type** (`string`): The type of input. Supported types: "radio", "number", "text", "select".
+--- - **label** (`string`, optional): The label for the input (used for "radio" and "select" types in OX).
+--- - **text** (`string`, optional): The text prompt for the input.
+--- - **name** (`string`): The identifier name for the input.
+--- - **isRequired** (`boolean`, optional): Whether the input is required.
+--- - **default** (`any`, optional): The default value for the input.
+--- - **options** (`table`, optional): A table of options for "radio" and "select" types.
+--- - **min** (`number`, optional): The minimum value (used for "select" type).
+--- - **max** (`number`, optional): The maximum value (used for "number" and "select" types).
+--- - **txt** (`string`, optional): Additional text or description for the input.
+---
+---@return table|nil table Returns the user's input as a table if the dialog is submitted, otherwise returns `nil`.
+---
+---@usage
+--- ```lua
+--- local userInput = createInput("Enter Details", {
+--- { type = "text", text = "Name", name = "playerName", isRequired = true },
+--- { type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
+--- { type = "radio", label = "Gender", name = "playerGender", options = {
+--- { text = "Male", value = "male" },
+--- { text = "Female", value = "female" },
+--- { text = "Other", value = "other" },
+--- }},
+--- })
+--- ```
+function createInput(title, opts)
+ local dialog = nil
+ local options = {}
+
+ if Config.System.Menu == "ox" then
+ for i = 1, #opts do
+ if opts[i].type == "radio" then
+ -- Convert radio options to select type for OX
+ for k in pairs(opts[i].options) do
+ opts[i].options[k].label = opts[i].options[k].text
+ end
+ options[i] = {
+ type = "select",
+ isRequired = opts[i].isRequired,
+ label = opts[i].label or opts[i].text,
+ name = opts[i].name,
+ default = opts[i].default or opts[i].options[1].value,
+ options = opts[i].options,
+ }
+ end
+ if opts[i].type == "number" then
+ options[i] = {
+ type = "number",
+ label = (opts[i].label or opts[i].text)..(opts[i].txt and " - "..opts[i].txt or ""),
+ isRequired = opts[i].isRequired,
+ name = opts[i].name,
+ options = opts[i].options,
+ }
+ end
+ if opts[i].type == "text" then
+ options[i] = {
+ type = "input",
+ label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
+ default = opts[i].default,
+ isRequired = opts[i].isRequired,
+ }
+ end
+ if opts[i].type == "select" then
+ options[i] = {
+ type = "select",
+ label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
+ isRequired = opts[i].isRequired,
+ name = opts[i].name,
+ options = opts[i].options,
+ min = opts[i].min,
+ max = opts[i].max,
+ default = opts[i].default,
+ }
+ end
+ end
+ dialog = exports[OXLibExport]:inputDialog(title, options)
+ return dialog
+ elseif Config.System.Menu == "qb" then
+ dialog = exports['qb-input']:ShowInput({ header = title, submitText = "Accept", inputs = opts })
+ return dialog
+ elseif Config.System.Menu == "gta" then
+ WarMenu.CreateMenu(tostring(opts),
+ title,
+ " ",
+ {
+ titleColor = { 222, 255, 255 },
+ maxOptionCountOnScreen = 15,
+ width = 0.25,
+ x = 0.7,
+ })
+ if WarMenu.IsAnyMenuOpened() then return end
+ WarMenu.OpenMenu(tostring(opts))
+
+ local close = true
+ local _comboBoxItems = {}
+ local _comboBoxIndex = { 1, 1 }
+
+ while true do
+ if WarMenu.Begin(tostring(opts)) then
+ for i = 1, #opts do
+ if opts[i].type == "radio" then
+ for k in pairs(opts[i].options) do
+ if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
+ _comboBoxItems[i][k] = opts[i].options[k].text
+ end
+ local _, comboBoxIndex = WarMenu.ComboBox(opts[i].label, _comboBoxItems[i], _comboBoxIndex[i])
+ if _comboBoxIndex[i] ~= comboBoxIndex then
+ _comboBoxIndex[i] = comboBoxIndex
+ end
+ end
+ if opts[i].type == "number" then
+ for b = 1, opts[i].max do
+ if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
+ _comboBoxItems[i][b] = b
+ end
+ local _, comboBoxIndex = WarMenu.ComboBox(opts[i].text, _comboBoxItems[i], _comboBoxIndex[i])
+ if _comboBoxIndex[i] ~= comboBoxIndex then
+ _comboBoxIndex[i] = comboBoxIndex
+ end
+ end
+ end
+ local pressed = WarMenu.Button("Pay")
+ if pressed then
+ WarMenu.CloseMenu()
+ close = false
+ local result = {}
+ for i = 1, #_comboBoxIndex do
+ result[i] = _comboBoxItems[i][_comboBoxIndex[i]]
+ end
+ return result
+ end
+ WarMenu.End()
+ else
+ return
+ end
+ if not WarMenu.IsAnyMenuOpened() and close then
+ if data.onExit then data.onExit() end
+ end
+ Wait(0)
+ end
+ elseif Config.System.Menu == "esx" then -- horrible input dialog, not even worth using, get OX
+
+ local results = {}
+ for i, opt in ipairs(opts) do
+ local prompt = opt.text or opt.label or "Enter value"
+ -- For radio/select types, append available options in the prompt.
+ if (opt.type == "radio" or opt.type == "select") and opt.options then
+ local choices = ""
+ for j, choice in ipairs(opt.options) do
+ choices = choices .. choice.text .. " (" .. tostring(choice.value) .. ")"
+ if j < #opt.options then choices = choices .. ", " end
+ end
+ prompt = prompt .. " [" .. choices .. "]"
+ elseif opt.type == "number" then
+ prompt = prompt .. " (number between " .. (opt.min or 0) .. " and " .. (opt.max or 100) .. ")"
+ end
+
+ local value = nil
+ ESX.UI.Menu.Open('dialog', getScript(), 'input_' .. i, {
+ title = prompt
+ }, function(data, menu)
+ value = data.value
+ menu.close()
+ end, function(data, menu)
+ menu.close()
+ end)
+
+ -- Wait until the player submits a value.
+ while value == nil do
+ Wait(0)
+ end
+
+ -- Convert to a number if needed.
+ if opt.type == "number" then
+ value = tonumber(value)
+ end
+ results[opt.name or i] = value
+ end
+ return results
+ end
end
\ No newline at end of file
diff --git a/shared/isAnimal.lua b/shared/isAnimal.lua
index c07ae3f..f45e7fa 100644
--- a/shared/isAnimal.lua
+++ b/shared/isAnimal.lua
@@ -1,442 +1,442 @@
-isCat, isDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false
-
-if not isServer() then
- onPlayerLoaded(function()
- Wait(2000)
- isCat, isDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false
- isPedAnimal()
- if isAnimal then
- local ped = PlayerPedId()
- local pedModel = GetEntityModel(ped)
-
- isCat = (isCat(ped) or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`)
-
- isDog, isBigDog = isDog(ped)
- isSmallDog = not isBigDog
- if isDog and pedModel == `a_c_coyote` then isDog = false end
-
- isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`)
-
- if pedModel == `ft-capmonkey2` then isDog = true end
- end
- end, true)
-
-
- --- Determines if a given Ped is classified as an animal.
- ---
- --- This function checks whether the specified Ped (or the player's Ped if none is provided)
- --- is listed within the predefined `AnimalPeds` tables. It iterates through all animal types
- --- to verify if the Ped's model hash matches any known animal models.
- ---
- ---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`).
- ---
- ---@return boolean `true` if the Ped is an animal, otherwise `false`.
- ---
- --- @usage
- --- ```lua
- --- local isPlayerAnimal = isAnimal()
- --- local isSpecificPedAnimal = isAnimal(somePedEntity)
- --- ```
- function isPedAnimal(ped)
- local PedModel = GetEntityModel(ped or PlayerPedId())
-
- for _, animalTypeTable in pairs(AnimalPeds) do
- for animalModelHash, _ in pairs(animalTypeTable) do
- if PedModel == animalModelHash then
- isAnimal = true
- break
- end
- end
- if isAnimal then
- debugPrint("^6Debug^7: ^2Ped is Animal^1")
- break
- end
- end
-
- return isAnimal
- end
-
- --- Checks if a given Ped is classified specifically as a cat.
- ---
- --- This function verifies whether the specified Ped (or the player's Ped if none is provided)
- --- matches any of the model hashes listed under `AnimalPeds.CatPeds`. It returns `true` if a match is found.
- ---
- ---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`).
- ---
- ---@return boolean `true` if the Ped is a cat, otherwise `false`.
- ---
- ---@usage
- --- ```lua
- --- if isCat() then
- --- print("Player is a cat!")
- --- end
- ---
- --- local anotherPed = GetPedInVehicleSeat(vehicle, -1)
- --- if isCat(anotherPed) then
- --- print("Driver is a cat!")
- --- end
- --- ```
- function isCat(ped)
- local PedModel = GetEntityModel(ped or PlayerPedId())
- for k, v in pairs(AnimalPeds.CatPeds) do
- if PedModel == k then
- return true
- end
- end
- return false
- end
-
- --- Determines if a given Ped is classified as a dog and identifies its size category.
- ---
- --- This function checks whether the specified Ped (or the player's Ped if none is provided)
- --- matches any model hashes listed under `AnimalPeds.BigDogs` or `AnimalPeds.SmallDogs`. It returns
- --- two values: the first indicates if the Ped is a dog, and the second specifies whether it's a
- --- large dog (`true`) or a small dog (`false`). If the Ped is not a dog, the second return value is `nil`.
- ---
- ---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`).
- ---
- ---@return boolean, boolean|nil Returns `true` and `true` if the Ped is a big dog,
- --- `true` and `false` if it's a small dog,
- --- or `false` and `nil` if it's not a dog.
- ---
- ---@usage
- --- ```lua
- --- local isDog, isBigDog = isDog()
- --- if isDog then
- --- if isBigDog then
- --- print("Player is a big dog!")
- --- else
- --- print("Player is a small dog!")
- --- end
- --- else
- --- print("Player is not a dog.")
- --- end
- ---
- --- local somePed = GetPedInVehicleSeat(vehicle, 0)
- --- local isPetDog, isLargeDog = isDog(somePed)
- --- if isPetDog then
- --- if isLargeDog then
- --- print("Passenger is a big dog!")
- --- else
- --- print("Passenger is a small dog!")
- --- end
- --- end
- --- ```
- function isDog(ped)
- local PedModel = GetEntityModel(ped or PlayerPedId())
- for k, v in pairs(AnimalPeds.BigDogs) do
- if PedModel == k then
- return true, true
- end
- end
-
- for k, v in pairs(AnimalPeds.SmallDogs) do
- if PedModel == k then
- return true, false
- end
- end
- return false, nil
- end
-
- --- Retrieves a list of all animal model hashes.
- ---
- --- This function compiles and returns a flat table containing all model hashes
- --- from the various animal categories defined within the `AnimalPeds` table.
- --- It's useful for iterating over or performing bulk operations on all animal models.
- ---
- ---@return table table A table containing all animal model hashes.
- ---
- ---@usage
- --- ```lua
- --- local allAnimalModels = getAnimalModels()
- --- for _, modelHash in ipairs(allAnimalModels) do
- --- print("Animal Model Hash:", modelHash)
- --- end
- --- ```
- function getAnimalModels()
- local animalTable = {}
- for k in pairs(AnimalPeds) do
- for v in pairs(AnimalPeds[k]) do
- animalTable[#animalTable+1] = v
- end
- end
- return animalTable
- end
-end
-
-AnimalPeds = {
- BigDogs = {
- -- Big Dogs
- [`a_c_chop`] = {
- deathAnim = "dead_right", deathDict = "creatures@chop@move",
- exitAnim = "getup_r", exitDict = "creatures@chop@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`a_c_k9`] = {
- deathAnim = "dead_right", deathDict = "creatures@chop@move",
- exitAnim = "getup_r", exitDict = "creatures@chop@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`a_c_husky`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`a_c_retriever`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`a_c_shepherd`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`a_c_rottweiler`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-aushep`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`golden_r`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-dobermanv2`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`doberman`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-gs`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`k9_husky`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-bloodhound`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`bernard`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-pterrier`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-labrador`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`dane`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft_malinois`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`abdog`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`dalmatian`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`a_c_dalmatian`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-boxer`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`ft-bs`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`chowchow`] = {
- deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
- exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
- sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
- },
- [`a_c_coyote`] = {
- deathAnim = "dead_right", deathDict = "creatures@coyote@move",
- exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
- sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
- },
- [`a_c_coyote_02`] = {
- deathAnim = "dead_right", deathDict = "creatures@coyote@move",
- exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
- sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
- },
- },
- SmallDogs = {
- -- Small Dogs
- [`a_c_poodle`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- [`ft-chihuahua`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- [`a_c_pug`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- [`a_c_pug_02`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- [`a_c_westy`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- [`ft-pretriever`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- [`ft-shepk9`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- },
- CatPeds = {
- -- Cat
- [`bshorthair`] = {
- deathAnim = "dead_right", deathDict = "creatures@cat@move",
- exitAnim = "getup_r", exitDict = "creatures@cat@getup",
- sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
- },
- [`a_c_cat_01`] = {
- deathAnim = "dead_right", deathDict = "creatures@cat@move",
- exitAnim = "getup_r", exitDict = "creatures@cat@getup",
- sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
- },
- [`ft-sphynx`] = {
- deathAnim = "dead_right", deathDict = "creatures@coyote@move",
- exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
- sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
- },
- },
- OtherPeds = {
- -- Other Animals
- [`ft-raccoon`] = {
- deathAnim = "dead_right", deathDict = "creatures@cat@move",
- exitAnim = "getup_r", exitDict = "creatures@cat@getup",
- sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
- },
- [`a_c_hen`] = {
- deathAnim = "dead_right", deathDict = "creatures@hen@move",
- exitAnim = "getup_r", exitDict = "creatures@hen@getup"
- },
- [`a_c_rabbit_01`] = {
- deathAnim = "dead_right", deathDict = "creatures@rabbit@move",
- exitAnim = "getup_r", exitDict = "creatures@rabbit@getup",
- sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
- },
- [`a_c_rabbit_02`] = {
- deathAnim = "dead_right", deathDict = "creatures@rabbit@move",
- exitAnim = "getup_r", exitDict = "creatures@rabbit@getup",
- sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
- },
- [`a_c_rat`] = {
- deathAnim = "dead_right", deathDict = "creatures@rat@move",
- exitAnim = "getup_r", exitDict = "creatures@rat@getup"
- },
- [`a_c_deer`] = {
- deathAnim = "dead_right", deathDict = "creatures@deer@move",
- exitAnim = "getup_r", exitDict = "creatures@deer@getup"
- },
- [`a_c_boar`] = {
- deathAnim = "dead_right", deathDict = "creatures@boar@move",
- exitAnim = "getup_r", exitDict = "creatures@boar@getup"
- },
- [`a_c_boar_02`] = {
- deathAnim = "dead_right", deathDict = "creatures@boar@move",
- exitAnim = "getup_r", exitDict = "creatures@boar@getup"
- },
- [`a_c_chicken`] = {
- deathAnim = "dead_right", deathDict = "creatures@chicken@move",
- exitAnim = "getup_r", exitDict = "creatures@chicken@getup"
- },
- [`a_c_pig`] = {
- deathAnim = "dead_right", deathDict = "creatures@pig@move",
- exitAnim = "getup_r", exitDict = "creatures@pig@getup"
- },
- [`a_c_sharkhammer`] = {
- deathAnim = "dead_right", deathDict = "creatures@sharkhammer@move",
- exitAnim = "getup_r", exitDict = "creatures@sharkhammer@getup"
- },
- [`a_c_sharktiger`] = {
- deathAnim = "dead_right", deathDict = "creatures@sharktiger@move",
- exitAnim = "getup_r", exitDict = "creatures@sharktiger@getup"
- },
- [`a_c_crow`] = {
- deathAnim = "dead_down", deathDict = "creatures@crow@move",
- exitAnim = "nill", exitDict = "creatures@pug@move", -- no get up anim
- },
- [`a_c_pigeon`] = {
- deathAnim = "dead_down", deathDict = "creatures@pigeon@move",
- exitAnim = "nill", exitDict = "creatures@pug@move", -- no get up anim
- },
- },
- Monekys = {
- [`ft-chimpanzee`] = {
- deathAnim = "dead", deathDict = "dead_a",
- exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
- },
- [`a_c_chimp`] = {
- deathAnim = "dead", deathDict = "dead_a",
- exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
- },
- [`a_c_chimp_02`] = {
- deathAnim = "dead", deathDict = "dead_a",
- exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
- },
- [`a_c_rhesus`] = {
- deathAnim = "dead", deathDict = "dead_a",
- exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
- },
- [`ft-capmonkey2`] = {
- deathAnim = "dead_right", deathDict = "creatures@pug@move",
- exitAnim = "getup_r", exitDict = "creatures@pug@getup",
- sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
- },
- }
+isCat, isDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false
+
+if not isServer() then
+ onPlayerLoaded(function()
+ Wait(2000)
+ isCat, isDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false
+ isPedAnimal()
+ if isAnimal then
+ local ped = PlayerPedId()
+ local pedModel = GetEntityModel(ped)
+
+ isCat = (isCat(ped) or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`)
+
+ isDog, isBigDog = isDog(ped)
+ isSmallDog = not isBigDog
+ if isDog and pedModel == `a_c_coyote` then isDog = false end
+
+ isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`)
+
+ if pedModel == `ft-capmonkey2` then isDog = true end
+ end
+ end, true)
+
+
+ --- Determines if a given Ped is classified as an animal.
+ ---
+ --- This function checks whether the specified Ped (or the player's Ped if none is provided)
+ --- is listed within the predefined `AnimalPeds` tables. It iterates through all animal types
+ --- to verify if the Ped's model hash matches any known animal models.
+ ---
+ ---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`).
+ ---
+ ---@return boolean `true` if the Ped is an animal, otherwise `false`.
+ ---
+ --- @usage
+ --- ```lua
+ --- local isPlayerAnimal = isAnimal()
+ --- local isSpecificPedAnimal = isAnimal(somePedEntity)
+ --- ```
+ function isPedAnimal(ped)
+ local PedModel = GetEntityModel(ped or PlayerPedId())
+
+ for _, animalTypeTable in pairs(AnimalPeds) do
+ for animalModelHash, _ in pairs(animalTypeTable) do
+ if PedModel == animalModelHash then
+ isAnimal = true
+ break
+ end
+ end
+ if isAnimal then
+ debugPrint("^6Debug^7: ^2Ped is Animal^1")
+ break
+ end
+ end
+
+ return isAnimal
+ end
+
+ --- Checks if a given Ped is classified specifically as a cat.
+ ---
+ --- This function verifies whether the specified Ped (or the player's Ped if none is provided)
+ --- matches any of the model hashes listed under `AnimalPeds.CatPeds`. It returns `true` if a match is found.
+ ---
+ ---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`).
+ ---
+ ---@return boolean `true` if the Ped is a cat, otherwise `false`.
+ ---
+ ---@usage
+ --- ```lua
+ --- if isCat() then
+ --- print("Player is a cat!")
+ --- end
+ ---
+ --- local anotherPed = GetPedInVehicleSeat(vehicle, -1)
+ --- if isCat(anotherPed) then
+ --- print("Driver is a cat!")
+ --- end
+ --- ```
+ function isCat(ped)
+ local PedModel = GetEntityModel(ped or PlayerPedId())
+ for k, v in pairs(AnimalPeds.CatPeds) do
+ if PedModel == k then
+ return true
+ end
+ end
+ return false
+ end
+
+ --- Determines if a given Ped is classified as a dog and identifies its size category.
+ ---
+ --- This function checks whether the specified Ped (or the player's Ped if none is provided)
+ --- matches any model hashes listed under `AnimalPeds.BigDogs` or `AnimalPeds.SmallDogs`. It returns
+ --- two values: the first indicates if the Ped is a dog, and the second specifies whether it's a
+ --- large dog (`true`) or a small dog (`false`). If the Ped is not a dog, the second return value is `nil`.
+ ---
+ ---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`).
+ ---
+ ---@return boolean, boolean|nil Returns `true` and `true` if the Ped is a big dog,
+ --- `true` and `false` if it's a small dog,
+ --- or `false` and `nil` if it's not a dog.
+ ---
+ ---@usage
+ --- ```lua
+ --- local isDog, isBigDog = isDog()
+ --- if isDog then
+ --- if isBigDog then
+ --- print("Player is a big dog!")
+ --- else
+ --- print("Player is a small dog!")
+ --- end
+ --- else
+ --- print("Player is not a dog.")
+ --- end
+ ---
+ --- local somePed = GetPedInVehicleSeat(vehicle, 0)
+ --- local isPetDog, isLargeDog = isDog(somePed)
+ --- if isPetDog then
+ --- if isLargeDog then
+ --- print("Passenger is a big dog!")
+ --- else
+ --- print("Passenger is a small dog!")
+ --- end
+ --- end
+ --- ```
+ function isDog(ped)
+ local PedModel = GetEntityModel(ped or PlayerPedId())
+ for k, v in pairs(AnimalPeds.BigDogs) do
+ if PedModel == k then
+ return true, true
+ end
+ end
+
+ for k, v in pairs(AnimalPeds.SmallDogs) do
+ if PedModel == k then
+ return true, false
+ end
+ end
+ return false, nil
+ end
+
+ --- Retrieves a list of all animal model hashes.
+ ---
+ --- This function compiles and returns a flat table containing all model hashes
+ --- from the various animal categories defined within the `AnimalPeds` table.
+ --- It's useful for iterating over or performing bulk operations on all animal models.
+ ---
+ ---@return table table A table containing all animal model hashes.
+ ---
+ ---@usage
+ --- ```lua
+ --- local allAnimalModels = getAnimalModels()
+ --- for _, modelHash in ipairs(allAnimalModels) do
+ --- print("Animal Model Hash:", modelHash)
+ --- end
+ --- ```
+ function getAnimalModels()
+ local animalTable = {}
+ for k in pairs(AnimalPeds) do
+ for v in pairs(AnimalPeds[k]) do
+ animalTable[#animalTable+1] = v
+ end
+ end
+ return animalTable
+ end
+end
+
+AnimalPeds = {
+ BigDogs = {
+ -- Big Dogs
+ [`a_c_chop`] = {
+ deathAnim = "dead_right", deathDict = "creatures@chop@move",
+ exitAnim = "getup_r", exitDict = "creatures@chop@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`a_c_k9`] = {
+ deathAnim = "dead_right", deathDict = "creatures@chop@move",
+ exitAnim = "getup_r", exitDict = "creatures@chop@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`a_c_husky`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`a_c_retriever`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`a_c_shepherd`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`a_c_rottweiler`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-aushep`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`golden_r`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-dobermanv2`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`doberman`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-gs`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`k9_husky`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-bloodhound`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`bernard`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-pterrier`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-labrador`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`dane`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft_malinois`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`abdog`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`dalmatian`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`a_c_dalmatian`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-boxer`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`ft-bs`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`chowchow`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
+ exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
+ sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
+ },
+ [`a_c_coyote`] = {
+ deathAnim = "dead_right", deathDict = "creatures@coyote@move",
+ exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
+ sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
+ },
+ [`a_c_coyote_02`] = {
+ deathAnim = "dead_right", deathDict = "creatures@coyote@move",
+ exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
+ sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
+ },
+ },
+ SmallDogs = {
+ -- Small Dogs
+ [`a_c_poodle`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ [`ft-chihuahua`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ [`a_c_pug`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ [`a_c_pug_02`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ [`a_c_westy`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ [`ft-pretriever`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ [`ft-shepk9`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ },
+ CatPeds = {
+ -- Cat
+ [`bshorthair`] = {
+ deathAnim = "dead_right", deathDict = "creatures@cat@move",
+ exitAnim = "getup_r", exitDict = "creatures@cat@getup",
+ sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
+ },
+ [`a_c_cat_01`] = {
+ deathAnim = "dead_right", deathDict = "creatures@cat@move",
+ exitAnim = "getup_r", exitDict = "creatures@cat@getup",
+ sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
+ },
+ [`ft-sphynx`] = {
+ deathAnim = "dead_right", deathDict = "creatures@coyote@move",
+ exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
+ sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
+ },
+ },
+ OtherPeds = {
+ -- Other Animals
+ [`ft-raccoon`] = {
+ deathAnim = "dead_right", deathDict = "creatures@cat@move",
+ exitAnim = "getup_r", exitDict = "creatures@cat@getup",
+ sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
+ },
+ [`a_c_hen`] = {
+ deathAnim = "dead_right", deathDict = "creatures@hen@move",
+ exitAnim = "getup_r", exitDict = "creatures@hen@getup"
+ },
+ [`a_c_rabbit_01`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rabbit@move",
+ exitAnim = "getup_r", exitDict = "creatures@rabbit@getup",
+ sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
+ },
+ [`a_c_rabbit_02`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rabbit@move",
+ exitAnim = "getup_r", exitDict = "creatures@rabbit@getup",
+ sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
+ },
+ [`a_c_rat`] = {
+ deathAnim = "dead_right", deathDict = "creatures@rat@move",
+ exitAnim = "getup_r", exitDict = "creatures@rat@getup"
+ },
+ [`a_c_deer`] = {
+ deathAnim = "dead_right", deathDict = "creatures@deer@move",
+ exitAnim = "getup_r", exitDict = "creatures@deer@getup"
+ },
+ [`a_c_boar`] = {
+ deathAnim = "dead_right", deathDict = "creatures@boar@move",
+ exitAnim = "getup_r", exitDict = "creatures@boar@getup"
+ },
+ [`a_c_boar_02`] = {
+ deathAnim = "dead_right", deathDict = "creatures@boar@move",
+ exitAnim = "getup_r", exitDict = "creatures@boar@getup"
+ },
+ [`a_c_chicken`] = {
+ deathAnim = "dead_right", deathDict = "creatures@chicken@move",
+ exitAnim = "getup_r", exitDict = "creatures@chicken@getup"
+ },
+ [`a_c_pig`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pig@move",
+ exitAnim = "getup_r", exitDict = "creatures@pig@getup"
+ },
+ [`a_c_sharkhammer`] = {
+ deathAnim = "dead_right", deathDict = "creatures@sharkhammer@move",
+ exitAnim = "getup_r", exitDict = "creatures@sharkhammer@getup"
+ },
+ [`a_c_sharktiger`] = {
+ deathAnim = "dead_right", deathDict = "creatures@sharktiger@move",
+ exitAnim = "getup_r", exitDict = "creatures@sharktiger@getup"
+ },
+ [`a_c_crow`] = {
+ deathAnim = "dead_down", deathDict = "creatures@crow@move",
+ exitAnim = "nill", exitDict = "creatures@pug@move", -- no get up anim
+ },
+ [`a_c_pigeon`] = {
+ deathAnim = "dead_down", deathDict = "creatures@pigeon@move",
+ exitAnim = "nill", exitDict = "creatures@pug@move", -- no get up anim
+ },
+ },
+ Monekys = {
+ [`ft-chimpanzee`] = {
+ deathAnim = "dead", deathDict = "dead_a",
+ exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
+ },
+ [`a_c_chimp`] = {
+ deathAnim = "dead", deathDict = "dead_a",
+ exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
+ },
+ [`a_c_chimp_02`] = {
+ deathAnim = "dead", deathDict = "dead_a",
+ exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
+ },
+ [`a_c_rhesus`] = {
+ deathAnim = "dead", deathDict = "dead_a",
+ exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
+ },
+ [`ft-capmonkey2`] = {
+ deathAnim = "dead_right", deathDict = "creatures@pug@move",
+ exitAnim = "getup_r", exitDict = "creatures@pug@getup",
+ sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
+ },
+ }
}
\ No newline at end of file
diff --git a/shared/itemcontrol.lua b/shared/itemcontrol.lua
index 4d7f128..e8a2620 100644
--- a/shared/itemcontrol.lua
+++ b/shared/itemcontrol.lua
@@ -1,606 +1,606 @@
--- Function to register items as usable for ESX, QBX, and QBcore --
----
---- This function registers an item as usable across different inventory systems such as ESX, QBcore, and QBX.
---- It checks which inventory system is active and registers the usable item accordingly.
----
----@param item string The name of the item to be registered as usable.
----@param funct function The function to execute when the item is used.
----
----@usage
---- ```lua
---- createUseableItem("health_potion", function(source)
---- -- Code to consume the health potion
---- end)
---- ```
-function createUseableItem(item, funct)
- if isStarted(ESXExport) then
- debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7es_extended", item)
- while not ESX do Wait(0) end
- ESX.RegisterUsableItem(item, funct)
- elseif isStarted(QBExport) and not isStarted(QBXExport) then
- debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qb-core", item)
- Core.Functions.CreateUseableItem(item, funct)
- elseif isStarted(QBXExport) then
- debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qbx_core", item)
- exports[QBXExport]:CreateUseableItem(item, funct)
- end
-end
-
--- Simple function to grab the item's image from inventories and retrieve it as a nui:// link --
----
---- This function retrieves the image URL of an item from various inventory systems and formats it as a `nui://` link.
---- It supports multiple inventory systems such as OXInv, QSInv, CoreInv, OrigenInv, QBInv, and CodeMInv.
----
----@param item string The name of the item whose image is to be retrieved.
----@return string link The `nui://` link to the item's image. Returns an empty string if the inventory system is not detected or the item doesn't exist.
----
----@usage
---- ```lua
---- local imageLink = invImg("health_potion")
---- if imageLink ~= "" then
---- print(imageLink)
---- end
---- ```
-function invImg(item)
- local imgLink = ""
- if item ~= "" and Items[item] then
- if isStarted(OXInv) then
- imgLink = "nui://"..OXInv.."/web/images/"..(Items[item].image or "")
- elseif isStarted(QSInv) then
- imgLink = "nui://"..QSInv.."/html/images/"..(Items[item].image or "")
- elseif isStarted(CoreInv) then
- imgLink = "nui://"..CoreInv.."/html/img/"..(Items[item].image or "")
- elseif isStarted(OrigenInv) then
- imgLink = "nui://"..OrigenInv.."/html/img/"..(Items[item].image or "")
- elseif isStarted(QBInv) then
- imgLink = "nui://"..QBInv.."/html/images/"..(Items[item].image or "")
- elseif isStarted(CodeMInv) then
- imgLink = "nui://"..CodeMInv.."/html/itemimages/"..(Items[item].image or "")
- else
- print("^4ERROR^7: ^2No Inventory detected for invImg ^7- ^2Check ^3exports^1.^2lua^7")
- end
- end
- return imgLink
-end
-
---- Adds an item to a player's inventory.
----
---- This function triggers a server event to add a specified amount of an item to the player's inventory.
----
----@param item string The name of the item to add.
----@param amount number The quantity of the item to add.
----@param info table|nil Additional information or metadata for the item.
----
----@usage
---- ```lua
---- addItem("health_potion", 2, { quality = "high" })
---- ```
-function addItem(item, amount, info, src)
- if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to give ^7'^3"..item.."^7'^2 but it doesn't exist") return end
- if src then
- TriggerEvent(getScript()..":server:toggleItem", true, item, amount, src, info)
- else
- TriggerServerEvent(getScript()..":server:toggleItem", true, item, amount, nil, info)
- end
-end
-
---- Removes an item from a player's inventory.
----
---- This function triggers a server event to remove a specified amount of an item from the player's inventory.
----
----@param item string The name of the item to remove.
----@param amount number The quantity of the item to remove.
----
----@usage
---- ```lua
---- removeItem("health_potion", 1)
---- ```
-function removeItem(item, amount, src)
- if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist") return end
- if src then
- TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, info)
- else
- TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, info)
- end
-end
-
---- Server event handler to toggle items in a player's inventory.
----
---- This event handles adding or removing items based on the parameters received.
---- It supports multiple inventory systems and includes exploit protection to prevent duplication.
----
----@param give boolean Indicates whether to add (`true`) or remove (`false`) the item.
----@param item string The name of the item to toggle.
----@param amount number The quantity of the item to toggle.
----@param newsrc number|nil The source ID of the player. If `nil`, it defaults to the event source.
----@param info table|nil Additional information or metadata for the item.
----
----@usage
---- ```lua
---- TriggerServerEvent("script:server:toggleItem", true, "health_potion", 1)
---- ```
-RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info)
- if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." ^7'^3"..item.."^7'^2 but it doesn't exist") return end
- local src = newsrc or source
- local addremove = (tostring(give) == "true" and "addItem" or "removeItem")
- debugPrint("^6Bridge^7: ^3toggleItem ^2triggered^7: ^6"..addremove.."^7 - '"..tostring(item).."' x"..(tostring(amount) or "1"))
- local remamount = (amount and amount or 1)
- if item == nil then return end
- if give == 0 or give == false then
- if hasItem(item, amount and amount or 1, src) then -- Check if the player has the item
- if isStarted(OXInv) then
- local success = exports[OXInv]:RemoveItem(src, item, (amount and amount or 1), nil)
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OXInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
- elseif isStarted(QSInv) then
- local success = exports[QSInv]:RemoveItem(src, item, amount)
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(CoreInv) then
- if isStarted(QBExport) then
- Core.Functions.GetPlayer(src).Functions.RemoveItem(item, amount, nil)
- elseif isStarted(ESXExport) then
- ESX.GetPlayerFromId(src).removeInventoryItem(item, count)
- end
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CoreInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(OrigenInv) then
- local success = exports[OrigenInv]:RemoveItem(src, item, amount)
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OrigenInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(CodeMInv) then
- local success = exports[CodeMInv]:RemoveItem(src, item, amount)
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CodeMInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(QBInv) then
- while remamount > 0 do
- if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then
- remamount -= 1
- else
- print("^1Error removing "..data.item.." Amount left to remove: "..remamount.."^7")
- break
- end
- end
- if Config.Crafting.showItemBox then
- TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', src, Items[item], "remove", (amount and amount or 1))
- end
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QBInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
- elseif isStarted(PSInv) then
- while remamount > 0 do
- if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then
- remamount -= 1
- else
- print("^1Error removing "..data.item.." Amount left to remove: "..remamount.."^7")
- break
- end
- end
- if Config.Crafting.showItemBox then
- TriggerClientEvent('inventory:client:ItemBox', src, Items[item], "remove", (amount and amount or 1))
- end
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..PSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
- else
- print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
- end
- else
- dupeWarn(src, item, amount) -- Trigger exploit protection
- end
- else
- local amount = amount and amount or 1
- if isStarted(OXInv) then
- local success = exports[OXInv]:AddItem(src, item, amount or 1, info)
- if not Items[item] or not Items[item].label then
- print("^1Error^7: "..addremove.." ["..OXInv.."] Player("..src..") "..Items[item]?.label.."("..item..") x"..(amount or 1))
- end
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OXInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(QSInv) then
- local success = exports[QSInv]:AddItem(src, item, amount)
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(CoreInv) then
- if isStarted(QBExport) or isStarted(QBXExport) then
- Core.Functions.GetPlayer(src).Functions.AddItem(item, amount, nil, nil)
- elseif isStarted(ESXExport) then
- ESX.GetPlayerFromId(src).addInventoryItem(item, amount)
- end
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CoreInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(CodeMInv) then
- local success = exports[CodeMInv]:AddItem(src, item, amount)
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CodeMInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
- elseif isStarted(OrigenInv) then
- local success = exports[OrigenInv]:AddItem(src, item, amount)
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OrigenInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(QBInv) then
- if Core.Functions.GetPlayer(src).Functions.AddItem(item, amount or 1, nil, info) then
- TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', src, Items[item], "add", amount and amount or 1)
- end
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QBInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
-
- elseif isStarted(PSInv) then
- if Core.Functions.GetPlayer(src).Functions.AddItem(item, amount or 1, nil, info) then
- if Config.Crafting.showItemBox then
- TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "add", amount and amount or 1)
- end
- end
- debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..PSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
- else
- print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
- end
- end
-end)
-
---- Protects against item duplication exploits by warning and potentially kicking the player.
----
---- This function is called when an attempt is made to remove an item that the player does not possess.
---- It logs the incident and kicks the player if `debugMode` is not enabled.
----
---- @param src number The source ID of the player attempting the exploit.
---- @param item string The name of the item being exploited.
----
---- @usage
---- ```lua
---- dupeWarn(playerId, "health_potion")
---- ```
-function dupeWarn(src, item)
- local name = getPlayer(src).name
- print("^5DupeWarn^7: "..name.." (^1"..tostring(src).."^7) ^2Tried to remove item ^7'^3"..item.."^7'^2 but it wasn't there^7")
- if not debugMode then
- DropPlayer(src, name.."("..tostring(src)..") Kicked for suspected duplicating items: "..item)
- end
- print("^5DupeWarn^7: "..name.."(^1"..tostring(src).."^7) ^2Dropped from server - exploit protection detected an item not being found in players inventory^7")
-end
-
---- Breaks a tool by reducing its durability or removing it if durability reaches zero.
----
---- This function handles the durability mechanics for tools. If a tool's durability drops to zero or below,
---- it removes the tool from the player's inventory and plays a breaking sound.
----
---- @param data table A table containing data about the tool being used.
---- - **item** (`string`): The name of the tool item.
---- - **damage** (`number`): The amount of durability damage to apply.
----
---- @usage
---- ```lua
---- breakTool({ item = "drill", damage = 10 })
---- ```
-function breakTool(data) -- WIP
- local durability, slot = getDurability(data.item)
- if not durability then durability = 100 end
- durability -= data.damage
- if durability <= 0 then
- removeItem(data.item, 1)
- local breakId = GetSoundId()
- PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
- else
- TriggerServerEvent(getScript()..":server:setMetaData", { item = data.item, slot = slot, metadata = { durability = durability } })
- end
-end
-
---- Retrieves the durability and slot of an item in a player's inventory.
----
---- This function searches the player's inventory for the specified item and returns its durability and slot number.
----
---- @param item string The name of the item to check.
---- @return number|nil The durability of the item. Returns `nil` if not found.
---- @return number|nil The slot number of the item. Returns `nil` if not found.
----
---- @usage
---- ```lua
---- local durability, slot = getDurability("drill")
---- if durability then
---- print("Durability:", durability)
---- end
---- ```
-function getDurability(item)
- local lowestSlot = 100
- local durability = nil
- if isStarted(QBInv) or isStarted(PSInv) then
- local itemcheck = Core.Functions.GetPlayerData().items
- for k, v in pairs(itemcheck) do
- if v.name == item then
- if v.slot <= lowestSlot then
- lowestSlot = v.slot
- durability = itemcheck[k].info.durability
- end
- end
- end
- end
-
- if isStarted(OXInv) then
- local itemcheck = exports[OXInv]:Search('slots', item)
- for k, v in pairs(itemcheck) do
- if v.slot <= lowestSlot then
- debugPrint(v.slot, itemcheck[k].metadata.durability)
- lowestSlot = v.slot
- durability = itemcheck[k].metadata.durability
- end
- end
- end
-
- if isStarted(QSInv) then
- local itemcheck = exports[QSInv]:getUserInventory()
- for k, v in pairs(itemcheck) do
- if v.name == item and v.slot <= lowestSlot then
- lowestSlot = v.slot
- durability = itemcheck[k].metadata.durability
- end
- end
- end
-
- if isStarted(OrigenInv) then
- local itemcheck = exports[OrigenInv]:getPlayerInventory()
- for k, v in pairs(itemcheck) do
- if v.name == item and v.slot <= lowestSlot then
- lowestSlot = v.slot
- durability = itemcheck[k].metadata.durability
- end
- end
- end
- return durability, lowestSlot
-end
-
---- Server event handler to set metadata for an item in a player's inventory.
----
---- This event updates the metadata (e.g., durability) of an item in the player's inventory.
----
----@param data table A table containing metadata information.
---- - **item** (`string`): The name of the item.
---- - **slot** (`number`): The slot number of the item in the inventory.
---- - **metadata** (`table`): The metadata to set for the item.
----
----@usage
---- ```lua
---- TriggerServerEvent("script:server:setMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } })
---- ```
-RegisterNetEvent(getScript()..":server:setMetaData", function(data)
- local src = source
- if isStarted(QBInv) or isStarted(PSInv) then
- debugPrint(src, data.item, 1, data.slot)
- local Player = Core.Functions.GetPlayer(src)
- Player.PlayerData.items[data.slot].info = data.metadata
- Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability
- Player.Functions.SetInventory(Player.PlayerData.items)
- end
-
- if isStarted(OXInv) then
- exports[OXInv]:SetMetadata(source, data.slot, data.metadata)
- end
-
- if isStarted(QSInv) then
- exports[QSInv]:SetItemMetadata(source, data.slot, data.metadata)
- end
-
- if isStarted(OrigenInv) then
- local item = exports[OrigenInv]:GetItemBySlot(source, data.slot)
- if item then
- exports[OrigenInv]:SetItemData(source, item.name, "durability", data.metadata.durability)
- end
- end
-end)
-
---- Checks if a player has the specified items in their inventory.
----
---- This function verifies whether a player possesses the required quantity of specified items.
---- It supports multiple inventory systems and provides detailed feedback on item availability.
----
----@param items string|table A single item name or a table of item names with their required amounts.
----@param amount number The quantity required for each item. Defaults to `1` if not specified.
----@param src number|nil The source ID of the player. If `nil`, it defaults to the caller.
----@return boolean Returns `true` if the player has all the required items in the specified amounts.
----@return table|nil Returns a table detailing which items are present or missing if not all items are found.
----
----@usage
---- ```lua
---- local hasAll, details = hasItem({"health_potion", "mana_potion"}, 2, playerId)
---- if hasAll then
---- -- Proceed with action
---- else
---- -- Inform the player about missing items
---- end
---- ```
-function hasItem(items, amount, src)
- local amount = amount and amount or 1
- local grabInv, foundInv = getPlayerInv(src)
- if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
-
- if grabInv then
- local hasTable = {}
- for item, amt in pairs(items) do
- if not Items[item] then print("^4ERROR^7: ^2Script can't find ingredient item in Shared Items - ^1"..item.."^7") end
- local count = 0
- for _, itemData in pairs(grabInv) do
- if itemData and (itemData.name == item) then count += (itemData.count or itemData.amount or 1) end
- end
- foundInv = foundInv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
- local foundMessage = "^6Bridge^7: ^3hasItem^7[^6"..foundInv.."^7]: "..tostring(item).." ^3"..count.."^7/^3"..amt
- if count >= amt then foundMessage = foundMessage.." ^5FOUND^7" else foundMessage = foundMessage .." ^1NOT FOUND^7" end
- debugPrint(foundMessage)
- hasTable[item] = { hasItem = count >= amt, count = count }
- end
- for k, v in pairs(hasTable) do if not v.hasItem then return false, hasTable end end
- return true, hasTable
- end
-end
-
---- Retrieves a player's inventory from the active inventory system.
----
---- This function fetches the player's inventory based on the active inventory system.
---- It supports multiple systems including OXInv, QSInv, OrigenInv, CoreInv, CodeMInv, QBInv, and PSInv.
----
----@param src number|nil The source ID of the player. If `nil`, it fetches the current player's inventory.
----@return table|nil The inventory items of the player.
----@return string|nil The name of the inventory system being used.
----
----@usage
---- ```lua
---- local inventory, system = getPlayerInv(playerId)
---- if inventory then
---- -- Process inventory
---- end
---- ```
-function getPlayerInv(src)
- local grabInv = nil
- local foundInv = ""
-
- if isStarted(OXInv) then
- foundInv = OXInv
- if src then grabInv = exports[OXInv]:GetInventoryItems(src)
- else grabInv = exports[OXInv]:GetPlayerItems() end
-
- elseif isStarted(QSInv) then
- foundInv = QSInv
- if src then grabInv = exports[QSInv]:GetInventory(src)
- else grabInv = exports[QSInv]:getUserInventory() end
-
- elseif isStarted(OrigenInv) then
- foundInv = OrigenInv
- if src then grabInv = exports[OrigenInv]:GetInventory(src)
- else grabInv = exports[OrigenInv]:getPlayerInventory() end
-
- elseif isStarted(CoreInv) then
- foundInv = CoreInv
- if src then
- if isStarted(QBExport) or isStarted(QBXExport) then
- grabInv = Core.Functions.GetPlayer(src).PlayerData.items
- elseif isStarted(ESXExport) then
- local Player = ESX.GetPlayerFromId(src)
- grabInv = Player.getInventory(false)
- end
- else
- local p = promise.new()
- Core.Functions.TriggerCallback('core_inventory:server:getInventory', function(cb) p:resolve(cb) end)
- local result = Citizen.Await(p)
- if type(result) == "string" then result = json.decode(result) end
- grabInv = result
- end
-
- elseif isStarted(CodeMInv) then
- foundInv = CodeMInv
- if src then grabInv = exports[CodeMInv]:GetInventory(src)
- else grabInv = exports[CodeMInv]:GetClientPlayerInventory() end
-
- elseif isStarted(QBInv) then
- foundInv = QBInv
- if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items
- else grabInv = Core.Functions.GetPlayerData().items end
-
- elseif isStarted(PSInv) then
- foundInv = PSInv
- if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items
- else grabInv = Core.Functions.GetPlayerData().items end
-
- else
- print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
- end
- return grabInv, foundInv
-end
-
---- Generates a random reward from a predefined reward pool.
----
---- This function is intended for job scripts where players receive random rewards upon completing certain tasks.
---- It ensures that the player has the required item before attempting to grant a reward.
----
----@param itemName string The name of the item to check for eligibility to receive a reward.
----
----@usage
---- ```lua
---- getRandomReward("gold_ring")
---- ```
-function getRandomReward(itemName) -- Intended for job scripts
- if Config.Rewards.RewardPool then
- local reward = false
- if type(Config.Rewards.RewardItem) == "string" then Config.Rewards.RewardItem = { Config.Rewards.RewardItem } end
- for k, v in pairs(Config.Rewards.RewardItem) do
- if v == itemName then reward = true break end
- end
- if reward then
- removeItem(itemName, 1)
- local totalRarity = 0
- for i=1, #Config.Rewards.RewardPool do
- totalRarity += Config.Rewards.RewardPool[i].rarity
- end
- debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Total Rarity ^7'^6"..totalRarity.."^7'")
-
- local randomNum = math.random(1, totalRarity)
- debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Random Number ^7'^6"..randomNum.."^7'")
- local currentRarity = 0
- for i=1, #Config.Rewards.RewardPool do
- currentRarity += Config.Rewards.RewardPool[i].rarity
- if randomNum <= currentRarity then
- debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Selected toy ^7'^6"..Config.Rewards.RewardPool[i].item.."^7'")
- addItem(Config.Rewards.RewardPool[i].item, 1)
- return
- end
- end
- end
- end
-end
-
---- Checks if a player can carry specific items in their inventory.
----
---- This function determines whether a player has enough capacity to carry the specified items.
---- It considers the weight of each item and the player's current inventory weight.
----
----@param itemTable table A table where keys are item names and values are the quantities to check.
----@param src number The source ID of the player.
----@return table A table where keys are item names and values are booleans indicating if the player can carry the specified quantity.
----
----@usage
---- ```lua
---- local canCarry = canCarry({ ["health_potion"] = 2, ["mana_potion"] = 3 }, playerId)
---- if canCarry["health_potion"] and canCarry["mana_potion"] then
---- -- Proceed with adding items
---- else
---- -- Inform the player they can't carry all items
---- end
---- ```
-function canCarry(itemTable, src)
- local resultTable = {}
- if src then
- if isStarted(OXInv) then
- for k, v in pairs(itemTable) do
- resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v)
- end
-
- elseif isStarted(QSInv) then
- for k, v in pairs(itemTable) do
- resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v)
- end
-
- elseif isStarted(CoreInv) then
- --??
-
- elseif isStarted(CodeMInv) then
- for k, v in pairs(itemTable) do
- local weight = Items[k].weight
- resultTable[k] = exports[CodeMInv]:CanCarryItem(src, weight, v)
- end
-
- elseif isStarted(OrigenInv) then
- for k, v in pairs(itemTable) do
- resultTable[k] = exports[OrigenInv]:canCarryItem(src, k, v)
- end
-
- elseif isStarted(QBInv) or isStarted(PSInv) then
- local Player = Core.Functions.GetPlayer(src)
- local items = Player.PlayerData.items
- local weight, totalWeight = 0, 0
- if not items then return false end
- for _, item in pairs(items) do weight += item.weight * item.amount end
-
- totalWeight = tonumber(weight)
-
- for k, v in pairs(itemTable) do
- local itemInfo = Items[k]
- if not itemInfo and not Player.Offline then
- triggerNotify(nil, 'Item does not exist', 'error', src)
- resultTable[k] = true
- else
- resultTable[k] = (totalWeight + (Items[k]['weight'] * v)) <= InventoryWeight
- end
- end
- end
- end
- return resultTable
+-- Function to register items as usable for ESX, QBX, and QBcore --
+---
+--- This function registers an item as usable across different inventory systems such as ESX, QBcore, and QBX.
+--- It checks which inventory system is active and registers the usable item accordingly.
+---
+---@param item string The name of the item to be registered as usable.
+---@param funct function The function to execute when the item is used.
+---
+---@usage
+--- ```lua
+--- createUseableItem("health_potion", function(source)
+--- -- Code to consume the health potion
+--- end)
+--- ```
+function createUseableItem(item, funct)
+ if isStarted(ESXExport) then
+ debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7es_extended", item)
+ while not ESX do Wait(0) end
+ ESX.RegisterUsableItem(item, funct)
+ elseif isStarted(QBExport) and not isStarted(QBXExport) then
+ debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qb-core", item)
+ Core.Functions.CreateUseableItem(item, funct)
+ elseif isStarted(QBXExport) then
+ debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qbx_core", item)
+ exports[QBXExport]:CreateUseableItem(item, funct)
+ end
+end
+
+-- Simple function to grab the item's image from inventories and retrieve it as a nui:// link --
+---
+--- This function retrieves the image URL of an item from various inventory systems and formats it as a `nui://` link.
+--- It supports multiple inventory systems such as OXInv, QSInv, CoreInv, OrigenInv, QBInv, and CodeMInv.
+---
+---@param item string The name of the item whose image is to be retrieved.
+---@return string link The `nui://` link to the item's image. Returns an empty string if the inventory system is not detected or the item doesn't exist.
+---
+---@usage
+--- ```lua
+--- local imageLink = invImg("health_potion")
+--- if imageLink ~= "" then
+--- print(imageLink)
+--- end
+--- ```
+function invImg(item)
+ local imgLink = ""
+ if item ~= "" and Items[item] then
+ if isStarted(OXInv) then
+ imgLink = "nui://"..OXInv.."/web/images/"..(Items[item].image or "")
+ elseif isStarted(QSInv) then
+ imgLink = "nui://"..QSInv.."/html/images/"..(Items[item].image or "")
+ elseif isStarted(CoreInv) then
+ imgLink = "nui://"..CoreInv.."/html/img/"..(Items[item].image or "")
+ elseif isStarted(OrigenInv) then
+ imgLink = "nui://"..OrigenInv.."/html/img/"..(Items[item].image or "")
+ elseif isStarted(QBInv) then
+ imgLink = "nui://"..QBInv.."/html/images/"..(Items[item].image or "")
+ elseif isStarted(CodeMInv) then
+ imgLink = "nui://"..CodeMInv.."/html/itemimages/"..(Items[item].image or "")
+ else
+ print("^4ERROR^7: ^2No Inventory detected for invImg ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+ end
+ return imgLink
+end
+
+--- Adds an item to a player's inventory.
+---
+--- This function triggers a server event to add a specified amount of an item to the player's inventory.
+---
+---@param item string The name of the item to add.
+---@param amount number The quantity of the item to add.
+---@param info table|nil Additional information or metadata for the item.
+---
+---@usage
+--- ```lua
+--- addItem("health_potion", 2, { quality = "high" })
+--- ```
+function addItem(item, amount, info, src)
+ if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to give ^7'^3"..item.."^7'^2 but it doesn't exist") return end
+ if src then
+ TriggerEvent(getScript()..":server:toggleItem", true, item, amount, src, info)
+ else
+ TriggerServerEvent(getScript()..":server:toggleItem", true, item, amount, nil, info)
+ end
+end
+
+--- Removes an item from a player's inventory.
+---
+--- This function triggers a server event to remove a specified amount of an item from the player's inventory.
+---
+---@param item string The name of the item to remove.
+---@param amount number The quantity of the item to remove.
+---
+---@usage
+--- ```lua
+--- removeItem("health_potion", 1)
+--- ```
+function removeItem(item, amount, src)
+ if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist") return end
+ if src then
+ TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, info)
+ else
+ TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, info)
+ end
+end
+
+--- Server event handler to toggle items in a player's inventory.
+---
+--- This event handles adding or removing items based on the parameters received.
+--- It supports multiple inventory systems and includes exploit protection to prevent duplication.
+---
+---@param give boolean Indicates whether to add (`true`) or remove (`false`) the item.
+---@param item string The name of the item to toggle.
+---@param amount number The quantity of the item to toggle.
+---@param newsrc number|nil The source ID of the player. If `nil`, it defaults to the event source.
+---@param info table|nil Additional information or metadata for the item.
+---
+---@usage
+--- ```lua
+--- TriggerServerEvent("script:server:toggleItem", true, "health_potion", 1)
+--- ```
+RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info)
+ if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." ^7'^3"..item.."^7'^2 but it doesn't exist") return end
+ local src = newsrc or source
+ local addremove = (tostring(give) == "true" and "addItem" or "removeItem")
+ debugPrint("^6Bridge^7: ^3toggleItem ^2triggered^7: ^6"..addremove.."^7 - '"..tostring(item).."' x"..(tostring(amount) or "1"))
+ local remamount = (amount and amount or 1)
+ if item == nil then return end
+ if give == 0 or give == false then
+ if hasItem(item, amount and amount or 1, src) then -- Check if the player has the item
+ if isStarted(OXInv) then
+ local success = exports[OXInv]:RemoveItem(src, item, (amount and amount or 1), nil)
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OXInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+ elseif isStarted(QSInv) then
+ local success = exports[QSInv]:RemoveItem(src, item, amount)
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(CoreInv) then
+ if isStarted(QBExport) then
+ Core.Functions.GetPlayer(src).Functions.RemoveItem(item, amount, nil)
+ elseif isStarted(ESXExport) then
+ ESX.GetPlayerFromId(src).removeInventoryItem(item, count)
+ end
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CoreInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(OrigenInv) then
+ local success = exports[OrigenInv]:RemoveItem(src, item, amount)
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OrigenInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(CodeMInv) then
+ local success = exports[CodeMInv]:RemoveItem(src, item, amount)
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CodeMInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(QBInv) then
+ while remamount > 0 do
+ if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then
+ remamount -= 1
+ else
+ print("^1Error removing "..data.item.." Amount left to remove: "..remamount.."^7")
+ break
+ end
+ end
+ if Config.Crafting.showItemBox then
+ TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', src, Items[item], "remove", (amount and amount or 1))
+ end
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QBInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+ elseif isStarted(PSInv) then
+ while remamount > 0 do
+ if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then
+ remamount -= 1
+ else
+ print("^1Error removing "..data.item.." Amount left to remove: "..remamount.."^7")
+ break
+ end
+ end
+ if Config.Crafting.showItemBox then
+ TriggerClientEvent('inventory:client:ItemBox', src, Items[item], "remove", (amount and amount or 1))
+ end
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..PSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+ else
+ print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+ else
+ dupeWarn(src, item, amount) -- Trigger exploit protection
+ end
+ else
+ local amount = amount and amount or 1
+ if isStarted(OXInv) then
+ local success = exports[OXInv]:AddItem(src, item, amount or 1, info)
+ if not Items[item] or not Items[item].label then
+ print("^1Error^7: "..addremove.." ["..OXInv.."] Player("..src..") "..Items[item]?.label.."("..item..") x"..(amount or 1))
+ end
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OXInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(QSInv) then
+ local success = exports[QSInv]:AddItem(src, item, amount)
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(CoreInv) then
+ if isStarted(QBExport) or isStarted(QBXExport) then
+ Core.Functions.GetPlayer(src).Functions.AddItem(item, amount, nil, nil)
+ elseif isStarted(ESXExport) then
+ ESX.GetPlayerFromId(src).addInventoryItem(item, amount)
+ end
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CoreInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(CodeMInv) then
+ local success = exports[CodeMInv]:AddItem(src, item, amount)
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..CodeMInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+ elseif isStarted(OrigenInv) then
+ local success = exports[OrigenInv]:AddItem(src, item, amount)
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..OrigenInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(QBInv) then
+ if Core.Functions.GetPlayer(src).Functions.AddItem(item, amount or 1, nil, info) then
+ TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', src, Items[item], "add", amount and amount or 1)
+ end
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..QBInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+
+ elseif isStarted(PSInv) then
+ if Core.Functions.GetPlayer(src).Functions.AddItem(item, amount or 1, nil, info) then
+ if Config.Crafting.showItemBox then
+ TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "add", amount and amount or 1)
+ end
+ end
+ debugPrint("^6Bridge^7: ^3"..addremove.."^7[^6"..PSInv.."^7] ^2Player^7("..src..") ^6"..Items[item].label.."^7("..item..") x^5"..(amount and amount or "1").."^7")
+ else
+ print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+ end
+end)
+
+--- Protects against item duplication exploits by warning and potentially kicking the player.
+---
+--- This function is called when an attempt is made to remove an item that the player does not possess.
+--- It logs the incident and kicks the player if `debugMode` is not enabled.
+---
+--- @param src number The source ID of the player attempting the exploit.
+--- @param item string The name of the item being exploited.
+---
+--- @usage
+--- ```lua
+--- dupeWarn(playerId, "health_potion")
+--- ```
+function dupeWarn(src, item)
+ local name = getPlayer(src).name
+ print("^5DupeWarn^7: "..name.." (^1"..tostring(src).."^7) ^2Tried to remove item ^7'^3"..item.."^7'^2 but it wasn't there^7")
+ if not debugMode then
+ DropPlayer(src, name.."("..tostring(src)..") Kicked for suspected duplicating items: "..item)
+ end
+ print("^5DupeWarn^7: "..name.."(^1"..tostring(src).."^7) ^2Dropped from server - exploit protection detected an item not being found in players inventory^7")
+end
+
+--- Breaks a tool by reducing its durability or removing it if durability reaches zero.
+---
+--- This function handles the durability mechanics for tools. If a tool's durability drops to zero or below,
+--- it removes the tool from the player's inventory and plays a breaking sound.
+---
+--- @param data table A table containing data about the tool being used.
+--- - **item** (`string`): The name of the tool item.
+--- - **damage** (`number`): The amount of durability damage to apply.
+---
+--- @usage
+--- ```lua
+--- breakTool({ item = "drill", damage = 10 })
+--- ```
+function breakTool(data) -- WIP
+ local durability, slot = getDurability(data.item)
+ if not durability then durability = 100 end
+ durability -= data.damage
+ if durability <= 0 then
+ removeItem(data.item, 1)
+ local breakId = GetSoundId()
+ PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
+ else
+ TriggerServerEvent(getScript()..":server:setMetaData", { item = data.item, slot = slot, metadata = { durability = durability } })
+ end
+end
+
+--- Retrieves the durability and slot of an item in a player's inventory.
+---
+--- This function searches the player's inventory for the specified item and returns its durability and slot number.
+---
+--- @param item string The name of the item to check.
+--- @return number|nil The durability of the item. Returns `nil` if not found.
+--- @return number|nil The slot number of the item. Returns `nil` if not found.
+---
+--- @usage
+--- ```lua
+--- local durability, slot = getDurability("drill")
+--- if durability then
+--- print("Durability:", durability)
+--- end
+--- ```
+function getDurability(item)
+ local lowestSlot = 100
+ local durability = nil
+ if isStarted(QBInv) or isStarted(PSInv) then
+ local itemcheck = Core.Functions.GetPlayerData().items
+ for k, v in pairs(itemcheck) do
+ if v.name == item then
+ if v.slot <= lowestSlot then
+ lowestSlot = v.slot
+ durability = itemcheck[k].info.durability
+ end
+ end
+ end
+ end
+
+ if isStarted(OXInv) then
+ local itemcheck = exports[OXInv]:Search('slots', item)
+ for k, v in pairs(itemcheck) do
+ if v.slot <= lowestSlot then
+ debugPrint(v.slot, itemcheck[k].metadata.durability)
+ lowestSlot = v.slot
+ durability = itemcheck[k].metadata.durability
+ end
+ end
+ end
+
+ if isStarted(QSInv) then
+ local itemcheck = exports[QSInv]:getUserInventory()
+ for k, v in pairs(itemcheck) do
+ if v.name == item and v.slot <= lowestSlot then
+ lowestSlot = v.slot
+ durability = itemcheck[k].metadata.durability
+ end
+ end
+ end
+
+ if isStarted(OrigenInv) then
+ local itemcheck = exports[OrigenInv]:getPlayerInventory()
+ for k, v in pairs(itemcheck) do
+ if v.name == item and v.slot <= lowestSlot then
+ lowestSlot = v.slot
+ durability = itemcheck[k].metadata.durability
+ end
+ end
+ end
+ return durability, lowestSlot
+end
+
+--- Server event handler to set metadata for an item in a player's inventory.
+---
+--- This event updates the metadata (e.g., durability) of an item in the player's inventory.
+---
+---@param data table A table containing metadata information.
+--- - **item** (`string`): The name of the item.
+--- - **slot** (`number`): The slot number of the item in the inventory.
+--- - **metadata** (`table`): The metadata to set for the item.
+---
+---@usage
+--- ```lua
+--- TriggerServerEvent("script:server:setMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } })
+--- ```
+RegisterNetEvent(getScript()..":server:setMetaData", function(data)
+ local src = source
+ if isStarted(QBInv) or isStarted(PSInv) then
+ debugPrint(src, data.item, 1, data.slot)
+ local Player = Core.Functions.GetPlayer(src)
+ Player.PlayerData.items[data.slot].info = data.metadata
+ Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability
+ Player.Functions.SetInventory(Player.PlayerData.items)
+ end
+
+ if isStarted(OXInv) then
+ exports[OXInv]:SetMetadata(source, data.slot, data.metadata)
+ end
+
+ if isStarted(QSInv) then
+ exports[QSInv]:SetItemMetadata(source, data.slot, data.metadata)
+ end
+
+ if isStarted(OrigenInv) then
+ local item = exports[OrigenInv]:GetItemBySlot(source, data.slot)
+ if item then
+ exports[OrigenInv]:SetItemData(source, item.name, "durability", data.metadata.durability)
+ end
+ end
+end)
+
+--- Checks if a player has the specified items in their inventory.
+---
+--- This function verifies whether a player possesses the required quantity of specified items.
+--- It supports multiple inventory systems and provides detailed feedback on item availability.
+---
+---@param items string|table A single item name or a table of item names with their required amounts.
+---@param amount number The quantity required for each item. Defaults to `1` if not specified.
+---@param src number|nil The source ID of the player. If `nil`, it defaults to the caller.
+---@return boolean Returns `true` if the player has all the required items in the specified amounts.
+---@return table|nil Returns a table detailing which items are present or missing if not all items are found.
+---
+---@usage
+--- ```lua
+--- local hasAll, details = hasItem({"health_potion", "mana_potion"}, 2, playerId)
+--- if hasAll then
+--- -- Proceed with action
+--- else
+--- -- Inform the player about missing items
+--- end
+--- ```
+function hasItem(items, amount, src)
+ local amount = amount and amount or 1
+ local grabInv, foundInv = getPlayerInv(src)
+ if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
+
+ if grabInv then
+ local hasTable = {}
+ for item, amt in pairs(items) do
+ if not Items[item] then print("^4ERROR^7: ^2Script can't find ingredient item in Shared Items - ^1"..item.."^7") end
+ local count = 0
+ for _, itemData in pairs(grabInv) do
+ if itemData and (itemData.name == item) then count += (itemData.count or itemData.amount or 1) end
+ end
+ foundInv = foundInv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
+ local foundMessage = "^6Bridge^7: ^3hasItem^7[^6"..foundInv.."^7]: "..tostring(item).." ^3"..count.."^7/^3"..amt
+ if count >= amt then foundMessage = foundMessage.." ^5FOUND^7" else foundMessage = foundMessage .." ^1NOT FOUND^7" end
+ debugPrint(foundMessage)
+ hasTable[item] = { hasItem = count >= amt, count = count }
+ end
+ for k, v in pairs(hasTable) do if not v.hasItem then return false, hasTable end end
+ return true, hasTable
+ end
+end
+
+--- Retrieves a player's inventory from the active inventory system.
+---
+--- This function fetches the player's inventory based on the active inventory system.
+--- It supports multiple systems including OXInv, QSInv, OrigenInv, CoreInv, CodeMInv, QBInv, and PSInv.
+---
+---@param src number|nil The source ID of the player. If `nil`, it fetches the current player's inventory.
+---@return table|nil The inventory items of the player.
+---@return string|nil The name of the inventory system being used.
+---
+---@usage
+--- ```lua
+--- local inventory, system = getPlayerInv(playerId)
+--- if inventory then
+--- -- Process inventory
+--- end
+--- ```
+function getPlayerInv(src)
+ local grabInv = nil
+ local foundInv = ""
+
+ if isStarted(OXInv) then
+ foundInv = OXInv
+ if src then grabInv = exports[OXInv]:GetInventoryItems(src)
+ else grabInv = exports[OXInv]:GetPlayerItems() end
+
+ elseif isStarted(QSInv) then
+ foundInv = QSInv
+ if src then grabInv = exports[QSInv]:GetInventory(src)
+ else grabInv = exports[QSInv]:getUserInventory() end
+
+ elseif isStarted(OrigenInv) then
+ foundInv = OrigenInv
+ if src then grabInv = exports[OrigenInv]:GetInventory(src)
+ else grabInv = exports[OrigenInv]:getPlayerInventory() end
+
+ elseif isStarted(CoreInv) then
+ foundInv = CoreInv
+ if src then
+ if isStarted(QBExport) or isStarted(QBXExport) then
+ grabInv = Core.Functions.GetPlayer(src).PlayerData.items
+ elseif isStarted(ESXExport) then
+ local Player = ESX.GetPlayerFromId(src)
+ grabInv = Player.getInventory(false)
+ end
+ else
+ local p = promise.new()
+ Core.Functions.TriggerCallback('core_inventory:server:getInventory', function(cb) p:resolve(cb) end)
+ local result = Citizen.Await(p)
+ if type(result) == "string" then result = json.decode(result) end
+ grabInv = result
+ end
+
+ elseif isStarted(CodeMInv) then
+ foundInv = CodeMInv
+ if src then grabInv = exports[CodeMInv]:GetInventory(src)
+ else grabInv = exports[CodeMInv]:GetClientPlayerInventory() end
+
+ elseif isStarted(QBInv) then
+ foundInv = QBInv
+ if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items
+ else grabInv = Core.Functions.GetPlayerData().items end
+
+ elseif isStarted(PSInv) then
+ foundInv = PSInv
+ if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items
+ else grabInv = Core.Functions.GetPlayerData().items end
+
+ else
+ print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+ return grabInv, foundInv
+end
+
+--- Generates a random reward from a predefined reward pool.
+---
+--- This function is intended for job scripts where players receive random rewards upon completing certain tasks.
+--- It ensures that the player has the required item before attempting to grant a reward.
+---
+---@param itemName string The name of the item to check for eligibility to receive a reward.
+---
+---@usage
+--- ```lua
+--- getRandomReward("gold_ring")
+--- ```
+function getRandomReward(itemName) -- Intended for job scripts
+ if Config.Rewards.RewardPool then
+ local reward = false
+ if type(Config.Rewards.RewardItem) == "string" then Config.Rewards.RewardItem = { Config.Rewards.RewardItem } end
+ for k, v in pairs(Config.Rewards.RewardItem) do
+ if v == itemName then reward = true break end
+ end
+ if reward then
+ removeItem(itemName, 1)
+ local totalRarity = 0
+ for i=1, #Config.Rewards.RewardPool do
+ totalRarity += Config.Rewards.RewardPool[i].rarity
+ end
+ debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Total Rarity ^7'^6"..totalRarity.."^7'")
+
+ local randomNum = math.random(1, totalRarity)
+ debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Random Number ^7'^6"..randomNum.."^7'")
+ local currentRarity = 0
+ for i=1, #Config.Rewards.RewardPool do
+ currentRarity += Config.Rewards.RewardPool[i].rarity
+ if randomNum <= currentRarity then
+ debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Selected toy ^7'^6"..Config.Rewards.RewardPool[i].item.."^7'")
+ addItem(Config.Rewards.RewardPool[i].item, 1)
+ return
+ end
+ end
+ end
+ end
+end
+
+--- Checks if a player can carry specific items in their inventory.
+---
+--- This function determines whether a player has enough capacity to carry the specified items.
+--- It considers the weight of each item and the player's current inventory weight.
+---
+---@param itemTable table A table where keys are item names and values are the quantities to check.
+---@param src number The source ID of the player.
+---@return table A table where keys are item names and values are booleans indicating if the player can carry the specified quantity.
+---
+---@usage
+--- ```lua
+--- local canCarry = canCarry({ ["health_potion"] = 2, ["mana_potion"] = 3 }, playerId)
+--- if canCarry["health_potion"] and canCarry["mana_potion"] then
+--- -- Proceed with adding items
+--- else
+--- -- Inform the player they can't carry all items
+--- end
+--- ```
+function canCarry(itemTable, src)
+ local resultTable = {}
+ if src then
+ if isStarted(OXInv) then
+ for k, v in pairs(itemTable) do
+ resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v)
+ end
+
+ elseif isStarted(QSInv) then
+ for k, v in pairs(itemTable) do
+ resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v)
+ end
+
+ elseif isStarted(CoreInv) then
+ --??
+
+ elseif isStarted(CodeMInv) then
+ for k, v in pairs(itemTable) do
+ local weight = Items[k].weight
+ resultTable[k] = exports[CodeMInv]:CanCarryItem(src, weight, v)
+ end
+
+ elseif isStarted(OrigenInv) then
+ for k, v in pairs(itemTable) do
+ resultTable[k] = exports[OrigenInv]:canCarryItem(src, k, v)
+ end
+
+ elseif isStarted(QBInv) or isStarted(PSInv) then
+ local Player = Core.Functions.GetPlayer(src)
+ local items = Player.PlayerData.items
+ local weight, totalWeight = 0, 0
+ if not items then return false end
+ for _, item in pairs(items) do weight += item.weight * item.amount end
+
+ totalWeight = tonumber(weight)
+
+ for k, v in pairs(itemTable) do
+ local itemInfo = Items[k]
+ if not itemInfo and not Player.Offline then
+ triggerNotify(nil, 'Item does not exist', 'error', src)
+ resultTable[k] = true
+ else
+ resultTable[k] = (totalWeight + (Items[k]['weight'] * v)) <= InventoryWeight
+ end
+ end
+ end
+ end
+ return resultTable
end
\ No newline at end of file
diff --git a/shared/jobfunctions.lua b/shared/jobfunctions.lua
index 16eadf5..2ceaf43 100644
--- a/shared/jobfunctions.lua
+++ b/shared/jobfunctions.lua
@@ -1,196 +1,196 @@
--- Global variable to track duty status
-onDuty = false
-
---- Scans the 'Jobs' and 'Gangs' tables to identify roles classified as Bosses.
----
---- This function iterates through the specified role's grades within the `Jobs` or `Gangs` tables.
---- It identifies which grades are marked as bosses (`isboss`) or have bank authorization (`bankAuth`).
---- The function returns a table where each role maps to the lowest grade number that qualifies as a boss.
----
----@param role string The name of the job or gang role to check for boss grades.
----
----@return table table A table containing roles mapped to their respective boss grade numbers.
----
----@usage
---- ```lua
---- local bosses = makeBossRoles("police")
---- if bosses["police"] then
---- print("Police role has a boss grade.")
---- end
---- ```
-function makeBossRoles(role)
- local boss = {}
- local data = (Jobs and Jobs[role]) or (Gangs and Gangs[role])
- if data then
- for grade, info in pairs(data.grades) do
- if info.isboss or info.bankAuth then
- boss[role] = boss[role] and math.min(boss[role], tonumber(grade)) or tonumber(grade)
- end
- end
- end
- return boss
-end
-
---- Checks if the player has a specific job and is on duty.
----
---- This function verifies whether the player possesses the specified job and, if applicable,
---- whether they are currently on duty. It provides a notification if the player fails these checks.
----
----@param job string The name of the job or gang to check.
----
----@return boolean Returns `true` if the player has the job (and is on duty if required), otherwise `false`.
----
----@usage
---- ```lua
---- if jobCheck("mechanic") then
---- -- Allow access to mechanic-related features
---- else
---- -- Deny access or notify the player
---- end
---- ```
-function jobCheck(job)
- canDo = true
- if Jobs[job] then
- if not hasJob(job) or not onDuty then
- triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"])
- canDo = false
- end
- end
- if Gangs[job] then
- if not hasJob(job) then
- canDo = false
- end
- end
- return canDo
-end
-
---- Toggles the player's duty status.
----
---- This function switches the player's duty state between on-duty and off-duty.
---- It integrates with QBcore's duty system if available; otherwise, it manually toggles the `onDuty` variable
---- and sends a notification to the player about their new duty status.
----
----@usage
---- ```lua
---- toggleDuty()
---- -- Player will receive a notification indicating their new duty status
---- ```
-function toggleDuty()
- if isStarted(QBExport) or isStarted(QBXExport) then
- TriggerServerEvent("QBCore:ToggleDuty")
- else
- onDuty = not onDuty
- if onDuty then
- triggerNotify(nil, "Now on duty", "success")
- else
- triggerNotify(nil, "Now off duty", "success")
- end
- end
-end
-
---- Initiates the hand-washing action for the player.
----
---- This function triggers an animation and a progress bar to simulate the player washing their hands.
---- Upon completion, it sends a success notification. If the action is canceled, it notifies the player of the cancellation.
----
----@param data table A table containing the coordinates where the hand-washing action takes place.
---- - **coords** (`vector3`): The position where the hand-washing animation and camera are focused.
----
----@return void
----
----@usage
---- ```lua
---- washHands({ coords = vector3(200.0, 300.0, 40.0) })
---- -- Player will perform the hand-washing animation at the specified location
---- ```
-function washHands(data) local ped = PlayerPedId()
- lookEnt(data.coords)
- local cam = createTempCam(ped, data.coords)
- if progressBar({
- label = Loc[Config.Lan].progressbar["progress_washing"],
- time = 5000,
- cancel = true,
- dict = "mp_arresting",
- anim = "a_uncuff",
- flag = 32,
- icon = "fas fa-hand-holding-droplet",
- cam = cam
- }) then
- triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
- else
- triggerNotify(nil, Loc[Config.Lan].error["cancel"], 'error')
- end
- ClearPedTasks(ped)
-end
-
---- Handles the player's interaction with a toilet or urinal.
----
---- This function manages the animations and progress bars associated with using a toilet or urinal.
---- Depending on whether the interaction is with a urinal (`data.urinal`), it plays the appropriate animation
---- and triggers server events upon successful completion. If the action is canceled, it notifies the player.
----
----@param data table A table containing data about the toilet interaction.
---- - **urinal** (`boolean`): Indicates whether the interaction is with a urinal (`true`) or a toilet (`false`).
---- - **sitcoords** (`vector4`): The coordinates and heading for the seating animation when using a toilet.
----
----@usage
---- ```lua
---- useToilet({ urinal = true })
---- -- Player uses a urinal with corresponding animations and notifications
----
---- useToilet({ urinal = false, sitcoords = vector4(215.76, -810.12, 29.73, 90.0) })
---- -- Player sits down to use a toilet with corresponding animations and notifications
---- ```
-function useToilet(data)
- if data.urinal then
- if progressBar({
- label = "Using Urinal",
- time = 5000,
- cancel = true,
- dict = "misscarsteal2peeing",
- anim = "peeing_loop",
- flag = 32
- }) then
- TriggerServerEvent(getScript().."server:Urinal")
- else
- lockInv(false)
- triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error')
- end
- else
- TaskStartScenarioAtPosition(PlayerPedId(), "PROP_HUMAN_SEAT_CHAIR_MP_PLAYER", data.sitcoords.x, data.sitcoords.y, data.sitcoords.z, data.sitcoords[4], 0, 1, true)
- if progressBar({
- label = "Using Toilet",
- time = 10000,
- cancel = true
- }) then
- TriggerServerEvent(getScript().."server:Urinal")
- ClearPedTasks(PlayerPedId())
- else
- lockInv(false)
- triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error')
- end
- end
-end
-
---- Teleports the player to specified coordinates with a fade effect.
----
---- This function fades the screen out, moves the player to the target coordinates (`data.telecoords`),
---- sets the player's heading, and then fades the screen back in. It's commonly used for door interactions
---- or teleportation points within the game.
----
----@param data table A table containing teleportation data.
---- - **telecoords** (`vector4`): The target coordinates and heading for the teleportation.
----
----@usage
---- ```lua
---- useDoor({ telecoords = vector4(215.76, -810.12, 29.73, 90.0) })
---- -- Player is teleported to the specified coordinates with a fade effect
---- ```
-function useDoor(data)
- DoScreenFadeOut(500)
- while not IsScreenFadedOut() do Wait(10) end
- SetEntityCoords(PlayerPedId(), data.telecoords.xyz, 0, 0, 0, false)
- SetEntityHeading(PlayerPedId(), data.telecoords.w)
- DoScreenFadeIn(1000)
- Wait(100)
-end
+-- Global variable to track duty status
+onDuty = false
+
+--- Scans the 'Jobs' and 'Gangs' tables to identify roles classified as Bosses.
+---
+--- This function iterates through the specified role's grades within the `Jobs` or `Gangs` tables.
+--- It identifies which grades are marked as bosses (`isboss`) or have bank authorization (`bankAuth`).
+--- The function returns a table where each role maps to the lowest grade number that qualifies as a boss.
+---
+---@param role string The name of the job or gang role to check for boss grades.
+---
+---@return table table A table containing roles mapped to their respective boss grade numbers.
+---
+---@usage
+--- ```lua
+--- local bosses = makeBossRoles("police")
+--- if bosses["police"] then
+--- print("Police role has a boss grade.")
+--- end
+--- ```
+function makeBossRoles(role)
+ local boss = {}
+ local data = (Jobs and Jobs[role]) or (Gangs and Gangs[role])
+ if data then
+ for grade, info in pairs(data.grades) do
+ if info.isboss or info.bankAuth then
+ boss[role] = boss[role] and math.min(boss[role], tonumber(grade)) or tonumber(grade)
+ end
+ end
+ end
+ return boss
+end
+
+--- Checks if the player has a specific job and is on duty.
+---
+--- This function verifies whether the player possesses the specified job and, if applicable,
+--- whether they are currently on duty. It provides a notification if the player fails these checks.
+---
+---@param job string The name of the job or gang to check.
+---
+---@return boolean Returns `true` if the player has the job (and is on duty if required), otherwise `false`.
+---
+---@usage
+--- ```lua
+--- if jobCheck("mechanic") then
+--- -- Allow access to mechanic-related features
+--- else
+--- -- Deny access or notify the player
+--- end
+--- ```
+function jobCheck(job)
+ canDo = true
+ if Jobs[job] then
+ if not hasJob(job) or not onDuty then
+ triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"])
+ canDo = false
+ end
+ end
+ if Gangs[job] then
+ if not hasJob(job) then
+ canDo = false
+ end
+ end
+ return canDo
+end
+
+--- Toggles the player's duty status.
+---
+--- This function switches the player's duty state between on-duty and off-duty.
+--- It integrates with QBcore's duty system if available; otherwise, it manually toggles the `onDuty` variable
+--- and sends a notification to the player about their new duty status.
+---
+---@usage
+--- ```lua
+--- toggleDuty()
+--- -- Player will receive a notification indicating their new duty status
+--- ```
+function toggleDuty()
+ if isStarted(QBExport) or isStarted(QBXExport) then
+ TriggerServerEvent("QBCore:ToggleDuty")
+ else
+ onDuty = not onDuty
+ if onDuty then
+ triggerNotify(nil, "Now on duty", "success")
+ else
+ triggerNotify(nil, "Now off duty", "success")
+ end
+ end
+end
+
+--- Initiates the hand-washing action for the player.
+---
+--- This function triggers an animation and a progress bar to simulate the player washing their hands.
+--- Upon completion, it sends a success notification. If the action is canceled, it notifies the player of the cancellation.
+---
+---@param data table A table containing the coordinates where the hand-washing action takes place.
+--- - **coords** (`vector3`): The position where the hand-washing animation and camera are focused.
+---
+---@return void
+---
+---@usage
+--- ```lua
+--- washHands({ coords = vector3(200.0, 300.0, 40.0) })
+--- -- Player will perform the hand-washing animation at the specified location
+--- ```
+function washHands(data) local ped = PlayerPedId()
+ lookEnt(data.coords)
+ local cam = createTempCam(ped, data.coords)
+ if progressBar({
+ label = Loc[Config.Lan].progressbar["progress_washing"],
+ time = 5000,
+ cancel = true,
+ dict = "mp_arresting",
+ anim = "a_uncuff",
+ flag = 32,
+ icon = "fas fa-hand-holding-droplet",
+ cam = cam
+ }) then
+ triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
+ else
+ triggerNotify(nil, Loc[Config.Lan].error["cancel"], 'error')
+ end
+ ClearPedTasks(ped)
+end
+
+--- Handles the player's interaction with a toilet or urinal.
+---
+--- This function manages the animations and progress bars associated with using a toilet or urinal.
+--- Depending on whether the interaction is with a urinal (`data.urinal`), it plays the appropriate animation
+--- and triggers server events upon successful completion. If the action is canceled, it notifies the player.
+---
+---@param data table A table containing data about the toilet interaction.
+--- - **urinal** (`boolean`): Indicates whether the interaction is with a urinal (`true`) or a toilet (`false`).
+--- - **sitcoords** (`vector4`): The coordinates and heading for the seating animation when using a toilet.
+---
+---@usage
+--- ```lua
+--- useToilet({ urinal = true })
+--- -- Player uses a urinal with corresponding animations and notifications
+---
+--- useToilet({ urinal = false, sitcoords = vector4(215.76, -810.12, 29.73, 90.0) })
+--- -- Player sits down to use a toilet with corresponding animations and notifications
+--- ```
+function useToilet(data)
+ if data.urinal then
+ if progressBar({
+ label = "Using Urinal",
+ time = 5000,
+ cancel = true,
+ dict = "misscarsteal2peeing",
+ anim = "peeing_loop",
+ flag = 32
+ }) then
+ TriggerServerEvent(getScript().."server:Urinal")
+ else
+ lockInv(false)
+ triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error')
+ end
+ else
+ TaskStartScenarioAtPosition(PlayerPedId(), "PROP_HUMAN_SEAT_CHAIR_MP_PLAYER", data.sitcoords.x, data.sitcoords.y, data.sitcoords.z, data.sitcoords[4], 0, 1, true)
+ if progressBar({
+ label = "Using Toilet",
+ time = 10000,
+ cancel = true
+ }) then
+ TriggerServerEvent(getScript().."server:Urinal")
+ ClearPedTasks(PlayerPedId())
+ else
+ lockInv(false)
+ triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error')
+ end
+ end
+end
+
+--- Teleports the player to specified coordinates with a fade effect.
+---
+--- This function fades the screen out, moves the player to the target coordinates (`data.telecoords`),
+--- sets the player's heading, and then fades the screen back in. It's commonly used for door interactions
+--- or teleportation points within the game.
+---
+---@param data table A table containing teleportation data.
+--- - **telecoords** (`vector4`): The target coordinates and heading for the teleportation.
+---
+---@usage
+--- ```lua
+--- useDoor({ telecoords = vector4(215.76, -810.12, 29.73, 90.0) })
+--- -- Player is teleported to the specified coordinates with a fade effect
+--- ```
+function useDoor(data)
+ DoScreenFadeOut(500)
+ while not IsScreenFadedOut() do Wait(10) end
+ SetEntityCoords(PlayerPedId(), data.telecoords.xyz, 0, 0, 0, false)
+ SetEntityHeading(PlayerPedId(), data.telecoords.w)
+ DoScreenFadeIn(1000)
+ Wait(100)
+end
diff --git a/shared/make/cameras.lua b/shared/make/cameras.lua
index ba52919..b08cd04 100644
--- a/shared/make/cameras.lua
+++ b/shared/make/cameras.lua
@@ -1,75 +1,75 @@
---- Creates a temporary camera at a specified position, pointing towards given coordinates.
---
--- This function creates a camera at a position relative to an entity or at a specified position and orients it to look at the target coordinates.
--- The camera is only created if `Config.Crafting.craftCam` is enabled in the configuration.
---
----@param ent entityId|coords The base position for the camera. Can be an entity handle or a `vector3` position.
--- If `ent` is an entity, the camera position is calculated as an offset from the entity's position using `GetOffsetFromEntityInWorldCoords`.
--- If `ent` is a `vector3`, it is used directly as the camera's position.
---
----@param coords vector3 The target `vector3` coordinates that the camera will point at.
---
----@return cam camID The handle of the created camera, or `nil` if the camera was not created (e.g., if `Config.Crafting.craftCam` is `false`).
---
----@usage
--- ```lua
--- local cam = createTempCam(entity, targetCoords)
--- ```
-function createTempCam(ent, coords)
- local cam = nil
- if Config.Crafting.craftCam then
- if debugMode then
- triggerNotify(nil, "ModCam Created", "success")
- end
- local camCoords = nil
- if type(ent) ~= "vector3" then
- camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
- else
- camCoords = ent
- end
- -- Create the camera with specified parameters
- cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
- -- Point the camera at the target coordinates
- PointCamAtCoord(cam, coords)
- end
- return cam
-end
-
---- Activates and starts rendering the temporary camera.
---
--- This function sets the specified camera as active and begins rendering it with a smooth transition.
--- The camera is only activated if `Config.Crafting.craftCam` is enabled in the configuration.
---
----@param cam camID The handle of the camera to activate and render.
---
----@usage
--- ```lua
--- startTempCam(cam)
--- ```
-function startTempCam(cam)
- if Config.Crafting.craftCam then
- SetCamActive(cam, true)
- RenderScriptCams(true, true, 1000, true, true)
- end
-end
-
---- Deactivates the temporary camera and stops rendering.
---
--- This function waits for one second, then stops rendering script cameras and destroys all cameras.
--- The delay allows for any transitions or animations to complete.
---
--- The camera is only deactivated if `Config.Crafting.craftCam` is enabled in the configuration.
---
----@usage
--- ```lua
--- stopTempCam()
--- ```
-function stopTempCam()
- if Config.Crafting.craftCam then
- CreateThread(function()
- Wait(1000)
- RenderScriptCams(false, true, 500, true, true)
- DestroyAllCams()
- end)
- end
+--- Creates a temporary camera at a specified position, pointing towards given coordinates.
+--
+-- This function creates a camera at a position relative to an entity or at a specified position and orients it to look at the target coordinates.
+-- The camera is only created if `Config.Crafting.craftCam` is enabled in the configuration.
+--
+---@param ent entityId|coords The base position for the camera. Can be an entity handle or a `vector3` position.
+-- If `ent` is an entity, the camera position is calculated as an offset from the entity's position using `GetOffsetFromEntityInWorldCoords`.
+-- If `ent` is a `vector3`, it is used directly as the camera's position.
+--
+---@param coords vector3 The target `vector3` coordinates that the camera will point at.
+--
+---@return cam camID The handle of the created camera, or `nil` if the camera was not created (e.g., if `Config.Crafting.craftCam` is `false`).
+--
+---@usage
+-- ```lua
+-- local cam = createTempCam(entity, targetCoords)
+-- ```
+function createTempCam(ent, coords)
+ local cam = nil
+ if Config.Crafting.craftCam then
+ if debugMode then
+ triggerNotify(nil, "ModCam Created", "success")
+ end
+ local camCoords = nil
+ if type(ent) ~= "vector3" then
+ camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
+ else
+ camCoords = ent
+ end
+ -- Create the camera with specified parameters
+ cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
+ -- Point the camera at the target coordinates
+ PointCamAtCoord(cam, coords)
+ end
+ return cam
+end
+
+--- Activates and starts rendering the temporary camera.
+--
+-- This function sets the specified camera as active and begins rendering it with a smooth transition.
+-- The camera is only activated if `Config.Crafting.craftCam` is enabled in the configuration.
+--
+---@param cam camID The handle of the camera to activate and render.
+--
+---@usage
+-- ```lua
+-- startTempCam(cam)
+-- ```
+function startTempCam(cam)
+ if Config.Crafting.craftCam then
+ SetCamActive(cam, true)
+ RenderScriptCams(true, true, 1000, true, true)
+ end
+end
+
+--- Deactivates the temporary camera and stops rendering.
+--
+-- This function waits for one second, then stops rendering script cameras and destroys all cameras.
+-- The delay allows for any transitions or animations to complete.
+--
+-- The camera is only deactivated if `Config.Crafting.craftCam` is enabled in the configuration.
+--
+---@usage
+-- ```lua
+-- stopTempCam()
+-- ```
+function stopTempCam()
+ if Config.Crafting.craftCam then
+ CreateThread(function()
+ Wait(1000)
+ RenderScriptCams(false, true, 500, true, true)
+ DestroyAllCams()
+ end)
+ end
end
\ No newline at end of file
diff --git a/shared/make/loaders.lua b/shared/make/loaders.lua
index f96c1ad..1b97373 100644
--- a/shared/make/loaders.lua
+++ b/shared/make/loaders.lua
@@ -1,239 +1,239 @@
-local time = 500
-
---- Loads a specified model into memory.
----
---- This function checks if the model is valid and not already loaded.
---- If not loaded, it requests the model and waits until it is loaded or times out.
----
----@param model string|number The name or hash of the model to load.
----
----@usage
---- ```lua
---- loadModel('prop_chair_01a')
---- ```
-function loadModel(model)
- if not IsModelValid(model) then print("^6Bridge^7: ^1ERROR^7: ^2Model^7 - '^6"..model.."^7' ^2does not exist in server") return
- else
- if not HasModelLoaded(model) then
- debugPrint("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'")
- while not HasModelLoaded(model) and time > 0 do time -= 1 RequestModel(model) Wait(0) end
- if not HasModelLoaded(model) then print("^6Bridge^7: ^3LoadModel^7: ^2Timed out loading model ^7'^6"..model.."^7'") end
- end
- time = 500
- end
-end
-
---- Unloads a model from memory.
----
---- This function marks a model as no longer needed, allowing the game to free up memory.
----
----@param model string|number The name or hash of the model to unload.
----
----@usage
---- ```lua
---- unloadModel('prop_chair_01a')
---- ```
-function unloadModel(model)
- debugPrint("^6Bridge^7: ^2Removing Model from memory cache^7: '^6"..model.."^7'")
- SetModelAsNoLongerNeeded(model)
-end
-
---- Loads an animation dictionary into memory.
----
---- This function checks if the animation dictionary exists and requests it.
---- It waits until the animation dictionary is loaded before proceeding.
----
----@param animDict string The name of the animation dictionary to load.
----
----@usage
---- ```lua
---- loadAnimDict('amb@world_human_hang_out_street@male_c@base')
---- ```
-function loadAnimDict(animDict)
- if not DoesAnimDictExist(animDict) then
- print("^6Bridge^7: ^1ERROR^7: ^2Anim Dictionary^7 - '^6"..animDict.."^7' ^2does not exist in server") return
- else
- debugPrint("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..animDict.."^7'")
- while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end
- end
-end
-
---- Unloads an animation dictionary from memory.
----
---- This function removes the animation dictionary from the game's memory cache.
----
----@param animDict string The name of the animation dictionary to unload.
----
----@usage
----@
---- ```lua
---- unloadAnimDict('amb@world_human_hang_out_street@male_c@base')
---- ```
-function unloadAnimDict(animDict)
- debugPrint("^6Bridge^7: ^2Removing Anim Dictionary from memory cache^7: '^6"..animDict.."^7'")
- RemoveAnimDict(animDict)
-end
-
---- Loads a particle effects (ptfx) dictionary into memory.
----
---- This function requests the named particle effects asset and waits until it's loaded.
----
----@param ptFxName string The name of the particle effects dictionary to load.
----
----@usage
---- ```lua
---- loadPtfxDict('core')
---- ```
-function loadPtfxDict(ptFxName)
- if not HasNamedPtfxAssetLoaded(ptFxName) then
- debugPrint("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..ptFxName.."^7'")
- while not HasNamedPtfxAssetLoaded(ptFxName) do RequestNamedPtfxAsset(ptFxName) Wait(5) end
- end
-end
-
---- Unloads a particle effects (ptfx) dictionary from memory.
----
---- This function removes the named particle effects asset from the game's memory cache.
----
----@param dict string The name of the particle effects dictionary to unload.
----
----@usage
---- ```lua
---- unloadPtfxDict('core')
---- ```
-function unloadPtfxDict(dict)
- debugPrint("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'")
- RemoveNamedPtfxAsset(dict)
-end
-
---- Loads a texture dictionary into memory.
----
---- This function requests the streamed texture dictionary and waits until it's loaded.
----
----@param dict string The name of the texture dictionary to load.
----
----@usage
---- ```lua
---- loadTextureDict('commonmenu')
---- ```
-function loadTextureDict(dict)
- if not HasStreamedTextureDictLoaded(dict) then
- debugPrint("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'")
- while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
- end
-end
-
---- Loads a script audio bank into memory.
----
---- This function requests a script audio bank and waits until it's loaded or times out.
----
----@param bank string The name of the script audio bank to load.
----
----@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
----
----@usage
---- ```lua
---- local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS')
---- ```
-function loadScriptBank(bank)
- local timeout = 2000
- debugPrint("^6Debug^7: ^2Loading ^3Script ^2AudioBank^7...")
- while not RequestScriptAudioBank(bank, 0) do Wait(10) timeout -= 10 if timeout <= 0 then break end end
-
- local success = RequestScriptAudioBank(bank, 0)
- debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
- return success
-end
-
---- Loads an ambient audio bank into memory.
----
---- This function requests an ambient audio bank and waits until it's loaded or times out.
----
----@param bank string The name of the ambient audio bank to load.
----
----@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
----
----@usage
---- ```lua
---- local success = loadAmbientBank('AMB_REVERB_GENERIC')
---- ```
-function loadAmbientBank(bank)
- local timeout = 2000
- debugPrint("^6Debug^7: ^2Loading ^3Ambient ^2AudioBank^7...")
- while not RequestAmbientAudioBank(bank, 0) do
- Wait(10)
- timeout -= 10
- if timeout <= 0 then break end
- end
- local success = RequestAmbientAudioBank(bank, 0)
- debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
- return success
-end
-
---- Plays an animation on a specified ped.
----
---- This function loads the animation dictionary and instructs the ped to play the animation.
----
----@param animDict string The name of the animation dictionary.
----@param animName string The name of the animation within the dictionary.
----@param duration number (optional) The duration to play the animation in milliseconds. Default is `30000`.
----@param flag number (optional) The animation flag controlling how the animation is played. Default is `50`.
----@param ped number (optional) The ped on which to play the animation. Defaults to the player's ped if not specified.
----@param speed number (optional) The speed multiplier for the animation. Default is `8.0`.
----
----@usage
---- ```lua
---- playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0)
---- ```
-function playAnim(animDict, animName, duration, flag, ped, speed)
- loadAnimDict(animDict)
- debugPrint("Attempting to make player play anim", animDict, animName)
- TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
-end
-
---- Stops a specified animation on a ped.
----
---- This function stops the animation and unloads the animation dictionary from memory.
----
----@param animDict string The name of the animation dictionary.
----@param animName string The name of the animation within the dictionary.
----@param ped number (optional) The ped on which to stop the animation. Defaults to the player's ped if not specified.
----
----@usage
---- ```lua
---- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
---- ```
-function stopAnim(animDict, animName, ped)
- debugPrint("Stopping anim for "..(ped or PlayerPedId()))
- StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
- StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
- unloadAnimDict(animDict)
-end
-
---- Plays a game sound from a specified coordinate or entity.
----
---- This function attempts to play a sound from either a coordinate or an entity, using the specified audio bank and sound name.
----
----@param bank string The name of the audio bank containing the sound.
----@param sound string The name of the sound to play.
----@param coords vector3|number A `vector3` coordinate or an entity handle from which to play the sound.
----@param synced boolean A boolean indicating whether the sound is synced across clients.
----@param range number (optional) The maximum range at which the sound can be heard. Default is `10.0`.
----
----@usage
---- ```lua
---- playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
---- ```
-function playGameSound(bank, sound, coords, synced, range)
- debugPrint("^6Debug^7: ^2Attempting to play: ^3"..sound.." ^7(^4"..bank.."^7')")
- local range = range or 10.0
- local soundId = GetSoundId()
- while not soundId do Wait(10) end
- if type(coords) == "vector3" or type(coords) == "vector4" then
- debugPrint("^6Debug^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz))
- PlaySoundFromCoord(soundId, sound, coords.x, coords.y, coords.z, bank, synced, range, 0)
- else
- debugPrint("^6Debug^7: ^2Playing sound from Entity^7: ^4"..coords.."^7")
- PlaySoundFromEntity(soundId, sound, coords, bank, synced, 0)
- end
+local time = 500
+
+--- Loads a specified model into memory.
+---
+--- This function checks if the model is valid and not already loaded.
+--- If not loaded, it requests the model and waits until it is loaded or times out.
+---
+---@param model string|number The name or hash of the model to load.
+---
+---@usage
+--- ```lua
+--- loadModel('prop_chair_01a')
+--- ```
+function loadModel(model)
+ if not IsModelValid(model) then print("^6Bridge^7: ^1ERROR^7: ^2Model^7 - '^6"..model.."^7' ^2does not exist in server") return
+ else
+ if not HasModelLoaded(model) then
+ debugPrint("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'")
+ while not HasModelLoaded(model) and time > 0 do time -= 1 RequestModel(model) Wait(0) end
+ if not HasModelLoaded(model) then print("^6Bridge^7: ^3LoadModel^7: ^2Timed out loading model ^7'^6"..model.."^7'") end
+ end
+ time = 500
+ end
+end
+
+--- Unloads a model from memory.
+---
+--- This function marks a model as no longer needed, allowing the game to free up memory.
+---
+---@param model string|number The name or hash of the model to unload.
+---
+---@usage
+--- ```lua
+--- unloadModel('prop_chair_01a')
+--- ```
+function unloadModel(model)
+ debugPrint("^6Bridge^7: ^2Removing Model from memory cache^7: '^6"..model.."^7'")
+ SetModelAsNoLongerNeeded(model)
+end
+
+--- Loads an animation dictionary into memory.
+---
+--- This function checks if the animation dictionary exists and requests it.
+--- It waits until the animation dictionary is loaded before proceeding.
+---
+---@param animDict string The name of the animation dictionary to load.
+---
+---@usage
+--- ```lua
+--- loadAnimDict('amb@world_human_hang_out_street@male_c@base')
+--- ```
+function loadAnimDict(animDict)
+ if not DoesAnimDictExist(animDict) then
+ print("^6Bridge^7: ^1ERROR^7: ^2Anim Dictionary^7 - '^6"..animDict.."^7' ^2does not exist in server") return
+ else
+ debugPrint("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..animDict.."^7'")
+ while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end
+ end
+end
+
+--- Unloads an animation dictionary from memory.
+---
+--- This function removes the animation dictionary from the game's memory cache.
+---
+---@param animDict string The name of the animation dictionary to unload.
+---
+---@usage
+---@
+--- ```lua
+--- unloadAnimDict('amb@world_human_hang_out_street@male_c@base')
+--- ```
+function unloadAnimDict(animDict)
+ debugPrint("^6Bridge^7: ^2Removing Anim Dictionary from memory cache^7: '^6"..animDict.."^7'")
+ RemoveAnimDict(animDict)
+end
+
+--- Loads a particle effects (ptfx) dictionary into memory.
+---
+--- This function requests the named particle effects asset and waits until it's loaded.
+---
+---@param ptFxName string The name of the particle effects dictionary to load.
+---
+---@usage
+--- ```lua
+--- loadPtfxDict('core')
+--- ```
+function loadPtfxDict(ptFxName)
+ if not HasNamedPtfxAssetLoaded(ptFxName) then
+ debugPrint("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..ptFxName.."^7'")
+ while not HasNamedPtfxAssetLoaded(ptFxName) do RequestNamedPtfxAsset(ptFxName) Wait(5) end
+ end
+end
+
+--- Unloads a particle effects (ptfx) dictionary from memory.
+---
+--- This function removes the named particle effects asset from the game's memory cache.
+---
+---@param dict string The name of the particle effects dictionary to unload.
+---
+---@usage
+--- ```lua
+--- unloadPtfxDict('core')
+--- ```
+function unloadPtfxDict(dict)
+ debugPrint("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'")
+ RemoveNamedPtfxAsset(dict)
+end
+
+--- Loads a texture dictionary into memory.
+---
+--- This function requests the streamed texture dictionary and waits until it's loaded.
+---
+---@param dict string The name of the texture dictionary to load.
+---
+---@usage
+--- ```lua
+--- loadTextureDict('commonmenu')
+--- ```
+function loadTextureDict(dict)
+ if not HasStreamedTextureDictLoaded(dict) then
+ debugPrint("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'")
+ while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
+ end
+end
+
+--- Loads a script audio bank into memory.
+---
+--- This function requests a script audio bank and waits until it's loaded or times out.
+---
+---@param bank string The name of the script audio bank to load.
+---
+---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
+---
+---@usage
+--- ```lua
+--- local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS')
+--- ```
+function loadScriptBank(bank)
+ local timeout = 2000
+ debugPrint("^6Debug^7: ^2Loading ^3Script ^2AudioBank^7...")
+ while not RequestScriptAudioBank(bank, 0) do Wait(10) timeout -= 10 if timeout <= 0 then break end end
+
+ local success = RequestScriptAudioBank(bank, 0)
+ debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
+ return success
+end
+
+--- Loads an ambient audio bank into memory.
+---
+--- This function requests an ambient audio bank and waits until it's loaded or times out.
+---
+---@param bank string The name of the ambient audio bank to load.
+---
+---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
+---
+---@usage
+--- ```lua
+--- local success = loadAmbientBank('AMB_REVERB_GENERIC')
+--- ```
+function loadAmbientBank(bank)
+ local timeout = 2000
+ debugPrint("^6Debug^7: ^2Loading ^3Ambient ^2AudioBank^7...")
+ while not RequestAmbientAudioBank(bank, 0) do
+ Wait(10)
+ timeout -= 10
+ if timeout <= 0 then break end
+ end
+ local success = RequestAmbientAudioBank(bank, 0)
+ debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
+ return success
+end
+
+--- Plays an animation on a specified ped.
+---
+--- This function loads the animation dictionary and instructs the ped to play the animation.
+---
+---@param animDict string The name of the animation dictionary.
+---@param animName string The name of the animation within the dictionary.
+---@param duration number (optional) The duration to play the animation in milliseconds. Default is `30000`.
+---@param flag number (optional) The animation flag controlling how the animation is played. Default is `50`.
+---@param ped number (optional) The ped on which to play the animation. Defaults to the player's ped if not specified.
+---@param speed number (optional) The speed multiplier for the animation. Default is `8.0`.
+---
+---@usage
+--- ```lua
+--- playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0)
+--- ```
+function playAnim(animDict, animName, duration, flag, ped, speed)
+ loadAnimDict(animDict)
+ debugPrint("Attempting to make player play anim", animDict, animName)
+ TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
+end
+
+--- Stops a specified animation on a ped.
+---
+--- This function stops the animation and unloads the animation dictionary from memory.
+---
+---@param animDict string The name of the animation dictionary.
+---@param animName string The name of the animation within the dictionary.
+---@param ped number (optional) The ped on which to stop the animation. Defaults to the player's ped if not specified.
+---
+---@usage
+--- ```lua
+--- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
+--- ```
+function stopAnim(animDict, animName, ped)
+ debugPrint("Stopping anim for "..(ped or PlayerPedId()))
+ StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
+ StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
+ unloadAnimDict(animDict)
+end
+
+--- Plays a game sound from a specified coordinate or entity.
+---
+--- This function attempts to play a sound from either a coordinate or an entity, using the specified audio bank and sound name.
+---
+---@param bank string The name of the audio bank containing the sound.
+---@param sound string The name of the sound to play.
+---@param coords vector3|number A `vector3` coordinate or an entity handle from which to play the sound.
+---@param synced boolean A boolean indicating whether the sound is synced across clients.
+---@param range number (optional) The maximum range at which the sound can be heard. Default is `10.0`.
+---
+---@usage
+--- ```lua
+--- playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
+--- ```
+function playGameSound(bank, sound, coords, synced, range)
+ debugPrint("^6Debug^7: ^2Attempting to play: ^3"..sound.." ^7(^4"..bank.."^7')")
+ local range = range or 10.0
+ local soundId = GetSoundId()
+ while not soundId do Wait(10) end
+ if type(coords) == "vector3" or type(coords) == "vector4" then
+ debugPrint("^6Debug^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz))
+ PlaySoundFromCoord(soundId, sound, coords.x, coords.y, coords.z, bank, synced, range, 0)
+ else
+ debugPrint("^6Debug^7: ^2Playing sound from Entity^7: ^4"..coords.."^7")
+ PlaySoundFromEntity(soundId, sound, coords, bank, synced, 0)
+ end
end
\ No newline at end of file
diff --git a/shared/make/makeBlip.lua b/shared/make/makeBlip.lua
index 126ef82..b773eae 100644
--- a/shared/make/makeBlip.lua
+++ b/shared/make/makeBlip.lua
@@ -1,119 +1,119 @@
---- Creates a blip at specified coordinates with given properties.
---
--- This function adds a map blip at the provided coordinates and sets various display properties such as sprite, color, scale, and more.
--- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
---
----@param data A table containing blip data and properties.
--- - **coords**: A `vector3` containing x, y, z coordinates where the blip will be placed.
--- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
--- - **col** (optional): The color ID of the blip. Default is `5`.
--- - **scale** (optional): The scale of the blip. Default is `0.7`.
--- - **disp** (optional): The display option of the blip. Default is `6`.
--- - **category** (optional): The category ID for the blip.
--- - **name**: The name of the blip, used for display on the map.
--- - **preview** (optional): A URL or image path for a preview image to display with the blip.
---
----@return blip blipID The handle of the created blip.
---
----@usage
--- ```lua
--- local blipData = {
--- coords = vector3(123.4, 567.8, 90.1),
--- sprite = 1,
--- col = 2,
--- scale = 0.8,
--- disp = 4,
--- category = 7,
--- name = "My Blip",
--- preview = "http://example.com/preview.png"
--- }
--- local blip = makeBlip(blipData)
--- ```
-function makeBlip(data)
- local blip = AddBlipForCoord(vec3(data.coords.x, data.coords.y, data.coords.z))
- SetBlipCoords(blip, data.coords.x, data.coords.y, data.coords.z) -- Manually set blip coordinates again as sometimes it just refuses
- SetBlipAsShortRange(blip, true)
- SetBlipSprite(blip, data.sprite or 106)
- SetBlipColour(blip, data.col or 5)
- SetBlipScale(blip, data.scale or 0.7)
- SetBlipDisplay(blip, data.disp or 6)
- if data.category then SetBlipCategory(blip, data.category) end
- BeginTextCommandSetBlipName('STRING')
- AddTextComponentString(tostring(data.name))
- EndTextCommandSetBlipName(blip)
- -- Handle preview image if certain resources are running
- if isStarted("fs_smallresources") or isStarted("blip_info") or isStarted("blipinfo") then
- if data.preview then
- local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
- if data.preview:find("http") or data.preview:find("nui") then
- createDui(txname, data.preview, vec2(512, 256), scriptTxd)
- else
- CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
- end
- exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'scriptTxd', txname)
- exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false)
- end
- end
- debugPrint("^6Bridge^7: ^6Blip ^2created^7: '^6"..data.name.."^7' - '"..formatCoord(data.coords).."'")
- return blip
-end
-
---- Creates a blip attached to a specified entity with given properties.
---
--- This function adds a map blip attached to the provided entity and sets various display properties such as sprite, color, scale, and more.
--- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
---
----@param data table A table containing blip data and properties.
--- - **entity**: The entity to which the blip will be attached.
--- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
--- - **col** (optional): The color ID of the blip. Default is `5`.
--- - **scale** (optional): The scale of the blip. Default is `0.7`.
--- - **disp** (optional): The display option of the blip. Default is `6`.
--- - **category** (optional): The category ID for the blip.
--- - **name**: The name of the blip, used for display on the map.
--- - **preview** (optional): A URL or image path for a preview image to display with the blip.
---
---
----@return number blipID The handle of the created blip.
----@usage
--- ```lua
--- local blipData = {
--- entity = myEntity,
--- sprite = 1,
--- col = 2,
--- scale = 0.8,
--- disp = 4,
--- category = 7,
--- name = "Entity Blip",
--- preview = "http://example.com/preview.png"
--- }
--- local blip = makeEntityBlip(blipData)
--- ```
-function makeEntityBlip(data)
- AddBlipForEntity(data.entity)
- local blip = GetBlipFromEntity(data.entity)
- SetBlipAsShortRange(blip, true)
- SetBlipSprite(blip, data.sprite or 106)
- SetBlipColour(blip, data.col or 5)
- SetBlipScale(blip, data.scale or 0.7)
- SetBlipDisplay(blip, data.disp or 6)
- if data.category then SetBlipCategory(blip, data.category) end
- BeginTextCommandSetBlipName('STRING')
- AddTextComponentString(tostring(data.name))
- EndTextCommandSetBlipName(blip)
- -- Handle preview image if certain resources are running
- if isStarted("fs_smallresources") or isStarted("blip-info") or isStarted("blipinfo") then
- if data.preview then
- local txname = string.gsub(tostring(data.name..'preview'), "[ ()~]", "")
- if data.preview:find("http") or data.preview:find("nui") then
- createDui(txname, data.preview, vec2(512, 256), scriptTxd)
- else
- CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
- end
- exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'previewTxd', txname)
- exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false)
- end
- end
- debugPrint("^6Bridge^7: ^6Blip ^2created for Entity^7: '^6"..data.name.."^7'")
- return blip
+--- Creates a blip at specified coordinates with given properties.
+--
+-- This function adds a map blip at the provided coordinates and sets various display properties such as sprite, color, scale, and more.
+-- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
+--
+---@param data A table containing blip data and properties.
+-- - **coords**: A `vector3` containing x, y, z coordinates where the blip will be placed.
+-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
+-- - **col** (optional): The color ID of the blip. Default is `5`.
+-- - **scale** (optional): The scale of the blip. Default is `0.7`.
+-- - **disp** (optional): The display option of the blip. Default is `6`.
+-- - **category** (optional): The category ID for the blip.
+-- - **name**: The name of the blip, used for display on the map.
+-- - **preview** (optional): A URL or image path for a preview image to display with the blip.
+--
+---@return blip blipID The handle of the created blip.
+--
+---@usage
+-- ```lua
+-- local blipData = {
+-- coords = vector3(123.4, 567.8, 90.1),
+-- sprite = 1,
+-- col = 2,
+-- scale = 0.8,
+-- disp = 4,
+-- category = 7,
+-- name = "My Blip",
+-- preview = "http://example.com/preview.png"
+-- }
+-- local blip = makeBlip(blipData)
+-- ```
+function makeBlip(data)
+ local blip = AddBlipForCoord(vec3(data.coords.x, data.coords.y, data.coords.z))
+ SetBlipCoords(blip, data.coords.x, data.coords.y, data.coords.z) -- Manually set blip coordinates again as sometimes it just refuses
+ SetBlipAsShortRange(blip, true)
+ SetBlipSprite(blip, data.sprite or 106)
+ SetBlipColour(blip, data.col or 5)
+ SetBlipScale(blip, data.scale or 0.7)
+ SetBlipDisplay(blip, data.disp or 6)
+ if data.category then SetBlipCategory(blip, data.category) end
+ BeginTextCommandSetBlipName('STRING')
+ AddTextComponentString(tostring(data.name))
+ EndTextCommandSetBlipName(blip)
+ -- Handle preview image if certain resources are running
+ if isStarted("fs_smallresources") or isStarted("blip_info") or isStarted("blipinfo") then
+ if data.preview then
+ local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
+ if data.preview:find("http") or data.preview:find("nui") then
+ createDui(txname, data.preview, vec2(512, 256), scriptTxd)
+ else
+ CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
+ end
+ exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'scriptTxd', txname)
+ exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false)
+ end
+ end
+ debugPrint("^6Bridge^7: ^6Blip ^2created^7: '^6"..data.name.."^7' - '"..formatCoord(data.coords).."'")
+ return blip
+end
+
+--- Creates a blip attached to a specified entity with given properties.
+--
+-- This function adds a map blip attached to the provided entity and sets various display properties such as sprite, color, scale, and more.
+-- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
+--
+---@param data table A table containing blip data and properties.
+-- - **entity**: The entity to which the blip will be attached.
+-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
+-- - **col** (optional): The color ID of the blip. Default is `5`.
+-- - **scale** (optional): The scale of the blip. Default is `0.7`.
+-- - **disp** (optional): The display option of the blip. Default is `6`.
+-- - **category** (optional): The category ID for the blip.
+-- - **name**: The name of the blip, used for display on the map.
+-- - **preview** (optional): A URL or image path for a preview image to display with the blip.
+--
+--
+---@return number blipID The handle of the created blip.
+---@usage
+-- ```lua
+-- local blipData = {
+-- entity = myEntity,
+-- sprite = 1,
+-- col = 2,
+-- scale = 0.8,
+-- disp = 4,
+-- category = 7,
+-- name = "Entity Blip",
+-- preview = "http://example.com/preview.png"
+-- }
+-- local blip = makeEntityBlip(blipData)
+-- ```
+function makeEntityBlip(data)
+ AddBlipForEntity(data.entity)
+ local blip = GetBlipFromEntity(data.entity)
+ SetBlipAsShortRange(blip, true)
+ SetBlipSprite(blip, data.sprite or 106)
+ SetBlipColour(blip, data.col or 5)
+ SetBlipScale(blip, data.scale or 0.7)
+ SetBlipDisplay(blip, data.disp or 6)
+ if data.category then SetBlipCategory(blip, data.category) end
+ BeginTextCommandSetBlipName('STRING')
+ AddTextComponentString(tostring(data.name))
+ EndTextCommandSetBlipName(blip)
+ -- Handle preview image if certain resources are running
+ if isStarted("fs_smallresources") or isStarted("blip-info") or isStarted("blipinfo") then
+ if data.preview then
+ local txname = string.gsub(tostring(data.name..'preview'), "[ ()~]", "")
+ if data.preview:find("http") or data.preview:find("nui") then
+ createDui(txname, data.preview, vec2(512, 256), scriptTxd)
+ else
+ CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
+ end
+ exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'previewTxd', txname)
+ exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false)
+ end
+ end
+ debugPrint("^6Bridge^7: ^6Blip ^2created for Entity^7: '^6"..data.name.."^7'")
+ return blip
end
\ No newline at end of file
diff --git a/shared/make/makePed.lua b/shared/make/makePed.lua
index bb79ac3..6305c32 100644
--- a/shared/make/makePed.lua
+++ b/shared/make/makePed.lua
@@ -1,230 +1,230 @@
---- A table to keep track of all created Peds.
-local Peds = {}
-
---- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area.
---
--- This function sets up a circular area using `createCirclePoly`. When the player enters this area, a Ped is created using `makePed`.
--- When the player exits the area, the Ped is deleted.
---
----@param data table A table containing Ped data and properties. Should include at least `model` and `coords`.
----@param coords vector4 A `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
----@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
----@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
----@param scenario boolean (optional) String specifying the scenario the Ped should perform.
----@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
----@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
---
----@usage
--- ```lua
--- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true)
--- ```
-function makeDistPed(data, coords, freeze, collision, scenario, anim, synced)
- createCirclePoly({
- name = keyGen()..keyGen(),
- coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
- radius = 50.0,
- onEnter = function()
- Peds[#Peds + 1] = makePed(data, coords, freeze, collision, scenario, anim, synced)
- end,
- onExit = function()
- DeletePed(Peds[#Peds])
- end,
- debug = debugMode,
- })
-end
-
---- Creates a Ped (pedestrian character) with specified properties.
---
--- This function creates a Ped at the given coordinates and applies appearance and clothing based on the provided data.
---
--- If `data` is a table with `custom` properties, it customizes the Ped's appearance accordingly.
---
----@param data modelHash|table Either a string/model hash of the Ped model to use, or a table containing `model` and `custom` data.
----@param coords vector4 `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
----@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
----@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
----@param scenario string (optional) String specifying the scenario the Ped should perform.
----@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
----@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
---
----@return ped entityID The handle of the created Ped.
---
----@usage
--- ```lua
--- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true)
--- ```
-function makePed(data, coords, freeze, collision, scenario, anim, synced)
- local ped = nil
- local model = nil
- if type(data) == "table" then
- model = data.model
- loadModel(data.model)
- ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
-
- -- Inheritance
- SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)
-
- -- Face Features
- for k, v in pairs({
- "noseWidth", "noseHeight", "noseSize", "noseBoneHeight", "nosePeakHeight", "noseBoneTwist",
- "eyebrowHeight", "eyebrowDepth",
- "cheekBoneHeight", "cheekBoneWidth", "cheeckWidth",
- "eyeOpening", "lipThickness",
- "jawWidth", "jawSize",
- "chinLowering", "chinLength", "chinSize", "chinHole",
- "neckThickness"
- }) do
- SetPedFaceFeature(ped, k - 1, data.custom[v])
- end
-
- -- Appearance
- SetPedComponentVariation(ped, 2, data.custom.Hair, 0, 0)
- SetPedHairColor(ped, data.custom.HairTexture, data.custom.HairHighlight or 0)
- SetPedHeadOverlay(ped, 2, data.custom.Eyebrows, data.custom.EyebrowsOpacity)
- SetPedHeadOverlayColor(ped, 2, 1, data.custom.EyebrowsColor, 0)
- SetPedEyeColor(ped, data.custom.Eyecolor)
- SetPedHeadOverlay(ped, 4, data.custom.Makeup, data.custom.MakeupOpacity)
- SetPedHeadOverlayColor(ped, 4, 1, data.custom.MakeupColor, 0)
- SetPedHeadOverlay(ped, 8, data.custom.Lipstick, data.custom.LipstickOpacity)
- SetPedHeadOverlayColor(ped, 8, 1, data.custom.LipstickColor, 0)
- SetPedHeadOverlay(ped, 1, data.custom.Beard, data.custom.BeardOpacity)
- SetPedHeadOverlayColor(ped, 1, 1, data.custom.BeardColor, 0)
-
- -- Clothes
- SetPedComponentVariation(ped, 1, data.custom.Mask, data.custom.MaskVariant, 0)
- SetPedComponentVariation(ped, 7, data.custom.Scarf, data.custom.ScarfVariant, 0)
- SetPedComponentVariation(ped, 11, data.custom.Jacket, data.custom.JacketVariant, 0)
- SetPedComponentVariation(ped, 8, data.custom.Shirt, data.custom.ShirtVariant, 0)
- SetPedComponentVariation(ped, 9, data.custom.Vest, data.custom.VestVariant, 0)
- SetPedComponentVariation(ped, 5, data.custom.Bags, data.custom.BagsVariant, 0)
- SetPedComponentVariation(ped, 3, data.custom.Arms, data.custom.ArmsVariant, 0)
- SetPedComponentVariation(ped, 4, data.custom.Pants, data.custom.PantsVariant, 0)
- SetPedComponentVariation(ped, 6, data.custom.Shoes, data.custom.ShoesVariant, 0)
- SetPedComponentVariation(ped, 10, data.custom.Decal, data.custom.DecalVariant, 0)
-
- -- Accessories
- SetPedPropIndex(ped, 0, data.custom.Hat, data.custom.HatVariant, true)
- SetPedPropIndex(ped, 1, data.custom.Glasses, data.custom.GlassesVariant, true)
-
- SetPedPropIndex(ped, 2, data.custom.Ear, data.custom.EarVariant, true)
- SetPedPropIndex(ped, 6, data.custom.Watches, data.custom.WatchesVariant, true)
- SetPedPropIndex(ped, 7, data.custom.Bracelets, data.custom.BraceletsVariant, true)
- else
- model = data
- loadModel(model)
- ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
- end
-
- SetEntityInvincible(ped, true)
- SetBlockingOfNonTemporaryEvents(ped, true)
- FreezeEntityPosition(ped, freeze and freeze or true)
-
- if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end
- if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end
- if anim then
- loadAnimDict(anim[1])
- TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
- end
-
- debugPrint("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^5"..(model).."^7' | ^2Coord^7: "..formatCoord(coords))
- unloadModel(model)
- Peds[#Peds + 1] = ped
- return ped
-end
-
---- Generates random Ped data by filling in missing customization options with random values.
---
--- This function takes in a data table that may have some customization options missing in `data.custom`.
---
--- It generates random values for any missing options and returns a new data table with complete customization.
---
----@param data table A table containing at least a `model` field, and possibly a `custom` table with customization options.
---
----@return generatedTable table A new table containing `model` and `custom` with all customization options filled.
---
----@usage
--- ```lua
--- local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} })
--- ```
-function GenerateRandomPedData(data)
- local newTable = {
- model = data.model,
- custom = {},
- }
- local isMale = data.model == `MP_M_Freemode_01`
- local randomTable = {
- -- Inheritance
- faceFather = math.random(0, 45), faceMother = math.random(0, 45), faceMix = (math.random(0, 9) / 10),
- skinFather = math.random(0, 45), skinMother = math.random(0, 45), skinMix = (math.random(0, 9) / 10),
- raceShape = math.random(0, 45), raceSkin = math.random(0, 45), raceMix = (math.random(0, 9) / 10),
-
- -- Face Features
- noseWidth = (math.random(0, 9) / 10),
- noseHeight = (math.random(0, 9) / 10),
- noseSize = (math.random(0, 9) / 10),
- noseBoneHeight = (math.random(0, 9) / 10),
- nosePeakHeight = (math.random(0, 9) / 10),
- noseBoneTwist = (math.random(0, 9) / 10),
-
- eyebrowHeight = (math.random(0, 9) / 10),
- eyebrowDepth = (math.random(0, 9) / 10),
-
- cheekBoneHeight = (math.random(0, 9) / 10),
- cheekBoneWidth = (math.random(0, 9) / 10),
- cheeckWidth = (math.random(0, 9) / 10),
-
- eyeOpening = (math.random(0, 9) / 10),
- lipThickness = (math.random(0, 9) / 10),
-
- jawWidth = (math.random(0, 9) / 10),
- jawSize = (math.random(0, 9) / 10),
-
- chinLowering = (math.random(0, 9) / 10),
- chinLength = (math.random(0, 9) / 10),
- chinSize = (math.random(0, 9) / 10),
- chinHole = (math.random(0, 9) / 10),
-
- neckThickness = (math.random(0, 9) / 10),
-
- -- Appearance
- Hair = math.random(0, isMale and 147 or 261), HairTexture = math.random(0, 63), HairHighlight = math.random(0, 63),
- Eyebrows = math.random(0, 33),
- EyebrowsOpacity = 0.9, EyebrowsColor = 0,
- Eyecolor = math.random(0, 30),
- Makeup = 0, MakeupOpacity = 0, MakeupColor = 0,
- Lipstick = 0, LipstickOpacity = 0, LipstickColor = 0,
- Beard = isMale and math.random(0, 28) or -1,
- BeardOpacity = isMale and 0.9 or 0.0, BeardColor = 0,
-
- -- Clothing
- Mask = math.random(0, 252), MaskVariant = 0,
- Scarf = math.random(0, isMale and 249 or 198), ScarfVariant = 0,
- Jacket = math.random(0, isMale and 634 or 713), JacketVariant = 0,
- Shirt = math.random(0, isMale and 237 or 299), ShirtVariant = 0,
- Vest = math.random(0, isMale and 81 or 91), VestVariant = 0,
- Bags = math.random(0, isMale and 138 or 148), BagsVariant = 0,
- Arms = math.random(0, isMale and 224 or 261), ArmsVariant = 0,
- Pants = math.random(0, isMale and 255 or 275), PantsVariant = 0,
- Shoes = math.random(0, isMale and 157 or 199), ShoesVariant = 0,
- Decal = math.random(0, isMale and 238 or 253), DecalVariant = 0,
-
- -- Accessories
- Hat = math.random(0, isMale and 232 or 229), HatVariant = 0,
- Glasses = math.random(0, isMale and 68 or 71), GlassesVariant = 0,
- Ear = math.random(0, isMale and 51 or 40), EarVariant = 0,
- Watches = math.random(0, isMale and 46 or 35), WatchesVariant = 0,
- Bracelets = math.random(0, isMale and 13 or 20), BraceletsVariant = 0,
- }
- for option in pairs(randomTable) do
- if not data.custom[option] then
- newTable.custom[option] = randomTable[option]
- debugPrint("^6Bridge^7: ^2Picking Random Ped option ^7[^5"..option.."^7]: ^6"..newTable.custom[option].."^7")
- else
- newTable.custom[option] = data.custom[option]
- end
- end
- return newTable
-end
-
---- Cleans up all created Peds when the resource stops.
+--- A table to keep track of all created Peds.
+local Peds = {}
+
+--- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area.
+--
+-- This function sets up a circular area using `createCirclePoly`. When the player enters this area, a Ped is created using `makePed`.
+-- When the player exits the area, the Ped is deleted.
+--
+---@param data table A table containing Ped data and properties. Should include at least `model` and `coords`.
+---@param coords vector4 A `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
+---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
+---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
+---@param scenario boolean (optional) String specifying the scenario the Ped should perform.
+---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
+---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
+--
+---@usage
+-- ```lua
+-- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true)
+-- ```
+function makeDistPed(data, coords, freeze, collision, scenario, anim, synced)
+ createCirclePoly({
+ name = keyGen()..keyGen(),
+ coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
+ radius = 50.0,
+ onEnter = function()
+ Peds[#Peds + 1] = makePed(data, coords, freeze, collision, scenario, anim, synced)
+ end,
+ onExit = function()
+ DeletePed(Peds[#Peds])
+ end,
+ debug = debugMode,
+ })
+end
+
+--- Creates a Ped (pedestrian character) with specified properties.
+--
+-- This function creates a Ped at the given coordinates and applies appearance and clothing based on the provided data.
+--
+-- If `data` is a table with `custom` properties, it customizes the Ped's appearance accordingly.
+--
+---@param data modelHash|table Either a string/model hash of the Ped model to use, or a table containing `model` and `custom` data.
+---@param coords vector4 `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
+---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
+---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
+---@param scenario string (optional) String specifying the scenario the Ped should perform.
+---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
+---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
+--
+---@return ped entityID The handle of the created Ped.
+--
+---@usage
+-- ```lua
+-- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true)
+-- ```
+function makePed(data, coords, freeze, collision, scenario, anim, synced)
+ local ped = nil
+ local model = nil
+ if type(data) == "table" then
+ model = data.model
+ loadModel(data.model)
+ ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
+
+ -- Inheritance
+ SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)
+
+ -- Face Features
+ for k, v in pairs({
+ "noseWidth", "noseHeight", "noseSize", "noseBoneHeight", "nosePeakHeight", "noseBoneTwist",
+ "eyebrowHeight", "eyebrowDepth",
+ "cheekBoneHeight", "cheekBoneWidth", "cheeckWidth",
+ "eyeOpening", "lipThickness",
+ "jawWidth", "jawSize",
+ "chinLowering", "chinLength", "chinSize", "chinHole",
+ "neckThickness"
+ }) do
+ SetPedFaceFeature(ped, k - 1, data.custom[v])
+ end
+
+ -- Appearance
+ SetPedComponentVariation(ped, 2, data.custom.Hair, 0, 0)
+ SetPedHairColor(ped, data.custom.HairTexture, data.custom.HairHighlight or 0)
+ SetPedHeadOverlay(ped, 2, data.custom.Eyebrows, data.custom.EyebrowsOpacity)
+ SetPedHeadOverlayColor(ped, 2, 1, data.custom.EyebrowsColor, 0)
+ SetPedEyeColor(ped, data.custom.Eyecolor)
+ SetPedHeadOverlay(ped, 4, data.custom.Makeup, data.custom.MakeupOpacity)
+ SetPedHeadOverlayColor(ped, 4, 1, data.custom.MakeupColor, 0)
+ SetPedHeadOverlay(ped, 8, data.custom.Lipstick, data.custom.LipstickOpacity)
+ SetPedHeadOverlayColor(ped, 8, 1, data.custom.LipstickColor, 0)
+ SetPedHeadOverlay(ped, 1, data.custom.Beard, data.custom.BeardOpacity)
+ SetPedHeadOverlayColor(ped, 1, 1, data.custom.BeardColor, 0)
+
+ -- Clothes
+ SetPedComponentVariation(ped, 1, data.custom.Mask, data.custom.MaskVariant, 0)
+ SetPedComponentVariation(ped, 7, data.custom.Scarf, data.custom.ScarfVariant, 0)
+ SetPedComponentVariation(ped, 11, data.custom.Jacket, data.custom.JacketVariant, 0)
+ SetPedComponentVariation(ped, 8, data.custom.Shirt, data.custom.ShirtVariant, 0)
+ SetPedComponentVariation(ped, 9, data.custom.Vest, data.custom.VestVariant, 0)
+ SetPedComponentVariation(ped, 5, data.custom.Bags, data.custom.BagsVariant, 0)
+ SetPedComponentVariation(ped, 3, data.custom.Arms, data.custom.ArmsVariant, 0)
+ SetPedComponentVariation(ped, 4, data.custom.Pants, data.custom.PantsVariant, 0)
+ SetPedComponentVariation(ped, 6, data.custom.Shoes, data.custom.ShoesVariant, 0)
+ SetPedComponentVariation(ped, 10, data.custom.Decal, data.custom.DecalVariant, 0)
+
+ -- Accessories
+ SetPedPropIndex(ped, 0, data.custom.Hat, data.custom.HatVariant, true)
+ SetPedPropIndex(ped, 1, data.custom.Glasses, data.custom.GlassesVariant, true)
+
+ SetPedPropIndex(ped, 2, data.custom.Ear, data.custom.EarVariant, true)
+ SetPedPropIndex(ped, 6, data.custom.Watches, data.custom.WatchesVariant, true)
+ SetPedPropIndex(ped, 7, data.custom.Bracelets, data.custom.BraceletsVariant, true)
+ else
+ model = data
+ loadModel(model)
+ ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
+ end
+
+ SetEntityInvincible(ped, true)
+ SetBlockingOfNonTemporaryEvents(ped, true)
+ FreezeEntityPosition(ped, freeze and freeze or true)
+
+ if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end
+ if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end
+ if anim then
+ loadAnimDict(anim[1])
+ TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
+ end
+
+ debugPrint("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^5"..(model).."^7' | ^2Coord^7: "..formatCoord(coords))
+ unloadModel(model)
+ Peds[#Peds + 1] = ped
+ return ped
+end
+
+--- Generates random Ped data by filling in missing customization options with random values.
+--
+-- This function takes in a data table that may have some customization options missing in `data.custom`.
+--
+-- It generates random values for any missing options and returns a new data table with complete customization.
+--
+---@param data table A table containing at least a `model` field, and possibly a `custom` table with customization options.
+--
+---@return generatedTable table A new table containing `model` and `custom` with all customization options filled.
+--
+---@usage
+-- ```lua
+-- local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} })
+-- ```
+function GenerateRandomPedData(data)
+ local newTable = {
+ model = data.model,
+ custom = {},
+ }
+ local isMale = data.model == `MP_M_Freemode_01`
+ local randomTable = {
+ -- Inheritance
+ faceFather = math.random(0, 45), faceMother = math.random(0, 45), faceMix = (math.random(0, 9) / 10),
+ skinFather = math.random(0, 45), skinMother = math.random(0, 45), skinMix = (math.random(0, 9) / 10),
+ raceShape = math.random(0, 45), raceSkin = math.random(0, 45), raceMix = (math.random(0, 9) / 10),
+
+ -- Face Features
+ noseWidth = (math.random(0, 9) / 10),
+ noseHeight = (math.random(0, 9) / 10),
+ noseSize = (math.random(0, 9) / 10),
+ noseBoneHeight = (math.random(0, 9) / 10),
+ nosePeakHeight = (math.random(0, 9) / 10),
+ noseBoneTwist = (math.random(0, 9) / 10),
+
+ eyebrowHeight = (math.random(0, 9) / 10),
+ eyebrowDepth = (math.random(0, 9) / 10),
+
+ cheekBoneHeight = (math.random(0, 9) / 10),
+ cheekBoneWidth = (math.random(0, 9) / 10),
+ cheeckWidth = (math.random(0, 9) / 10),
+
+ eyeOpening = (math.random(0, 9) / 10),
+ lipThickness = (math.random(0, 9) / 10),
+
+ jawWidth = (math.random(0, 9) / 10),
+ jawSize = (math.random(0, 9) / 10),
+
+ chinLowering = (math.random(0, 9) / 10),
+ chinLength = (math.random(0, 9) / 10),
+ chinSize = (math.random(0, 9) / 10),
+ chinHole = (math.random(0, 9) / 10),
+
+ neckThickness = (math.random(0, 9) / 10),
+
+ -- Appearance
+ Hair = math.random(0, isMale and 147 or 261), HairTexture = math.random(0, 63), HairHighlight = math.random(0, 63),
+ Eyebrows = math.random(0, 33),
+ EyebrowsOpacity = 0.9, EyebrowsColor = 0,
+ Eyecolor = math.random(0, 30),
+ Makeup = 0, MakeupOpacity = 0, MakeupColor = 0,
+ Lipstick = 0, LipstickOpacity = 0, LipstickColor = 0,
+ Beard = isMale and math.random(0, 28) or -1,
+ BeardOpacity = isMale and 0.9 or 0.0, BeardColor = 0,
+
+ -- Clothing
+ Mask = math.random(0, 252), MaskVariant = 0,
+ Scarf = math.random(0, isMale and 249 or 198), ScarfVariant = 0,
+ Jacket = math.random(0, isMale and 634 or 713), JacketVariant = 0,
+ Shirt = math.random(0, isMale and 237 or 299), ShirtVariant = 0,
+ Vest = math.random(0, isMale and 81 or 91), VestVariant = 0,
+ Bags = math.random(0, isMale and 138 or 148), BagsVariant = 0,
+ Arms = math.random(0, isMale and 224 or 261), ArmsVariant = 0,
+ Pants = math.random(0, isMale and 255 or 275), PantsVariant = 0,
+ Shoes = math.random(0, isMale and 157 or 199), ShoesVariant = 0,
+ Decal = math.random(0, isMale and 238 or 253), DecalVariant = 0,
+
+ -- Accessories
+ Hat = math.random(0, isMale and 232 or 229), HatVariant = 0,
+ Glasses = math.random(0, isMale and 68 or 71), GlassesVariant = 0,
+ Ear = math.random(0, isMale and 51 or 40), EarVariant = 0,
+ Watches = math.random(0, isMale and 46 or 35), WatchesVariant = 0,
+ Bracelets = math.random(0, isMale and 13 or 20), BraceletsVariant = 0,
+ }
+ for option in pairs(randomTable) do
+ if not data.custom[option] then
+ newTable.custom[option] = randomTable[option]
+ debugPrint("^6Bridge^7: ^2Picking Random Ped option ^7[^5"..option.."^7]: ^6"..newTable.custom[option].."^7")
+ else
+ newTable.custom[option] = data.custom[option]
+ end
+ end
+ return newTable
+end
+
+--- Cleans up all created Peds when the resource stops.
onResourceStop(function() for i = 1, #Peds do DeletePed(Peds[i]) end end, true)
\ No newline at end of file
diff --git a/shared/make/makeProp.lua b/shared/make/makeProp.lua
index aa7080e..ed82ade 100644
--- a/shared/make/makeProp.lua
+++ b/shared/make/makeProp.lua
@@ -1,90 +1,90 @@
-local Props = {}
-
---- Creates a prop (object) in the game world at specified coordinates.
----
---- This function loads the model, creates the object, sets its heading, and freezes it if specified.
----
----@param data table A table containing prop data.
---- - **prop** `string`: The model name or hash of the prop to create.
---- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
----@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
----@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
----
----@return number entityID The handle of the created prop object.
----
----@usage
---- ```lua
---- local propData = {
---- prop = 'prop_chair_01a',
---- coords = vector4(123.4, 567.8, 90.1, 180.0)
---- }
---- local prop = makeProp(propData, true, false)
---- ```
-function makeProp(data, freeze, synced)
- loadModel(data.prop)
- local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
- SetEntityHeading(prop, data.coords.w + 180.0)
- FreezeEntityPosition(prop, freeze or false)
-
- debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords))
- SetModelAsNoLongerNeeded(data.prop)
- Props[#Props + 1] = prop
- return prop
-end
-
---- Creates a prop that appears when the player is within a certain distance.
----
---- This function sets up a proximity area, and when the player enters it, the prop is created.
---- When the player exits the area, the prop is destroyed.
----
----@param data table A table containing prop data.
---- - **prop** `string`: The model name or hash of the prop to create.
---- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
----@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
----@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
----
----@usage
---- ```lua
---- local propData = {
---- prop = 'prop_chair_01a',
---- coords = vector4(123.4, 567.8, 90.1, 180.0)
---- }
---- makeDistProp(propData, true, false)
---- ```
-function makeDistProp(data, freeze, synced)
- local prop = nil
- createCirclePoly({
- name = keyGen()..keyGen(),
- coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
- radius = 50.0,
- onEnter = function()
- prop = makeProp(data, freeze, synced)
- end,
- onExit = function()
- destroyProp(prop)
- end,
- debug = debugMode,
- })
-end
-
---- Destroys a prop, detaching it if attached to the player beforehand.
----
----@param entity number The handle of the prop entity to destroy.
----
----@usage
---- ```lua
---- destroyProp(prop)
---- ```
-function destroyProp(entity)
- if entity then
- debugPrint("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'")
- if IsEntityAttachedToEntity(entity, PlayerPedId()) then
- SetEntityAsMissionEntity(entity)
- DetachEntity(entity, true, true)
- end
- DeleteObject(entity)
- end
-end
-
---- Cleans up all created props when the resource stops.
-onResourceStop(function() for i = 1, #Props do destroyProp(Props[i]) end end, true)
+local Props = {}
+
+--- Creates a prop (object) in the game world at specified coordinates.
+---
+--- This function loads the model, creates the object, sets its heading, and freezes it if specified.
+---
+---@param data table A table containing prop data.
+--- - **prop** `string`: The model name or hash of the prop to create.
+--- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
+---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
+---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
+---
+---@return number entityID The handle of the created prop object.
+---
+---@usage
+--- ```lua
+--- local propData = {
+--- prop = 'prop_chair_01a',
+--- coords = vector4(123.4, 567.8, 90.1, 180.0)
+--- }
+--- local prop = makeProp(propData, true, false)
+--- ```
+function makeProp(data, freeze, synced)
+ loadModel(data.prop)
+ local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
+ SetEntityHeading(prop, data.coords.w + 180.0)
+ FreezeEntityPosition(prop, freeze or false)
+
+ debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords))
+ SetModelAsNoLongerNeeded(data.prop)
+ Props[#Props + 1] = prop
+ return prop
+end
+
+--- Creates a prop that appears when the player is within a certain distance.
+---
+--- This function sets up a proximity area, and when the player enters it, the prop is created.
+--- When the player exits the area, the prop is destroyed.
+---
+---@param data table A table containing prop data.
+--- - **prop** `string`: The model name or hash of the prop to create.
+--- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
+---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
+---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
+---
+---@usage
+--- ```lua
+--- local propData = {
+--- prop = 'prop_chair_01a',
+--- coords = vector4(123.4, 567.8, 90.1, 180.0)
+--- }
+--- makeDistProp(propData, true, false)
+--- ```
+function makeDistProp(data, freeze, synced)
+ local prop = nil
+ createCirclePoly({
+ name = keyGen()..keyGen(),
+ coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
+ radius = 50.0,
+ onEnter = function()
+ prop = makeProp(data, freeze, synced)
+ end,
+ onExit = function()
+ destroyProp(prop)
+ end,
+ debug = debugMode,
+ })
+end
+
+--- Destroys a prop, detaching it if attached to the player beforehand.
+---
+---@param entity number The handle of the prop entity to destroy.
+---
+---@usage
+--- ```lua
+--- destroyProp(prop)
+--- ```
+function destroyProp(entity)
+ if entity then
+ debugPrint("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'")
+ if IsEntityAttachedToEntity(entity, PlayerPedId()) then
+ SetEntityAsMissionEntity(entity)
+ DetachEntity(entity, true, true)
+ end
+ DeleteObject(entity)
+ end
+end
+
+--- Cleans up all created props when the resource stops.
+onResourceStop(function() for i = 1, #Props do destroyProp(Props[i]) end end, true)
diff --git a/shared/make/makeVeh.lua b/shared/make/makeVeh.lua
index 6af1e06..9c65af3 100644
--- a/shared/make/makeVeh.lua
+++ b/shared/make/makeVeh.lua
@@ -1,73 +1,73 @@
-local Vehicles = {}
-
---- Creates a vehicle with the specified model and coordinates.
----
---- This function loads the vehicle model, creates the vehicle in the world at the given coordinates, sets initial properties, and returns the vehicle handle.
----
----@param model string|number The model name or hash of the vehicle to create.
----@param coords vector4 The coordinates where the vehicle will be placed, including x, y, z, and w (heading).
----
----@return number entityID The handle of the created vehicle.
----
----@usage
---- ```lua
---- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0))
---- ```
-function makeVeh(model, coords)
- loadModel(model)
- local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false)
- SetVehicleHasBeenOwnedByPlayer(veh, true)
- SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
- Wait(100)
- SetVehicleNeedsToBeHotwired(veh, false)
- SetVehRadioStation(veh, 'OFF')
- SetVehicleFuelLevel(veh, 100.0)
- SetVehicleModKit(veh, 0)
- SetVehicleOnGroundProperly(veh)
-
- debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords))
- unloadModel(model)
- Vehicles[#Vehicles + 1] = veh
- return veh
-end
-
---- Attempts to gain network control of a vehicle and set it as a mission entity.
----
---- This function forces synchronization of a vehicle with other players by requesting network control and setting the vehicle as a mission entity.
----
----@param entity number The handle of the vehicle entity to push.
----
----@usage
---- ```lua
---- pushVehicle(vehicle)
---- ```
-function pushVehicle(entity)
- SetVehicleModKit(entity, 0)
- if entity ~= 0 and DoesEntityExist(entity) then
- if not NetworkHasControlOfEntity(entity) then
- debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.")
- NetworkRequestControlOfEntity(entity)
- local timeout = 2000
- while timeout > 0 and not NetworkHasControlOfEntity(entity) do
- Wait(100)
- timeout -= 100
- end
- if NetworkHasControlOfEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.") end
- end
- if not IsEntityAMissionEntity(entity) then
- debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.")
- SetEntityAsMissionEntity(entity, true, true)
- local timeout = 2000
- while timeout > 0 and not IsEntityAMissionEntity(entity) do
- Wait(100)
- timeout -= 100
- end
- if IsEntityAMissionEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") end
- end
- end
-end
-
---- Cleans up all created vehicles when the resource stops.
-onResourceStop(function(r)
- for i = 1, #Vehicles do DeleteVehicle(Vehicles[i]) end
+local Vehicles = {}
+
+--- Creates a vehicle with the specified model and coordinates.
+---
+--- This function loads the vehicle model, creates the vehicle in the world at the given coordinates, sets initial properties, and returns the vehicle handle.
+---
+---@param model string|number The model name or hash of the vehicle to create.
+---@param coords vector4 The coordinates where the vehicle will be placed, including x, y, z, and w (heading).
+---
+---@return number entityID The handle of the created vehicle.
+---
+---@usage
+--- ```lua
+--- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0))
+--- ```
+function makeVeh(model, coords)
+ loadModel(model)
+ local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false)
+ SetVehicleHasBeenOwnedByPlayer(veh, true)
+ SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
+ Wait(100)
+ SetVehicleNeedsToBeHotwired(veh, false)
+ SetVehRadioStation(veh, 'OFF')
+ SetVehicleFuelLevel(veh, 100.0)
+ SetVehicleModKit(veh, 0)
+ SetVehicleOnGroundProperly(veh)
+
+ debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords))
+ unloadModel(model)
+ Vehicles[#Vehicles + 1] = veh
+ return veh
+end
+
+--- Attempts to gain network control of a vehicle and set it as a mission entity.
+---
+--- This function forces synchronization of a vehicle with other players by requesting network control and setting the vehicle as a mission entity.
+---
+---@param entity number The handle of the vehicle entity to push.
+---
+---@usage
+--- ```lua
+--- pushVehicle(vehicle)
+--- ```
+function pushVehicle(entity)
+ SetVehicleModKit(entity, 0)
+ if entity ~= 0 and DoesEntityExist(entity) then
+ if not NetworkHasControlOfEntity(entity) then
+ debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.")
+ NetworkRequestControlOfEntity(entity)
+ local timeout = 2000
+ while timeout > 0 and not NetworkHasControlOfEntity(entity) do
+ Wait(100)
+ timeout -= 100
+ end
+ if NetworkHasControlOfEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.") end
+ end
+ if not IsEntityAMissionEntity(entity) then
+ debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.")
+ SetEntityAsMissionEntity(entity, true, true)
+ local timeout = 2000
+ while timeout > 0 and not IsEntityAMissionEntity(entity) do
+ Wait(100)
+ timeout -= 100
+ end
+ if IsEntityAMissionEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") end
+ end
+ end
+end
+
+--- Cleans up all created vehicles when the resource stops.
+onResourceStop(function(r)
+ for i = 1, #Vehicles do DeleteVehicle(Vehicles[i]) end
end)
\ No newline at end of file
diff --git a/shared/make/progressBars.lua b/shared/make/progressBars.lua
index 634b01a..a802b1b 100644
--- a/shared/make/progressBars.lua
+++ b/shared/make/progressBars.lua
@@ -1,209 +1,209 @@
-local inProgress = false
-
---- Displays a progress bar using the configured progress bar system.
----
---- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
---- It supports shared progress bars between players, animations, camera effects, and more.
----
----@param data table A table containing the progress bar configuration.
---- - **label** (`string`): The text label to display on the progress bar.
---- - **time** (`number`): The duration of the progress bar in milliseconds.
---- - **dict** (`string`, optional): The animation dictionary to use.
---- - **anim** (`string`, optional): The animation name to play.
---- - **task** (`string`, optional): The task scenario to perform.
---- - **flag** (`number`, optional): The animation flag.
---- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
---- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
---- - **icon** (`string`, optional): The icon to display (for qb progress bar).
---- - **cam** (`number`, optional): The camera handle to use.
---- - **shared** (`table`, optional): Data for shared progress bars.
---- - **pid** (`number`): The player ID to share the progress bar with.
---- - **label** (`string`): The label to display on the shared progress bar.
----
---- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
----
----@usage
---- ```lua
---- local success = progressBar({
---- label = "Processing...",
---- time = 5000,
---- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
---- anim = "base",
---- flag = 49,
---- cancel = true,
---- })
---- ```
-function progressBar(data)
- local ped = PlayerPedId()
- if data.shared then
- debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
- storedPID = data.shared.pid
- TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
- end
- local result = nil
- if data.cam then startTempCam(data.cam) end
- if Config.System.ProgressBar == "ox" then
- if exports[OXLibExport]:progressBar({
- duration = debugMode and 1000 or data.time,
- label = data.label,
- useWhileDead = data.dead or false,
- canCancel = data.cancel and data.cancel or true,
- anim = {
- dict = data.dict,
- clip = data.anim,
- flag = (data.flag == 8 and 32 or data.flag) or nil,
- scenario = data.task
- },
- disable = {
- combat = true
- },
- }) then
- result = true
- else
- result = false
- end
-
- elseif Config.System.ProgressBar == "qb" then
- Core.Functions.Progressbar("progbar",
- data.label,
- debugMode and 1000 or data.time,
- data.dead or false,
- data.cancel or true,
- { disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true },
- { animDict = data.dict, anim = data.anim, flags = data.flag or 32, task = data.task }, {}, {},
- function()
- result = true
- end, function()
- result = false
- end, data.icon)
-
- elseif Config.System.ProgressBar == "esx" then
- ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
- FreezePlayer = true,
- animation = {
- type = data.anim,
- dict = data.dict,
- scenario = data.task,
- },
- onFinish = function()
- result = true
- end,
- onCancel = function()
- result = false
- end
- })
-
- elseif Config.System.ProgressBar == "gta" then
- local wait = debugMode and 1000 or data.time
- inProgress = true
- if not (data.dead or false) then
- lockInv(true)
- displaySpinner(data.label)
- if data.dict then
- playAnim(data.dict, data.anim, -1, (data.flag == 8 and 32 or data.flag) or nil)
- end
- if data.task then
- TaskStartScenarioInPlace(ped, data.task, -1, true)
- end
- while inProgress and wait > 0 do
- wait -= 15
- local waitTimer = 0
- DisablePlayerFiring(ped, true)
- DisableControlAction(0, 25, true) -- Disable aim
- DisableControlAction(0, 21, true) -- Disable sprint
- DisableControlAction(0, 30, true) -- Disable move left/right
- DisableControlAction(0, 31, true) -- Disable move forward/back
- DisableControlAction(0, 36, true) -- Disable stealth
- if data.cam ~= nil then
- DisableControlAction(0, 1, true) -- Disable look left/right
- DisableControlAction(0, 2, true) -- Disable look up/down
- DisableControlAction(0, 106, true) -- Disable vehicle mouse control
- end
- if data.cancel then
- if IsControlJustReleased(0, 202) or IsControlJustReleased(0, 77) then -- Cancel key (Backspace or Delete)
- inProgress = false
- waitTimer = 1500
- displaySpinner(Loc[Config.Lan].error["cancel"])
- end
- end
- Wait(waitTimer)
- end
- inProgress = false
- if data.dict then stopAnim(data.dict, data.anim, ped) end
- ClearPedTasks(ped)
- end
- stopSpinner()
- result = (wait <= 0)
- end
-
- while result == nil do Wait(10) end
-
- -- Cleanup
- FreezeEntityPosition(ped, false)
- lockInv(false)
- if data.cam then stopTempCam(data.cam) end
- if result == false and data.shared then
- debugPrint("^6Bridge^7: ^2Sending cancel to ^6"..storedPID.."^7")
- TriggerServerEvent(getScript().."server:sharedProg:cancel", storedPID)
- end
- storedPID = nil
- return result
-end
-
---- Stops the current progress bar.
----
---- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
-function stopPropgressBar()
- if Config.System.ProgressBar == "ox" then
- exports[OXLibExport]:cancelProgress()
- elseif Config.System.ProgressBar == "qb" then
- TriggerEvent("progressbar:client:cancel")
- elseif Config.System.ProgressBar == "gta" then
- inProgress = false
- BusyspinnerOff()
- end
-end
-
--- System to handle sending/sharing progress bars between players --
--- For example, healing someone --
-
-local storedPID = nil
-
---- Server event handler for starting a shared progress bar.
---- This event is triggered when a player wants to start a progress bar on another player.
---- It adjusts the data to prevent loops and sends the data to the target client.
-RegisterNetEvent(getScript()..":server:sharedProg:Start", function(data)
- local pid = data.shared.pid -- Get player ID from the client
- data.label = data.shared.label -- Set progress bar label to the shared label
- data.cancel = false -- Make it so it can't be canceled
- data.dead = true -- Allow progress bar even if player is dead
- data.shared = nil -- Remove shared info to prevent loops
- data.anim = nil -- Remove animation so players don't share it
- debugPrint("^6Bridge^7: ^6"..source.." ^2is sending shared progressBar to player^7, ^6"..pid.."^7")
- TriggerClientEvent(getScript()..":client:sharedProg:Start", pid, data)
-end)
-
---- Client event handler for starting a shared progress bar.
---- This event is triggered when the server wants the client to start a shared progress bar.
-RegisterNetEvent(getScript()..":client:sharedProg:Start", function(data)
- debugPrint("^6Bridge^7: ^2You have been sent a progressBar^7")
- progressBar(data)
-end)
-
---- Server event handler for canceling a shared progress bar.
---- This event is triggered when a progress bar is canceled and the server needs to notify the other player.
-RegisterNetEvent(getScript()..":server:sharedProg:Cancel", function(pid)
- debugPrint("^6Bridge^7: ^2Sending cancel progressBar to ^6"..pid.."^7")
- TriggerClientEvent(getScript()..":client:sharedProg:Cancel", pid)
-end)
-
---- Client event handler for canceling a shared progress bar.
---- This event is triggered when the server wants the client to cancel a shared progress bar.
-RegisterNetEvent(getScript()..":client:sharedProg:Cancel", function()
- debugPrint("^6Bridge^7: ^2Receiving cancel progressBar^7")
- stopPropgressBar()
-end)
-
---- Cleans up when the resource stops.
---- This event handler ensures that any active spinners or progress bars are stopped when the resource is stopped.
-onResourceStop(function() stopSpinner() end, true)
+local inProgress = false
+
+--- Displays a progress bar using the configured progress bar system.
+---
+--- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
+--- It supports shared progress bars between players, animations, camera effects, and more.
+---
+---@param data table A table containing the progress bar configuration.
+--- - **label** (`string`): The text label to display on the progress bar.
+--- - **time** (`number`): The duration of the progress bar in milliseconds.
+--- - **dict** (`string`, optional): The animation dictionary to use.
+--- - **anim** (`string`, optional): The animation name to play.
+--- - **task** (`string`, optional): The task scenario to perform.
+--- - **flag** (`number`, optional): The animation flag.
+--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
+--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
+--- - **icon** (`string`, optional): The icon to display (for qb progress bar).
+--- - **cam** (`number`, optional): The camera handle to use.
+--- - **shared** (`table`, optional): Data for shared progress bars.
+--- - **pid** (`number`): The player ID to share the progress bar with.
+--- - **label** (`string`): The label to display on the shared progress bar.
+---
+--- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
+---
+---@usage
+--- ```lua
+--- local success = progressBar({
+--- label = "Processing...",
+--- time = 5000,
+--- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
+--- anim = "base",
+--- flag = 49,
+--- cancel = true,
+--- })
+--- ```
+function progressBar(data)
+ local ped = PlayerPedId()
+ if data.shared then
+ debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
+ storedPID = data.shared.pid
+ TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
+ end
+ local result = nil
+ if data.cam then startTempCam(data.cam) end
+ if Config.System.ProgressBar == "ox" then
+ if exports[OXLibExport]:progressBar({
+ duration = debugMode and 1000 or data.time,
+ label = data.label,
+ useWhileDead = data.dead or false,
+ canCancel = data.cancel and data.cancel or true,
+ anim = {
+ dict = data.dict,
+ clip = data.anim,
+ flag = (data.flag == 8 and 32 or data.flag) or nil,
+ scenario = data.task
+ },
+ disable = {
+ combat = true
+ },
+ }) then
+ result = true
+ else
+ result = false
+ end
+
+ elseif Config.System.ProgressBar == "qb" then
+ Core.Functions.Progressbar("progbar",
+ data.label,
+ debugMode and 1000 or data.time,
+ data.dead or false,
+ data.cancel or true,
+ { disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true },
+ { animDict = data.dict, anim = data.anim, flags = data.flag or 32, task = data.task }, {}, {},
+ function()
+ result = true
+ end, function()
+ result = false
+ end, data.icon)
+
+ elseif Config.System.ProgressBar == "esx" then
+ ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
+ FreezePlayer = true,
+ animation = {
+ type = data.anim,
+ dict = data.dict,
+ scenario = data.task,
+ },
+ onFinish = function()
+ result = true
+ end,
+ onCancel = function()
+ result = false
+ end
+ })
+
+ elseif Config.System.ProgressBar == "gta" then
+ local wait = debugMode and 1000 or data.time
+ inProgress = true
+ if not (data.dead or false) then
+ lockInv(true)
+ displaySpinner(data.label)
+ if data.dict then
+ playAnim(data.dict, data.anim, -1, (data.flag == 8 and 32 or data.flag) or nil)
+ end
+ if data.task then
+ TaskStartScenarioInPlace(ped, data.task, -1, true)
+ end
+ while inProgress and wait > 0 do
+ wait -= 15
+ local waitTimer = 0
+ DisablePlayerFiring(ped, true)
+ DisableControlAction(0, 25, true) -- Disable aim
+ DisableControlAction(0, 21, true) -- Disable sprint
+ DisableControlAction(0, 30, true) -- Disable move left/right
+ DisableControlAction(0, 31, true) -- Disable move forward/back
+ DisableControlAction(0, 36, true) -- Disable stealth
+ if data.cam ~= nil then
+ DisableControlAction(0, 1, true) -- Disable look left/right
+ DisableControlAction(0, 2, true) -- Disable look up/down
+ DisableControlAction(0, 106, true) -- Disable vehicle mouse control
+ end
+ if data.cancel then
+ if IsControlJustReleased(0, 202) or IsControlJustReleased(0, 77) then -- Cancel key (Backspace or Delete)
+ inProgress = false
+ waitTimer = 1500
+ displaySpinner(Loc[Config.Lan].error["cancel"])
+ end
+ end
+ Wait(waitTimer)
+ end
+ inProgress = false
+ if data.dict then stopAnim(data.dict, data.anim, ped) end
+ ClearPedTasks(ped)
+ end
+ stopSpinner()
+ result = (wait <= 0)
+ end
+
+ while result == nil do Wait(10) end
+
+ -- Cleanup
+ FreezeEntityPosition(ped, false)
+ lockInv(false)
+ if data.cam then stopTempCam(data.cam) end
+ if result == false and data.shared then
+ debugPrint("^6Bridge^7: ^2Sending cancel to ^6"..storedPID.."^7")
+ TriggerServerEvent(getScript().."server:sharedProg:cancel", storedPID)
+ end
+ storedPID = nil
+ return result
+end
+
+--- Stops the current progress bar.
+---
+--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
+function stopPropgressBar()
+ if Config.System.ProgressBar == "ox" then
+ exports[OXLibExport]:cancelProgress()
+ elseif Config.System.ProgressBar == "qb" then
+ TriggerEvent("progressbar:client:cancel")
+ elseif Config.System.ProgressBar == "gta" then
+ inProgress = false
+ BusyspinnerOff()
+ end
+end
+
+-- System to handle sending/sharing progress bars between players --
+-- For example, healing someone --
+
+local storedPID = nil
+
+--- Server event handler for starting a shared progress bar.
+--- This event is triggered when a player wants to start a progress bar on another player.
+--- It adjusts the data to prevent loops and sends the data to the target client.
+RegisterNetEvent(getScript()..":server:sharedProg:Start", function(data)
+ local pid = data.shared.pid -- Get player ID from the client
+ data.label = data.shared.label -- Set progress bar label to the shared label
+ data.cancel = false -- Make it so it can't be canceled
+ data.dead = true -- Allow progress bar even if player is dead
+ data.shared = nil -- Remove shared info to prevent loops
+ data.anim = nil -- Remove animation so players don't share it
+ debugPrint("^6Bridge^7: ^6"..source.." ^2is sending shared progressBar to player^7, ^6"..pid.."^7")
+ TriggerClientEvent(getScript()..":client:sharedProg:Start", pid, data)
+end)
+
+--- Client event handler for starting a shared progress bar.
+--- This event is triggered when the server wants the client to start a shared progress bar.
+RegisterNetEvent(getScript()..":client:sharedProg:Start", function(data)
+ debugPrint("^6Bridge^7: ^2You have been sent a progressBar^7")
+ progressBar(data)
+end)
+
+--- Server event handler for canceling a shared progress bar.
+--- This event is triggered when a progress bar is canceled and the server needs to notify the other player.
+RegisterNetEvent(getScript()..":server:sharedProg:Cancel", function(pid)
+ debugPrint("^6Bridge^7: ^2Sending cancel progressBar to ^6"..pid.."^7")
+ TriggerClientEvent(getScript()..":client:sharedProg:Cancel", pid)
+end)
+
+--- Client event handler for canceling a shared progress bar.
+--- This event is triggered when the server wants the client to cancel a shared progress bar.
+RegisterNetEvent(getScript()..":client:sharedProg:Cancel", function()
+ debugPrint("^6Bridge^7: ^2Receiving cancel progressBar^7")
+ stopPropgressBar()
+end)
+
+--- Cleans up when the resource stops.
+--- This event handler ensures that any active spinners or progress bars are stopped when the resource is stopped.
+onResourceStop(function() stopSpinner() end, true)
diff --git a/shared/notify.lua b/shared/notify.lua
index f44f380..3741ff5 100644
--- a/shared/notify.lua
+++ b/shared/notify.lua
@@ -1,88 +1,88 @@
--- NOTIFICATIONS --
--- This function is widely used to display notifications to the player, can be used server side or client side --
-
---- Displays notifications to the player using the configured notification system.
----
---- This function supports multiple notification systems based on the `Config.System.Notify` setting.
---- It can be triggered from both client-side and server-side scripts. Depending on the configuration,
---- it utilizes different exports or events to display the notification.
----
----@param title string|nil The title of the notification. Optional, used by certain notification systems.
----@param message string The main message content of the notification.
----@param type string The type/category of the notification (e.g., "success", "error", "info").
----@param src number|nil Optional. The server ID of the player to send the notification to. If `nil`, the notification is sent to the caller.
----
----@usage
---- ```lua
---- -- Client-side usage without specifying a player (shows to the current player)
---- triggerNotify("Success", "You have completed the task!", "success")
----
---- -- Server-side usage specifying a player by their server ID
---- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId)
---- ```
-function triggerNotify(title, message, type, src)
- if Config.System.Notify == "okok" then
- if not src then TriggerEvent('okokNotify:Alert', title, message, 6000, type)
- else TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type) end
- elseif Config.System.Notify == "qb" then
- if not src then TriggerEvent("QBCore:Notify", message, type)
- else TriggerClientEvent("QBCore:Notify", src, message, type) end
- elseif Config.System.Notify == "ox" then
- if not src then TriggerEvent('ox_lib:notify', {title = title, description = message, type = type or "success"})
- else TriggerClientEvent('ox_lib:notify', src, { type = type or "success", title = title, description = message }) end
- elseif Config.System.Notify == "gta" then
- if not src then TriggerEvent(getScript()..":DisplayGTANotify", title, message)
- else TriggerClientEvent(getScript()..":DisplayGTANotify", src, title, message) end
- elseif Config.System.Notify == "esx" then
- if not src then exports["esx_notify"]:Notify(type, 4000, message)
- else TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, title, message) end
- end
-end
-
---- Registers a server-side event to display ESX notifications to clients.
----
---- This event listens for `DisplayESXNotify` and triggers the ESX notification on the client side.
----
---- @param type string The type/category of the notification (e.g., "success", "error", "info").
---- @param title string The title of the notification.
---- @param text string The main message content of the notification.
----
---- @usage
---- ```lua
---- -- Server-side event trigger
---- TriggerClientEvent(getScript()..":DisplayESXNotify", playerId, "success", "Achievement Unlocked", "You have unlocked a new achievement!")
---- ```
-RegisterNetEvent(getScript()..":DisplayESXNotify", function(type, title, text)
- exports["esx_notify"]:Notify(type, 4000, text)
-end)
-
---- Displays default GTA-style text notifications.
----
---- This event handles displaying text-based notifications using GTA's native functions.
---- It supports specific scenarios by assigning different icons based on the script name.
----
----@param title string The title or identifier for the notification, used to select the appropriate icon.
----@param text string The main message content of the notification.
----
----@usage
---- ```lua
---- -- Client-side event trigger
---- TriggerEvent(getScript()..":DisplayGTANotify", "taxiname", "Taxi service has arrived.")
---- ```
-RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text)
- local iconTable = {}
- if getScript() == "jim-npcservice" then
- iconTable = {
- [Loc[Config.Lan].notify["taxiname"]] = "CHAR_TAXI",
- [Loc[Config.Lan].notify["limoname"]] = "CHAR_CASINO",
- [Loc[Config.Lan].notify["ambiname"]] = "CHAR_CALL911",
- [Loc[Config.Lan].notify["pilotname"]] = "CHAR_DEFAULT",
- [Loc[Config.Lan].notify["planename"]] = "CHAR_BOATSITE2",
- [Loc[Config.Lan].notify["heliname"]] = "CHAR_BOATSITE2",
- }
- end
- BeginTextCommandThefeedPost("STRING")
- AddTextComponentSubstringKeyboardDisplay(text)
- EndTextCommandThefeedPostMessagetext(iconTable[title] or "CHAR_DEFAULT", iconTable[title] or "CHAR_DEFAULT", true, 1, title, nil, text)
- EndTextCommandThefeedPostTicker(true, false)
+-- NOTIFICATIONS --
+-- This function is widely used to display notifications to the player, can be used server side or client side --
+
+--- Displays notifications to the player using the configured notification system.
+---
+--- This function supports multiple notification systems based on the `Config.System.Notify` setting.
+--- It can be triggered from both client-side and server-side scripts. Depending on the configuration,
+--- it utilizes different exports or events to display the notification.
+---
+---@param title string|nil The title of the notification. Optional, used by certain notification systems.
+---@param message string The main message content of the notification.
+---@param type string The type/category of the notification (e.g., "success", "error", "info").
+---@param src number|nil Optional. The server ID of the player to send the notification to. If `nil`, the notification is sent to the caller.
+---
+---@usage
+--- ```lua
+--- -- Client-side usage without specifying a player (shows to the current player)
+--- triggerNotify("Success", "You have completed the task!", "success")
+---
+--- -- Server-side usage specifying a player by their server ID
+--- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId)
+--- ```
+function triggerNotify(title, message, type, src)
+ if Config.System.Notify == "okok" then
+ if not src then TriggerEvent('okokNotify:Alert', title, message, 6000, type)
+ else TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type) end
+ elseif Config.System.Notify == "qb" then
+ if not src then TriggerEvent("QBCore:Notify", message, type)
+ else TriggerClientEvent("QBCore:Notify", src, message, type) end
+ elseif Config.System.Notify == "ox" then
+ if not src then TriggerEvent('ox_lib:notify', {title = title, description = message, type = type or "success"})
+ else TriggerClientEvent('ox_lib:notify', src, { type = type or "success", title = title, description = message }) end
+ elseif Config.System.Notify == "gta" then
+ if not src then TriggerEvent(getScript()..":DisplayGTANotify", title, message)
+ else TriggerClientEvent(getScript()..":DisplayGTANotify", src, title, message) end
+ elseif Config.System.Notify == "esx" then
+ if not src then exports["esx_notify"]:Notify(type, 4000, message)
+ else TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, title, message) end
+ end
+end
+
+--- Registers a server-side event to display ESX notifications to clients.
+---
+--- This event listens for `DisplayESXNotify` and triggers the ESX notification on the client side.
+---
+--- @param type string The type/category of the notification (e.g., "success", "error", "info").
+--- @param title string The title of the notification.
+--- @param text string The main message content of the notification.
+---
+--- @usage
+--- ```lua
+--- -- Server-side event trigger
+--- TriggerClientEvent(getScript()..":DisplayESXNotify", playerId, "success", "Achievement Unlocked", "You have unlocked a new achievement!")
+--- ```
+RegisterNetEvent(getScript()..":DisplayESXNotify", function(type, title, text)
+ exports["esx_notify"]:Notify(type, 4000, text)
+end)
+
+--- Displays default GTA-style text notifications.
+---
+--- This event handles displaying text-based notifications using GTA's native functions.
+--- It supports specific scenarios by assigning different icons based on the script name.
+---
+---@param title string The title or identifier for the notification, used to select the appropriate icon.
+---@param text string The main message content of the notification.
+---
+---@usage
+--- ```lua
+--- -- Client-side event trigger
+--- TriggerEvent(getScript()..":DisplayGTANotify", "taxiname", "Taxi service has arrived.")
+--- ```
+RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text)
+ local iconTable = {}
+ if getScript() == "jim-npcservice" then
+ iconTable = {
+ [Loc[Config.Lan].notify["taxiname"]] = "CHAR_TAXI",
+ [Loc[Config.Lan].notify["limoname"]] = "CHAR_CASINO",
+ [Loc[Config.Lan].notify["ambiname"]] = "CHAR_CALL911",
+ [Loc[Config.Lan].notify["pilotname"]] = "CHAR_DEFAULT",
+ [Loc[Config.Lan].notify["planename"]] = "CHAR_BOATSITE2",
+ [Loc[Config.Lan].notify["heliname"]] = "CHAR_BOATSITE2",
+ }
+ end
+ BeginTextCommandThefeedPost("STRING")
+ AddTextComponentSubstringKeyboardDisplay(text)
+ EndTextCommandThefeedPostMessagetext(iconTable[title] or "CHAR_DEFAULT", iconTable[title] or "CHAR_DEFAULT", true, 1, title, nil, text)
+ EndTextCommandThefeedPostTicker(true, false)
end)
\ No newline at end of file
diff --git a/shared/playerfunctions.lua b/shared/playerfunctions.lua
index cda232e..1745b3f 100644
--- a/shared/playerfunctions.lua
+++ b/shared/playerfunctions.lua
@@ -475,6 +475,18 @@ function getPlayer(source)
name = info.getName(),
cash = info.getMoney(),
bank = info.getAccount("bank").money,
+
+ firstname = info.variables.firstName,
+ lastname = info.variables.lastName,
+
+ source = info.source,
+ job = info.job.name,
+ --jobBoss = info.job.isboss,
+ --gang = info.gang.name,
+ --gangBoss = info.gang.isboss,
+ onDuty = info.job.onDuty,
+ --account = info.charinfo.account,
+ --citizenId = info.citizenid,
}
elseif isStarted(OXCoreExport) then
@@ -492,9 +504,19 @@ function getPlayer(source)
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayer(src)
Player = {
+ firstname = info.PlayerData.charinfo.firstname,
+ lastname = info.PlayerData.charinfo.lastname,
name = info.PlayerData.charinfo.firstname.." "..info.PlayerData.charinfo.lastname,
cash = exports[OXInv]:Search(src, 'count', "money"),
bank = info.Functions.GetMoney("bank"),
+ source = info.PlayerData.source,
+ job = info.PlayerData.job.name,
+ jobBoss = info.PlayerData.job.isboss,
+ gang = info.PlayerData.gang.name,
+ gangBoss = info.PlayerData.gang.isboss,
+ onDuty = info.PlayerData.job.onduty,
+ account = info.PlayerData.charinfo.account,
+ citizenId = info.PlayerData.citizenid,
}
elseif isStarted(QBExport) and not isStarted(QBXExport) then
@@ -541,13 +563,26 @@ function getPlayer(source)
else
if isStarted(ESXExport) and ESX ~= nil then
local info = ESX.GetPlayerData()
+ --jsonPrint(info)
local cash, bank = 0, 0
for k, v in pairs(ESX.GetPlayerData().accounts) do
if v.name == "money" then cash = v.money end
if v.name == "bank" then bank = v.money end
end
Player = {
- name = ('%s %s'):format(info.firstName, info.lastName),
+ firstname = info.firstName,
+ lastname = info.lastName,
+
+ source = GetPlayerServerId(PlayerId()),
+ job = info.job.name,
+ --jobBoss = info.job.isboss,
+ --gang = info.gang.name,
+ --gangBoss = info.gang.isboss,
+ onDuty = info.job.onDuty,
+ --account = info.charinfo.account,
+ --citizenId = info.citizenid,
+
+ name = info.firstName.." "..info.lastName,
cash = cash,
bank = bank,
}
diff --git a/shared/polyZone.lua b/shared/polyZone.lua
index 1210551..bcc07e6 100644
--- a/shared/polyZone.lua
+++ b/shared/polyZone.lua
@@ -1,116 +1,116 @@
--- This automatically detects what polyzone script it should use to create a polyzone --
--- if ox_lib is detected, it will automatically use that instead of PolyZone --
--- createPoly({ name = 'name', debug = true, points = { vec2(), vec2() }, onEnter = function() end, onExit = function() end, })
----
---- Creates a polygonal zone using the detected polyzone library (ox_lib or PolyZone).
----
---- This function automatically detects whether `ox_lib` or `PolyZone` is active and creates a polygonal zone accordingly.
---- It supports setting up entry and exit callbacks for the zone.
----
----@param data table A table containing the zone configuration.
---- - **name** (`string`): The name of the zone.
---- - **debug** (`boolean`): Whether to enable debug mode for the zone.
---- - **points** (`table`): A list of `vec2` points defining the polygon.
---- - **onEnter** (`function`): Callback function to execute when a player enters the zone.
---- - **onExit** (`function`): Callback function to execute when a player exits the zone.
----
----@return table|nil table Returns the created zone object or `nil` if creation failed.
----
----@usage
---- ```lua
---- createPoly({
---- name = 'testZone',
---- debug = true,
---- points = { vec2(100.0, 100.0), vec2(200.0, 100.0), vec2(200.0, 200.0), vec2(100.0, 200.0) },
---- onEnter = function() print("Entered Test Zone") end,
---- onExit = function() print("Exited Test Zone") end,
---- })
---- ```
-function createPoly(data)
- local Location = nil
- if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone
- debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name)
- for i = 1, #data.points do
- data.points[i] = vec3(data.points[i].x, data.points[i].y, 12.0)
- end
- data.thickness = 1000
- Location = lib.zones.poly(data)
- elseif isStarted("PolyZone") then
- debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4PolyZone^7': "..data.name)
- Location = PolyZone:Create(data.points, { name = data.name, debugPoly = data.debug })
- Location:onPlayerInOut(function(isPointInside)
- if isPointInside then data.onEnter() else data.onExit() end
- end)
- else
- print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7")
- end
- return Location
-end
-
---- Creates a circular zone using the detected polyzone library (ox_lib or PolyZone).
----
---- This function automatically detects whether `ox_lib` or `PolyZone` is active and creates a circular zone accordingly.
---- It supports setting up entry and exit callbacks for the zone.
----
----@param data table A table containing the circular zone configuration.
---- - **name** (`string`): The name of the circular zone.
---- - **coords** (`vector3`): The center coordinates of the circle.
---- - **radius** (`number`): The radius of the circle.
---- - **onEnter** (`function`): Callback function to execute when a player enters the zone.
---- - **onExit** (`function`): Callback function to execute when a player exits the zone.
----
----@return table|nil table Returns the created circular zone object or `nil` if creation failed.
----
----@usage
---- ```lua
---- createCirclePoly({
---- name = 'circleZone',
---- coords = vector3(150.0, 150.0, 20.0),
---- radius = 50.0,
---- onEnter = function() print("Entered Circle Zone") end,
---- onExit = function() print("Exited Circle Zone") end,
---- })
---- ```
-function createCirclePoly(data)
- local Location = nil
- if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone
- debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name)
- Location = lib.zones.sphere(data)
- elseif isStarted("PolyZone") then
- debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7PolyZone ".. data.name)
- Location = CircleZone:Create(data.coords, data.radius, { name = data.name, debugPoly = debugMode })
- Location:onPlayerInOut(function(isPointInside)
- if isPointInside then
- data.onEnter()
- else
- data.onExit()
- end
- end)
- else
- print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7")
- end
- debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
- return Location
-end
-
---- Removes a previously created polyzone.
----
---- This function detects the active polyzone library (`ox_lib` or `PolyZone`) and removes the specified zone accordingly.
----
---- @param Location table The zone object to be removed.
----
---- @usage
---- ```lua
---- local zone = createPoly({...})
---- -- Later in the code
---- removePolyZone(zone)
---- ```
-function removePolyZone(Location)
- if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone
- debugPrint("^6Bridge^7: ^2Removing ^2poly with ^7"..OXLibExport)
- Location:remove()
- elseif isStarted("PolyZone") then
- debugPrint("^6Bridge^7: ^2poly with ^7PolyZone")
- Location:destroy()
- end
+-- This automatically detects what polyzone script it should use to create a polyzone --
+-- if ox_lib is detected, it will automatically use that instead of PolyZone --
+-- createPoly({ name = 'name', debug = true, points = { vec2(), vec2() }, onEnter = function() end, onExit = function() end, })
+---
+--- Creates a polygonal zone using the detected polyzone library (ox_lib or PolyZone).
+---
+--- This function automatically detects whether `ox_lib` or `PolyZone` is active and creates a polygonal zone accordingly.
+--- It supports setting up entry and exit callbacks for the zone.
+---
+---@param data table A table containing the zone configuration.
+--- - **name** (`string`): The name of the zone.
+--- - **debug** (`boolean`): Whether to enable debug mode for the zone.
+--- - **points** (`table`): A list of `vec2` points defining the polygon.
+--- - **onEnter** (`function`): Callback function to execute when a player enters the zone.
+--- - **onExit** (`function`): Callback function to execute when a player exits the zone.
+---
+---@return table|nil table Returns the created zone object or `nil` if creation failed.
+---
+---@usage
+--- ```lua
+--- createPoly({
+--- name = 'testZone',
+--- debug = true,
+--- points = { vec2(100.0, 100.0), vec2(200.0, 100.0), vec2(200.0, 200.0), vec2(100.0, 200.0) },
+--- onEnter = function() print("Entered Test Zone") end,
+--- onExit = function() print("Exited Test Zone") end,
+--- })
+--- ```
+function createPoly(data)
+ local Location = nil
+ if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone
+ debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name)
+ for i = 1, #data.points do
+ data.points[i] = vec3(data.points[i].x, data.points[i].y, 12.0)
+ end
+ data.thickness = 1000
+ Location = lib.zones.poly(data)
+ elseif isStarted("PolyZone") then
+ debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4PolyZone^7': "..data.name)
+ Location = PolyZone:Create(data.points, { name = data.name, debugPoly = data.debug })
+ Location:onPlayerInOut(function(isPointInside)
+ if isPointInside then data.onEnter() else data.onExit() end
+ end)
+ else
+ print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+ return Location
+end
+
+--- Creates a circular zone using the detected polyzone library (ox_lib or PolyZone).
+---
+--- This function automatically detects whether `ox_lib` or `PolyZone` is active and creates a circular zone accordingly.
+--- It supports setting up entry and exit callbacks for the zone.
+---
+---@param data table A table containing the circular zone configuration.
+--- - **name** (`string`): The name of the circular zone.
+--- - **coords** (`vector3`): The center coordinates of the circle.
+--- - **radius** (`number`): The radius of the circle.
+--- - **onEnter** (`function`): Callback function to execute when a player enters the zone.
+--- - **onExit** (`function`): Callback function to execute when a player exits the zone.
+---
+---@return table|nil table Returns the created circular zone object or `nil` if creation failed.
+---
+---@usage
+--- ```lua
+--- createCirclePoly({
+--- name = 'circleZone',
+--- coords = vector3(150.0, 150.0, 20.0),
+--- radius = 50.0,
+--- onEnter = function() print("Entered Circle Zone") end,
+--- onExit = function() print("Exited Circle Zone") end,
+--- })
+--- ```
+function createCirclePoly(data)
+ local Location = nil
+ if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone
+ debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name)
+ Location = lib.zones.sphere(data)
+ elseif isStarted("PolyZone") then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7PolyZone ".. data.name)
+ Location = CircleZone:Create(data.coords, data.radius, { name = data.name, debugPoly = debugMode })
+ Location:onPlayerInOut(function(isPointInside)
+ if isPointInside then
+ data.onEnter()
+ else
+ data.onExit()
+ end
+ end)
+ else
+ print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+ debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
+ return Location
+end
+
+--- Removes a previously created polyzone.
+---
+--- This function detects the active polyzone library (`ox_lib` or `PolyZone`) and removes the specified zone accordingly.
+---
+--- @param Location table The zone object to be removed.
+---
+--- @usage
+--- ```lua
+--- local zone = createPoly({...})
+--- -- Later in the code
+--- removePolyZone(zone)
+--- ```
+function removePolyZone(Location)
+ if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone
+ debugPrint("^6Bridge^7: ^2Removing ^2poly with ^7"..OXLibExport)
+ Location:remove()
+ elseif isStarted("PolyZone") then
+ debugPrint("^6Bridge^7: ^2poly with ^7PolyZone")
+ Location:destroy()
+ end
end
\ No newline at end of file
diff --git a/shared/scaleEntity.lua b/shared/scaleEntity.lua
index e4d2a5e..1247ebe 100644
--- a/shared/scaleEntity.lua
+++ b/shared/scaleEntity.lua
@@ -1,77 +1,77 @@
-local cacheOrigScale = {}
-local initialOffset = {}
-
---- Sets the scale of an entity.
----
---- This function scales an entity by adjusting its forward, right, and up vectors.
---- It also applies an initial offset to maintain the entity's position relative to the ground.
----
----@param entity number The entity ID to scale.
----@param scale number The scale factor to apply to the entity.
----
----@usage
---- ```lua
---- -- Scale an entity to twice its original size
---- SetEntityScale(entityId, 2.0)
---- ```
-function SetEntityScale(entity, scale)
- local forward, right, up = GetEntityMatrix(entity)
- if not cacheOrigScale[entity] then
- cacheOrigScale[entity] = {
- forward = forward,
- right = right,
- up = up
- }
- end
- local minDim, maxDim = GetModelDimensions(GetEntityModel(entity))
- local originalHeight = maxDim.z - minDim.z
- local newHeight = originalHeight * scale
- initialOffset[entity] = (newHeight - originalHeight) / 3
-
- local forwardTemp = cacheOrigScale[entity].forward * scale
- local rightTemp = cacheOrigScale[entity].right * scale
- local upTemp = cacheOrigScale[entity].up * scale
-
- -- Apply the initial offset to the current position
- local currentPosition = GetEntityCoords(entity)
- local newPosition = vector3(currentPosition.x, currentPosition.y, currentPosition.z + initialOffset[entity])
-
- SetEntityMatrix(entity, forwardTemp, rightTemp, upTemp, currentPosition)
-end
-
---- Resets the scale of an entity to its original values.
----
---- This function restores an entity's original forward, right, and up vectors,
---- effectively undoing any scaling applied by `SetEntityScale`.
----
----@param entity number The entity ID to reset.
----
----@usage
---- ```lua
---- -- Reset the scale of an entity
---- resetScale(entityId)
---- ```
-function resetScale(entity)
- if cacheOrigScale[entity] then
- SetEntityMatrix(entity, cacheOrigScale[entity].forward, cacheOrigScale[entity].right, cacheOrigScale[entity].up, GetEntityCoords(entity))
- cacheOrigScale[entity] = nil
- end
-end
-
---[[
-CreateThread(function()
- -- Example usage:
- -- local prop = makeProp({prop = "v_res_r_figcat", coords = vec4(-1025.88, -1417.58, 5.43, 76.30)}, false, false)
- -- local ped = makePed(`a_c_cat_01`, vec4(-1022.42, -1429.97, 13.79, 68.36), true, false, nil)
- -- SetEntityCollision(prop, false, true)
-
- SetEntityScale(prop, 12)
- --[[CreateThread(function()
- while true do
- Wait(1000)
- resetScale(prop)
- Wait(1000)
- end
- end)
-end)
+local cacheOrigScale = {}
+local initialOffset = {}
+
+--- Sets the scale of an entity.
+---
+--- This function scales an entity by adjusting its forward, right, and up vectors.
+--- It also applies an initial offset to maintain the entity's position relative to the ground.
+---
+---@param entity number The entity ID to scale.
+---@param scale number The scale factor to apply to the entity.
+---
+---@usage
+--- ```lua
+--- -- Scale an entity to twice its original size
+--- SetEntityScale(entityId, 2.0)
+--- ```
+function SetEntityScale(entity, scale)
+ local forward, right, up = GetEntityMatrix(entity)
+ if not cacheOrigScale[entity] then
+ cacheOrigScale[entity] = {
+ forward = forward,
+ right = right,
+ up = up
+ }
+ end
+ local minDim, maxDim = GetModelDimensions(GetEntityModel(entity))
+ local originalHeight = maxDim.z - minDim.z
+ local newHeight = originalHeight * scale
+ initialOffset[entity] = (newHeight - originalHeight) / 3
+
+ local forwardTemp = cacheOrigScale[entity].forward * scale
+ local rightTemp = cacheOrigScale[entity].right * scale
+ local upTemp = cacheOrigScale[entity].up * scale
+
+ -- Apply the initial offset to the current position
+ local currentPosition = GetEntityCoords(entity)
+ local newPosition = vector3(currentPosition.x, currentPosition.y, currentPosition.z + initialOffset[entity])
+
+ SetEntityMatrix(entity, forwardTemp, rightTemp, upTemp, currentPosition)
+end
+
+--- Resets the scale of an entity to its original values.
+---
+--- This function restores an entity's original forward, right, and up vectors,
+--- effectively undoing any scaling applied by `SetEntityScale`.
+---
+---@param entity number The entity ID to reset.
+---
+---@usage
+--- ```lua
+--- -- Reset the scale of an entity
+--- resetScale(entityId)
+--- ```
+function resetScale(entity)
+ if cacheOrigScale[entity] then
+ SetEntityMatrix(entity, cacheOrigScale[entity].forward, cacheOrigScale[entity].right, cacheOrigScale[entity].up, GetEntityCoords(entity))
+ cacheOrigScale[entity] = nil
+ end
+end
+
+--[[
+CreateThread(function()
+ -- Example usage:
+ -- local prop = makeProp({prop = "v_res_r_figcat", coords = vec4(-1025.88, -1417.58, 5.43, 76.30)}, false, false)
+ -- local ped = makePed(`a_c_cat_01`, vec4(-1022.42, -1429.97, 13.79, 68.36), true, false, nil)
+ -- SetEntityCollision(prop, false, true)
+
+ SetEntityScale(prop, 12)
+ --[[CreateThread(function()
+ while true do
+ Wait(1000)
+ resetScale(prop)
+ Wait(1000)
+ end
+ end)
+end)
]]
\ No newline at end of file
diff --git a/shared/scaleforms.lua b/shared/scaleforms.lua
index 0dece47..2fd04bf 100644
--- a/shared/scaleforms.lua
+++ b/shared/scaleforms.lua
@@ -1,61 +1,61 @@
-function makeInstructionalButtons(info)
- local build = RequestScaleformMovie("instructional_buttons")
- while not HasScaleformMovieLoaded(build) do Wait(0) end
-
- DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
- BeginScaleformMovieMethod(build, "CLEAR_ALL")
- EndScaleformMovieMethod()
- BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
- ScaleformMovieMethodAddParamInt(200)
- EndScaleformMovieMethod()
-
- for i = 1, #info do
- BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
- ScaleformMovieMethodAddParamInt(i - 1)
- for k = 1, #info[i].keys do
- ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
- end
- BeginTextCommandScaleformString("STRING")
- AddTextComponentSubstringKeyboardDisplay(info[i].text)
- EndTextCommandScaleformString()
- EndScaleformMovieMethod()
- end
-
- BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS")
- EndScaleformMovieMethod()
- BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR")
- ScaleformMovieMethodAddParamInt(0)
- ScaleformMovieMethodAddParamInt(0)
- ScaleformMovieMethodAddParamInt(0)
- ScaleformMovieMethodAddParamInt(80)
- EndScaleformMovieMethod()
-
- DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0)
-end
-
--- Testing showing variables on the screen instead of only in f8
-function debugScaleForm(textTable, loc)
- if debugMode then
- -- Define the display position (top left corner)
- local loc = loc or vec2(0.05, 0.65)
-
- -- Calculate dynamic height based on the number of lines in the textTable
- local lineHeight = 0.025 -- Height of each line of text
- local totalHeight = #textTable * lineHeight -- Dynamic height based on number of lines
- local boxPadding = 0.01 -- Padding to add around the text inside the box
- local size = vec2(0.18, totalHeight + boxPadding * 2) -- Width remains fixed, height is dynamic
-
- DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255)
-
- for i = 1, #textTable do
- local textLine = textTable[i]
-
- SetTextScale(0.30, 0.30)
-
- BeginTextCommandDisplayText("STRING")
- AddTextComponentSubstringKeyboardDisplay(textLine)
-
- EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
- end
- end
+function makeInstructionalButtons(info)
+ local build = RequestScaleformMovie("instructional_buttons")
+ while not HasScaleformMovieLoaded(build) do Wait(0) end
+
+ DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
+ BeginScaleformMovieMethod(build, "CLEAR_ALL")
+ EndScaleformMovieMethod()
+ BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
+ ScaleformMovieMethodAddParamInt(200)
+ EndScaleformMovieMethod()
+
+ for i = 1, #info do
+ BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
+ ScaleformMovieMethodAddParamInt(i - 1)
+ for k = 1, #info[i].keys do
+ ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
+ end
+ BeginTextCommandScaleformString("STRING")
+ AddTextComponentSubstringKeyboardDisplay(info[i].text)
+ EndTextCommandScaleformString()
+ EndScaleformMovieMethod()
+ end
+
+ BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS")
+ EndScaleformMovieMethod()
+ BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR")
+ ScaleformMovieMethodAddParamInt(0)
+ ScaleformMovieMethodAddParamInt(0)
+ ScaleformMovieMethodAddParamInt(0)
+ ScaleformMovieMethodAddParamInt(80)
+ EndScaleformMovieMethod()
+
+ DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0)
+end
+
+-- Testing showing variables on the screen instead of only in f8
+function debugScaleForm(textTable, loc)
+ if debugMode then
+ -- Define the display position (top left corner)
+ local loc = loc or vec2(0.05, 0.65)
+
+ -- Calculate dynamic height based on the number of lines in the textTable
+ local lineHeight = 0.025 -- Height of each line of text
+ local totalHeight = #textTable * lineHeight -- Dynamic height based on number of lines
+ local boxPadding = 0.01 -- Padding to add around the text inside the box
+ local size = vec2(0.18, totalHeight + boxPadding * 2) -- Width remains fixed, height is dynamic
+
+ DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255)
+
+ for i = 1, #textTable do
+ local textLine = textTable[i]
+
+ SetTextScale(0.30, 0.30)
+
+ BeginTextCommandDisplayText("STRING")
+ AddTextComponentSubstringKeyboardDisplay(textLine)
+
+ EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
+ end
+ end
end
\ No newline at end of file
diff --git a/shared/scaleforms/bigMessageInstance.lua b/shared/scaleforms/bigMessageInstance.lua
index 76cb4b7..ae581cb 100644
--- a/shared/scaleforms/bigMessageInstance.lua
+++ b/shared/scaleforms/bigMessageInstance.lua
@@ -1,277 +1,277 @@
-BigMessage = {}
-BigMessage.__index = BigMessage
-
-function BigMessage:new()
- local self = setmetatable({}, BigMessage)
- self.scaleform = nil
- self.startTime = 0
- self.duration = 0
- self.transition = "TRANSITION_OUT"
- self.transitionDuration = 0.15
- self.transitionPreventAutoExpansion = false
- self.transitionExecuted = false
- self.manualDispose = false
- self.isDisplaying = false
- return self
-end
-
-function BigMessage:Load()
- if self.scaleform then return end
- self.scaleform = RequestScaleformMovie("MP_BIG_MESSAGE_FREEMODE")
- while not HasScaleformMovieLoaded(self.scaleform) do
- Wait(0)
- end
-end
-
--- Dispose of the scaleform
-function BigMessage:Dispose()
- if not self.scaleform then return end
-
- if self.manualDispose then
- BeginScaleformMovieMethod(self.scaleform, self.transition)
- ScaleformMovieMethodAddParamBool(false)
- ScaleformMovieMethodAddParamFloat(self.transitionDuration)
- ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
- EndScaleformMovieMethod()
-
- Wait((self.transitionDuration * 0.5) * 1000)
-
- self.manualDispose = false
- end
-
- self.startTime = 0
- self.transitionExecuted = false
- SetScaleformMovieAsNoLongerNeeded(self.scaleform)
- self.scaleform = nil
- self.isDisplaying = false
-end
-
-function BigMessage:Update()
- if not self.scaleform then return end
- DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
-
- if self.manualDispose then return end
-
- if self.startTime ~= 0 and (GetGameTimer() - self.startTime) > self.duration then
- if not self.transitionExecuted then
- BeginScaleformMovieMethod(self.scaleform, self.transition)
- ScaleformMovieMethodAddParamBool(false)
- ScaleformMovieMethodAddParamFloat(self.transitionDuration)
- ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
- EndScaleformMovieMethod()
- self.transitionExecuted = true
- self.duration = self.duration + ((self.transitionDuration * 0.5) * 1000)
- else
- self:Dispose()
- end
- end
-end
-
-function BigMessage:SetTransition(transition, duration, preventAutoExpansion)
- self.transition = transition or "TRANSITION_OUT"
- self.transitionDuration = duration or 0.4
- self.transitionPreventAutoExpansion = preventAutoExpansion or true
-end
-
-function BigMessage:StartUpdate()
- if self.isDisplaying then return end
- self.isDisplaying = true
- CreateThread(function()
- while self.isDisplaying do
- Wait(0)
- self:Update()
- end
- end)
-end
-
---- Displays a mission passed message.
----
---- @param msg string The main message to display.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
-function BigMessage:ShowMissionPassedMessage(msg, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
- ScaleformMovieMethodAddParamPlayerNameString(msg)
- ScaleformMovieMethodAddParamPlayerNameString("")
- ScaleformMovieMethodAddParamInt(100)
- ScaleformMovieMethodAddParamBool(true)
- ScaleformMovieMethodAddParamInt(0)
- ScaleformMovieMethodAddParamBool(true)
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
---- Displays a colored shard message.
----
---- @param msg string The main message to display.
---- @param desc string The description text.
---- @param textColor number The color index for the text.
---- @param bgColor number The color index for the background.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
-function BigMessage:ShowColoredShard(msg, desc, textColor, bgColor, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CENTERED_MP_MESSAGE")
- ScaleformMovieMethodAddParamPlayerNameString(msg)
- ScaleformMovieMethodAddParamPlayerNameString(desc)
- ScaleformMovieMethodAddParamInt(bgColor)
- ScaleformMovieMethodAddParamInt(textColor)
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
---- Displays an old-style mission passed message.
----
---- @param msg string The main message to display.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
----
---- @return void
-function BigMessage:ShowOldMessage(msg, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
- ScaleformMovieMethodAddParamPlayerNameString(msg)
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
---- Displays a simple shard message.
----
---- @param msg string The main message to display.
---- @param subtitle string The subtitle text.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
----
---- @return void
-function BigMessage:ShowSimpleShard(msg, subtitle, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CREW_RANKUP_MP_MESSAGE")
- ScaleformMovieMethodAddParamPlayerNameString(msg)
- ScaleformMovieMethodAddParamPlayerNameString(subtitle)
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
---- Displays a rank-up message.
----
---- @param msg string The main message to display.
---- @param subtitle string The subtitle text.
---- @param rank number The rank level achieved.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
-function BigMessage:ShowRankupMessage(msg, subtitle, rank, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_BIG_MP_MESSAGE")
- ScaleformMovieMethodAddParamPlayerNameString(msg)
- ScaleformMovieMethodAddParamPlayerNameString(subtitle)
- ScaleformMovieMethodAddParamInt(rank)
- ScaleformMovieMethodAddParamPlayerNameString("")
- ScaleformMovieMethodAddParamPlayerNameString("")
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
---- Displays a weapon purchased message.
----
---- @param bigMessage string The main message to display.
---- @param weaponName string The name of the weapon purchased.
---- @param weaponHash number The hash identifier of the weapon.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
-function BigMessage:ShowWeaponPurchasedMessage(bigMessage, weaponName, weaponHash, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_WEAPON_PURCHASED")
- ScaleformMovieMethodAddParamPlayerNameString(bigMessage)
- ScaleformMovieMethodAddParamPlayerNameString(weaponName)
- ScaleformMovieMethodAddParamInt(weaponHash)
- ScaleformMovieMethodAddParamPlayerNameString("")
- ScaleformMovieMethodAddParamInt(100)
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
---- Displays a large multiplayer message.
----
---- @param msg string The main message to display.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
-function BigMessage:ShowMpMessageLarge(msg, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_CENTERED_MP_MESSAGE_LARGE")
- ScaleformMovieMethodAddParamPlayerNameString(msg)
- ScaleformMovieMethodAddParamPlayerNameString("")
- ScaleformMovieMethodAddParamInt(100)
- ScaleformMovieMethodAddParamBool(true)
- ScaleformMovieMethodAddParamInt(100)
- EndScaleformMovieMethod()
-
- BeginScaleformMovieMethod(self.scaleform, "TRANSITION_IN")
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
---- Displays a "Wasted" multiplayer message.
----
---- @param msg string The main message to display.
---- @param subtitle string The subtitle text.
---- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
---- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
-function BigMessage:ShowMpWastedMessage(msg, subtitle, duration, manualDispose)
- duration = duration or 5000
- self:Load()
- self.startTime = GetGameTimer()
- self.manualDispose = manualDispose or false
-
- BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_WASTED_MP_MESSAGE")
- ScaleformMovieMethodAddParamPlayerNameString(msg)
- ScaleformMovieMethodAddParamPlayerNameString(subtitle)
- EndScaleformMovieMethod()
-
- self.duration = duration
- self:StartUpdate()
-end
-
+BigMessage = {}
+BigMessage.__index = BigMessage
+
+function BigMessage:new()
+ local self = setmetatable({}, BigMessage)
+ self.scaleform = nil
+ self.startTime = 0
+ self.duration = 0
+ self.transition = "TRANSITION_OUT"
+ self.transitionDuration = 0.15
+ self.transitionPreventAutoExpansion = false
+ self.transitionExecuted = false
+ self.manualDispose = false
+ self.isDisplaying = false
+ return self
+end
+
+function BigMessage:Load()
+ if self.scaleform then return end
+ self.scaleform = RequestScaleformMovie("MP_BIG_MESSAGE_FREEMODE")
+ while not HasScaleformMovieLoaded(self.scaleform) do
+ Wait(0)
+ end
+end
+
+-- Dispose of the scaleform
+function BigMessage:Dispose()
+ if not self.scaleform then return end
+
+ if self.manualDispose then
+ BeginScaleformMovieMethod(self.scaleform, self.transition)
+ ScaleformMovieMethodAddParamBool(false)
+ ScaleformMovieMethodAddParamFloat(self.transitionDuration)
+ ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
+ EndScaleformMovieMethod()
+
+ Wait((self.transitionDuration * 0.5) * 1000)
+
+ self.manualDispose = false
+ end
+
+ self.startTime = 0
+ self.transitionExecuted = false
+ SetScaleformMovieAsNoLongerNeeded(self.scaleform)
+ self.scaleform = nil
+ self.isDisplaying = false
+end
+
+function BigMessage:Update()
+ if not self.scaleform then return end
+ DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
+
+ if self.manualDispose then return end
+
+ if self.startTime ~= 0 and (GetGameTimer() - self.startTime) > self.duration then
+ if not self.transitionExecuted then
+ BeginScaleformMovieMethod(self.scaleform, self.transition)
+ ScaleformMovieMethodAddParamBool(false)
+ ScaleformMovieMethodAddParamFloat(self.transitionDuration)
+ ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
+ EndScaleformMovieMethod()
+ self.transitionExecuted = true
+ self.duration = self.duration + ((self.transitionDuration * 0.5) * 1000)
+ else
+ self:Dispose()
+ end
+ end
+end
+
+function BigMessage:SetTransition(transition, duration, preventAutoExpansion)
+ self.transition = transition or "TRANSITION_OUT"
+ self.transitionDuration = duration or 0.4
+ self.transitionPreventAutoExpansion = preventAutoExpansion or true
+end
+
+function BigMessage:StartUpdate()
+ if self.isDisplaying then return end
+ self.isDisplaying = true
+ CreateThread(function()
+ while self.isDisplaying do
+ Wait(0)
+ self:Update()
+ end
+ end)
+end
+
+--- Displays a mission passed message.
+---
+--- @param msg string The main message to display.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+function BigMessage:ShowMissionPassedMessage(msg, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
+ ScaleformMovieMethodAddParamPlayerNameString(msg)
+ ScaleformMovieMethodAddParamPlayerNameString("")
+ ScaleformMovieMethodAddParamInt(100)
+ ScaleformMovieMethodAddParamBool(true)
+ ScaleformMovieMethodAddParamInt(0)
+ ScaleformMovieMethodAddParamBool(true)
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
+--- Displays a colored shard message.
+---
+--- @param msg string The main message to display.
+--- @param desc string The description text.
+--- @param textColor number The color index for the text.
+--- @param bgColor number The color index for the background.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+function BigMessage:ShowColoredShard(msg, desc, textColor, bgColor, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CENTERED_MP_MESSAGE")
+ ScaleformMovieMethodAddParamPlayerNameString(msg)
+ ScaleformMovieMethodAddParamPlayerNameString(desc)
+ ScaleformMovieMethodAddParamInt(bgColor)
+ ScaleformMovieMethodAddParamInt(textColor)
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
+--- Displays an old-style mission passed message.
+---
+--- @param msg string The main message to display.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+---
+--- @return void
+function BigMessage:ShowOldMessage(msg, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
+ ScaleformMovieMethodAddParamPlayerNameString(msg)
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
+--- Displays a simple shard message.
+---
+--- @param msg string The main message to display.
+--- @param subtitle string The subtitle text.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+---
+--- @return void
+function BigMessage:ShowSimpleShard(msg, subtitle, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CREW_RANKUP_MP_MESSAGE")
+ ScaleformMovieMethodAddParamPlayerNameString(msg)
+ ScaleformMovieMethodAddParamPlayerNameString(subtitle)
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
+--- Displays a rank-up message.
+---
+--- @param msg string The main message to display.
+--- @param subtitle string The subtitle text.
+--- @param rank number The rank level achieved.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+function BigMessage:ShowRankupMessage(msg, subtitle, rank, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_BIG_MP_MESSAGE")
+ ScaleformMovieMethodAddParamPlayerNameString(msg)
+ ScaleformMovieMethodAddParamPlayerNameString(subtitle)
+ ScaleformMovieMethodAddParamInt(rank)
+ ScaleformMovieMethodAddParamPlayerNameString("")
+ ScaleformMovieMethodAddParamPlayerNameString("")
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
+--- Displays a weapon purchased message.
+---
+--- @param bigMessage string The main message to display.
+--- @param weaponName string The name of the weapon purchased.
+--- @param weaponHash number The hash identifier of the weapon.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+function BigMessage:ShowWeaponPurchasedMessage(bigMessage, weaponName, weaponHash, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_WEAPON_PURCHASED")
+ ScaleformMovieMethodAddParamPlayerNameString(bigMessage)
+ ScaleformMovieMethodAddParamPlayerNameString(weaponName)
+ ScaleformMovieMethodAddParamInt(weaponHash)
+ ScaleformMovieMethodAddParamPlayerNameString("")
+ ScaleformMovieMethodAddParamInt(100)
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
+--- Displays a large multiplayer message.
+---
+--- @param msg string The main message to display.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+function BigMessage:ShowMpMessageLarge(msg, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_CENTERED_MP_MESSAGE_LARGE")
+ ScaleformMovieMethodAddParamPlayerNameString(msg)
+ ScaleformMovieMethodAddParamPlayerNameString("")
+ ScaleformMovieMethodAddParamInt(100)
+ ScaleformMovieMethodAddParamBool(true)
+ ScaleformMovieMethodAddParamInt(100)
+ EndScaleformMovieMethod()
+
+ BeginScaleformMovieMethod(self.scaleform, "TRANSITION_IN")
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
+--- Displays a "Wasted" multiplayer message.
+---
+--- @param msg string The main message to display.
+--- @param subtitle string The subtitle text.
+--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
+--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false.
+function BigMessage:ShowMpWastedMessage(msg, subtitle, duration, manualDispose)
+ duration = duration or 5000
+ self:Load()
+ self.startTime = GetGameTimer()
+ self.manualDispose = manualDispose or false
+
+ BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_WASTED_MP_MESSAGE")
+ ScaleformMovieMethodAddParamPlayerNameString(msg)
+ ScaleformMovieMethodAddParamPlayerNameString(subtitle)
+ EndScaleformMovieMethod()
+
+ self.duration = duration
+ self:StartUpdate()
+end
+
return BigMessage
\ No newline at end of file
diff --git a/shared/scaleforms/countDownHandler.lua b/shared/scaleforms/countDownHandler.lua
index c271d82..3cb1fc9 100644
--- a/shared/scaleforms/countDownHandler.lua
+++ b/shared/scaleforms/countDownHandler.lua
@@ -1,116 +1,116 @@
-CountdownHandler = {}
-CountdownHandler.__index = CountdownHandler
-
-function CountdownHandler:new()
- local self = setmetatable({}, CountdownHandler)
- self.scaleform = nil
- self.renderCountdown = false
- self.colour = { r = 255, g = 255, b = 255, a = 255 }
- return self
-end
-
-function CountdownHandler:Load()
- if self.scaleform then return end
- self.scaleform = RequestScaleformMovie("COUNTDOWN")
- while not HasScaleformMovieLoaded(self.scaleform) do
- Wait(0)
- end
-end
-
-function CountdownHandler:Dispose()
- if self.scaleform then
- SetScaleformMovieAsNoLongerNeeded(self.scaleform)
- self.scaleform = nil
- end
-end
-
-function CountdownHandler:Update()
- if self.scaleform then
- DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
- end
-end
-
-function CountdownHandler:ShowMessage(message)
- local r, g, b, a = self.colour.r, self.colour.g, self.colour.b, self.colour.a
-
- BeginScaleformMovieMethod(self.scaleform, "SET_MESSAGE")
- ScaleformMovieMethodAddParamPlayerNameString(message)
- ScaleformMovieMethodAddParamInt(r)
- ScaleformMovieMethodAddParamInt(g)
- ScaleformMovieMethodAddParamInt(b)
- ScaleformMovieMethodAddParamBool(true)
- EndScaleformMovieMethod()
-
- BeginScaleformMovieMethod(self.scaleform, "FADE_MP")
- ScaleformMovieMethodAddParamPlayerNameString(message)
- ScaleformMovieMethodAddParamInt(r)
- ScaleformMovieMethodAddParamInt(g)
- ScaleformMovieMethodAddParamInt(b)
- EndScaleformMovieMethod()
-end
-
---- Starts the countdown with the specified number and HUD color.
----
---- @param number number|nil The starting number for the countdown. Defaults to 3.
---- @param hudColour number|nil The HUD color index. Defaults to 18.
----
---- @return boolean `true` when the countdown has finished.
----
---- @usage
---- ```lua
---- -- Start a countdown of 5 seconds with HUD color 25
---- if CountdownHandler:Start(5, 25) then
---- print("Countdown Complete")
---- end
---- ```
-function CountdownHandler:Start(number, hudColour)
- local finished = false
- number = number or 3
- hudColour = hudColour or 18
-
- local r, g, b, a = GetHudColour(hudColour)
- self.colour = { r = r, g = g, b = b, a = a }
-
- self:Load()
-
- self.renderCountdown = true
- CreateThread(function()
- while self.renderCountdown do
- Wait(0)
- self:Update()
- end
- end)
-
- -- Begin the countdown
- CreateThread(function()
- local currentNumber = number
- while currentNumber > 0 do
- -- Play countdown sound
- playSound("Count")
- self:ShowMessage(tostring(currentNumber))
- Wait(1000)
- currentNumber = currentNumber - 1
- end
- playSound("Go")
-
- self:ShowMessage("GO")
- finished = true
-
- Wait(1000)
- self.renderCountdown = false
- self:Dispose()
- finished = true
- end)
- while not finished do Wait(10) end
- return true
-end
-
--- Create an instance of CountdownHandler
-CountdownHandler = CountdownHandler:new()
-
--- Optional: Register an event to start the countdown
-RegisterNetEvent(getScript()..":startCountdown", function(number, hudColour)
- CountdownHandler:Start(number, hudColour)
-end)
-
+CountdownHandler = {}
+CountdownHandler.__index = CountdownHandler
+
+function CountdownHandler:new()
+ local self = setmetatable({}, CountdownHandler)
+ self.scaleform = nil
+ self.renderCountdown = false
+ self.colour = { r = 255, g = 255, b = 255, a = 255 }
+ return self
+end
+
+function CountdownHandler:Load()
+ if self.scaleform then return end
+ self.scaleform = RequestScaleformMovie("COUNTDOWN")
+ while not HasScaleformMovieLoaded(self.scaleform) do
+ Wait(0)
+ end
+end
+
+function CountdownHandler:Dispose()
+ if self.scaleform then
+ SetScaleformMovieAsNoLongerNeeded(self.scaleform)
+ self.scaleform = nil
+ end
+end
+
+function CountdownHandler:Update()
+ if self.scaleform then
+ DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
+ end
+end
+
+function CountdownHandler:ShowMessage(message)
+ local r, g, b, a = self.colour.r, self.colour.g, self.colour.b, self.colour.a
+
+ BeginScaleformMovieMethod(self.scaleform, "SET_MESSAGE")
+ ScaleformMovieMethodAddParamPlayerNameString(message)
+ ScaleformMovieMethodAddParamInt(r)
+ ScaleformMovieMethodAddParamInt(g)
+ ScaleformMovieMethodAddParamInt(b)
+ ScaleformMovieMethodAddParamBool(true)
+ EndScaleformMovieMethod()
+
+ BeginScaleformMovieMethod(self.scaleform, "FADE_MP")
+ ScaleformMovieMethodAddParamPlayerNameString(message)
+ ScaleformMovieMethodAddParamInt(r)
+ ScaleformMovieMethodAddParamInt(g)
+ ScaleformMovieMethodAddParamInt(b)
+ EndScaleformMovieMethod()
+end
+
+--- Starts the countdown with the specified number and HUD color.
+---
+--- @param number number|nil The starting number for the countdown. Defaults to 3.
+--- @param hudColour number|nil The HUD color index. Defaults to 18.
+---
+--- @return boolean `true` when the countdown has finished.
+---
+--- @usage
+--- ```lua
+--- -- Start a countdown of 5 seconds with HUD color 25
+--- if CountdownHandler:Start(5, 25) then
+--- print("Countdown Complete")
+--- end
+--- ```
+function CountdownHandler:Start(number, hudColour)
+ local finished = false
+ number = number or 3
+ hudColour = hudColour or 18
+
+ local r, g, b, a = GetHudColour(hudColour)
+ self.colour = { r = r, g = g, b = b, a = a }
+
+ self:Load()
+
+ self.renderCountdown = true
+ CreateThread(function()
+ while self.renderCountdown do
+ Wait(0)
+ self:Update()
+ end
+ end)
+
+ -- Begin the countdown
+ CreateThread(function()
+ local currentNumber = number
+ while currentNumber > 0 do
+ -- Play countdown sound
+ playSound("Count")
+ self:ShowMessage(tostring(currentNumber))
+ Wait(1000)
+ currentNumber = currentNumber - 1
+ end
+ playSound("Go")
+
+ self:ShowMessage("GO")
+ finished = true
+
+ Wait(1000)
+ self.renderCountdown = false
+ self:Dispose()
+ finished = true
+ end)
+ while not finished do Wait(10) end
+ return true
+end
+
+-- Create an instance of CountdownHandler
+CountdownHandler = CountdownHandler:new()
+
+-- Optional: Register an event to start the countdown
+RegisterNetEvent(getScript()..":startCountdown", function(number, hudColour)
+ CountdownHandler:Start(number, hudColour)
+end)
+
return CountdownHandler
\ No newline at end of file
diff --git a/shared/scaleforms/debugScaleform.lua b/shared/scaleforms/debugScaleform.lua
index a7a7767..b6cc394 100644
--- a/shared/scaleforms/debugScaleform.lua
+++ b/shared/scaleforms/debugScaleform.lua
@@ -1,41 +1,41 @@
-
---- Displays debug information on the player's screen.
----
---- This function renders a semi-transparent box with multiple lines of text for debugging purposes.
---- It is controlled by the `debugMode` flag and can be positioned dynamically on the screen.
----
---- @param textTable table A table containing strings to display.
---- @param loc vector2|nil Optional. The screen position to display the debug box. Defaults to `vec2(0.05, 0.65)`.
----
---- @usage
---- ```lua
---- debugScaleForm({
---- "Player Position: X=123.45 Y=678.90 Z=12.34",
---- "Current Action: Running",
---- })
---- ```
-function debugScaleForm(textTable, loc)
- if debugMode then
- -- Define the display position (top left corner)
- local loc = loc or vec2(0.05, 0.65)
-
- -- Calculate dynamic height based on the number of lines in the textTable
- local lineHeight = 0.025 -- Height of each line of text
- local totalHeight = #textTable * lineHeight -- Dynamic height based on number of lines
- local boxPadding = 0.01 -- Padding to add around the text inside the box
- local size = vec2(0.18, totalHeight + boxPadding * 2) -- Width remains fixed, height is dynamic
-
- DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255)
-
- for i = 1, #textTable do
- local textLine = textTable[i]
-
- SetTextScale(0.30, 0.30)
-
- BeginTextCommandDisplayText("STRING")
- AddTextComponentSubstringKeyboardDisplay(textLine)
-
- EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
- end
- end
-end
+
+--- Displays debug information on the player's screen.
+---
+--- This function renders a semi-transparent box with multiple lines of text for debugging purposes.
+--- It is controlled by the `debugMode` flag and can be positioned dynamically on the screen.
+---
+--- @param textTable table A table containing strings to display.
+--- @param loc vector2|nil Optional. The screen position to display the debug box. Defaults to `vec2(0.05, 0.65)`.
+---
+--- @usage
+--- ```lua
+--- debugScaleForm({
+--- "Player Position: X=123.45 Y=678.90 Z=12.34",
+--- "Current Action: Running",
+--- })
+--- ```
+function debugScaleForm(textTable, loc)
+ if debugMode then
+ -- Define the display position (top left corner)
+ local loc = loc or vec2(0.05, 0.65)
+
+ -- Calculate dynamic height based on the number of lines in the textTable
+ local lineHeight = 0.025 -- Height of each line of text
+ local totalHeight = #textTable * lineHeight -- Dynamic height based on number of lines
+ local boxPadding = 0.01 -- Padding to add around the text inside the box
+ local size = vec2(0.18, totalHeight + boxPadding * 2) -- Width remains fixed, height is dynamic
+
+ DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255)
+
+ for i = 1, #textTable do
+ local textLine = textTable[i]
+
+ SetTextScale(0.30, 0.30)
+
+ BeginTextCommandDisplayText("STRING")
+ AddTextComponentSubstringKeyboardDisplay(textLine)
+
+ EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
+ end
+ end
+end
diff --git a/shared/scaleforms/instructionalButtons.lua b/shared/scaleforms/instructionalButtons.lua
index 075967a..e1d7f35 100644
--- a/shared/scaleforms/instructionalButtons.lua
+++ b/shared/scaleforms/instructionalButtons.lua
@@ -1,50 +1,50 @@
---- Creates instructional buttons using the detected polyzone library (ox_lib or PolyZone).
----
---- This function generates instructional buttons on the player's screen based on the provided information.
---- It supports different polyzone libraries by automatically detecting which one is active.
----
----@param info table A table containing the instructional buttons configuration.
---- - **keys** (`table`): A list of control keys to display.
---- - **text** (`string`): The description text for the buttons.
----
----@usage
---- ```lua
---- makeInstructionalButtons({
---- { keys = { 38 }, text = "Interact" },
---- { keys = { 47 }, text = "Pick Up" },
---- })
---- ```
-function makeInstructionalButtons(info)
- local build = RequestScaleformMovie("instructional_buttons")
- while not HasScaleformMovieLoaded(build) do Wait(0) end
-
- DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
- BeginScaleformMovieMethod(build, "CLEAR_ALL")
- EndScaleformMovieMethod()
- BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
- ScaleformMovieMethodAddParamInt(200)
- EndScaleformMovieMethod()
-
- for i = 1, #info do
- BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
- ScaleformMovieMethodAddParamInt(i - 1)
- for k = 1, #info[i].keys do
- ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
- end
- BeginTextCommandScaleformString("STRING")
- AddTextComponentSubstringKeyboardDisplay(info[i].text)
- EndTextCommandScaleformString()
- EndScaleformMovieMethod()
- end
-
- BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS")
- EndScaleformMovieMethod()
- BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR")
- ScaleformMovieMethodAddParamInt(0)
- ScaleformMovieMethodAddParamInt(0)
- ScaleformMovieMethodAddParamInt(0)
- ScaleformMovieMethodAddParamInt(80)
- EndScaleformMovieMethod()
-
- DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0)
+--- Creates instructional buttons using the detected polyzone library (ox_lib or PolyZone).
+---
+--- This function generates instructional buttons on the player's screen based on the provided information.
+--- It supports different polyzone libraries by automatically detecting which one is active.
+---
+---@param info table A table containing the instructional buttons configuration.
+--- - **keys** (`table`): A list of control keys to display.
+--- - **text** (`string`): The description text for the buttons.
+---
+---@usage
+--- ```lua
+--- makeInstructionalButtons({
+--- { keys = { 38 }, text = "Interact" },
+--- { keys = { 47 }, text = "Pick Up" },
+--- })
+--- ```
+function makeInstructionalButtons(info)
+ local build = RequestScaleformMovie("instructional_buttons")
+ while not HasScaleformMovieLoaded(build) do Wait(0) end
+
+ DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
+ BeginScaleformMovieMethod(build, "CLEAR_ALL")
+ EndScaleformMovieMethod()
+ BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
+ ScaleformMovieMethodAddParamInt(200)
+ EndScaleformMovieMethod()
+
+ for i = 1, #info do
+ BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
+ ScaleformMovieMethodAddParamInt(i - 1)
+ for k = 1, #info[i].keys do
+ ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
+ end
+ BeginTextCommandScaleformString("STRING")
+ AddTextComponentSubstringKeyboardDisplay(info[i].text)
+ EndTextCommandScaleformString()
+ EndScaleformMovieMethod()
+ end
+
+ BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS")
+ EndScaleformMovieMethod()
+ BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR")
+ ScaleformMovieMethodAddParamInt(0)
+ ScaleformMovieMethodAddParamInt(0)
+ ScaleformMovieMethodAddParamInt(0)
+ ScaleformMovieMethodAddParamInt(80)
+ EndScaleformMovieMethod()
+
+ DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0)
end
\ No newline at end of file
diff --git a/shared/scaleforms/timerBars.lua b/shared/scaleforms/timerBars.lua
index c25b561..1124e79 100644
--- a/shared/scaleforms/timerBars.lua
+++ b/shared/scaleforms/timerBars.lua
@@ -1,60 +1,60 @@
-function createTimerHud(title, data, alpha)
- loadTextureDict("timerbars")
-
- local loc = vec2(0.89, 0.90)
- alpha = alpha or 255 -- Default to fully opaque if alpha is not provided
-
- if title then
- local x = loc.x+0.037
- local y = 0.1
-
- DrawSprite("timerbars", "all_black_bg", x, y, 0.12, 0.05, 0.0, 255, 255, 255, alpha)
- SetTextScale(0.80, 0.80)
- SetTextWrap(0.75, 0.985)
- SetTextJustification(2)
- SetTextFont(4)
- SetTextColour(255, 255, 255, alpha)
- BeginTextCommandDisplayText("STRING")
- AddTextComponentSubstringKeyboardDisplay("~y~"..title)
- EndTextCommandDisplayText(x+0.06, y - 0.026)
- end
-
- local displayIndex = 0
- for i = #data, 1, -1 do
- local space = 0.044 * displayIndex
-
- DrawSprite("timerbars", "all_black_bg", loc.x+0.02, loc.y - space, 0.15, 0.04, 0.0, 255, 255, 255, alpha)
- SetTextScale(0.0, 0.35)
- SetTextWrap(0.5, 0.92)
- SetTextJustification(2)
- SetTextColour(255, 255, 255, alpha)
- BeginTextCommandDisplayText("STRING")
- AddTextComponentSubstringKeyboardDisplay(data[i].stat:upper())
- EndTextCommandDisplayText(loc.x-0.05, (loc.y - space) - 0.0125)
-
- SetTextScale(0.55, 0.55)
- SetTextWrap(0.85, 0.98 - (data[i].multi and 0.026 or 0.0))
- SetTextFont(4)
- SetTextJustification(2)
- SetTextColour(255, 255, 255, alpha)
- if data[i].multi then
- local startX = 0.071
- DrawSprite("timerbars", "circle_checkpoints",
- loc.x + startX, (loc.y - space)+0.005,
- 0.011, 0.018, 0.0, 255, 191, 0, 200)
-
- DrawSprite("timerbars", "circle_checkpoints",
- loc.x + (startX + 0.008), (loc.y - space)+0.005,
- 0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 1 and 200 or 75)
-
- DrawSprite("timerbars", "circle_checkpoints",
- loc.x + (startX + 0.016), (loc.y - space)+0.005,
- 0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 2 and 200 or 75)
- end
- BeginTextCommandDisplayText("STRING")
- AddTextComponentSubstringKeyboardDisplay(data[i].value)
- EndTextCommandDisplayText(loc.x - 0.02, (loc.y - space) - 0.017)
- displayIndex += 1
- end
- makeInstructionalButtons({ { text = "Exit", keys = { 194 }}})
+function createTimerHud(title, data, alpha)
+ loadTextureDict("timerbars")
+
+ local loc = vec2(0.89, 0.90)
+ alpha = alpha or 255 -- Default to fully opaque if alpha is not provided
+
+ if title then
+ local x = loc.x+0.037
+ local y = 0.1
+
+ DrawSprite("timerbars", "all_black_bg", x, y, 0.12, 0.05, 0.0, 255, 255, 255, alpha)
+ SetTextScale(0.80, 0.80)
+ SetTextWrap(0.75, 0.985)
+ SetTextJustification(2)
+ SetTextFont(4)
+ SetTextColour(255, 255, 255, alpha)
+ BeginTextCommandDisplayText("STRING")
+ AddTextComponentSubstringKeyboardDisplay("~y~"..title)
+ EndTextCommandDisplayText(x+0.06, y - 0.026)
+ end
+
+ local displayIndex = 0
+ for i = #data, 1, -1 do
+ local space = 0.044 * displayIndex
+
+ DrawSprite("timerbars", "all_black_bg", loc.x+0.02, loc.y - space, 0.15, 0.04, 0.0, 255, 255, 255, alpha)
+ SetTextScale(0.0, 0.35)
+ SetTextWrap(0.5, 0.92)
+ SetTextJustification(2)
+ SetTextColour(255, 255, 255, alpha)
+ BeginTextCommandDisplayText("STRING")
+ AddTextComponentSubstringKeyboardDisplay(data[i].stat:upper())
+ EndTextCommandDisplayText(loc.x-0.05, (loc.y - space) - 0.0125)
+
+ SetTextScale(0.55, 0.55)
+ SetTextWrap(0.85, 0.98 - (data[i].multi and 0.026 or 0.0))
+ SetTextFont(4)
+ SetTextJustification(2)
+ SetTextColour(255, 255, 255, alpha)
+ if data[i].multi then
+ local startX = 0.071
+ DrawSprite("timerbars", "circle_checkpoints",
+ loc.x + startX, (loc.y - space)+0.005,
+ 0.011, 0.018, 0.0, 255, 191, 0, 200)
+
+ DrawSprite("timerbars", "circle_checkpoints",
+ loc.x + (startX + 0.008), (loc.y - space)+0.005,
+ 0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 1 and 200 or 75)
+
+ DrawSprite("timerbars", "circle_checkpoints",
+ loc.x + (startX + 0.016), (loc.y - space)+0.005,
+ 0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 2 and 200 or 75)
+ end
+ BeginTextCommandDisplayText("STRING")
+ AddTextComponentSubstringKeyboardDisplay(data[i].value)
+ EndTextCommandDisplayText(loc.x - 0.02, (loc.y - space) - 0.017)
+ displayIndex += 1
+ end
+ makeInstructionalButtons({ { text = "Exit", keys = { 194 }}})
end
\ No newline at end of file
diff --git a/shared/stashcontrol.lua b/shared/stashcontrol.lua
index 0a0c280..16bd44e 100644
--- a/shared/stashcontrol.lua
+++ b/shared/stashcontrol.lua
@@ -1,271 +1,271 @@
-if isServer() then
- createCallback(getScript()..':server:GetStashItems', function(source, stashName) local stash = getStash(stashName) return stash end)
-end
-
-local stashCache ={}
-function GetStashTimeout(stashName, stop)
- if stop then stashCache = {} return end
- local stash = stashCache[stashName]
- if not stash then
- stashCache[stashName] = { items = {}, timeout = 0 }
- stash = stashCache[stashName]
- end
- if #stash.items > 0 then return true end
- if stash.timeout <= 0 then
- stash.items = triggerCallback(getScript()..':server:GetStashItems', stashName)
- stash.timeout = 10000
- CreateThread(function()
- while stash.timeout > 0 do stash.timeout -= 1000 Wait(1000) end
- stashCache[stashName] = nil
- end)
- end
-
- return false
-end
-
-function checkHasItem(stashes, itemTable)
- if not stashes then return hasItem(itemTable), nil end
- if type(stashes) == "table" then
- local succeses = 0
- local itemCount = 0
- for _, item in pairs(itemTable) do itemCount += 1 end
- for _, name in pairs(stashes) do
- GetStashTimeout(name)
- for item, amount in pairs(itemTable) do
- debugPrint("^6Bridge^7: ^2Checking"..(name and " ^7'^6"..name.."^7'" or "").." ^2ingredients^7 - ^6"..item.."^7")
- if stashhasItem(stashCache[name].items, item, amount) then
- succeses += 1
- if succeses == itemCount then return true, name end
- end
- end
- end
- else
- debugPrint("^6Bridge^7: ^2Checking"..(stashes and " ^7'^6"..stashes.."^7'" or "").." ^2ingredients^7 - ^6"..k.."^7")
- GetStashTimeout(stashes)
- return stashhasItem(stashCache[stashes].items, itemTable), stashes
- end
-
- return false, nil
-end
-
-
--- Stash Items
-function openStash(data)
- if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
- if isStarted(OXInv) then
- exports[OXInv]:openInventory('stash', data.stash)
- elseif isStarted(CodeMInv) then
- exports[CodeMInv]:OpenStash(data.stash, StashWeight, 100)
- elseif isStarted(QBInv) then
- if QBInvNew then
- TriggerServerEvent(getScript()..':server:OpenStashQB', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 })
- else
- TriggerEvent("inventory:client:SetCurrentStash", data.stash)
- TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
- end
- else
- TriggerEvent("inventory:client:SetCurrentStash", data.stash)
- TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
- end
- lookEnt(data.coords)
-end
-
-RegisterNetEvent(getScript()..':server:OpenStashQB', function(data)
- exports[QBInv]:OpenInventory(source, data.stashName, data)
-end)
-
-function getStash(stashName) local stashResource = ""
- if type(stashName) ~= "string" then return print("Stash name was not a string %s(%s)", stashName, type(stashName)) end
- local stashItems, items = {}, {}
- if isStarted(OXInv) then stashResource = OXInv
- stashItems = exports[OXInv]:Inventory(stashName).items
-
- elseif isStarted(QSInv) then stashResource = QSInv
- stashItems = exports[QSInv]:GetStashItems(stashName)
-
- elseif isStarted(CoreInv) then stashResource = CoreInv
- stashItems = exports[CoreInv]:getInventory(stashName)
-
- elseif isStarted(CodeMInv) then stashResource = CodeMInv
- stashItems = exports[CodeMInv]:GetInventoryItems('Stash', stashName)
-
- elseif isStarted(OrigenInv) then stashResource = OrigenInv
- stashItems = exports[OrigenInv]:GetStashItems(stashName)
-
- elseif isStarted(PSInv) then stashResource = PSInv
- local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
- if result then stashItems = json.decode(result) end
- elseif isStarted(QBInv) then stashResource = QBInv
- local result = MySQL.scalar.await("SELECT items FROM "..(QBInvNew and "inventories" or "stashitem").." WHERE identifier = ?", { stashName })
- if result then stashItems = json.decode(result) end
- end
-
- debugPrint("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7"..stashResource)
- if stashItems then
- for _, item in pairs(stashItems) do
- local itemInfo = Items[item.name:lower()]
- if itemInfo then
- local indexNum = #items+1 -- Added to help recreate missing slot numbers
- items[(item.slot and item.slot) or indexNum] = {
- name = itemInfo.name or nil,
- amount = tonumber(item.amount) or tonumber(item.count),
- info = item.info or "",
- label = itemInfo.label or nil,
- description = itemInfo.description or "",
- weight = itemInfo.weight or nil,
- type = itemInfo.type or nil,
- unique = itemInfo.unique or nil,
- useable = itemInfo.useable or nil,
- image = itemInfo.image or nil,
- slot = (item.slot and item.slot) or indexNum,
- metadata = (item.metadata and item.metadata) or nil,
- }
- end
- end
- debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for ^7'^6"..stashName.."^7' ^2retrieved^7")
- end
- return items
-end
-
-function stashRemoveItem(stashItems, stashName, items) local amount = amount and amount or 1
- -- print("stashItems: "..json.encode(stashItems, { indent = true}))
- -- print("stashName: "..json.encode(stashName, { indent = true}))
- -- print("items: "..json.encode(items, { indent = true}))
- if isStarted(OXInv) then
- for k, v in pairs(items) do
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
- if type(stashName) == "table" then
- for _, name in pairs(stashName) do
- local success = exports[OXInv]:RemoveItem(name, k, v)
- if success then
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
- break
- end
- end
- else
- exports[OXInv]:RemoveItem(stashName, k, v)
- end
- end
-
- elseif isStarted(QSInv) then
- for k, v in pairs(items) do
- for l in pairs(stashItems) do
- if stashItems[l].name == k then
- if (stashItems[l].amount - v) <= 0 then
- debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
- stashItems[l] = nil
- else
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
- exports[QSInv]:RemoveItemIntoStash(stashName, k, v, l)
- end
- end
- end
- end
-
- elseif isStarted(CoreInv) then
- for k, v in pairs(items) do
- exports[CoreInv]:removeItemExact(stashName, k, v)
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CoreInv, k, v)
- end
-
- elseif isStarted(CodeMInv) then
- for k, v in pairs(items) do
- for l in pairs(stashItems) do
- if stashItems[l].name == k then
- if (stashItems[l].amount - v) <= 0 then
- debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
- stashItems[l] = nil
- else
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CodeMInv, k, v)
- stashItems[l].amount -= v
- end
- end
- end
- end
- debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
-
- elseif isStarted(OrigenInv) then
- for k, v in pairs(items) do
- exports[OrigenInv]:RemoveFromStash(stashName, k, v)
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OrigenInv, k, v)
- end
-
- elseif isStarted(PSInv) then
- for k, v in pairs(items) do
- for l in pairs(stashItems) do
- if stashItems[l].name == k then
- if (stashItems[l].amount - v) <= 0 then
- debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
- stashItems[l] = nil
- else
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
- stashItems[l].amount -= v
- end
- end
- end
- end
- debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
- MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) })
- elseif isStarted(QBInv) then
- if QBInvNew then
- for k, v in pairs(items) do
- exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
- debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
- end
- debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName[1].."^7'")
- MySQL.Async.insert('INSERT INTO inventories (identifier, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) })
- else
- for k, v in pairs(items) do
- for l in pairs(stashItems) do
- if stashItems[l].name == k then
- if (stashItems[l].amount - v) <= 0 then
- if Config.System.Debug then
- print("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
- end
- stashItems[l] = nil
- else
- if Config.System.Debug then
- print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
- end
- stashItems[l].amount -= v
- end
- end
- end
- end
- debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
- MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) })
- end
- else
- print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
- end
-end
-RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
-
-function stashhasItem(stashItems, items, amount)
- local invs = {OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, QBInv, PSInv}
- local foundInv = ""
- for _, inv in ipairs(invs) do
- if isStarted(inv) then
- foundInv = inv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
- break
- end
- end
-
- if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
- local hasTable = {}
- for item, amount in pairs(items) do
- local count = 0
- for _, itemData in pairs(stashItems) do
- if itemData and (itemData.name == item) then
- count += (itemData.amount or 1)
- end
- end
-
- local debugMsg = string.format("^6Bridge^7: ^3stashHasItem^7[^6%s^7]: %s '%s' ^3%d^7/^3%d^7", foundInv, (count >= amount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, amount)
- debugPrint(debugMsg)
-
- hasTable[item] = { hasItem = (count >= amount), count = count }
- end
- for k, v in pairs(hasTable) do if v.hasItem == false then return false, hasTable end end
- return true, hasTable
+if isServer() then
+ createCallback(getScript()..':server:GetStashItems', function(source, stashName) local stash = getStash(stashName) return stash end)
+end
+
+local stashCache ={}
+function GetStashTimeout(stashName, stop)
+ if stop then stashCache = {} return end
+ local stash = stashCache[stashName]
+ if not stash then
+ stashCache[stashName] = { items = {}, timeout = 0 }
+ stash = stashCache[stashName]
+ end
+ if #stash.items > 0 then return true end
+ if stash.timeout <= 0 then
+ stash.items = triggerCallback(getScript()..':server:GetStashItems', stashName)
+ stash.timeout = 10000
+ CreateThread(function()
+ while stash.timeout > 0 do stash.timeout -= 1000 Wait(1000) end
+ stashCache[stashName] = nil
+ end)
+ end
+
+ return false
+end
+
+function checkHasItem(stashes, itemTable)
+ if not stashes then return hasItem(itemTable), nil end
+ if type(stashes) == "table" then
+ local succeses = 0
+ local itemCount = 0
+ for _, item in pairs(itemTable) do itemCount += 1 end
+ for _, name in pairs(stashes) do
+ GetStashTimeout(name)
+ for item, amount in pairs(itemTable) do
+ debugPrint("^6Bridge^7: ^2Checking"..(name and " ^7'^6"..name.."^7'" or "").." ^2ingredients^7 - ^6"..item.."^7")
+ if stashhasItem(stashCache[name].items, item, amount) then
+ succeses += 1
+ if succeses == itemCount then return true, name end
+ end
+ end
+ end
+ else
+ debugPrint("^6Bridge^7: ^2Checking"..(stashes and " ^7'^6"..stashes.."^7'" or "").." ^2ingredients^7 - ^6"..k.."^7")
+ GetStashTimeout(stashes)
+ return stashhasItem(stashCache[stashes].items, itemTable), stashes
+ end
+
+ return false, nil
+end
+
+
+-- Stash Items
+function openStash(data)
+ if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
+ if isStarted(OXInv) then
+ exports[OXInv]:openInventory('stash', data.stash)
+ elseif isStarted(CodeMInv) then
+ exports[CodeMInv]:OpenStash(data.stash, StashWeight, 100)
+ elseif isStarted(QBInv) then
+ if QBInvNew then
+ TriggerServerEvent(getScript()..':server:OpenStashQB', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 })
+ else
+ TriggerEvent("inventory:client:SetCurrentStash", data.stash)
+ TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
+ end
+ else
+ TriggerEvent("inventory:client:SetCurrentStash", data.stash)
+ TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
+ end
+ lookEnt(data.coords)
+end
+
+RegisterNetEvent(getScript()..':server:OpenStashQB', function(data)
+ exports[QBInv]:OpenInventory(source, data.stashName, data)
+end)
+
+function getStash(stashName) local stashResource = ""
+ if type(stashName) ~= "string" then return print("Stash name was not a string %s(%s)", stashName, type(stashName)) end
+ local stashItems, items = {}, {}
+ if isStarted(OXInv) then stashResource = OXInv
+ stashItems = exports[OXInv]:Inventory(stashName).items
+
+ elseif isStarted(QSInv) then stashResource = QSInv
+ stashItems = exports[QSInv]:GetStashItems(stashName)
+
+ elseif isStarted(CoreInv) then stashResource = CoreInv
+ stashItems = exports[CoreInv]:getInventory(stashName)
+
+ elseif isStarted(CodeMInv) then stashResource = CodeMInv
+ stashItems = exports[CodeMInv]:GetInventoryItems('Stash', stashName)
+
+ elseif isStarted(OrigenInv) then stashResource = OrigenInv
+ stashItems = exports[OrigenInv]:GetStashItems(stashName)
+
+ elseif isStarted(PSInv) then stashResource = PSInv
+ local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
+ if result then stashItems = json.decode(result) end
+ elseif isStarted(QBInv) then stashResource = QBInv
+ local result = MySQL.scalar.await("SELECT items FROM "..(QBInvNew and "inventories" or "stashitem").." WHERE identifier = ?", { stashName })
+ if result then stashItems = json.decode(result) end
+ end
+
+ debugPrint("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7"..stashResource)
+ if stashItems then
+ for _, item in pairs(stashItems) do
+ local itemInfo = Items[item.name:lower()]
+ if itemInfo then
+ local indexNum = #items+1 -- Added to help recreate missing slot numbers
+ items[(item.slot and item.slot) or indexNum] = {
+ name = itemInfo.name or nil,
+ amount = tonumber(item.amount) or tonumber(item.count),
+ info = item.info or "",
+ label = itemInfo.label or nil,
+ description = itemInfo.description or "",
+ weight = itemInfo.weight or nil,
+ type = itemInfo.type or nil,
+ unique = itemInfo.unique or nil,
+ useable = itemInfo.useable or nil,
+ image = itemInfo.image or nil,
+ slot = (item.slot and item.slot) or indexNum,
+ metadata = (item.metadata and item.metadata) or nil,
+ }
+ end
+ end
+ debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for ^7'^6"..stashName.."^7' ^2retrieved^7")
+ end
+ return items
+end
+
+function stashRemoveItem(stashItems, stashName, items) local amount = amount and amount or 1
+ -- print("stashItems: "..json.encode(stashItems, { indent = true}))
+ -- print("stashName: "..json.encode(stashName, { indent = true}))
+ -- print("items: "..json.encode(items, { indent = true}))
+ if isStarted(OXInv) then
+ for k, v in pairs(items) do
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
+ if type(stashName) == "table" then
+ for _, name in pairs(stashName) do
+ local success = exports[OXInv]:RemoveItem(name, k, v)
+ if success then
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
+ break
+ end
+ end
+ else
+ exports[OXInv]:RemoveItem(stashName, k, v)
+ end
+ end
+
+ elseif isStarted(QSInv) then
+ for k, v in pairs(items) do
+ for l in pairs(stashItems) do
+ if stashItems[l].name == k then
+ if (stashItems[l].amount - v) <= 0 then
+ debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
+ stashItems[l] = nil
+ else
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
+ exports[QSInv]:RemoveItemIntoStash(stashName, k, v, l)
+ end
+ end
+ end
+ end
+
+ elseif isStarted(CoreInv) then
+ for k, v in pairs(items) do
+ exports[CoreInv]:removeItemExact(stashName, k, v)
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CoreInv, k, v)
+ end
+
+ elseif isStarted(CodeMInv) then
+ for k, v in pairs(items) do
+ for l in pairs(stashItems) do
+ if stashItems[l].name == k then
+ if (stashItems[l].amount - v) <= 0 then
+ debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
+ stashItems[l] = nil
+ else
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CodeMInv, k, v)
+ stashItems[l].amount -= v
+ end
+ end
+ end
+ end
+ debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
+
+ elseif isStarted(OrigenInv) then
+ for k, v in pairs(items) do
+ exports[OrigenInv]:RemoveFromStash(stashName, k, v)
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OrigenInv, k, v)
+ end
+
+ elseif isStarted(PSInv) then
+ for k, v in pairs(items) do
+ for l in pairs(stashItems) do
+ if stashItems[l].name == k then
+ if (stashItems[l].amount - v) <= 0 then
+ debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
+ stashItems[l] = nil
+ else
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
+ stashItems[l].amount -= v
+ end
+ end
+ end
+ end
+ debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
+ MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) })
+ elseif isStarted(QBInv) then
+ if QBInvNew then
+ for k, v in pairs(items) do
+ exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
+ debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
+ end
+ debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName[1].."^7'")
+ MySQL.Async.insert('INSERT INTO inventories (identifier, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) })
+ else
+ for k, v in pairs(items) do
+ for l in pairs(stashItems) do
+ if stashItems[l].name == k then
+ if (stashItems[l].amount - v) <= 0 then
+ if Config.System.Debug then
+ print("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
+ end
+ stashItems[l] = nil
+ else
+ if Config.System.Debug then
+ print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
+ end
+ stashItems[l].amount -= v
+ end
+ end
+ end
+ end
+ debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
+ MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) })
+ end
+ else
+ print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
+ end
+end
+RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
+
+function stashhasItem(stashItems, items, amount)
+ local invs = {OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, QBInv, PSInv}
+ local foundInv = ""
+ for _, inv in ipairs(invs) do
+ if isStarted(inv) then
+ foundInv = inv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
+ break
+ end
+ end
+
+ if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
+ local hasTable = {}
+ for item, amount in pairs(items) do
+ local count = 0
+ for _, itemData in pairs(stashItems) do
+ if itemData and (itemData.name == item) then
+ count += (itemData.amount or 1)
+ end
+ end
+
+ local debugMsg = string.format("^6Bridge^7: ^3stashHasItem^7[^6%s^7]: %s '%s' ^3%d^7/^3%d^7", foundInv, (count >= amount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, amount)
+ debugPrint(debugMsg)
+
+ hasTable[item] = { hasItem = (count >= amount), count = count }
+ end
+ for k, v in pairs(hasTable) do if v.hasItem == false then return false, hasTable end end
+ return true, hasTable
end
\ No newline at end of file
diff --git a/shared/targets.lua b/shared/targets.lua
index ea8b253..9206b5c 100644
--- a/shared/targets.lua
+++ b/shared/targets.lua
@@ -1,404 +1,457 @@
--- This is for experimental targets based on GTA in-world text prompts --
-local TextTargets = {}
-local Keys = {
- [322] = "ESC", [288] = "F1", [289] = "F2", [170] = "F3", [166] = "F5",
- [167] = "F6", [168] = "F7", [169] = "F8", [56] = "F9", [57] = "F10",
- [243] = "~", [157] = "1", [158] = "2", [160] = "3", [164] = "4", [165] = "5",
- [159] = "6", [161] = "7", [162] = "8", [163] = "9", [84] = "-", [83] = "=",
- [177] = "BACKSPACE", [37] = "TAB",
- [44] = "Q", [32] = "W", [38] = "E", [45] = "R", [245] = "T", [246] = "Y",
- [303] = "U", [199] = "P",
- [39] = "[", [40] = "]", [18] = "ENTER", [137] = "CAPS",
- [34] = "A", [8] = "S", [9] = "D", [23] = "F", [47] = "G",
- [74] = "H", [311] = "K", [182] = "L", [21] = "LEFTSHIFT",
- [20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N",
- [244] = "M", [82] = ",", [81] = "."
-}
-
--- Target Creation --
--- Target Entities, this is more based on qb-target's style of target creation, and translates those into ox or qb-target code --
-local targetEntities = {}
-
---- Creates a target for an entity with specified options and interaction distance.
----
---- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
---- based on the server configuration. It translates qb-target style options into the appropriate format
---- for the detected targeting system.
----
----@param entity number The entity ID to create a target for.
----@param opts table A table of option configurations for the target.
---- - **icon** (`string`): The icon to display for the option.
---- - **label** (`string`): The label text for the option.
---- - **item** (`string|nil`): (Optional) The item associated with the option.
---- - **job** (`string|nil`): (Optional) The job required to interact with the option.
---- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
---- - **action** (`function|nil`): (Optional) The function to execute when the option is selected.
----@param dist number The interaction distance for the target.
----
----@usage
---- ```lua
---- createEntityTarget(entityId, {
---- { icon = "fas fa-car", label = "Open Vehicle", action = openVehicle },
---- { icon = "fas fa-lock", label = "Lock Vehicle", action = lockVehicle }
---- }, 2.5)
---- ```
-function createEntityTarget(entity, opts, dist)
- targetEntities[#targetEntities + 1] = entity
- local entityCoords = GetEntityCoords(entity)
- if Config.System.DontUseTarget then
- debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6DrawText ^7"..entity)
- local existingTarget = nil
- for key, target in pairs(TextTargets) do
- if #(target.coords - entityCoords) < 0.01 then -- Adjust the threshold for coordinate matching
- existingTarget = target
- break
- end
- end
-
- if existingTarget then
- -- Combine options
- local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Extend key table as needed
- for i = 1, #opts do
- local key = keyTable[#existingTarget.options + i]
- opts[i].key = key
- existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label
- existingTarget.options[#existingTarget.options + 1] = opts[i]
- end
- else
- -- Create new target
- local tempText = {}
- local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
- for i = 1, #opts do
- opts[i].key = keyTable[i]
- tempText[#tempText + 1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label
- end
- TextTargets[entity] = { coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z), buttontext = tempText, options = opts, dist = dist }
- end
- elseif isStarted(OXTargetExport) then
- debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..OXTargetExport.." ^7"..entity)
- local options = {}
- for i = 1, #opts do
- options[i] = {
- icon = opts[i].icon,
- label = opts[i].label,
- item = opts[i].item or nil,
- groups = opts[i].job or opts[i].gang,
- onSelect = opts[i].action,
- canInteract = function(_, distance)
- return distance < dist and true or false
- end
- }
- end
- exports[OXTargetExport]:addLocalEntity(entity, options)
- elseif isStarted(QBTargetExport) then
- debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..QBTargetExport.." ^7"..entity)
- local options = { options = opts, distance = dist }
- exports[QBTargetExport]:AddTargetEntity(entity, options)
- end
-end
-
-local boxTargets = {}
-
---- Creates a box-shaped target zone with specified options and interaction distance.
----
---- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
---- based on the server configuration. It translates qb-target style options into the appropriate format
---- for the detected targeting system.
----
----@param data table A table containing the box zone configuration.
---- - **name** (`string`): The name identifier for the zone.
---- - **coords** (`vector3`): The center coordinates of the box.
---- - **width** (`number`): The width of the box.
---- - **height** (`number`): The height of the box.
---- - **options** (`table`): A table with additional options:
---- - **heading** (`number`): The rotation angle of the box.
---- - **debugPoly** (`boolean`): Whether to enable debug mode for the zone.
----
----@param opts table A table of option configurations for the target.
---- - **icon** (`string`): The icon to display for the option.
---- - **label** (`string`): The label text for the option.
---- - **item** (`string|nil`): (Optional) The item associated with the option.
---- - **job** (`string|nil`): (Optional) The job required to interact with the option.
---- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
---- - **onSelect** (`function|nil`): (Optional) The function to execute when the option is selected.
----@param dist number The interaction distance for the target.
----
----@return string|table name identifier or target object of the created zone.
----
----@usage
---- ```lua
---- createBoxTarget({
---- name = 'storageBox',
---- coords = vector3(100.0, 200.0, 30.0),
---- width = 2.0,
---- height = 2.0,
---- options = { heading = 0, debugPoly = false }
---- }, {
---- { icon = "fas fa-box", label = "Open Storage", action = openStorage }
---- }, 1.5)
---- ```
-function createBoxTarget(data, opts, dist)
- if Config.System.DontUseTarget then
- debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^7"..data[1])
- local existingTarget = nil
- for key, target in pairs(TextTargets) do
- if #(target.coords - data[2]) < 0.01 then -- Adjust the threshold as needed for coordinate precision
- existingTarget = target
- break
- end
- end
- local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
-
- if existingTarget then
- -- Combine options
- for i = 1, #opts do
- local key = keyTable[#existingTarget.options + i]
- opts[i].key = key
- existingTarget.buttontext[#existingTarget.buttontext+1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label
- existingTarget.options[#existingTarget.options+1] = opts[i]
- end
- else
- -- Create new target
- local tempText = {}
- for i = 1, #opts do
- opts[i].key = keyTable[i]
- tempText[#tempText+1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label
- end
- TextTargets[data[1]] = { coords = data[2], buttontext = tempText, options = opts, dist = 1.5 }
- end
- return data[1]
- elseif isStarted(OXTargetExport) then
- debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^7"..data[1])
- local options = {}
- for i = 1, #opts do
- options[i] = {
- icon = opts[i].icon,
- label = opts[i].label,
- item = opts[i].item or nil,
- groups = opts[i].job or opts[i].gang,
- onSelect = opts[i].onSelect or opts[i].action,
- canInteract = function(_, distance)
- return distance < dist and true or false
- end
- }
- end
- if not data[5].useZ then
- local z = data[2].z + math.abs(data[5].maxZ - data[5].minZ) / 2
- data[2] = vec3(data[2].x, data[2].y, z)
- end
- local target = exports[OXTargetExport]:addBoxZone({
- coords = data[2],
- size = vec3(data[4], data[3], (data[5].useZ or not data[5].maxZ) and data[2].z or math.abs(data[5].maxZ - data[5].minZ)),
- rotation = data[5].heading,
- debug = data[5].debugPoly,
- options = options
- })
- boxTargets[#boxTargets+1] = target
- return target
- elseif isStarted(QBTargetExport) then
- debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^7"..data[1])
- local options = { options = opts, distance = dist }
- local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
- boxTargets[#boxTargets+1] = target
- return data[1]
- end
-end
-
-local circleTargets = {}
-
---- Creates a circular target zone with specified options and interaction distance.
----
---- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
---- based on the server configuration. It translates qb-target style options into the appropriate format
---- for the detected targeting system.
----
----@param data table A table containing the circle zone configuration.
---- - **name** (`string`): The name identifier for the zone.
---- - **coords** (`vector3`): The center coordinates of the circle.
---- - **radius** (`number`): The radius of the circle.
---- - **options** (`table`): A table with additional options:
---- - **debugPoly** (`boolean`): Whether to enable debug mode for the zone.
----
----@param opts table A table of option configurations for the target.
---- - **icon** (`string`): The icon to display for the option.
---- - **label** (`string`): The label text for the option.
---- - **item** (`string|nil`): (Optional) The item associated with the option.
---- - **job** (`string|nil`): (Optional) The job required to interact with the option.
---- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
---- - **onSelect** (`function|nil`): (Optional) The function to execute when the option is selected.
----@param dist number The interaction distance for the target.
----
----@return string|table name identifier or target object of the created zone.
----
----@usage
---- ```lua
---- createCircleTarget({
---- name = 'centralPark',
---- coords = vector3(200.0, 300.0, 40.0),
---- radius = 50.0,
---- options = { debugPoly = false }
---- }, {
---- { icon = "fas fa-tree", label = "Relax", action = relaxAction }
---- }, 2.0)
---- ```
-function createCircleTarget(data, opts, dist)
- if Config.System.DontUseTarget then
- debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6DrawText ^7"..data[1])
- local existingTarget = nil
- for key, target in pairs(TextTargets) do
- if #(target.coords - data[2]) < 0.01 then -- Adjust the threshold for precision
- existingTarget = target
- break
- end
- end
-
- if existingTarget then
- -- Combine options
- local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Extend key table as needed
- for i = 1, #opts do
- local key = keyTable[#existingTarget.options + i]
- opts[i].key = key
- existingTarget.buttontext[#existingTarget.buttontext+1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label
- existingTarget.options[#existingTarget.options+1] = opts[i]
- end
- else
- -- Create new target
- local tempText = ""
- local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
- for i = 1, #opts do
- opts[i].key = keyTable[i]
- tempText[#tempText+1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label
- end
- TextTargets[data[1]] = { coords = data[2], buttontext = tempText, options = opts, dist = dist }
- end
- return data[1]
- elseif isStarted(OXTargetExport) then
- debugPrint("^6Bridge^7: ^2Creating new ^3Sphere^2 target with ^6"..OXTargetExport.." ^7"..data[1])
- local options = {}
- for i = 1, #opts do
- options[i] = {
- icon = opts[i].icon,
- label = opts[i].label,
- item = opts[i].item or nil,
- groups = opts[i].job or opts[i].gang,
- onSelect = opts[i].onSelect or opts[i].action,
- canInteract = function(_, distance)
- return distance < dist and true or false
- end
- }
- end
- local target = exports[OXTargetExport]:addSphereZone({
- coords = data[2],
- radius = data[3],
- debug = data[4].debugPoly,
- options = options
- })
- circleTargets[#circleTargets+1] = target
- return target
- elseif isStarted(QBTargetExport) then
- debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6"..QBTargetExport.." ^7"..data[1])
- local options = { options = opts, distance = dist }
- local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options)
- circleTargets[#circleTargets+1] = target
- return data[1]
- end
-end
-
--- Simple function to remove an entity target created within the script --
---- Removes a previously created entity target.
----
---- This function removes the target associated with the specified entity based on the active targeting system.
----
---- @param entity number The entity ID whose target should be removed.
----
---- @usage
---- removeEntityTarget(entityId)
-function removeEntityTarget(entity)
- if isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveTargetEntity(entity) end
- if isStarted(OXTargetExport) then exports[OXTargetExport]:removeLocalEntity(entity, nil) end
- if Config.System.DontUseTarget then TextTargets[entity] = nil end
-end
-
--- Simple function to remove circle or box targets in the script --
---- Removes a previously created zone target.
----
---- This function removes the target associated with the specified zone based on the active targeting system.
----
---- @param target string|table The name identifier or target object of the zone to remove.
----
---- @usage
---- ```lua
---- removeZoneTarget('centralPark')
---- removeZoneTarget(targetObject)
---- ```
-function removeZoneTarget(target)
- if isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(target) end
- if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(target, true) end
- if Config.System.DontUseTarget then TextTargets[target] = nil end
-end
-
--- If no target script is found, default to DrawText3D targets -- * experimental *
-if Config.System.DontUseTarget and not isServer() then
- CreateThread(function()
- while true do
- local pedCoords = GetEntityCoords(PlayerPedId())
- local camCoords = GetGameplayCamCoord()
- local camRotation = GetGameplayCamRot(2) -- Get camera rotation in degrees
- local camForwardVector = RotationToDirection(camRotation) -- Convert rotation to direction vector
-
- local closestTarget = nil
- local closestDist = math.huge
-
- for k, v in pairs(TextTargets) do
- local targetCoords = v.coords
- local dist = #(pedCoords - targetCoords)
- local vecToTarget = targetCoords - camCoords
-
- -- Normalize the vector to the target
- local vecToTargetNormalized = normalizeVector(vecToTarget)
-
- -- Dot product to check if facing the target
- local dot = camForwardVector.x * vecToTargetNormalized.x + camForwardVector.y * vecToTargetNormalized.y + camForwardVector.z * vecToTargetNormalized.z
-
- local isFacingTarget = dot > 0.5 -- Adjust threshold as needed
-
- if dist <= v.dist and isFacingTarget then
- if dist < closestDist then
- closestDist = dist
- closestTarget = v
- end
- end
- end
-
- for k, v in pairs(TextTargets) do
- local isClosest = (v == closestTarget)
- if #(pedCoords - v.coords) <= v.dist then
- for i = 1, #v.options do
- if IsControlJustPressed(0, v.options[i].key) and isClosest then
- if v.options[i].onSelect then v.options[i].onSelect() end
- if v.options[i].action then v.options[i].action() end
- end
- end
- DrawText3D(vec3(v.coords.x, v.coords.y, v.coords.z + 0.7), concatenateText(v.buttontext), isClosest)
- end
- end
- Wait(0)
- end
- end)
-end
-
--- If the current loaded script is stopped, automatically remove targets --
-onResourceStop(function()
- for i = 1, #targetEntities do
- if isStarted(OXTargetExport) then exports[OXTargetExport]:removeLocalEntity(targetEntities[i], nil)
- elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveTargetEntity(targetEntities[i]) end
- end
- for i = 1, #boxTargets do
- if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(boxTargets[i], true)
- elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(boxTargets[i].name) end
- end
- for i = 1, #circleTargets do
- if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(circleTargets[i], true)
- elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(circleTargets[i].name) end
- end
+-- This is for experimental targets based on GTA in-world text prompts --
+local TextTargets = {}
+local Keys = {
+ [322] = "ESC", [288] = "F1", [289] = "F2", [170] = "F3", [166] = "F5",
+ [167] = "F6", [168] = "F7", [169] = "F8", [56] = "F9", [57] = "F10",
+ [243] = "~", [157] = "1", [158] = "2", [160] = "3", [164] = "4", [165] = "5",
+ [159] = "6", [161] = "7", [162] = "8", [163] = "9", [84] = "-", [83] = "=",
+ [177] = "BACKSPACE", [37] = "TAB",
+ [44] = "Q", [32] = "W", [38] = "E", [45] = "R", [245] = "T", [246] = "Y",
+ [303] = "U", [199] = "P",
+ [39] = "[", [40] = "]", [18] = "ENTER", [137] = "CAPS",
+ [34] = "A", [8] = "S", [9] = "D", [23] = "F", [47] = "G",
+ [74] = "H", [311] = "K", [182] = "L", [21] = "LEFTSHIFT",
+ [20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N",
+ [244] = "M", [82] = ",", [81] = "."
+}
+
+-- Target Creation --
+-- Target Entities, this is more based on qb-target's style of target creation, and translates those into ox or qb-target code --
+local targetEntities = {}
+
+--- Creates a target for an entity with specified options and interaction distance.
+---
+--- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
+--- based on the server configuration. It translates qb-target style options into the appropriate format
+--- for the detected targeting system.
+---
+---@param entity number The entity ID to create a target for.
+---@param opts table A table of option configurations for the target.
+--- - **icon** (`string`): The icon to display for the option.
+--- - **label** (`string`): The label text for the option.
+--- - **item** (`string|nil`): (Optional) The item associated with the option.
+--- - **job** (`string|nil`): (Optional) The job required to interact with the option.
+--- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
+--- - **action** (`function|nil`): (Optional) The function to execute when the option is selected.
+---@param dist number The interaction distance for the target.
+---
+---@usage
+--- ```lua
+--- createEntityTarget(entityId, {
+--- { icon = "fas fa-car", label = "Open Vehicle", action = openVehicle },
+--- { icon = "fas fa-lock", label = "Lock Vehicle", action = lockVehicle }
+--- }, 2.5)
+--- ```
+function createEntityTarget(entity, opts, dist)
+ targetEntities[#targetEntities + 1] = entity
+ local entityCoords = GetEntityCoords(entity)
+ if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6DrawText ^7"..entity)
+ local existingTarget = nil
+ for key, target in pairs(TextTargets) do
+ if #(target.coords - entityCoords) < 0.01 then -- Adjust the threshold for coordinate matching
+ existingTarget = target
+ break
+ end
+ end
+
+ if existingTarget then
+ -- Combine options
+ local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Extend key table as needed
+ for i = 1, #opts do
+ local key = keyTable[#existingTarget.options + i]
+ opts[i].key = key
+ existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label
+ existingTarget.options[#existingTarget.options + 1] = opts[i]
+ end
+ else
+ -- Create new target
+ local tempText = {}
+ local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
+ for i = 1, #opts do
+ opts[i].key = keyTable[i]
+ tempText[#tempText + 1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label
+ end
+ TextTargets[entity] = { coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z), buttontext = tempText, options = opts, dist = dist }
+ end
+ elseif isStarted(OXTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..OXTargetExport.." ^7"..entity)
+ local options = {}
+ for i = 1, #opts do
+ options[i] = {
+ icon = opts[i].icon,
+ label = opts[i].label,
+ item = opts[i].item or nil,
+ groups = opts[i].job or opts[i].gang,
+ onSelect = opts[i].action,
+ canInteract = function(_, distance)
+ return distance < dist and true or false
+ end
+ }
+ end
+ exports[OXTargetExport]:addLocalEntity(entity, options)
+ elseif isStarted(QBTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..QBTargetExport.." ^7"..entity)
+ local options = { options = opts, distance = dist }
+ exports[QBTargetExport]:AddTargetEntity(entity, options)
+ end
+end
+
+local boxTargets = {}
+
+--- Creates a box-shaped target zone with specified options and interaction distance.
+---
+--- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
+--- based on the server configuration. It translates qb-target style options into the appropriate format
+--- for the detected targeting system.
+---
+---@param data table A table containing the box zone configuration.
+--- - **name** (`string`): The name identifier for the zone.
+--- - **coords** (`vector3`): The center coordinates of the box.
+--- - **width** (`number`): The width of the box.
+--- - **height** (`number`): The height of the box.
+--- - **options** (`table`): A table with additional options:
+--- - **heading** (`number`): The rotation angle of the box.
+--- - **debugPoly** (`boolean`): Whether to enable debug mode for the zone.
+---
+---@param opts table A table of option configurations for the target.
+--- - **icon** (`string`): The icon to display for the option.
+--- - **label** (`string`): The label text for the option.
+--- - **item** (`string|nil`): (Optional) The item associated with the option.
+--- - **job** (`string|nil`): (Optional) The job required to interact with the option.
+--- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
+--- - **onSelect** (`function|nil`): (Optional) The function to execute when the option is selected.
+---@param dist number The interaction distance for the target.
+---
+---@return string|table name identifier or target object of the created zone.
+---
+---@usage
+--- ```lua
+--- createBoxTarget({
+--- name = 'storageBox',
+--- coords = vector3(100.0, 200.0, 30.0),
+--- width = 2.0,
+--- height = 2.0,
+--- options = { heading = 0, debugPoly = false }
+--- }, {
+--- { icon = "fas fa-box", label = "Open Storage", action = openStorage }
+--- }, 1.5)
+--- ```
+function createBoxTarget(data, opts, dist)
+ if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^7"..data[1])
+ local existingTarget = nil
+ for key, target in pairs(TextTargets) do
+ if #(target.coords - data[2]) < 0.01 then -- Adjust the threshold as needed for coordinate precision
+ existingTarget = target
+ break
+ end
+ end
+ local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
+
+ if existingTarget then
+ -- Combine options
+ for i = 1, #opts do
+ local key = keyTable[#existingTarget.options + i]
+ opts[i].key = key
+ existingTarget.buttontext[#existingTarget.buttontext+1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label
+ existingTarget.options[#existingTarget.options+1] = opts[i]
+ end
+ else
+ -- Create new target
+ local tempText = {}
+ for i = 1, #opts do
+ opts[i].key = keyTable[i]
+ tempText[#tempText+1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label
+ end
+ TextTargets[data[1]] = { coords = data[2], buttontext = tempText, options = opts, dist = 1.5 }
+ end
+ return data[1]
+ elseif isStarted(OXTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^7"..data[1])
+ local options = {}
+ for i = 1, #opts do
+ options[i] = {
+ icon = opts[i].icon,
+ label = opts[i].label,
+ item = opts[i].item or nil,
+ groups = opts[i].job or opts[i].gang,
+ onSelect = opts[i].onSelect or opts[i].action,
+ canInteract = function(_, distance)
+ return distance < dist and true or false
+ end
+ }
+ end
+ if not data[5].useZ then
+ local z = data[2].z + math.abs(data[5].maxZ - data[5].minZ) / 2
+ data[2] = vec3(data[2].x, data[2].y, z)
+ end
+ local target = exports[OXTargetExport]:addBoxZone({
+ coords = data[2],
+ size = vec3(data[4], data[3], (data[5].useZ or not data[5].maxZ) and data[2].z or math.abs(data[5].maxZ - data[5].minZ)),
+ rotation = data[5].heading,
+ debug = data[5].debugPoly,
+ options = options
+ })
+ boxTargets[#boxTargets+1] = target
+ return target
+ elseif isStarted(QBTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^7"..data[1])
+ local options = { options = opts, distance = dist }
+ local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
+ boxTargets[#boxTargets+1] = target
+ return data[1]
+ end
+end
+
+local circleTargets = {}
+
+--- Creates a circular target zone with specified options and interaction distance.
+---
+--- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
+--- based on the server configuration. It translates qb-target style options into the appropriate format
+--- for the detected targeting system.
+---
+---@param data table A table containing the circle zone configuration.
+--- - **name** (`string`): The name identifier for the zone.
+--- - **coords** (`vector3`): The center coordinates of the circle.
+--- - **radius** (`number`): The radius of the circle.
+--- - **options** (`table`): A table with additional options:
+--- - **debugPoly** (`boolean`): Whether to enable debug mode for the zone.
+---
+---@param opts table A table of option configurations for the target.
+--- - **icon** (`string`): The icon to display for the option.
+--- - **label** (`string`): The label text for the option.
+--- - **item** (`string|nil`): (Optional) The item associated with the option.
+--- - **job** (`string|nil`): (Optional) The job required to interact with the option.
+--- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
+--- - **onSelect** (`function|nil`): (Optional) The function to execute when the option is selected.
+---@param dist number The interaction distance for the target.
+---
+---@return string|table name identifier or target object of the created zone.
+---
+---@usage
+--- ```lua
+--- createCircleTarget({
+--- name = 'centralPark',
+--- coords = vector3(200.0, 300.0, 40.0),
+--- radius = 50.0,
+--- options = { debugPoly = false }
+--- }, {
+--- { icon = "fas fa-tree", label = "Relax", action = relaxAction }
+--- }, 2.0)
+--- ```
+function createCircleTarget(data, opts, dist)
+ if Config.System.DontUseTarget then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6DrawText ^7"..data[1])
+ local existingTarget = nil
+ for key, target in pairs(TextTargets) do
+ if #(target.coords - data[2]) < 0.01 then -- Adjust the threshold for precision
+ existingTarget = target
+ break
+ end
+ end
+
+ if existingTarget then
+ -- Combine options
+ local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Extend key table as needed
+ for i = 1, #opts do
+ local key = keyTable[#existingTarget.options + i]
+ opts[i].key = key
+ existingTarget.buttontext[#existingTarget.buttontext+1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label
+ existingTarget.options[#existingTarget.options+1] = opts[i]
+ end
+ else
+ -- Create new target
+ local tempText = ""
+ local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
+ for i = 1, #opts do
+ opts[i].key = keyTable[i]
+ tempText[#tempText+1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label
+ end
+ TextTargets[data[1]] = { coords = data[2], buttontext = tempText, options = opts, dist = dist }
+ end
+ return data[1]
+ elseif isStarted(OXTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Sphere^2 target with ^6"..OXTargetExport.." ^7"..data[1])
+ local options = {}
+ for i = 1, #opts do
+ options[i] = {
+ icon = opts[i].icon,
+ label = opts[i].label,
+ item = opts[i].item or nil,
+ groups = opts[i].job or opts[i].gang,
+ onSelect = opts[i].onSelect or opts[i].action,
+ canInteract = function(_, distance)
+ return distance < dist and true or false
+ end
+ }
+ end
+ local target = exports[OXTargetExport]:addSphereZone({
+ coords = data[2],
+ radius = data[3],
+ debug = data[4].debugPoly,
+ options = options
+ })
+ circleTargets[#circleTargets+1] = target
+ return target
+ elseif isStarted(QBTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6"..QBTargetExport.." ^7"..data[1])
+ local options = { options = opts, distance = dist }
+ local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options)
+ circleTargets[#circleTargets+1] = target
+ return data[1]
+ end
+end
+
+local targetEntities = {}
+
+--- Creates a target for an entity with specified options and interaction distance.
+---
+--- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
+--- based on the server configuration. It translates qb-target style options into the appropriate format
+--- for the detected targeting system.
+---
+---@param entity number The entity ID to create a target for.
+---@param opts table A table of option configurations for the target.
+--- - **icon** (`string`): The icon to display for the option.
+--- - **label** (`string`): The label text for the option.
+--- - **item** (`string|nil`): (Optional) The item associated with the option.
+--- - **job** (`string|nil`): (Optional) The job required to interact with the option.
+--- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
+--- - **action** (`function|nil`): (Optional) The function to execute when the option is selected.
+---@param dist number The interaction distance for the target.
+---
+---@usage
+--- ```lua
+--- createEntityTarget(entityId, {
+--- { icon = "fas fa-car", label = "Open Vehicle", action = openVehicle },
+--- { icon = "fas fa-lock", label = "Lock Vehicle", action = lockVehicle }
+--- }, 2.5)
+--- ```
+function createModelTarget(models, opts, dist)
+ if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
+ --
+ elseif isStarted(OXTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..OXTargetExport)
+ local options = {}
+ for i = 1, #opts do
+ options[i] = {
+ icon = opts[i].icon,
+ label = opts[i].label,
+ item = opts[i].item or nil,
+ groups = opts[i].job or opts[i].gang,
+ onSelect = opts[i].action,
+ canInteract = function(_, distance)
+ return distance < dist and true or false
+ end
+ }
+ end
+ exports[OXTargetExport]:addModel(models, options)
+ elseif isStarted(QBTargetExport) then
+ debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..QBTargetExport)
+ local options = { options = opts, distance = dist }
+ exports[QBTargetExport]:AddTargetModel(models, options)
+ end
+end
+
+
+
+-- Simple function to remove an entity target created within the script --
+--- Removes a previously created entity target.
+---
+--- This function removes the target associated with the specified entity based on the active targeting system.
+---
+--- @param entity number The entity ID whose target should be removed.
+---
+--- @usage
+--- removeEntityTarget(entityId)
+function removeEntityTarget(entity)
+ if isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveTargetEntity(entity) end
+ if isStarted(OXTargetExport) then exports[OXTargetExport]:removeLocalEntity(entity, nil) end
+ if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) then TextTargets[entity] = nil end
+end
+
+-- Simple function to remove circle or box targets in the script --
+--- Removes a previously created zone target.
+---
+--- This function removes the target associated with the specified zone based on the active targeting system.
+---
+--- @param target string|table The name identifier or target object of the zone to remove.
+---
+--- @usage
+--- ```lua
+--- removeZoneTarget('centralPark')
+--- removeZoneTarget(targetObject)
+--- ```
+function removeZoneTarget(target)
+ if isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(target) end
+ if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(target, true) end
+ if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) then TextTargets[target] = nil end
+end
+
+-- If no target script is found, default to DrawText3D targets -- * experimental *
+if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) and not isServer() then
+ CreateThread(function()
+ while true do
+ local pedCoords = GetEntityCoords(PlayerPedId())
+ local camCoords = GetGameplayCamCoord()
+ local camRotation = GetGameplayCamRot(2) -- Get camera rotation in degrees
+ local camForwardVector = RotationToDirection(camRotation) -- Convert rotation to direction vector
+
+ local closestTarget = nil
+ local closestDist = math.huge
+
+ for k, v in pairs(TextTargets) do
+ local targetCoords = v.coords
+ local dist = #(pedCoords - targetCoords)
+ local vecToTarget = targetCoords - camCoords
+
+ -- Normalize the vector to the target
+ local vecToTargetNormalized = normalizeVector(vecToTarget)
+
+ -- Dot product to check if facing the target
+ local dot = camForwardVector.x * vecToTargetNormalized.x + camForwardVector.y * vecToTargetNormalized.y + camForwardVector.z * vecToTargetNormalized.z
+
+ local isFacingTarget = dot > 0.5 -- Adjust threshold as needed
+
+ if dist <= v.dist and isFacingTarget then
+ if dist < closestDist then
+ closestDist = dist
+ closestTarget = v
+ end
+ end
+ end
+
+ for k, v in pairs(TextTargets) do
+ local isClosest = (v == closestTarget)
+ if #(pedCoords - v.coords) <= v.dist then
+ for i = 1, #v.options do
+ if IsControlJustPressed(0, v.options[i].key) and isClosest then
+ if v.options[i].onSelect then v.options[i].onSelect() end
+ if v.options[i].action then v.options[i].action() end
+ end
+ end
+ DrawText3D(vec3(v.coords.x, v.coords.y, v.coords.z + 0.7), concatenateText(v.buttontext), isClosest)
+ end
+ end
+ Wait(0)
+ end
+ end)
+end
+
+-- If the current loaded script is stopped, automatically remove targets --
+onResourceStop(function()
+ for i = 1, #targetEntities do
+ if isStarted(OXTargetExport) then exports[OXTargetExport]:removeLocalEntity(targetEntities[i], nil)
+ elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveTargetEntity(targetEntities[i]) end
+ end
+ for i = 1, #boxTargets do
+ if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(boxTargets[i], true)
+ elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(boxTargets[i].name) end
+ end
+ for i = 1, #circleTargets do
+ if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(circleTargets[i], true)
+ elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(circleTargets[i].name) end
+ end
end, true)
\ No newline at end of file
diff --git a/shared/vehicles.lua b/shared/vehicles.lua
index e2e9612..2afcf6d 100644
--- a/shared/vehicles.lua
+++ b/shared/vehicles.lua
@@ -1,250 +1,250 @@
--- Get Vehicle Info --
-local lastCar = nil
-local carInfo = {}
-
---- Searches the 'Vehicles' table for a specific vehicle's 'name', 'price', and 'class'.
----
---- This function checks if the provided vehicle is different from the last searched vehicle.
---- If it's a new vehicle, it retrieves its model and searches the 'Vehicles' table for matching entries.
---- It populates the `carInfo` table with the vehicle's name, price, and class.
---- If the vehicle is not found in the table, it defaults to using the vehicle's display name and sets the price to 0.
----
----@param vehicle number The entity ID of the vehicle to search for.
----
----@return table|nil table containing the vehicle's `name`, `price`, and `class`, or `nil` if the vehicle is invalid.
----
----@usage
---- ```lua
---- local info = searchCar(vehicleEntity)
---- print(info.name, info.price, info.class)
---- ```
-function searchCar(vehicle)
- if lastCar ~= vehicle then -- If same car, use previous info
- lastCar = vehicle
- carInfo = {}
- local model = GetEntityModel(vehicle)
- local classlist = {
- "Compacts", --1
- "Sedans", --2
- "SUVs", --3
- "Coupes", --4
- "Muscle", --5
- "Sports Classics", --6
- "Sports", --7
- "Super", --8
- "Motorcycles", --9
- "Off-road", --10
- "Industrial", --11
- "Utility", --12
- "Vans", --13
- "Cycles", --14
- "Boats", --15
- "Helicopters", --16
- "Planes", --17
- "Service", --18
- "Emergency", --19
- "Military", --20
- "Commercial", --21
- "Trains", --22
- }
- if Vehicles then
- for k, v in pairs(Vehicles) do
- if tonumber(v.hash) == model or GetHashKey(v.hash) == model or GetHashKey(v.model) == model then
- debugPrint("^6Bridge^7: ^2Vehicle info found in^7 ^4Vehicles^7 ^2table^7: ^6"..(v.hash and v.hash or v.model).. " ^7(^6"..Vehicles[k].name.."^7)")
- carInfo.name = Vehicles[k].name.." "..Vehicles[k].brand
- carInfo.price = Vehicles[k].price
- carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
- break
- end
- end
-
- if not carInfo.name then
- debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
- carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
- carInfo.price = 0
- carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
- end
- return carInfo
- else
- if not carInfo.name then
- debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
- carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
- carInfo.price = 0
- carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
- end
- end
- else
- return carInfo
- end
-end
-
--- Vehicle Properties --
-
---- Retrieves the properties of a given vehicle.
----
---- This function fetches the vehicle's properties based on the active framework (QBCore or ox).
---- It utilizes the framework's native functions or events to obtain the vehicle's mod list and other details.
----
---- @param vehicle number The entity ID of the vehicle.
----
---- @return table|nil table containing the vehicle's properties, or `nil` if the vehicle is invalid or the framework is not detected.
----
---- @usage
---- ```lua
---- local props = getVehicleProperties(vehicleEntity)
---- if props then
---- -- Manipulate vehicle properties
---- end
---- ```
-function getVehicleProperties(vehicle)
- local properties = {}
- if vehicle == nil then return nil end
- if isStarted(QBExport) and not isStarted(QBXExport) then
- properties = Core.Functions.GetVehicleProperties(vehicle)
- debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
- elseif isStarted(OXLibExport) then
- properties = lib.getVehicleProperties(vehicle)
- debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
- end
- return properties
-end
-
---- Sets the properties of a given vehicle.
----
---- This function applies the provided properties to the vehicle using the active framework's functions or events.
---- It first retrieves the current properties and checks for differences before applying the new ones.
----
----@param vehicle number The entity ID of the vehicle.
----@param props table The properties to set on the vehicle.
----
----@usage
---- ```lua
---- setVehicleProperties(vehicleEntity, newProperties)
---- ```
-function setVehicleProperties(vehicle, props)
- local oldProps = getVehicleProperties(vehicle)
- if checkDifferences(vehicle, props) then
- --if debugMode then debugDifferences(vehicle, props) end
- if not DoesEntityExist(vehicle) then
- print(("Unable to set vehicle properties for '%s' (entity does not exist)"):format(vehicle))
- end
- if isStarted(QBExport) and not isStarted(QBXExport) then
- Core.Functions.SetVehicleProperties(vehicle, props)
- debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
- else
- TriggerServerEvent(getScript()..":ox:setVehicleProperties", VehToNet(vehicle), props)
- end
- else
- debugPrint("^6Bridge^7: ^2No Changes Found ^7 [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
- end
-end
-
---- Checks for differences between the current and new vehicle properties.
----
---- This function compares each property of the vehicle to determine if any changes have been made.
---- It logs the differences for debugging purposes.
----
----@param vehicle number The entity ID of the vehicle.
----@param newProps table The new properties to compare against the current ones.
----
----@return boolean `true` if differences are found, `false` otherwise.
----
----@usage
---- ```lua
---- if checkDifferences(vehicleEntity, newProperties) then
---- setVehicleProperties(vehicleEntity, newProperties)
---- end
---- ```
-function checkDifferences(vehicle, newProps)
- local oldProps = getVehicleProperties(vehicle)
- debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7")
- local allow = false
- for k in pairs(oldProps) do
- if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then
- allow = true
- debugPrint("^6Bridge^7: ^5Old ^7[^3"..k.."^7] - "..json.encode(oldProps[k], { indent = true }))
- debugPrint("^6Bridge^7: ^5New ^7[^3"..k.."^7] - "..json.encode(newProps[k], { indent = true }))
- end
- end
- return allow
-end
-
---- Handles setting vehicle properties received from the server.
----
---- This event listens for the `ox:setVehicleProperties` event and applies the received properties to the vehicle.
----
----@event
----@param netId number The network ID of the vehicle.
----@param props table The properties to set on the vehicle.
----
----@usage
---- -- Server-side: TriggerClientEvent(getScript()..":ox:setVehicleProperties", netId, properties)
-RegisterNetEvent(getScript()..":ox:setVehicleProperties", function(netId, props)
- local vehicle = NetworkGetEntityFromNetworkId(netId)
- local value = props
- Entity(vehicle).state[getScript()..':setVehicleProperties'] = value
-end)
-
---- Handles state bag changes for setting vehicle properties.
----
---- This handler listens for changes to the vehicle's state bag and applies the new properties accordingly.
----
----@param bagName string The name of the state bag.
----@param key string The key that changed.
----@param value table The new value of the state.
----
----@usage
---- -- Automatically handled when the state bag changes
-AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagName, _, value)
- if not value or not GetEntityFromStateBagName then return end
- local entity = GetEntityFromStateBagName(bagName)
- local networked = not bagName:find('localEntity')
- debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..entity.."^7] - [^3"..GetEntityModel(entity).."^7] - [^3"..value.plate.."^7]")
-
- if networked and NetworkGetEntityOwner(entity) ~= cache.playerId then return end
-
- if lib.setVehicleProperties(entity, value) then
- Entity(entity).state:set('setVehicleProperties', nil, true)
- end
-end)
-
---- Pushes a vehicle to other players by syncing it.
----
---- This function ensures that the vehicle is controlled by the current player and is set as a mission entity.
---- It requests network control and sets the vehicle accordingly to synchronize changes across clients.
----
----@param entity number The entity ID of the vehicle to push.
----
----@usage
---- ```lua
---- pushVehicle(vehicleEntity)
---- ```
-function pushVehicle(entity)
- SetVehicleModKit(entity, 0)
- if entity ~= 0 and DoesEntityExist(entity) then
- if not NetworkHasControlOfEntity(entity) then
- debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.")
- NetworkRequestControlOfEntity(entity)
- local timeout = 2000
- while timeout > 0 and not NetworkHasControlOfEntity(entity) do
- Wait(100)
- timeout = timeout - 100
- end
- if NetworkHasControlOfEntity(entity) then
- debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.")
- end
- end
- if not IsEntityAMissionEntity(entity) then
- debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' &2entity^7.")
- SetEntityAsMissionEntity(entity, true, true)
- local timeout = 2000
- while timeout > 0 and not IsEntityAMissionEntity(entity) do
- Wait(100)
- timeout = timeout - 100
- end
- if IsEntityAMissionEntity(entity) then
- debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.")
- end
- end
- end
-end
+-- Get Vehicle Info --
+local lastCar = nil
+local carInfo = {}
+
+--- Searches the 'Vehicles' table for a specific vehicle's 'name', 'price', and 'class'.
+---
+--- This function checks if the provided vehicle is different from the last searched vehicle.
+--- If it's a new vehicle, it retrieves its model and searches the 'Vehicles' table for matching entries.
+--- It populates the `carInfo` table with the vehicle's name, price, and class.
+--- If the vehicle is not found in the table, it defaults to using the vehicle's display name and sets the price to 0.
+---
+---@param vehicle number The entity ID of the vehicle to search for.
+---
+---@return table|nil table containing the vehicle's `name`, `price`, and `class`, or `nil` if the vehicle is invalid.
+---
+---@usage
+--- ```lua
+--- local info = searchCar(vehicleEntity)
+--- print(info.name, info.price, info.class)
+--- ```
+function searchCar(vehicle)
+ if lastCar ~= vehicle then -- If same car, use previous info
+ lastCar = vehicle
+ carInfo = {}
+ local model = GetEntityModel(vehicle)
+ local classlist = {
+ "Compacts", --1
+ "Sedans", --2
+ "SUVs", --3
+ "Coupes", --4
+ "Muscle", --5
+ "Sports Classics", --6
+ "Sports", --7
+ "Super", --8
+ "Motorcycles", --9
+ "Off-road", --10
+ "Industrial", --11
+ "Utility", --12
+ "Vans", --13
+ "Cycles", --14
+ "Boats", --15
+ "Helicopters", --16
+ "Planes", --17
+ "Service", --18
+ "Emergency", --19
+ "Military", --20
+ "Commercial", --21
+ "Trains", --22
+ }
+ if Vehicles then
+ for k, v in pairs(Vehicles) do
+ if tonumber(v.hash) == model or GetHashKey(v.hash) == model or GetHashKey(v.model) == model then
+ debugPrint("^6Bridge^7: ^2Vehicle info found in^7 ^4Vehicles^7 ^2table^7: ^6"..(v.hash and v.hash or v.model).. " ^7(^6"..Vehicles[k].name.."^7)")
+ carInfo.name = Vehicles[k].name.." "..Vehicles[k].brand
+ carInfo.price = Vehicles[k].price
+ carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
+ break
+ end
+ end
+
+ if not carInfo.name then
+ debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
+ carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
+ carInfo.price = 0
+ carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
+ end
+ return carInfo
+ else
+ if not carInfo.name then
+ debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
+ carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
+ carInfo.price = 0
+ carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
+ end
+ end
+ else
+ return carInfo
+ end
+end
+
+-- Vehicle Properties --
+
+--- Retrieves the properties of a given vehicle.
+---
+--- This function fetches the vehicle's properties based on the active framework (QBCore or ox).
+--- It utilizes the framework's native functions or events to obtain the vehicle's mod list and other details.
+---
+--- @param vehicle number The entity ID of the vehicle.
+---
+--- @return table|nil table containing the vehicle's properties, or `nil` if the vehicle is invalid or the framework is not detected.
+---
+--- @usage
+--- ```lua
+--- local props = getVehicleProperties(vehicleEntity)
+--- if props then
+--- -- Manipulate vehicle properties
+--- end
+--- ```
+function getVehicleProperties(vehicle)
+ local properties = {}
+ if vehicle == nil then return nil end
+ if isStarted(QBExport) and not isStarted(QBXExport) then
+ properties = Core.Functions.GetVehicleProperties(vehicle)
+ debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
+ elseif isStarted(OXLibExport) then
+ properties = lib.getVehicleProperties(vehicle)
+ debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
+ end
+ return properties
+end
+
+--- Sets the properties of a given vehicle.
+---
+--- This function applies the provided properties to the vehicle using the active framework's functions or events.
+--- It first retrieves the current properties and checks for differences before applying the new ones.
+---
+---@param vehicle number The entity ID of the vehicle.
+---@param props table The properties to set on the vehicle.
+---
+---@usage
+--- ```lua
+--- setVehicleProperties(vehicleEntity, newProperties)
+--- ```
+function setVehicleProperties(vehicle, props)
+ local oldProps = getVehicleProperties(vehicle)
+ if checkDifferences(vehicle, props) then
+ --if debugMode then debugDifferences(vehicle, props) end
+ if not DoesEntityExist(vehicle) then
+ print(("Unable to set vehicle properties for '%s' (entity does not exist)"):format(vehicle))
+ end
+ if isStarted(QBExport) and not isStarted(QBXExport) then
+ Core.Functions.SetVehicleProperties(vehicle, props)
+ debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
+ else
+ TriggerServerEvent(getScript()..":ox:setVehicleProperties", VehToNet(vehicle), props)
+ end
+ else
+ debugPrint("^6Bridge^7: ^2No Changes Found ^7 [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
+ end
+end
+
+--- Checks for differences between the current and new vehicle properties.
+---
+--- This function compares each property of the vehicle to determine if any changes have been made.
+--- It logs the differences for debugging purposes.
+---
+---@param vehicle number The entity ID of the vehicle.
+---@param newProps table The new properties to compare against the current ones.
+---
+---@return boolean `true` if differences are found, `false` otherwise.
+---
+---@usage
+--- ```lua
+--- if checkDifferences(vehicleEntity, newProperties) then
+--- setVehicleProperties(vehicleEntity, newProperties)
+--- end
+--- ```
+function checkDifferences(vehicle, newProps)
+ local oldProps = getVehicleProperties(vehicle)
+ debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7")
+ local allow = false
+ for k in pairs(oldProps) do
+ if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then
+ allow = true
+ debugPrint("^6Bridge^7: ^5Old ^7[^3"..k.."^7] - "..json.encode(oldProps[k], { indent = true }))
+ debugPrint("^6Bridge^7: ^5New ^7[^3"..k.."^7] - "..json.encode(newProps[k], { indent = true }))
+ end
+ end
+ return allow
+end
+
+--- Handles setting vehicle properties received from the server.
+---
+--- This event listens for the `ox:setVehicleProperties` event and applies the received properties to the vehicle.
+---
+---@event
+---@param netId number The network ID of the vehicle.
+---@param props table The properties to set on the vehicle.
+---
+---@usage
+--- -- Server-side: TriggerClientEvent(getScript()..":ox:setVehicleProperties", netId, properties)
+RegisterNetEvent(getScript()..":ox:setVehicleProperties", function(netId, props)
+ local vehicle = NetworkGetEntityFromNetworkId(netId)
+ local value = props
+ Entity(vehicle).state[getScript()..':setVehicleProperties'] = value
+end)
+
+--- Handles state bag changes for setting vehicle properties.
+---
+--- This handler listens for changes to the vehicle's state bag and applies the new properties accordingly.
+---
+---@param bagName string The name of the state bag.
+---@param key string The key that changed.
+---@param value table The new value of the state.
+---
+---@usage
+--- -- Automatically handled when the state bag changes
+AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagName, _, value)
+ if not value or not GetEntityFromStateBagName then return end
+ local entity = GetEntityFromStateBagName(bagName)
+ local networked = not bagName:find('localEntity')
+ debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..entity.."^7] - [^3"..GetEntityModel(entity).."^7] - [^3"..value.plate.."^7]")
+
+ if networked and NetworkGetEntityOwner(entity) ~= cache.playerId then return end
+
+ if lib.setVehicleProperties(entity, value) then
+ Entity(entity).state:set('setVehicleProperties', nil, true)
+ end
+end)
+
+--- Pushes a vehicle to other players by syncing it.
+---
+--- This function ensures that the vehicle is controlled by the current player and is set as a mission entity.
+--- It requests network control and sets the vehicle accordingly to synchronize changes across clients.
+---
+---@param entity number The entity ID of the vehicle to push.
+---
+---@usage
+--- ```lua
+--- pushVehicle(vehicleEntity)
+--- ```
+function pushVehicle(entity)
+ SetVehicleModKit(entity, 0)
+ if entity ~= 0 and DoesEntityExist(entity) then
+ if not NetworkHasControlOfEntity(entity) then
+ debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.")
+ NetworkRequestControlOfEntity(entity)
+ local timeout = 2000
+ while timeout > 0 and not NetworkHasControlOfEntity(entity) do
+ Wait(100)
+ timeout = timeout - 100
+ end
+ if NetworkHasControlOfEntity(entity) then
+ debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.")
+ end
+ end
+ if not IsEntityAMissionEntity(entity) then
+ debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' &2entity^7.")
+ SetEntityAsMissionEntity(entity, true, true)
+ local timeout = 2000
+ while timeout > 0 and not IsEntityAMissionEntity(entity) do
+ Wait(100)
+ timeout = timeout - 100
+ end
+ if IsEntityAMissionEntity(entity) then
+ debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.")
+ end
+ end
+ end
+end
diff --git a/shared/versioncheck.lua b/shared/versioncheck.lua
index 1d08e22..2b2dbde 100644
--- a/shared/versioncheck.lua
+++ b/shared/versioncheck.lua
@@ -1,47 +1,47 @@
--- Version check for jim_bridge --
-function CheckBridgeVersion()
- if isServer() then
- local currentVersion = "^3"..GetResourceMetadata("jim_bridge", 'version'):gsub("%.", "^7.^3").."^7"
- PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, newestVersion, headers)
- if not newestVersion then print("^1Currently unable to run a version check for ^7'^3jim_bridge^7' ("..currentVersion.."^7)") return end
- newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
- print(newestVersion == currentVersion and "^7'^3jim_bridge^7' - ^6You are running the latest version.^7 ("..currentVersion..")" or "^7'^3jim_bridge^7' - ^1You are currently running an outdated version^7, ^1please update^7!")
- end)
- end
-end
---CheckBridgeVersion()
-
--- Print Script names
-function capitalize(str)
- return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
-end
-
-local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
-local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
-local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
-local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
-
-print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
-
--- Loaded script Version Check, requires CheckVersion() to be placed in a server file
-function CheckVersion()
- if isServer() then
- local currentVersion = "^3"..GetResourceMetadata(getScript(), 'version'):gsub("%.", "^7.^3").."^7"
- PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..getScript()..'.txt', function(err, newestVersion, headers)
- if not newestVersion then
- PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..getScript()..'/master/version.txt', function(err, freeVersion, headers)
- if not freeVersion then print("^1Currently unable to run a version check for ^7'^3"..getScript().."^7' ("..currentVersion.."^7)") return end
- local currentVersion = "^3"..GetResourceMetadata(getScript(), 'version'):gsub("%.", "^7.^3").."^7"
- freeVersion = "^3"..freeVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
- print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..freeVersion)
- print(freeVersion == currentVersion and "^7'^3"..getScript().."^7' - ^6You are running the latest version.^7 ("..currentVersion..")" or "^7'^3"..getScript().."^7' - ^1You are currently running an outdated version^7, ^1please update^7!")
- end)
- else
- newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
- print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..newestVersion)
- print(newestVersion == currentVersion and '^6You are running the latest version.^7 ('..currentVersion..')' or "^1You are currently running an outdated version^7, ^1please update^7!")
- end
- end)
- end
-end
+-- Version check for jim_bridge --
+function CheckBridgeVersion()
+ if isServer() then
+ local currentVersion = "^3"..GetResourceMetadata("jim_bridge", 'version'):gsub("%.", "^7.^3").."^7"
+ PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, newestVersion, headers)
+ if not newestVersion then print("^1Currently unable to run a version check for ^7'^3jim_bridge^7' ("..currentVersion.."^7)") return end
+ newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
+ print(newestVersion == currentVersion and "^7'^3jim_bridge^7' - ^6You are running the latest version.^7 ("..currentVersion..")" or "^7'^3jim_bridge^7' - ^1You are currently running an outdated version^7, ^1please update^7!")
+ end)
+ end
+end
+--CheckBridgeVersion()
+
+-- Print Script names
+function capitalize(str)
+ return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
+end
+
+local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
+local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
+local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
+local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
+
+print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
+
+-- Loaded script Version Check, requires CheckVersion() to be placed in a server file
+function CheckVersion()
+ if isServer() then
+ local currentVersion = "^3"..GetResourceMetadata(getScript(), 'version'):gsub("%.", "^7.^3").."^7"
+ PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..getScript()..'.txt', function(err, newestVersion, headers)
+ if not newestVersion then
+ PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..getScript()..'/master/version.txt', function(err, freeVersion, headers)
+ if not freeVersion then print("^1Currently unable to run a version check for ^7'^3"..getScript().."^7' ("..currentVersion.."^7)") return end
+ local currentVersion = "^3"..GetResourceMetadata(getScript(), 'version'):gsub("%.", "^7.^3").."^7"
+ freeVersion = "^3"..freeVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
+ print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..freeVersion)
+ print(freeVersion == currentVersion and "^7'^3"..getScript().."^7' - ^6You are running the latest version.^7 ("..currentVersion..")" or "^7'^3"..getScript().."^7' - ^1You are currently running an outdated version^7, ^1please update^7!")
+ end)
+ else
+ newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
+ print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..newestVersion)
+ print(newestVersion == currentVersion and '^6You are running the latest version.^7 ('..currentVersion..')' or "^1You are currently running an outdated version^7, ^1please update^7!")
+ end
+ end)
+ end
+end
--CheckVersion()
\ No newline at end of file
diff --git a/shared/wrapperfunctions.lua b/shared/wrapperfunctions.lua
index 6919b1f..66e56e5 100644
--- a/shared/wrapperfunctions.lua
+++ b/shared/wrapperfunctions.lua
@@ -1,260 +1,265 @@
--- Phone Mails
-
---- Sends a phone mail using the detected phone system.
----
---- This function detects the active phone resource (e.g., gksphone, yflip-phone, qb-phone, etc.)
---- and sends a mail using the appropriate method for that phone system.
----
---- @param data table A table containing the mail data.
---- - **subject** (`string`): The subject of the email.
---- - **sender** (`string`): The sender of the email.
---- - **message** (`string`): The body content of the email.
---- - **actions** (`table|nil`): (Optional) Action buttons associated with the email.
----
---- @usage
---- ```lua
---- sendPhoneMail({
---- subject = "Welcome!",
---- sender = "Admin",
---- message = "Thank you for joining our server.",
---- actions = {
---- { label = "Reply", action = replyFunction }
---- }
---- })
---- ```
-function sendPhoneMail(data) local phoneResource = ""
- if isStarted("gksphone") then phoneResource = "gksphone"
- exports["gksphone"]:SendNewMail(data)
-
- elseif isStarted("yflip-phone") then phoneResource = "yflip-phone"
- TriggerServerEvent(getScript()..":yflip:SendMail", data)
-
- elseif isStarted("qs-smartphone") then phoneResource = "qs-smartphone"
- TriggerServerEvent('qs-smartphone:server:sendNewMail', data)
-
- elseif isStarted("qs-smartphone-pro") then phoneResource = "qs-smartphone-pro"
- TriggerServerEvent('phone:sendNewMail', data)
-
- elseif isStarted("roadphone") then phoneResource = "roadphone"
- data.message = data.message:gsub("%
", "\n")
- exports['roadphone']:sendMail(data)
-
- elseif isStarted("lb-phone") then phoneResource = "lb-phone"
- data.message = data.message:gsub("%
", "\n")
- TriggerServerEvent(getScript()..":lbphone:SendMail", data)
-
- elseif isStarted("qb-phone") then phoneResource = "qb-phone"
- TriggerServerEvent('qb-phone:server:sendNewMail', data)
-
- elseif isStarted("jpr-phonesystem") then phoneResource = "jpr-phonesystem"
- TriggerServerEvent(getScript()..":jpr:SendMail", data)
- end
-
- if phoneResource ~= "" then debugPrint("^6Bridge^7[^3"..phoneResource.."^7]: ^2Sending mail to player")
- else print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7 - ^2No supported phone found") end
-end
-
---- Handles sending mail for lb-phone.
----
---- This event listens for the `lbphone:SendMail` event and sends an email using lb-phone's API.
----
---- @event
---- @param data table The mail data.
---- - **subject** (`string`): The subject of the email.
---- - **message** (`string`): The body content of the email.
---- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
----
---- @usage
---- ```
---- -- Server-side:
---- TriggerClientEvent(getScript()..":lbphone:SendMail", data)
---- ```
-RegisterNetEvent(getScript()..":lbphone:SendMail", function(data)
- local src = source
- local phoneNumber = exports["lb-phone"]:GetEquippedPhoneNumber(src)
- local emailAddress = exports["lb-phone"]:GetEmailAddress(phoneNumber)
- if data.actions then data.buttons = data.actions end
- exports["lb-phone"]:SendMail({
- to = emailAddress,
- subject = data.subject,
- message = data.message,
- actions = data.buttons,
- })
-end)
-
---- Handles sending mail for yflip-phone.
----
---- This event listens for the `yflip:SendMail` event and sends an email using yflip-phone's API.
----
---- @event
---- @param data table The mail data.
---- - **subject** (`string`): The subject of the email.
---- - **sender** (`string`): The sender of the email.
---- - **message** (`string`): The body content of the email.
---- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
----
---- @usage
---- ```lua
---- -- Server-side:
---- TriggerClientEvent(getScript()..":yflip:SendMail", data)
---- ```
-RegisterNetEvent(getScript()..":yflip:SendMail", function(data)
- local src = source
- exports["yflip-phone"]:SendMail({
- title = data.subject,
- sender = data.sender,
- senderDisplayName = data.sender,
- content = data.message,
- actions = data.buttons,
- }, 'source', src)
-end)
-
---- Handles sending mail for jpr-phonesystem.
----
---- This event listens for the `jpr:SendMail` event and sends an email using jpr-phonesystem's API.
----
---- @event
---- @param data table The mail data.
---- - **subject** (`string`): The subject of the email.
---- - **sender** (`string`): The sender of the email.
---- - **message** (`string`): The body content of the email.
---- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
----
---- @return void
----
---- @usage
---- ```lua
---- -- Server-side:
---- TriggerClientEvent(getScript()..":jpr:SendMail", data)
---- ```
-RegisterNetEvent(getScript()..":jpr:SendMail", function(data)
- local src = source
- local Player = Core.Functions.GetPlayer(src)
- TriggerEvent('jpr-phonesystem:server:sendEmail', {
- Assunto = data.subject, -- Subject
- Conteudo = data.message, -- Content
- Enviado = data.sender, -- Submitted by
- Destinatario = Player.PlayerData.citizenid, -- Target
- Event = {}, -- Optional
- })
-end)
-
--- Server-Side Functions for Registering Commands, Stashes, and Shops
-
---- Registers a command with the active command system.
----
---- This function detects whether the server is using OXLib or qb-core for command registration
---- and registers the command accordingly.
----
---- @param command string The name of the command to register.
---- @param options table A table containing command options.
---- - **help** (`string`): The help description for the command.
---- - **params** (`table`): A table of parameters for the command.
---- - **callback** (`function`): The function to execute when the command is called.
---- - **autocomplete** (`function|nil`): (Optional) A function for autocompletion.
---- - **restrictedGroup** (`string|nil`): (Optional) The user group required to execute the command.
----
---- @usage
---- ````lua
---- -- Server Side:
---- registerCommand("greet", {
---- "Greets the player",
---- { name = "name", help = "Name of the player to greet" },
---- function(source, args) print("Hello, " .. args[1] .. "!") end,
---- nil,
---- "admin"
---- })
---- ```
-function registerCommand(command, options)
- if isStarted(OXLibExport) then
- debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7"..OXLibExport, command)
- lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4])
- elseif isStarted(QBExport) and not isStarted(QBXExport) then
- debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7 "..QBExport, command)
- Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] and options[5] or nil)
- end
-end
-
---- Registers a stash with the active inventory system.
----
---- This function detects whether the server is using OXInv or QSInv and registers the stash accordingly.
----
---- @param name string The unique identifier for the stash.
---- @param label string The display name for the stash.
---- @param slots number|nil (Optional) The number of slots in the stash. Defaults to 50.
---- @param weight number|nil (Optional) The maximum weight the stash can hold. Defaults to 4,000,000.
---- @param owner string|nil (Optional) The owner identifier for personal stashes.
---- @param coords table|nil (Optional) The coordinates for the stash location.
----
---- @usage
---- ```lua
---- registerStash("playerStash", "Player Stash", 100, 8000000, "player123", { x = 100.0, y = 200.0, z = 30.0 })
---- ```
-function registerStash(name, label, slots, weight, owner, coords)
- if isStarted(OXInv) then
- debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Stash^7:", name, label, owner or nil)
- exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil)
- elseif isStarted(QSInv) then
- debugPrint("^6Bridge^7: ^2Registering ^3QS ^2Stash^7:", name, label)
- exports[QSInv]:RegisterStash(name, slots or 50, weight or 4000000)
- end
-end
-
---- Registers a shop with the active inventory system.
----
---- This function detects whether the server is using OXInv or QBInv and registers the shop accordingly.
----
---- @param name string The unique identifier for the shop.
---- @param label string The display name for the shop.
---- @param items table The list of items available in the shop.
---- @param society string|nil (Optional) The society identifier for shared shops.
----
---- @usage
---- ```lua
---- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons")
---- ```
-function registerShop(name, label, items, society)
- if isStarted(OXInv) then
- debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Store^7:", name, label)
- exports[OXInv]:RegisterShop(
- name, {
- name = label,
- inventory = items,
- society = society,
- }
- )
- elseif isStarted(QBInv) and QBInvNew then
- debugPrint("^6Bridge^7: ^2Registering ^3QB ^2Store^7:", name, label)
- print(json.encode(items, {indent = true}))
- exports[QBInv]:CreateShop({
- name = name,
- label = label,
- slots = #items,
- items = items,
- society = society,
- })
- end
-end
-
--- Server-Side Event Registration
-
-if isServer() then
- --- Registers an event to create an OX stash from the server.
- ---
- --- @event
- --- @param name string The unique identifier for the stash.
- --- @param label string The display name for the stash.
- --- @param slots number|nil (Optional) The number of slots in the stash.
- --- @param weight number|nil (Optional) The maximum weight the stash can hold.
- --- @param owner string|nil (Optional) The owner identifier for personal stashes.
- --- @param coords table|nil (Optional) The coordinates for the stash location.
- ---
- --- @usage
- --- ```lua
- --- -- Server-side:
- --- TriggerEvent(getScript()..":server:makeOXStash", name, label, slots, weight, owner, coords)
- --- ```
- RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords)
- registerStash(name, label, slots, weight, owner, coords)
- end)
+-- Phone Mails
+
+--- Sends a phone mail using the detected phone system.
+---
+--- This function detects the active phone resource (e.g., gksphone, yflip-phone, qb-phone, etc.)
+--- and sends a mail using the appropriate method for that phone system.
+---
+--- @param data table A table containing the mail data.
+--- - **subject** (`string`): The subject of the email.
+--- - **sender** (`string`): The sender of the email.
+--- - **message** (`string`): The body content of the email.
+--- - **actions** (`table|nil`): (Optional) Action buttons associated with the email.
+---
+--- @usage
+--- ```lua
+--- sendPhoneMail({
+--- subject = "Welcome!",
+--- sender = "Admin",
+--- message = "Thank you for joining our server.",
+--- actions = {
+--- { label = "Reply", action = replyFunction }
+--- }
+--- })
+--- ```
+function sendPhoneMail(data) local phoneResource = ""
+ if isStarted("gksphone") then phoneResource = "gksphone"
+ exports["gksphone"]:SendNewMail(data)
+
+ elseif isStarted("yflip-phone") then phoneResource = "yflip-phone"
+ TriggerServerEvent(getScript()..":yflip:SendMail", data)
+
+ elseif isStarted("qs-smartphone") then phoneResource = "qs-smartphone"
+ TriggerServerEvent('qs-smartphone:server:sendNewMail', data)
+
+ elseif isStarted("qs-smartphone-pro") then phoneResource = "qs-smartphone-pro"
+ TriggerServerEvent('phone:sendNewMail', data)
+
+ elseif isStarted("roadphone") then phoneResource = "roadphone"
+ data.message = data.message:gsub("%
", "\n")
+ exports['roadphone']:sendMail(data)
+
+ elseif isStarted("lb-phone") then phoneResource = "lb-phone"
+ data.message = data.message:gsub("%
", "\n")
+ TriggerServerEvent(getScript()..":lbphone:SendMail", data)
+
+ elseif isStarted("qb-phone") then phoneResource = "qb-phone"
+ TriggerServerEvent('qb-phone:server:sendNewMail', data)
+
+ elseif isStarted("jpr-phonesystem") then phoneResource = "jpr-phonesystem"
+ TriggerServerEvent(getScript()..":jpr:SendMail", data)
+ end
+
+ if phoneResource ~= "" then debugPrint("^6Bridge^7[^3"..phoneResource.."^7]: ^2Sending mail to player")
+ else print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7 - ^2No supported phone found") end
+end
+
+--- Handles sending mail for lb-phone.
+---
+--- This event listens for the `lbphone:SendMail` event and sends an email using lb-phone's API.
+---
+--- @event
+--- @param data table The mail data.
+--- - **subject** (`string`): The subject of the email.
+--- - **message** (`string`): The body content of the email.
+--- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
+---
+--- @usage
+--- ```
+--- -- Server-side:
+--- TriggerClientEvent(getScript()..":lbphone:SendMail", data)
+--- ```
+RegisterNetEvent(getScript()..":lbphone:SendMail", function(data)
+ local src = source
+ local phoneNumber = exports["lb-phone"]:GetEquippedPhoneNumber(src)
+ local emailAddress = exports["lb-phone"]:GetEmailAddress(phoneNumber)
+ if data.actions then data.buttons = data.actions end
+ exports["lb-phone"]:SendMail({
+ to = emailAddress,
+ subject = data.subject,
+ message = data.message,
+ actions = data.buttons,
+ })
+end)
+
+--- Handles sending mail for yflip-phone.
+---
+--- This event listens for the `yflip:SendMail` event and sends an email using yflip-phone's API.
+---
+--- @event
+--- @param data table The mail data.
+--- - **subject** (`string`): The subject of the email.
+--- - **sender** (`string`): The sender of the email.
+--- - **message** (`string`): The body content of the email.
+--- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
+---
+--- @usage
+--- ```lua
+--- -- Server-side:
+--- TriggerClientEvent(getScript()..":yflip:SendMail", data)
+--- ```
+RegisterNetEvent(getScript()..":yflip:SendMail", function(data)
+ local src = source
+ exports["yflip-phone"]:SendMail({
+ title = data.subject,
+ sender = data.sender,
+ senderDisplayName = data.sender,
+ content = data.message,
+ actions = data.buttons,
+ }, 'source', src)
+end)
+
+--- Handles sending mail for jpr-phonesystem.
+---
+--- This event listens for the `jpr:SendMail` event and sends an email using jpr-phonesystem's API.
+---
+--- @event
+--- @param data table The mail data.
+--- - **subject** (`string`): The subject of the email.
+--- - **sender** (`string`): The sender of the email.
+--- - **message** (`string`): The body content of the email.
+--- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
+---
+--- @return void
+---
+--- @usage
+--- ```lua
+--- -- Server-side:
+--- TriggerClientEvent(getScript()..":jpr:SendMail", data)
+--- ```
+RegisterNetEvent(getScript()..":jpr:SendMail", function(data)
+ local src = source
+ local Player = Core.Functions.GetPlayer(src)
+ TriggerEvent('jpr-phonesystem:server:sendEmail', {
+ Assunto = data.subject, -- Subject
+ Conteudo = data.message, -- Content
+ Enviado = data.sender, -- Submitted by
+ Destinatario = Player.PlayerData.citizenid, -- Target
+ Event = {}, -- Optional
+ })
+end)
+
+-- Server-Side Functions for Registering Commands, Stashes, and Shops
+
+--- Registers a command with the active command system.
+---
+--- This function detects whether the server is using OXLib or qb-core for command registration
+--- and registers the command accordingly.
+---
+--- @param command string The name of the command to register.
+--- @param options table A table containing command options.
+--- - **help** (`string`): The help description for the command.
+--- - **params** (`table`): A table of parameters for the command.
+--- - **callback** (`function`): The function to execute when the command is called.
+--- - **autocomplete** (`function|nil`): (Optional) A function for autocompletion.
+--- - **restrictedGroup** (`string|nil`): (Optional) The user group required to execute the command.
+---
+--- @usage
+--- ````lua
+--- -- Server Side:
+--- registerCommand("greet", {
+--- "Greets the player",
+--- { name = "name", help = "Name of the player to greet" },
+--- function(source, args) print("Hello, " .. args[1] .. "!") end,
+--- nil,
+--- "admin"
+--- })
+--- ```
+function registerCommand(command, options)
+ if isStarted(OXLibExport) then
+ debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7"..OXLibExport, command)
+ lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4])
+ elseif isStarted(QBExport) and not isStarted(QBXExport) then
+ debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7 "..QBExport, command)
+ Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] and options[5] or nil)
+ elseif isStarted(ESXExport) then
+ debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7 ESX Legacy", command)
+ ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError)
+ options[4](xPlayer.source, args, showError)
+ end, false, { help = options[1] })
+ end
+end
+
+--- Registers a stash with the active inventory system.
+---
+--- This function detects whether the server is using OXInv or QSInv and registers the stash accordingly.
+---
+--- @param name string The unique identifier for the stash.
+--- @param label string The display name for the stash.
+--- @param slots number|nil (Optional) The number of slots in the stash. Defaults to 50.
+--- @param weight number|nil (Optional) The maximum weight the stash can hold. Defaults to 4,000,000.
+--- @param owner string|nil (Optional) The owner identifier for personal stashes.
+--- @param coords table|nil (Optional) The coordinates for the stash location.
+---
+--- @usage
+--- ```lua
+--- registerStash("playerStash", "Player Stash", 100, 8000000, "player123", { x = 100.0, y = 200.0, z = 30.0 })
+--- ```
+function registerStash(name, label, slots, weight, owner, coords)
+ if isStarted(OXInv) then
+ debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Stash^7:", name, label, owner or nil)
+ exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil)
+ elseif isStarted(QSInv) then
+ debugPrint("^6Bridge^7: ^2Registering ^3QS ^2Stash^7:", name, label)
+ exports[QSInv]:RegisterStash(name, slots or 50, weight or 4000000)
+ end
+end
+
+--- Registers a shop with the active inventory system.
+---
+--- This function detects whether the server is using OXInv or QBInv and registers the shop accordingly.
+---
+--- @param name string The unique identifier for the shop.
+--- @param label string The display name for the shop.
+--- @param items table The list of items available in the shop.
+--- @param society string|nil (Optional) The society identifier for shared shops.
+---
+--- @usage
+--- ```lua
+--- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons")
+--- ```
+function registerShop(name, label, items, society)
+ if isStarted(OXInv) then
+ debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Store^7:", name, label)
+ exports[OXInv]:RegisterShop(
+ name, {
+ name = label,
+ inventory = items,
+ society = society,
+ }
+ )
+ elseif isStarted(QBInv) and QBInvNew then
+ debugPrint("^6Bridge^7: ^2Registering ^3QB ^2Store^7:", name, label)
+ print(json.encode(items, {indent = true}))
+ exports[QBInv]:CreateShop({
+ name = name,
+ label = label,
+ slots = #items,
+ items = items,
+ society = society,
+ })
+ end
+end
+
+-- Server-Side Event Registration
+
+if isServer() then
+ --- Registers an event to create an OX stash from the server.
+ ---
+ --- @event
+ --- @param name string The unique identifier for the stash.
+ --- @param label string The display name for the stash.
+ --- @param slots number|nil (Optional) The number of slots in the stash.
+ --- @param weight number|nil (Optional) The maximum weight the stash can hold.
+ --- @param owner string|nil (Optional) The owner identifier for personal stashes.
+ --- @param coords table|nil (Optional) The coordinates for the stash location.
+ ---
+ --- @usage
+ --- ```lua
+ --- -- Server-side:
+ --- TriggerEvent(getScript()..":server:makeOXStash", name, label, slots, weight, owner, coords)
+ --- ```
+ RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords)
+ registerStash(name, label, slots, weight, owner, coords)
+ end)
end
\ No newline at end of file
diff --git a/starter.lua b/starter.lua
index bd95cb5..5b9236a 100644
--- a/starter.lua
+++ b/starter.lua
@@ -1,89 +1,89 @@
-Exports = {
- QBExport = "qb-core",
- QBXExport = "qbx_core",
- ESXExport = "es_extended",
- OXCoreExport = "ox_core",
-
- OXInv = "ox_inventory",
- QBInv = "qb-inventory",
- PSInv = "ps-inventory",
- QSInv = "qs-inventory",
- CoreInv = "core_inventory",
- CodeMInv = "codem-inventory",
- OrigenInv = "origen_inventory",
-
- OXLibExport = "ox_lib",
-
- QBMenuExport = "qb-menu",
-
- QBTargetExport = "qb-target",
- OXTargetExport = "ox_target"
-}
-
--- Required variables
-debugMode = Config.System.Debug
-
-QBInvNew = true
-
-InventoryWeight = 120000
-
--- Load files here into the invoking script
-for _, v in pairs({ -- This is a specific load order
- 'helpers.lua', -- needs to be first
- '_loaders.lua',
-
- '_eventDebug.lua',
- 'coreloader.lua', -- needs to be second to load all core related stuff before everything else
- 'callback.lua',
-
- 'duifunctions.lua',
-
- -- Native Scaleforms
- 'scaleforms/bigMessageInstance.lua',
- 'scaleforms/countDownHandler.lua',
- 'scaleforms/debugScaleform.lua',
- 'scaleforms/instructionalButtons.lua',
- 'scaleforms/timerBars.lua',
-
- -- Required functions
- 'make/loaders.lua',
- 'make/makeBlip.lua',
- 'make/makePed.lua',
- 'make/makeProp.lua',
- 'make/makeVeh.lua',
- 'make/cameras.lua',
- 'make/progressBars.lua',
-
- 'wrapperfunctions.lua',
- 'polyZone.lua',
- 'itemcontrol.lua',
- 'playerfunctions.lua',
- 'jobfunctions.lua',
-
- -- Interactions
- 'targets.lua',
- 'contextmenus.lua',
- 'input.lua',
- 'notify.lua',
- 'drawText.lua',
-
- -- Crafting / Shops / Stashes
- 'crafting.lua',
- 'stashcontrol.lua',
-
- -- Kind of "other"
- 'isAnimal.lua',
- 'scaleEntity.lua',
- 'vehicles.lua',
- 'effects.lua',
- 'versioncheck.lua'
-}) do
- if debugMode then
- print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
- end
- local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v)))
- fileLoader()
- if debugMode then
- print("^5Success^7: ^2loaded file^7: 'jim_bridge/shared/"..v.."'!")
- end
+Exports = {
+ QBExport = "qb-core",
+ QBXExport = "qbx_core",
+ ESXExport = "es_extended",
+ OXCoreExport = "ox_core",
+
+ OXInv = "ox_inventory",
+ QBInv = "qb-inventory",
+ PSInv = "ps-inventory",
+ QSInv = "qs-inventory",
+ CoreInv = "core_inventory",
+ CodeMInv = "codem-inventory",
+ OrigenInv = "origen_inventory",
+
+ OXLibExport = "ox_lib",
+
+ QBMenuExport = "qb-menu",
+
+ QBTargetExport = "qb-target",
+ OXTargetExport = "ox_target"
+}
+
+-- Required variables
+debugMode = Config.System.Debug
+
+QBInvNew = true
+
+InventoryWeight = 120000
+
+-- Load files here into the invoking script
+for _, v in pairs({ -- This is a specific load order
+ 'helpers.lua', -- needs to be first
+ '_loaders.lua',
+
+ '_eventDebug.lua',
+ 'coreloader.lua', -- needs to be second to load all core related stuff before everything else
+ 'callback.lua',
+
+ 'duifunctions.lua',
+
+ -- Native Scaleforms
+ 'scaleforms/bigMessageInstance.lua',
+ 'scaleforms/countDownHandler.lua',
+ 'scaleforms/debugScaleform.lua',
+ 'scaleforms/instructionalButtons.lua',
+ 'scaleforms/timerBars.lua',
+
+ -- Required functions
+ 'make/loaders.lua',
+ 'make/makeBlip.lua',
+ 'make/makePed.lua',
+ 'make/makeProp.lua',
+ 'make/makeVeh.lua',
+ 'make/cameras.lua',
+ 'make/progressBars.lua',
+
+ 'wrapperfunctions.lua',
+ 'polyZone.lua',
+ 'itemcontrol.lua',
+ 'playerfunctions.lua',
+ 'jobfunctions.lua',
+
+ -- Interactions
+ 'targets.lua',
+ 'contextmenus.lua',
+ 'input.lua',
+ 'notify.lua',
+ 'drawText.lua',
+
+ -- Crafting / Shops / Stashes
+ 'crafting.lua',
+ 'stashcontrol.lua',
+
+ -- Kind of "other"
+ 'isAnimal.lua',
+ 'scaleEntity.lua',
+ 'vehicles.lua',
+ 'effects.lua',
+ 'versioncheck.lua'
+}) do
+ if debugMode then
+ print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
+ end
+ local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v)))
+ fileLoader()
+ if debugMode then
+ print("^5Success^7: ^2loaded file^7: 'jim_bridge/shared/"..v.."'!")
+ end
end
\ No newline at end of file
diff --git a/version.txt b/version.txt
index 5625e59..6609db5 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-1.2
+1.2