diff --git a/shared/_loaders.lua b/shared/_loaders.lua index 78f87b4..07c617a 100644 --- a/shared/_loaders.lua +++ b/shared/_loaders.lua @@ -1,81 +1,101 @@ ---- 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) +--[[ + Player & Resource Event Utility Functions + ------------------------------------------- + This module provides functions to: + • Execute code when the player character is loaded or unloaded. + • Execute code on resource start and stop. + • Wait for the player to be logged in before proceeding. +]] + +------------------------------------------------------------- +-- Player Loaded and Unloaded Events +------------------------------------------------------------- + +--- Executes a function when the player character is loaded. +--- If onStart is true, the function will also run on resource start (after ensuring the player is logged in). --- --- @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`. ---- +--- @param onStart boolean (optional) If true, also execute on resource start. Default is false. --- @usage --- ```lua --- onPlayerLoaded(function() ---- -- Your code here +--- print("Player logged in") +--- -- Your initialization code here. --- end, true) --- ``` function onPlayerLoaded(func, onStart) - local onPlayerName = "" + local onPlayerFramework = "" 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()") + if not waitForLogin() then return end + + loaded = true + debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 executed through ^3onPlayerLoaded^7()") Wait(2000) func() end, true) end + if not loaded then local tempFunc = function() - debugPrint("^6Bridge^7: ^2Executing ^3onPlayerLoaded^7()") + debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded") func() end - if isStarted(QBExport) or isStarted(QBXExport) then onPlayerName = QBExport + + if isStarted(QBExport) or isStarted(QBXExport) then + onPlayerFramework = QBExport AddEventHandler('QBCore:Client:OnPlayerLoaded', tempFunc) - elseif isStarted(ESXExport) then onPlayerName = ESXExport + elseif isStarted(ESXExport) then + onPlayerFramework = ESXExport AddEventHandler('esx:playerLoaded', tempFunc) - elseif isStarted(OXCoreExport) then onPlayerName = OXCoreExport + elseif isStarted(OXCoreExport) then + onPlayerFramework = OXCoreExport AddEventHandler('ox:playerLoaded', tempFunc) end - if onPlayerName ~= "" then - debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^2 with ^7"..onPlayerName) + + if onPlayerFramework ~= "" then + debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^7()^2 with ^3" .. onPlayerFramework.."^7") else - print("^4ERROR^7: ^2No Core detected for onPlayerLoaded ^7- ^2Check ^3exports^1.^2lua^7") + print("^4ERROR^7: No supported core detected for onPlayerLoaded - Check exports.lua") end end end ---trying to add unload functions for when players switch ped +--- Executes a function when the player character is unloaded. +--- @param func function The function to execute when the player unloads. +--- @usage +--- ```lua +--- onPlayerUnload(function() +--- print("Player has logged out of their character") +--- -- Your cleanup code here. +--- end) +--- ``` function onPlayerUnload(func) - AddEventHandler('QBCore:Client:OnPlayerUnload', function() - func() - end) - AddEventHandler('ox:playerLogout', function() - func() - end) + AddEventHandler('QBCore:Client:OnPlayerUnload', function() func() end) + AddEventHandler('ox:playerLogout', function() func() end) + + --AddEventHandler('esx:playerLogout', function() func() end) + -- ^ Only server side for now, need a way to send it to client if not already available end +------------------------------------------------------------- +-- Resource Start and Stop Events +------------------------------------------------------------- --- 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`. ---- +--- @param func function The function to execute. +--- @param thisScript boolean (optional) If true, only runs when this resource starts (default true). --- @usage --- ```lua --- onResourceStart(function() ---- -- Your code here +--- print("Script ensured") +--- -- Initialization code on resource start. --- end, true) --- ``` function onResourceStart(func, thisScript) - debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^2") + debugPrint("^6Bridge^7: Registering ^3onResourceStart^7()") AddEventHandler('onResourceStart', function(resourceName) if getScript() == resourceName and (thisScript or true) then func() @@ -84,20 +104,16 @@ function onResourceStart(func, thisScript) 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`. ---- +--- @param func function The function to execute. +--- @param thisScript boolean (optional) If true, only runs when this resource stops (default true). --- @usage --- ```lua --- onResourceStop(function() ---- -- Cleanup code here +--- -- Cleanup code here. --- end, true) --- ``` function onResourceStop(func, thisScript) - debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^2") + debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^7()") AddEventHandler('onResourceStop', function(resourceName) if getScript() == resourceName and (thisScript or true) then func() @@ -105,17 +121,41 @@ function onResourceStop(func, thisScript) end) end ---- Waits until the player is logged in before continuing execution. ---- ---- This function blocks execution until `LocalPlayer.state.isLoggedIn` is `true`. ---- ----@usage ---- ```lua +------------------------------------------------------------- +-- Wait for Login +------------------------------------------------------------- + +--- Blocks execution until the player is logged in. +--- @usage --- waitForLogin() ---- ``` function waitForLogin() - while not LocalPlayer.state.isLoggedIn do - debugPrint("Waiting") - Wait(100) + local timeout = 10000 -- 10 seconds in milliseconds + local startTime = GetGameTimer() + local loggedIn = false + + if isStarted(ESXExport) then + debugPrint("^6Bridge^7: ^3ESX waitForLogin^7() ^2running^7") + while (GetGameTimer() - startTime) < timeout do + local playerData = ESX.GetPlayerData() + if playerData and playerData.job then + loggedIn = true + break + end + Wait(100) + end + else + -- For other frameworks, use LocalPlayer.state.isLoggedIn. + while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do + Wait(100) + end + loggedIn = LocalPlayer.state.isLoggedIn end -end \ No newline at end of file + + if not loggedIn then + print("^4Error^7: ^2Timeout reached while waiting for player login^7.") + return false + else + debugPrint("^6Bridge^7: ^2Player Login Detected^7.") + return true + end +end diff --git a/shared/versioncheck.lua b/shared/_versioncheck.lua similarity index 100% rename from shared/versioncheck.lua rename to shared/_versioncheck.lua diff --git a/shared/contextmenus.lua b/shared/contextmenus.lua index d6a35cc..4bc0778 100644 --- a/shared/contextmenus.lua +++ b/shared/contextmenus.lua @@ -1,25 +1,37 @@ +--[[ + Menu Opening Module + --------------------- + This module provides a unified function to open menus using the configured menu system. + Supported systems include: + • jim-nui (kinda) + • ox (or ox_context) + • qb (using QBMenuExport) + • gta (using WarMenu) + • esx (using ESX.UI.Menu) +]] + --- 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`. +--- - 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. +--- - 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 @@ -36,6 +48,7 @@ --- ``` function openMenu(Menu, data) if Config.System.Menu == "jim" then + -- Insert "Return" option if onBack is defined. if data.onBack then table.insert(Menu, 1, { icon = "fas fa-circle-arrow-left", @@ -65,6 +78,7 @@ function openMenu(Menu, data) if data.onSelected and Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end + -- If no title, use header or txt as title/label. if not Menu[k].title then if Menu[k].header ~= nil and Menu[k].header ~= "" then Menu[k].title = Menu[k].header @@ -75,6 +89,7 @@ function openMenu(Menu, data) Menu[k].label = Menu[k].txt end end + -- Copy parameters from 'params' if available. if Menu[k].params then Menu[k].event = Menu[k].params.event Menu[k].args = Menu[k].params.args or {} @@ -143,17 +158,10 @@ function openMenu(Menu, data) 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 + Menu[k].params = { + isAction = true, + event = Menu[k].onSelect or function() 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 @@ -162,15 +170,12 @@ function openMenu(Menu, data) 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, - }) + 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() @@ -239,7 +244,6 @@ function openMenu(Menu, data) onSelect = data.onBack, }) end - ESX.UI.Menu.Open("default", getScript(), "Example_Menu", { title = data.header, align = 'top-right', @@ -260,15 +264,11 @@ function openMenu(Menu, data) end end ---- A line break constant used for formatting menu headers. +--- A line break constant used for menu header formatting. 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`. ---- +--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes. +--- @return boolean boolean True if using ox or gta menus, otherwise false. --- @usage --- ```lua --- if isOx() then @@ -280,7 +280,7 @@ function isOx() return (Config.System.Menu == "ox" or Config.System.Menu == "gta --- Checks if any WarMenu menu is currently open. --- ---- @return boolean Returns `true` if a WarMenu menu is open; otherwise, `false`. +--- @return boolean boolean Returns `true` if a WarMenu menu is open; otherwise, `false`. --- --- @usage --- ```lua diff --git a/shared/coreloader.lua b/shared/coreloader.lua index 174e5bd..331c02a 100644 --- a/shared/coreloader.lua +++ b/shared/coreloader.lua @@ -1,32 +1,63 @@ --- Create empty Variables -- +--[[ + Resource Initialization Module + -------------------------------- + This module initializes and loads shared data (Items, Vehicles, Jobs, Gangs) from the + various frameworks/inventory systems (OX, QB, ESX, etc.). It also corrects export names, + caches framework exports into simple variables, and prints debug information if enabled. +]] + +------------------------------------------------------------- +-- Global Variable Initialization +------------------------------------------------------------- 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' -- +------------------------------------------------------------- +-- Correct QB Inventory Export +------------------------------------------------------------- +-- Ensure that the QB inventory export is corrected from 'qb-inventory' to 'ps-inventory' or 'lj-inventory' if needed. 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 "" +------------------------------------------------------------- +-- Framework Exports and Inventory Identifiers +------------------------------------------------------------- +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 "" +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) -- +------------------------------------------------------------- +-- Debug: Print Found Exports +------------------------------------------------------------- +-- Print a list of all exports that are currently started (if debugMode is enabled). for _, v in pairs(Exports) do - if isStarted(v) then debugPrint("^6Bridge^7: '^3"..v.."^7' ^2export found ^7") end + if isStarted(v) then + debugPrint("^6Bridge^7: '^3"..v.."^7' export found") + end end +------------------------------------------------------------- +-- Resource Variables for Items, Jobs, and Vehicles +------------------------------------------------------------- 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 -- +------------------------------------------------------------- +-- Loading Items +------------------------------------------------------------- +-- Load and compile shared items from the detected inventory system. if isStarted(OXInv) then itemResource = OXInv Items = exports[OXInv]:Items() @@ -54,14 +85,13 @@ elseif isStarted(QBExport) then 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) + debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7"..itemResource) end CreateThread(function() while not ESX do Wait(0) end @@ -72,25 +102,23 @@ elseif isStarted(ESXExport) then end if not isServer() then Items = triggerCallback(getScript()..":getItems") - debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource) + 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") + print("^4ERROR^7: ^2No Core Items detected ^7- ^2Check ^3starter^1.^2lua^7") else - debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource) + 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 -- +------------------------------------------------------------- +-- Loading Vehicles +------------------------------------------------------------- +-- Compile vehicles from the detected frameworks into a unified table. if isStarted(QBXExport) or isStarted(QBExport) then Core = Core or exports[QBExport]:GetCoreObject() Vehicles = Core and Core.Shared.Vehicles @@ -101,15 +129,15 @@ if isStarted(QBXExport) or isStarted(QBExport) then 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) @@ -122,43 +150,56 @@ elseif isStarted(ESXExport) 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) } + 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") + print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3starter^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 +------------------------------------------------------------- +-- Loading Jobs and Gangs +------------------------------------------------------------- +-- Compile jobs and gangs from the detected framework. +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 +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 + 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 + 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 +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 @@ -169,12 +210,11 @@ elseif isStarted(QBExport) then jobResource = QBExport 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 + local count = countTable(Jobs[k].grades) - 1 Jobs[k].grades[tostring(count)].isBoss = true end Gangs = Jobs @@ -192,6 +232,8 @@ elseif isStarted(ESXExport) then 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 + debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Jobs).." ^3Jobs^2 from ^7"..jobResource) + debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Gangs).." ^3Gangs^2 from ^7"..jobResource) +end diff --git a/shared/crafting.lua b/shared/crafting.lua index b96a1fa..a9cb3d8 100644 --- a/shared/crafting.lua +++ b/shared/crafting.lua @@ -1,24 +1,34 @@ -local CraftLock = false +--[[ + Crafting, Selling, and Shop Module + ----------------------------------- + This module provides functions for opening crafting menus, handling multi-crafting, + performing the crafting process (with animations and progress bars), selling items, + and opening shop interfaces. It integrates with various inventory and menu systems, + and uses server callbacks to check item carry capacity. +]] ---- Opens a crafting menu based on the provided data. +------------------------------------------------------------- +-- Global Variables +------------------------------------------------------------- +CraftLock = false + +------------------------------------------------------------- +-- Crafting Menu +------------------------------------------------------------- + +--- Opens the crafting menu based on provided data. +--- Checks job restrictions, builds the recipe menu, and opens the menu. --- ---- 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 Crafting menu configuration containing: +--- - craftable (`table`) Table with Header, Recipes, Anims, and (optionally) craftedItems. +--- - coords (`vector3`) The coordinates where the crafting menu is being opened. +--- - stashTable|stashName (`string\table`) Name(s) of the stash for checking item availability. +--- - job|gang (`string`) Job or gang requirements. +--- - onBack (optional): Function to call when returning. --- ----@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 +--- @usage --- ```lua ---- craftingMenu({ +---craftingMenu({ --- craftable = { --- Header = "Weapon Crafting", --- Recipes = { @@ -34,158 +44,103 @@ local CraftLock = false --- }, --- }, --- coords = vector3(100.0, 200.0, 300.0), ---- stashTable = "crafting_stash", ---- job = "mechanic", -- Optional ---- onBack = function() print("Returning to previous menu") end, ---- }) ---- ``` +---stashTable = "crafting_stash", +--- job = "mechanic", +--- onBack = function() print("Returning to previous menu") end, +---}) function craftingMenu(data) - -- Prevent opening the menu if crafting is locked. if CraftLock then return end - -- If a job or gang restriction exists and the player doesn't pass the job check, exit early. + -- Job or gang check; exit if not authorized. if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end - -- Display a temporary "thinking" notification/menu depending on the configured system. + -- Display a temporary "thinking" notification. 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 - -- Normalize stash name: if stashTable is provided, assign it to stashName. + -- Normalize stash name. data.stashName = data.stashTable or data.stashName - -- Initialize an empty menu table and a flag for job verification. - local Menu, hasjob = {}, false - -- Get the list of recipes from the provided data. + local Menu = {} local Recipes = data.craftable.Recipes - local craftedItems = {} - - -- Create a temporary table to collect required item amounts for each recipe. local tempCarryTable = {} + + -- Build a table of all required ingredients (default quantity is 1). for i = 1, #Recipes do - -- Iterate over each key in the current recipe. for k in pairs(Recipes[i]) do if k == "hasCrafted" and not data.craftable.craftedItems then craftedItems = GetMetadata(nil, "craftedItems") or {} data.craftable.craftedItems = craftedItems end - -- Ignore meta keys: "amount", "metadata", "job", and "gang". if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then - -- Record the required amount for this ingredient (default to 1 if not specified). tempCarryTable[k] = Recipes[i].amount or 1 end end end - -- Trigger a server callback to check if the player can carry the required items. + -- Check if the player can carry the required items (server callback). local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable) - - -- Process each recipe to build the menu entries. + -- Process each recipe to create menu entries. for i = 1, #Recipes do - -- Ensure the recipe has an "amount" field; default to 1 if missing. if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end - - -- Loop through each key-value pair in the recipe. - for k, v in pairs(Recipes[i]) do - -- Skip meta keys that are not ingredients. + for k, _ in pairs(Recipes[i]) do local excludeKeys = { - amount = true, - metadata = true, - description = true, - info = true, - job = true, - gang = true, - oneUse = true, - slot = true, - blueprintRef = true, - craftingLevel = true, - craftedItems = true, - hasCrafted = true, - exp = true, + amount = true, metadata = true, description = true, info = true, + job = true, gang = true, oneUse = true, slot = true, + blueprintRef = true, craftingLevel = true, craftedItems = true, + hasCrafted = true, exp = true, anim = true, time = true, } - if not excludeKeys[k] then - - -- Check job requirements if specified for the recipe. + local hasjob = true if Recipes[i].job then for l, b in pairs(Recipes[i].job) do - -- hasJob returns true if the player meets the job criteria. hasjob = hasJob(l, nil, b) - if hasjob == true then break end + if hasjob then break end end - else - hasjob = true end - - -- Initialize variables for menu display text, disable flag, and any metadata. - local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil) - if hasjob then - -- Build tables for ingredient details. + local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil) local itemTable = {} local metaTable = {} - - -- Iterate over the ingredients for the current key. + -- Build ingredient details. for l, b in pairs(Recipes[i][tostring(k)]) do - -- Append item label and quantity to the settext string. - -- Use a line break (br) if settext is not empty. settext = settext..(settext ~= "" and br or "")..(Items[l] and Items[l].label or "error - "..l)..(b > 1 and " x"..b or "") - -- Populate the metaTable with item labels and their amounts. metaTable[Items[l] and Items[l].label or "error - "..l] = b - -- Build a simple table of items required. itemTable[l] = b - Wait(0) -- Yield to avoid freezing the game. + Wait(0) end - -- Wait until the server callback (canCarryTable) has returned. while not canCarryTable do Wait(0) end - - -- Determine if the recipe should be disabled by checking if the player has the required items. 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 "") - -- Construct the header text for this menu item using metadata or default item label. - 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 "") - - -- Append an emoji to indicate carry status: - -- If not disabled and the player cannot carry the item, append 📦, - -- otherwise append ✔️ if they can carry it. - -- if jim-crafting and its a blueprint item that has/hasnt been crafting prefix with ✨ to represent its a new item if not disable then if not canCarryTable[k] then - setheader = setheader .. " 📦" + setheader = setheader.." 📦" else - setheader = setheader .. " ✔️" + setheader = setheader.." ✔️" end elseif not canCarryTable[k] then - setheader = setheader .. " 📦" + setheader = setheader.." 📦" end - if Recipes[i]["hasCrafted"] ~= nil then - if craftedItems[k] == nil then - setheader = "✨ "..setheader - end + if Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil then + setheader = "✨ "..setheader end - -- Add the constructed menu item into the Menu table. + Menu[#Menu + 1] = { - -- Show an arrow if the item is enabled and can be carried. arrow = not disable and canCarryTable[k], - -- Disable the menu item based on the state of QBMenuExport and carry-check. isMenuHeader = disable or not canCarryTable[k], - -- Set icon and image for the menu item (using metadata image if available). icon = invImg((metadata and metadata.image) or tostring(k)), image = invImg((metadata and metadata.image) or tostring(k)), - -- Final header text, appending ❌ if disabled or cannot be carried. header = setheader..((disable or not canCarryTable[k]) and " ❌" or ""), - -- Set description text if QBMenuExport is started. txt = (isStarted(QBMenuExport) or disable) and settext or nil, - -- Attach the metadata table containing ingredient details. metadata = metaTable, - -- Define the onSelect function to trigger crafting actions if the item is selectable. - onSelect = ((not disable and canCarryTable[k]) and (function() - -- Build transaction data with details needed for crafting. + onSelect = (not disable and canCarryTable[k]) and function() local transdata = { item = k, craft = data.craftable.Recipes[i], @@ -193,23 +148,21 @@ function craftingMenu(data) coords = data.coords, stashName = data.stashName, onBack = data.onBack, - metadata = metadata + metadata = metadata, } - -- Call multiCraft or makeItem based on configuration. if Config.Crafting.MultiCraft then multiCraft(transdata) else makeItem(transdata) end - end) or nil), + end or nil, } end end - Wait(0) -- Yield within the loop to maintain responsiveness. + Wait(0) end end - -- Open the final crafting menu with the built Menu table and provided header/onBack configuration. openMenu(Menu, { header = data.craftable.Header, headertxt = data.craftable.Headertxt, @@ -217,32 +170,33 @@ function craftingMenu(data) canClose = true, onExit = function() end, }) - - -- Trigger an action (likely camera or player focus) to look at the specified coordinates. lookEnt(data.coords) end +------------------------------------------------------------- +-- Multi-Craft Menu +------------------------------------------------------------- --- 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`. +--- Presents the player with multiple crafting quantities 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. +--- @param data table Crafting configuration containing: +--- - item `string`) The item to craft. +--- - craft (`table`) The crafting recipe. +--- - craftable (`table`) Crafting options. +--- - coords (`vector3`) where crafting occurs. +--- - stashName (`string`) The stash name(s) for item availability. +--- - onBack (`function`) Callback when returning. +--- - metadata (`table`) (optional): Metadata for the crafted item. --- ----@usage +--- @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), +--- coords = vector3(100,200,300), --- stashName = "crafting_stash", --- onBack = function() craftingMenu(data) end, --- metadata = { label = "Custom Pistol", image = "custom_pistol.png" }, @@ -250,28 +204,31 @@ end --- ``` function multiCraft(data) local Menu = {} - local success = Config.Crafting.MultiCraftAmounts + local amounts = Config.Crafting.MultiCraftAmounts local metadata = data.metadata or nil - Menu[#Menu+1] = { + + -- Header for the multi-craft menu. + 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 + + for k in pairsByKeys(amounts) 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 "") + 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, + header = "Craft - x"..(k * data.craft.amount), txt = settext, - onSelect = function () + onSelect = function() makeItem({ item = data.item, craft = data.craft, @@ -281,37 +238,41 @@ function multiCraft(data) stashName = stashname, stashTable = data.stashName, onBack = data.onBack, - metadata = data.metadata + metadata = data.metadata, }) end, } end - openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end, }) + + openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end }) end +------------------------------------------------------------- +-- Crafting Process +------------------------------------------------------------- + --- Initiates the crafting process for a specified item. --- ---- This function handles the crafting animation, progress bar, item removal, and item creation. +--- Plays crafting animations, shows progress bars, removes ingredients, and triggers 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. +--- @param data table Crafting configuration containing: +--- - item `string`) The item to craft. +--- - craft (`table`) The crafting recipe. +--- - craftable (`table`) Crafting options. +--- - amount (`number`) (optional): Quantity to craft (default 1). +--- - coords (`vector3`) where crafting occurs. +--- - stashName (`string`) The stash name(s) for item availability. +--- - onBack (`function`) Callback when returning. +--- - metadata (`table`) (optional): Metadata for the crafted item. --- ----@usage +--- @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), +--- coords = vector3(100,200,300), --- stashName = "crafting_stash", --- onBack = function() craftingMenu(data) end, --- metadata = { label = "Custom Pistol", image = "custom_pistol.png" }, @@ -320,40 +281,31 @@ end 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 " - 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 + data.stashName = data.stashTable or data.stashName + + 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 " + 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 craftAmount = (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 canReturn = true local crafted, crafting = true, true local cam = createTempCam(PlayerPedId(), data.coords) startTempCam(cam) - for i = 1, amount do - countTable(data.craft) + for i = 1, craftAmount do for k, v in pairs(data.craft) do local excludeKeys = { - amount = true, - info = true, - metadata = true, - description = true, - job = true, - gang = true, - oneUse = true, - slot = true, - blueprintRef = true, - craftingLevel = true, - craftedItems = true, - hasCrafted = true, - exp = true, + amount = true, info = true, metadata = true, description = true, + job = true, gang = true, oneUse = true, slot = true, + blueprintRef = true, craftingLevel = true, craftedItems = true, + hasCrafted = true, exp = true, anim = true, time = true, } - if not excludeKeys[k] then if type(v) == "table" then for l, b in pairs(v) do @@ -366,7 +318,7 @@ function makeItem(data) 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 + TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b) else crafted, crafting = false, false break @@ -376,9 +328,8 @@ function makeItem(data) 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) + craftProp = makeProp({ prop = prop.model, coords = vec4(0, 0, 0, 0), true, true }) + AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true) end if crafting and progressBar({ label = bartext..((metadata and metadata.label) or Items[data.item].label), @@ -393,16 +344,14 @@ function makeItem(data) CreateThread(function() if data.craft["hasCrafted"] ~= nil then debugPrint("hasCrafted Found, marking '"..data.item.."' as crafted for player") - data.craftable.craftedItems[data.item] = true - triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems ) + triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems) end Wait(100) if data.craft["exp"] ~= nil then craftingLevel += data.craft["exp"].give - jsonPrint(data.craft["exp"]) - debugPrint("exp Found, giving exp for '"..data.item.."'") + debugPrint("exp found, giving exp for '"..data.item.."'") triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel) end end) @@ -411,7 +360,6 @@ function makeItem(data) local breakId = GetSoundId() PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0) canReturn = false - -- If recipe is removed it doesn't try to open menu again, it was causing blank menus for some reason end else crafting = false @@ -431,16 +379,22 @@ function makeItem(data) ClearPedTasks(PlayerPedId()) end +------------------------------------------------------------- +-- Server Event Handler: Crafted Item +------------------------------------------------------------- + --- Server event handler for giving the crafted item to the player. --- ---- This event is triggered when the crafting process is completed successfully. +--- Removes required ingredients from the player's inventory or stash, +--- then adds the crafted item to their inventory. --- --- @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 stashName string|table The stash name(s) to remove ingredients 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 + local src = source + local hasItems, hasTable = hasItem(ItemMake, 1, src) if stashName then local itemRemove = {} if type(stashName) == "table" then @@ -468,22 +422,22 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, end end end - addItem(ItemMake, amount, metadata, src) - --if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end + addItem(ItemMake, craftable.amount or 1, metadata, src) + -- Optionally, add experience here: + -- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end end) ---- Opens a selling menu based on the provided data. +------------------------------------------------------------- +-- Selling Menu and Animation +------------------------------------------------------------- + +--- Opens a selling menu with available items and prices. --- ---- 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 +--- @param data table Contains selling menu data: +--- - sellTable (`table`) Table with Header and Items (item names and prices). +--- - ped (optional) (`number`) Ped entity involved. +--- - onBack (optional) (`function`) Callback for returning. +--- @usage --- ```lua --- sellMenu({ --- sellTable = { @@ -505,10 +459,10 @@ function sellMenu(data) 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] = { + Menu[#Menu + 1] = { isMenuHeader = not hasTable[k].hasItem, icon = invImg(k), - header = Items[k].label.. (hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""), + 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 }) @@ -518,7 +472,7 @@ function sellMenu(data) else for k, v in pairsByKeys(data.sellTable) do if type(v) == "table" then - Menu[#Menu +1] = { + Menu[#Menu + 1] = { arrow = true, header = k, txt = "Amount of items: "..countTable(v.Items), @@ -531,19 +485,24 @@ function sellMenu(data) 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 }) + 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. +--- Plays the selling animation and processes the sale 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. +--- Checks if the player has the item, plays animations, triggers the server event for selling, +--- and then calls the onBack callback if provided. --- +--- @param data table Contains: +--- `- item: The item to sell. +--- `- price: Price per item. +--- `- ped (optional): Ped entity involved. +--- `- onBack (optional): Callback to call on completion. ---@usage --- ```lua --- sellAnim({ @@ -558,16 +517,20 @@ function sellAnim(data) 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 + + -- Remove any attached clipboard objects. + for _, obj in pairs(GetGamePool('CObject')) do + for _, model in pairs({ `p_cs_clipboard` }) do + if GetEntityModel(obj) == model and IsEntityAttachedToEntity(data.ped, obj) then + DeleteObject(obj) + DetachEntity(obj, 0, 0) + SetEntityAsMissionEntity(obj, true, true) + Wait(100) + DeleteEntity(obj) end end end + TriggerServerEvent(getScript().."Sellitems", data) lookEnt(data.ped) local dict = "mp_common" @@ -579,11 +542,8 @@ function sellAnim(data) 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. +--- Server event handler for processing item sales. +--- Removes sold items from inventory and funds the player based on the sale. RegisterNetEvent(getScript().."Sellitems", function(data) local src = source local hasItems, hasTable = hasItem(data.item, 1, src) @@ -595,17 +555,18 @@ RegisterNetEvent(getScript().."Sellitems", function(data) end end) +------------------------------------------------------------- +-- Shop Interface +------------------------------------------------------------- + --- 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. ---- +--- Checks job/gang restrictions, then uses the active inventory system to open the shop. +--- @param data table Contains: +--- - shop (`string`) The shop identifier. +--- - items (`table`) The items available in the shop. +--- - coords (`vector3`) where the shop is located. +--- - job/gang (optional) (`string`) Job or gang requirements. ---@usage --- ```lua --- openShop({ @@ -617,30 +578,34 @@ end) --- ``` 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 + TriggerServerEvent(getScript()..':server:OpenShopNewQB', data.shop) else TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items) end + + --elseif isStarted(OrigenInv) then -- Needs testing, not sure if i did this right + -- exports[OrigenInv]:openInventory('shop', data.shop, data.items) + 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. +--- Server event handler for opening a shop using the new QB inventory system. RegisterNetEvent(getScript()..':server:OpenShopNewQB', function(data) exports[QBInv]:OpenShop(source, data) end) ---- Server-side callback registration for checking if the player can carry items. +------------------------------------------------------------- +-- Server Callback Registration +------------------------------------------------------------- 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 a72420e..66b908e 100644 --- a/shared/drawText.lua +++ b/shared/drawText.lua @@ -1,53 +1,80 @@ -local radarTable = {} +--[[ + Text Drawing Module + --------------------- + This module provides functions to display and hide text on screen using + various frameworks: QB, OX, GTA, and ESX. +]] + +local radarTable = {} -- Table to store image references for drawing text --- 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'. +--- Depending on Config.System.drawText, this function will use different methods to +--- display text along with optional images/icons. --- ----@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. +--- @param image string|nil Optional image/icon identifier to display with the text. +--- @param input table An array of strings; each string is a line of text to display. +--- @param style string|nil Optional style code for default GTA popups (e.g., "~g~" for green). +--- @param oxStyleTable table|nil Optional table specifying style parameters for the OX text UI. --- ---@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') +function drawText(image, input, style, oxStyleTable) + local text = "" - 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 + if Config.System.drawText == "qb" then + -- Concatenate lines for QB system with HTML line breaks. 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 + text = text..input[i]..""..(input[i + 1] and "
" or "") + end + text = text:gsub("%:", ":") + if image then + text = ''..text + end + exports[QBExport]:DrawText(text, 'left') + + elseif Config.System.drawText == "ox" then + -- Append newline spacing to each input line. + 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 + -- Concatenate input lines and apply GTA style formatting. + 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 + -- ESX-based text UI uses similar HTML formatting as QB. + for i = 1, #input do + text = text..input[i]..""..(input[i + 1] and "
" or "") + end + text = text:gsub("%:", ":") + if image then + text = ''..text + end + ESX.TextUI(text, nil) + end end ---- Hides any text currently being displayed on the screen. +--- Hides any text currently 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. +--- Clears the text using the appropriate method for the configured draw text system. +--- +--- @usage +--- ```lua +--- hideText() +--- ``` function hideText() if Config.System.drawText == "qb" then exports[QBExport]:HideText() diff --git a/shared/duifunctions.lua b/shared/duifunctions.lua index 178c189..ff5d777 100644 --- a/shared/duifunctions.lua +++ b/shared/duifunctions.lua @@ -1,122 +1,165 @@ --- DUI STUFF -- * Experimental * -- +--[[ + DUI Module (Experimental) + -------------------------- + This module handles the creation, modification, and removal of custom DUI (Display UI) + elements using runtime textures. It supports both client and server functionality to update DUI + images dynamically. +]] +-- Create a runtime texture dictionary on the client if not running on the server. 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 +------------------------------------------------------------- +-- DUI Client Functions +------------------------------------------------------------- -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 +--- Creates or updates a DUI element. +--- +--- @param name string The unique name for the DUI element. +--- @param http string The URL to load into the DUI. +--- @param size table A table with .x and .y fields specifying the DUI dimensions. +--- @param txd table The runtime texture dictionary where the DUI texture will be created. +--- @usage +--- ```lua +--- createDui("logo", "https://example.com/logo.png", { x = 512, y = 256 }, scriptTxd) +--- ``` +function createDui(name, http, size, txd) + if not customDUIList[name] then + local newDui = CreateDui(http, math.floor(size.x), math.floor(size.y)) + while not GetDuiHandle(newDui) do Wait(0) end + CreateRuntimeTextureFromDuiHandle(txd, name, GetDuiHandle(newDui)) + customDUIList[name] = newDui + SetDuiUrl(customDUIList[name], http) + else + SetDuiUrl(customDUIList[name], http) end end +--- Opens a DUI selection input allowing the user to change the DUI image URL. +--- +--- @param data table A table containing DUI data: +--- - name: The key name in the DUI list. +--- - texn: The texture name. +--- - texd: The texture dictionary. +--- - size: A table with .x and .y dimensions. +--- +--- @usage +--- ```lua +--- DuiSelect({ name = "logo", texn = "logoTex", texd = "someTxd", size = { x = 512, y = 256 } }) +--- ``` +function DuiSelect(data) + local imagePreview = "" + for k, v in pairs(duiList[data.name]) do + if v.tex.texn == data.texn and duiList[data.name][k] then + imagePreview = "
- Current Image -
" .. + "
" .. + "Size: ["..math.floor(data.size.x)..", "..math.floor(data.size.y).."]

" + end + end + local dialog = exports['qb-input']:ShowInput({ + header = imagePreview..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 and dialog.url then + data.url = dialog.url + -- Scan URL for valid image extension and banned words. + local searchList = { "png", "jpg", "jpeg", "gif", "webp", "bmp" } + local banList = { "porn" } + local searchFound = false + for _, ext in pairs(searchList) do + if string.find(tostring(data.url), ext) then + searchFound = true + break + end + end + for _, banned in pairs(banList) do + if string.find(tostring(data.url), banned) then + searchFound = false + print("BANNED WORD: "..banned) + break + end + end + if searchFound then + TriggerServerEvent(getScript()..":Server:ChangeDUI", data) + end + end +end + +--- Client event handler to update DUI elements. RegisterNetEvent(getScript()..":Client:ChangeDUI", function(data) - debugPrint("^6Bridge^7: ^2Recieving new DUI ^7- ^6"..data.url.."^7") + debugPrint("^6Bridge^7: ^2Receiving 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)) + createDui(data.texn, tostring(data.url), data.size, scriptTxd) + AddReplaceTexture(tostring(data.texd), tostring(data.texn), getScript().."scriptTxd", tostring(data.texn)) end end) +--- Client event handler to clear DUI elements. 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 + SetDuiUrl(customDUIList[data.name], nil) + end end end) --- DUI SERVER +------------------------------------------------------------- +-- DUI Server Functions +------------------------------------------------------------- + +--- Server event handler to change DUI settings. +--- If no URL is provided, resets to the preset value. 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 + for k, v in pairs(duiList[data.name]) do + if v.tex.texn == data.texn then + debugPrint("^6Bridge^7: ^2Preset: ^6"..tostring(duiList[data.name][k].preset).."^7") + data.url = duiList[data.name][k].preset + end + end + end + 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 - -- 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) + TriggerClientEvent(getScript()..":Client:ChangeDUI", -1, data) end) +--- Server event handler to clear DUI settings. 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 + for k, v in pairs(duiList[data.name]) do + if v.tex.texn == data.texn then + duiList[data.name][k].url = "-" + end + end + end TriggerClientEvent(getScript()..":Client:ClearDUI", -1, data) - --duiList[tostring(data.tex)].url = "" end) -AddEventHandler('onResourceStop', function(r) if r ~= getScript() then return end +------------------------------------------------------------- +-- Resource Cleanup +------------------------------------------------------------- + +onResourceStop(function() 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 + for i = 1, #v do + RemoveReplaceTexture(tostring(v[i].tex.texd), tostring(v[i].tex.texn)) + end end -end) +end, true) + +------------------------------------------------------------- +-- DUI List Callback (Server) +------------------------------------------------------------- if isServer() then createCallback(getScript()..":Server:duiList", function(source) - return duiList + return duiList end) end \ No newline at end of file diff --git a/shared/helpers.lua b/shared/helpers.lua index 067d1f2..41c30e9 100644 --- a/shared/helpers.lua +++ b/shared/helpers.lua @@ -1,15 +1,19 @@ ---- 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. +--[[ + Utility Functions for Resource Management and Debugging + ---------------------------------------------------------- + This script provides a set of utility functions for managing resources, + debugging, and handling common tasks in the game environment. + It includes functions to check resource states, generate unique keys, + format numbers and coordinates, handle JSON data, perform raycasts, and more. +]] ---[[ Resource and Environment Checks ]]-- +------------------------------------------------------------- +-- 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`. ---- +--- @param script string The name of the resource. +--- @return boolean boolean True if the resource state contains "start", false otherwise. ---@usage --- ```lua --- if isStarted("myResource") then @@ -22,12 +26,8 @@ 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. ---- +--- Retrieves the current resource name, caching it for efficiency. +--- @return string string The current resource name. --- @usage --- ```lua --- local currentScript = getScript() @@ -38,12 +38,8 @@ function getScript() 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`. ---- +--- Determines if the current context is the server. +--- @return boolean boolean True if running on the server, false otherwise. ---@usage --- ```lua --- if isServer() then @@ -56,14 +52,13 @@ function isServer() return IsDuplicityVersion() end ---[[ Debugging Functions ]]-- +------------------------------------------------------------- +-- Debugging and JSON Utilities +------------------------------------------------------------- ---- 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. ---- +--- Prints debug messages if debugMode is enabled. +--- Concatenates all arguments and prints them with debug info. +--- @param ... any One or more values to print. --- @usage --- ```lua --- debugPrint("Player has joined:", playerName) @@ -71,15 +66,13 @@ end function debugPrint(...) if debugMode then local args = {...} - local output = table.concat(args, " ") -- Concatenate all arguments with a space + local output = table.concat(args, " ") 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. ---- +--- @param ... any One or more values to print. --- @usage --- ```lua --- eventPrint("Event triggered:", eventName) @@ -90,24 +83,22 @@ function eventPrint(...) end end --- Function to recursively colorize the JSON data +--- Returns the keys of a table in sorted order. +--- @param tbl table The table to sort keys for. +--- @return table table A sorted array of keys. 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 + 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 +--- @return table table A new table with colorized keys and values. --- ```lua --- local colorizedData = colorizeTable(myTable) --- jsonPrint(colorizedData) @@ -116,18 +107,19 @@ 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" + newData["^6"..tostring(k).."^7"] = + (type(v) == "table" and colorizeTable(v)) + or (tostring(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. +--- @param indent string The indentation string (e.g., " "). +--- @param level number The current level of indentation. --- @return string The formatted JSON string. ---- --- @usage --- ```lua --- local jsonString = encodeOrderedJSON(myTable, " ", 0) @@ -143,10 +135,8 @@ function encodeOrderedJSON(data, indent, level) return table.concat(jsonParts) end ---- Prints a table as a colorized and ordered JSON string if debugging mode is enabled. ---- +--- Prints a table as a colorized and ordered JSON string if debugMode is enabled. --- @param data table The table to print. ---- --- @usage --- ```lua --- jsonPrint(myTable) @@ -158,9 +148,7 @@ function jsonPrint(data) 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() @@ -177,9 +165,7 @@ function GetPrintTime() end --- Generates a unique 3-character alphanumeric key. ---- ---- @return string GeneratedString A randomly generated 3-character string. ---- +--- @return string string The generated key. --- @usage --- ```lua --- local uniqueKey = keyGen() @@ -187,20 +173,28 @@ end --- ``` 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", + "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 + for i = 1, 3 do + GeneratedID = GeneratedID..charset[math.random(1, #charset)] + end return GeneratedID end +------------------------------------------------------------- +-- Formatting and Vector Math Functions +------------------------------------------------------------- + --- Formats a number with commas as thousand separators. ---- --- @param amount number The number to format. ---- @return string commaValue The formatted number string with commas. ---- +--- @return string string The formatted number. --- @usage --- ```lua --- local formattedNumber = cv(1000000) -- "1,000,000" @@ -208,15 +202,17 @@ end --- `` 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 + 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. ---- +--- @param coord table A vector3 or vector4 with x, y, z (and optional w). +--- @return string string The formatted coordinate string. --- @usage --- ```lua --- local formattedCoord = formatCoord(vector3(100.0, 200.0, 300.0)) @@ -233,47 +229,42 @@ function formatCoord(coord) 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. +--- Calculates the center point of a list of coordinates. +--- @param tbl table An array 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) +function getCenterOfZones(tbl) local totalX, totalY, totalZ = 0, 0, 0 - - for _, coord in ipairs(table) do + for _, coord in ipairs(tbl) do totalX = totalX + coord.x totalY = totalY + coord.y totalZ = totalZ + coord.z end - - local count = #table + local count = #tbl 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. ---- +--- @param tbl table The table to count. +--- @return number number The key count. --- @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 +function countTable(tbl) + local i = 0 + for _ in pairs(tbl) do i = i + 1 end + return i +end - ---- Returns an iterator that iterates over a table's keys in sorted order. ---- +--- Returns an iterator over a table's keys in sorted order. --- @param t table The table to iterate over. ---- @return function function An iterator function. ---- +--- @return function An iterator function for sorted keys. --- @usage --- ```lua --- for k, v in pairsByKeys(myTable) do @@ -281,7 +272,6 @@ function countTable(table) local i = 0 for keys in pairs(table) do i += 1 end re --- 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 = {} @@ -289,24 +279,20 @@ function pairsByKeys(t) 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. ---- +--- Creates a new table with consecutive numerical indices sorted by the 'id' field. +--- @param originalTable table The table containing entries with an 'id' field. +--- @return table table A sorted table with consecutive indices. --- @usage +--- ```lua --- 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) + 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 @@ -315,112 +301,9 @@ function createConsecutiveTable(originalTable) 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. ---- +--- @param tbl table The table containing strings. +--- @return string string The concatenated string. --- @usage --- ```lua --- local combinedText = concatenateText({"Line 1", "Line 2", "Line 3"}) @@ -429,74 +312,69 @@ end 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 + result = result..tbl[i]..(i < #tbl and "\n" or "") 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. ---- +--- Converts a rotation (degrees) to a direction vector. +--- @param rot vector3 A vector3 with rotation values. +--- @return vector3 vector3 The forward direction vector. --- @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)) + 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., "█████░░░░░". ---- +--- Creates a basic progress bar string. +--- @param percentage number Completion percentage (0-100). +--- @return string string 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 + local perc = math.ceil(percentage) + local total = 10 + local filled = math.floor((perc / 100) * total) + local empty = total - filled + return string.rep("█", filled)..string.rep("░", empty) end --- Normalizes a 3D vector. ---- ---- @param vec vector3 A vector3 table with `x`, `y`, and `z` components. ---- @return vector3 vector3 The normalized vector3. ---- +--- @param vec vector3 A vector3 table. +--- @return vector3 vector3 A normalized vector. --- @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) + local len = math.sqrt(vec.x^2 + vec.y^2 + vec.z^2) + if len ~= 0 then + return vec3(vec.x / len, vec.y / len, vec.z / len) 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. ---- +------------------------------------------------------------- +-- Drawing and Raycasting Functions +------------------------------------------------------------- + +--- Draws a line between two coordinates (for debugging). +--- @param startCoords vector3 The starting coordinate. +--- @param endCoords vector3 The ending coordinate. +--- @param col vector4 A vector4 specifying color and opacity. --- @usage --- ```lua --- drawLine(vector3(100, 200, 300), vector3(150, 250, 350), vector4(255, 0, 0, 255)) @@ -504,21 +382,19 @@ end function drawLine(startCoords, endCoords, col) if debugMode then CreateThread(function() - local showCount = 1000 - while showCount >= 0 do + local count = 1000 + while count >= 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 + count -= 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. ---- +--- Draws a sphere at the specified coordinates (for debugging). +--- @param coords vector3 The center of the sphere. +--- @param col vector4 A vector4 specifying color and opacity. --- @usage --- ```lua --- drawSphere(vector3(100, 200, 300), vector4(0, 255, 0, 255)) @@ -526,24 +402,22 @@ end function drawSphere(coords, col) if debugMode then CreateThread(function() - local showCount = 1000 - while showCount >= 0 do + local count = 1000 + while count >= 0 do DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w) - showCount -= 1 + count -= 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`. ---- +--- Performs a raycast between two coordinates and returns the results. +--- @param startCoords vector3 The starting coordinate. +--- @param endCoords vector3 The ending coordinate. +--- @param entity number|nil An entity to ignore. +--- @param flags number|nil Optional raycast flags (default: 4294967295). +--- @return multiple Multiple values returned by GetShapeTestResultIncludingMaterial. --- @usage --- ```lua --- local hit, hitPos, material = PerformRaycast(startVec, endVec, playerPed, 1) @@ -553,47 +427,41 @@ end --- 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 + 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 + ) + ) 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. ---- +--- Adjusts the Z-coordinate of a position to the ground level. +--- @param coords vector4 A vector3 or vector4 with x, y, z (and optional w). +--- @return vector3|vector4 vector The coordinates adjusted for 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) + return vec4(coords.x, coords.y, zPos, coords.w) else - coords = vec3(coords.x, coords.y, zPos) + return 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`. ---- +--- Ensures a network vehicle exists from its network ID. +--- @param vehNetID number The network ID. +--- @return number number The vehicle entity, or 0 if not found. --- @usage --- ```lua --- local vehicle = ensureNetToVeh(netID) @@ -619,16 +487,16 @@ function ensureNetToVeh(vehNetID) 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`. ---- +--- Ensures a network entity exists from its network ID. +--- @param entNetID number The network ID. +--- @return number number The entity, or 0 if not found. --- @usage +--- ```lua --- 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 @@ -647,7 +515,9 @@ function ensureNetToEnt(entNetID) return entity end ---[[ Material Definitions ]]-- +------------------------------------------------------------- +-- Material and Prop Functions +------------------------------------------------------------- --- A table mapping material names to their corresponding hash values. --- @@ -869,22 +739,17 @@ local materials = { 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. ---- +--- Retrieves the ground material at a given position. +--- @param coords vector3 The coordinate to test. +--- @return number|nil number The material hash if hit, nil otherwise. +--- @return string string The material name. --- @usage --- ```lua ---- local materialHash, materialName = GetGroundMaterialAtPosition(vector3(100, 200, 300)) ---- print("Ground material:", materialName) +--- local matHash, matName = GetGroundMaterialAtPosition(vector3(100,200,300)) +--- print("Material:", matName) --- ``` 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" @@ -894,14 +759,10 @@ function GetGroundMaterialAtPosition(coords) break end end - if hit then return materialHash, materialName - else return nil, materialName 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. ---- +--- Retrieves the dimensions (width, depth, height) of a prop/model. --- @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. diff --git a/shared/input.lua b/shared/input.lua index dd3145b..6c18958 100644 --- a/shared/input.lua +++ b/shared/input.lua @@ -1,25 +1,28 @@ --- INPUT -- --- Multiscript input script function to create simple input text boxes -- +--[[ + Input Dialog Module + --------------------- + This module provides a function to create a simple input dialog compatible with + multiple menu systems (OX, QB, GTA/WarMenu, and ESX). It supports various input + types such as radio buttons, numbers, text, and select dropdowns. ---- Creates a simple input dialog compatible with multiple menu systems. +]] + +--- Creates a simple input dialog using the configured menu system. --- ---- 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 or header of the input dialog. +--- @param opts table A table of input option definitions. Each option should include: +--- - type (string): The input type ("radio", "number", "text", "select"). +--- - label (string, optional): A label for the input (used in radio/select for OX). +--- - text (string, optional): The text prompt for the input. +--- - name (string): The identifier for the input. +--- - isRequired (boolean, optional): Whether input is mandatory. +--- - default (any, optional): The default value. +--- - options (table, optional): A table of choices for "radio" and "select" types. +--- - min (number, optional): Minimum value (for "number" and "select"). +--- - max (number, optional): Maximum value. +--- - txt (string, optional): Additional description. --- ----@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`. +--- @return table|nil table Returns the user's input as a table if submitted, otherwise nil. --- ---@usage --- ```lua diff --git a/shared/inventories.lua b/shared/inventories.lua new file mode 100644 index 0000000..e094f1c --- /dev/null +++ b/shared/inventories.lua @@ -0,0 +1,133 @@ +------------------------------------------------------------- +-- Item Availability & Inventory Retrieval +------------------------------------------------------------- +--- +--- Locks or unlocks the player's inventory. +--- Freezes/unfreezes the player's position, sets inventory busy state, and toggles hotbar usage. +--- +--- @param toggle boolean True to lock inventory; false to unlock. +--- +--- @usage +--- ```lua +--- lockInv(true) -- Lock inventory. +--- lockInv(false) -- Unlock inventory. +--- ``` +function lockInv(toggle) + FreezeEntityPosition(PlayerPedId(), toggle) + LocalPlayer.state:set("inv_busy", toggle, true) + TriggerEvent('inventory:client:busy:status', toggle) + TriggerEvent('canUseInventoryAndHotbar:toggle', not toggle) +end +--- Checks if a player has the specified items in their inventory. +--- +--- Verifies whether the required quantities are present. Returns a boolean and a table of details. +--- +--- @param items string|table A single item name or table with required amounts. +--- @param amount number The required quantity (default 1). +--- @param src number|nil Player source ID (defaults to caller). +--- @return boolean boolean True if all items are available; otherwise, false. +--- @return table|nil table Table detailing counts for each item. +--- +---@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 based on the active inventory system. +--- +--- @param src number|nil The player source ID (if nil, retrieves current player's inventory). +--- @return table|nil table The inventory items. +--- @return string|nil string The name of the inventory system. +--- +---@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]:getInventory() end + + elseif isStarted(CoreInv) then + foundInv = CoreInv + if src then grabInv = exports[CoreInv]:getInventory(src) + else grabInv = exports[CoreInv]:getInventory() 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 + + elseif ESX and isStarted(ESXExport) then + foundInv = ESX + if src then + local xPlayer = ESX.GetPlayerFromId(src) + grabInv = xPlayer.inventory + else + local xPlayer = ESX.GetPlayerData() -- Client side, if available + grabInv = xPlayer.inventory + end + + else + print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3starter^1.^2lua^7") + end + return grabInv, foundInv +end \ No newline at end of file diff --git a/shared/isAnimal.lua b/shared/isAnimal.lua index 418cb38..f99c118 100644 --- a/shared/isAnimal.lua +++ b/shared/isAnimal.lua @@ -1,36 +1,76 @@ +--[[ + Animal Detection Module + ------------------------- + This module determines whether a Ped is an animal and categorizes it as a cat, dog, + or other type (e.g., coyote). It uses predefined model hashes stored in the AnimalPeds table. + + Global Flags: + - isCat, isDog, isBigDog, isSmallDog, isCoyote, isAnimal: Booleans to track the player's + current animal classification. + + When running client-side (not on the server), the module checks the player's Ped after they load. + + Usage Examples: + -- Check if the player's Ped is an animal: + local animalStatus = isPedAnimal() + + -- Check if a given Ped is a cat: + if isCat(somePed) then print("This is a cat!") end + + -- Determine if a Ped is a dog and whether it's big or small: + local isDogFlag, isBig = isDog(somePed) + + -- Retrieve a flat list of all animal model hashes: + local allAnimalModels = getAnimalModels() + + File Separation Suggestion: + For scalability, consider separating this module into two files: + • AnimalDetection.lua (for functions and callbacks) + • AnimalPedsData.lua (for the AnimalPeds table) +]] + +-- Global animal classification flags. isCat, isDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false if not isServer() then onPlayerLoaded(function() Wait(2000) + -- Reset classification flags isCat, isDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false + -- Check if the player's Ped is an animal. isPedAnimal() if isAnimal then local ped = PlayerPedId() local pedModel = GetEntityModel(ped) + -- Determine if the Ped is a cat: + -- Also treat 'ft-raccoon' as a cat unless it is 'ft-sphynx' isCat = (isCat(ped) or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`) + -- Determine if the Ped is a dog and whether it's big: isDog, isBigDog = isDog(ped) isSmallDog = not isBigDog if isDog and pedModel == `a_c_coyote` then isDog = false end + -- Determine if the Ped is a coyote (special case): isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`) + -- Special override: if model is 'ft-capmonkey2', treat as a dog. if pedModel == `ft-capmonkey2` then isDog = true end end end, true) - --- Determines if a given Ped is classified as an animal. + ------------------------------------------------------------- + -- Animal Classification Functions + ------------------------------------------------------------- + + --- Determines whether 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. + --- Checks if the Ped's model hash appears in any of the animal categories defined in AnimalPeds. --- - ---@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`. + --- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped. + --- @return boolean boolean True if the Ped is an animal, otherwise false. --- --- @usage --- ```lua @@ -39,31 +79,24 @@ if not isServer() then --- ``` function isPedAnimal(ped) local PedModel = GetEntityModel(ped or PlayerPedId()) - - for _, animalTypeTable in pairs(AnimalPeds) do - for animalModelHash, _ in pairs(animalTypeTable) do + for _, animalCategory in pairs(AnimalPeds) do + for animalModelHash, _ in pairs(animalCategory) do if PedModel == animalModelHash then isAnimal = true - break + debugPrint("^6Bridge^7: ^2Ped is Animal") + return true end end - if isAnimal then - debugPrint("^6Bridge^7: ^2Ped is Animal^1") - break - end end - - return isAnimal + return false end - --- Checks if a given Ped is classified specifically as a cat. + --- Checks if a given Ped is classified 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. + --- Iterates through the CatPeds table and returns true if the Ped's model matches. --- - ---@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`. + --- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped. + --- @return boolean True if the Ped is a cat, otherwise false. --- ---@usage --- ```lua @@ -78,24 +111,20 @@ if not isServer() then --- ``` function isCat(ped) local PedModel = GetEntityModel(ped or PlayerPedId()) - for k, v in pairs(AnimalPeds.CatPeds) do - if PedModel == k then + for modelHash, _ in pairs(AnimalPeds.CatPeds) do + if PedModel == modelHash then return true end end return false end - --- Determines if a given Ped is classified as a dog and identifies its size category. + --- Determines if a given Ped is 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`. + --- Checks the BigDogs and SmallDogs tables to see if the Ped's model matches any dog model. --- - ---@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, + --- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped. + ---@return boolean, boolean|nil boolean 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. --- @@ -124,27 +153,24 @@ if not isServer() then --- ``` function isDog(ped) local PedModel = GetEntityModel(ped or PlayerPedId()) - for k, v in pairs(AnimalPeds.BigDogs) do - if PedModel == k then + for modelHash, _ in pairs(AnimalPeds.BigDogs) do + if PedModel == modelHash then return true, true end end - - for k, v in pairs(AnimalPeds.SmallDogs) do - if PedModel == k then + for modelHash, _ in pairs(AnimalPeds.SmallDogs) do + if PedModel == modelHash then return true, false end end return false, nil end - --- Retrieves a list of all animal model hashes. + --- Compiles and returns a flat table 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. + --- Iterates through every category in AnimalPeds and collects all model hashes. --- - ---@return table table A table containing all animal model hashes. + --- @return table table A table containing all animal model hashes. --- ---@usage --- ```lua @@ -154,289 +180,83 @@ if not isServer() then --- end --- ``` function getAnimalModels() - local animalTable = {} - for k in pairs(AnimalPeds) do - for v in pairs(AnimalPeds[k]) do - animalTable[#animalTable+1] = v + local animalModels = {} + for _, animalCategory in pairs(AnimalPeds) do + for modelHash, _ in pairs(animalCategory) do + table.insert(animalModels, modelHash) end end - return animalTable + return animalModels end end - +------------------------------------------------------------- +-- Animal Models Data +------------------------------------------------------------- +-- Define the animal models and their associated animations. 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" - }, + [`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" - }, + [`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" - }, + [`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 - }, + [`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" }, + [`a_c_pigeon`] = { deathAnim = "dead_down", deathDict = "creatures@pigeon@move", exitAnim = "nill", exitDict = "creatures@pug@move" }, }, 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" - }, + [`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 d388422..7281f1c 100644 --- a/shared/itemcontrol.lua +++ b/shared/itemcontrol.lua @@ -1,12 +1,28 @@ --- Function to register items as usable for ESX, QBX, and QBcore -- +--[[ + Usable Items & Inventory Utilities Module + ------------------------------------------- + This module provides functions for: + • Registering items as usable across different inventory systems (ESX, QBcore, QBX). + • Retrieving an item's image as a NUI link. + • Adding and removing items from a player's inventory. + • Toggling items in inventory (with server event for exploit protection). + • Checking for item duplication exploits. + • Handling tool durability mechanics. + • Checking item availability and retrieving inventory. + • Granting random rewards from a reward pool. + • Checking if a player can carry specific items based on weight. +]] + +------------------------------------------------------------- +-- Registering Usable Items +------------------------------------------------------------- + +--- Registers an item as usable for ESX, QBcore, or QBX. --- ---- 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. +--- @param funct function The function to execute when the item is used. --- ----@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 +--- @usage --- ```lua --- createUseableItem("health_potion", function(source) --- -- Code to consume the health potion @@ -23,23 +39,24 @@ function createUseableItem(item, funct) elseif isStarted(QBXExport) then debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qbx_core", item) exports[QBXExport]:CreateUseableItem(item, funct) + else + print("^4ERROR^7: No supported framework detected for registering usable item: ^3"..item.."^7") end end --- Simple function to grab the item's image from inventories and retrieve it as a nui:// link -- +------------------------------------------------------------- +-- Item Image Retrieval +------------------------------------------------------------- + +--- Retrieves the NUI link for an item's image from the active inventory system. --- ---- 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 item name. +--- @return string string A `nui://` link to the item's image, or an empty string if not found. --- ----@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 +--- @usage --- ```lua --- local imageLink = invImg("health_potion") ---- if imageLink ~= "" then ---- print(imageLink) ---- end +--- if imageLink ~= "" then print(imageLink) end --- ``` function invImg(item) local imgLink = "" @@ -50,33 +67,39 @@ function invImg(item) imgLink = "nui://"..QSInv.."/html/images/"..(Items[item].image or "") elseif isStarted(CoreInv) then imgLink = "nui://"..CoreInv.."/html/img/"..(Items[item].image or "") + elseif isStarted(CodeMInv) then + imgLink = "nui://"..CodeMInv.."/html/itemimages/"..(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") + print("^4ERROR^7: ^2No Inventory detected for invImg - Check exports.lua") end end return imgLink end +------------------------------------------------------------- +-- Adding and Removing Items +------------------------------------------------------------- + --- 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. +--- Triggers a server event (or local event) to add the specified item. --- ----@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. +--- @param item string The item name. +--- @param amount number The quantity to add. +--- @param info table|nil Additional metadata for the item. +--- @param src number|nil Optional player source; if nil, defaults to the caller. --- ----@usage +--- @usage --- ```lua --- addItem("health_potion", 2, { quality = "high" }) --- ``` function addItem(item, amount, info, src) if not Items[item] then print("^6Bridge^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 @@ -86,17 +109,23 @@ 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. +--- Triggers a server event (or local event) to remove the specified item. --- ----@param item string The name of the item to remove. ----@param amount number The quantity of the item to remove. +--- @param item string The item name. +--- @param amount number The quantity to remove. +--- @param src number|nil Optional player source. +--- @param slot number|nil Optional inventory slot. --- ----@usage +--- @usage --- ```lua --- removeItem("health_potion", 1) --- ``` function removeItem(item, amount, src, slot) - if not Items[item] then print("^6Bridge^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist") return end + if not Items[item] then + print("^6Bridge^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, nil, slot) else @@ -104,161 +133,188 @@ function removeItem(item, amount, src, slot) end end ---- Server event handler to toggle items in a player's inventory. +------------------------------------------------------------- +-- Toggle Items (Server Event) +------------------------------------------------------------- + +--- Server event handler to toggle (add or remove) an item from 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. +--- This function validates the item, then calls the appropriate export functions based on the active inventory system. +--- It also includes exploit protection via the dupeWarn function. --- ----@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. +--- @param give boolean True to add the item, false to remove. +--- @param item string The item name. +--- @param amount number The quantity. +--- @param newsrc number|nil The player source; defaults to event source. +--- @param info table|nil Additional metadata. +--- @param slot number|nil Optional inventory slot. --- ----@usage +--- @usage --- ```lua ---- TriggerServerEvent("script:server:toggleItem", true, "health_potion", 1) +--- TriggerServerEvent(getScript()..":server:toggleItem", true, "health_potion", 1) --- ``` RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info, slot) - if not Items[item] then print("^6Bridge^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." ^7'^3"..item.."^7'^2 but it doesn't exist") return end + if not Items[item] then + print("^6Bridge^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." '^3"..item.."^7' 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) + local action = (tostring(give) == "true" and "addItem" or "removeItem") + local remamount = amount or 1 if item == nil then return end + + -- Grab the current inventory (you can expand usage of 'inv' if needed) + local invName = "" 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") + if not hasItem(item, amount or 1, src) then + dupeWarn(src, item, amount) - 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") + else + if isStarted(OXInv) then invName = OXInv + exports[OXInv]:RemoveItem(src, item, remamount, nil) - 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(QSInv) then invName = QSInv + exports[QSInv]:RemoveItem(src, item, remamount) - 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(CoreInv) then invName = CoreInv + exports[CoreInv]:removeItem(src, item, remamount) - elseif isStarted(QBInv) then + elseif isStarted(OrigenInv) then invName = OrigenInv + exports[OrigenInv]:removeItem(src, item, remamount) + + elseif isStarted(CodeMInv) then invName = CodeMInv + exports[CodeMInv]:RemoveItem(src, item, remamount) + + elseif isStarted(QBInv) then invName = QBInv while remamount > 0 do if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then remamount -= 1 else - print("^1Error removing "..data.item.." Amount left to remove: "..remamount.."^7") + print("^1Error removing "..item.." Amount left: "..remamount) 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)) + TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "remove", 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 + elseif isStarted(PSInv) then invName = PSInv while remamount > 0 do - if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then + if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then remamount -= 1 else - print("^1Error removing "..data.item.." Amount left to remove: "..remamount.."^7") + print("^1Error removing "..item.." Amount left: "..remamount) break end end if Config.Crafting.showItemBox then - TriggerClientEvent('inventory:client:ItemBox', src, Items[item], "remove", (amount and amount or 1)) + TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", 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 + ----- + -- Fallback for if no inventory found: + ----- + if invName == "" then + if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions + invName = isStarted(QBXExport) and QBXExport or isStarted(QBExport) and QBExport + Core.Functions.GetPlayer(src).Functions.RemoveItem(item, remamount, slot) + + elseif ESX and isStarted(ESXExport) then -- if esx then use core functions + invName = ESX + ESX.GetPlayerFromId(src).removeInventoryItem(item, remamount) + + end + end + -- Final check for if inventory was found + if invName == "" then + print("^4ERROR^7: No Inventory detected - Check starter.lua") + else + debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..Items[item].label.."("..item..") x"..(amount or 1)) + end 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)) + local amountToAdd = amount or 1 + if isStarted(OXInv) then invName = OXInv + exports[OXInv]:AddItem(src, item, amountToAdd, info, slot) + + elseif isStarted(QSInv) then invName = QSInv + exports[QSInv]:AddItem(src, item, amountToAdd, slot, info) + + elseif isStarted(CoreInv) then invName = CoreInv + exports[CoreInv]:addItem(src, item, amountToAdd, info) + + elseif isStarted(CodeMInv) then invName = CodeMInv + exports[CodeMInv]:AddItem(src, item, amountToAdd, slot, info) + + elseif isStarted(OrigenInv) then invName = OrigenInv + exports[OrigenInv]:addItem(src, item, amountToAdd, info, slot) + + elseif isStarted(QBInv) then invName = QBInv + if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then + TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "add", amountToAdd) 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 + elseif isStarted(PSInv) then invName = PSInv + if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then if Config.Crafting.showItemBox then - TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "add", amount and amount or 1) + TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "add", amountToAdd) 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") + end + + if invName == "" then + if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions + invName = isStarted(QBXExport) and QBXExport or isStarted(QBExport) and QBExport + Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) + + elseif ESX and isStarted(ESXExport) then -- if esx then use core functions + invName = ESX + ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd) + end + end + + -- Final check for if inventory was found + if invName == "" then + print("^4ERROR^7: No Inventory detected - Check starter.lua") else - print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7") + debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..Items[item].label.."("..item..") x"..(amount or 1)) end end end) ---- Protects against item duplication exploits by warning and potentially kicking the player. +------------------------------------------------------------- +-- Exploit Protection +------------------------------------------------------------- + +--- Warns and kicks a player if they try to remove an item they don't have. --- ---- 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. +--- @param src number The player's source ID. +--- @param item string The item name. --- --- @usage --- ```lua --- dupeWarn(playerId, "health_potion") --- ``` -function dupeWarn(src, item) +function dupeWarn(src, item, amount) 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") + print("^5DupeWarn^7: "..name.." (^1"..tostring(src).."^7) ^2Tried to remove item '^3"..item.."^7' but it wasn't there") 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. +------------------------------------------------------------- +-- Tool Durability & Metadata +------------------------------------------------------------- + +--- Reduces the durability of a tool by a specified damage amount. --- ---- 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. +--- If durability reaches zero or below, the tool is removed and a break sound is played. --- ---- @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. +--- @param data table Contains: +--- - item (string): The tool's name. +--- - damage (number): The damage % to apply. --- --- @usage --- ```lua @@ -277,19 +333,20 @@ function breakTool(data) -- WIP end end ---- Retrieves the durability and slot of an item in a player's inventory. +--- Retrieves the durability and slot number 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. +--- Searches through the player's inventory for the specified item. --- ---- @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. +--- @param item string The item name. +--- @return number|nil number The durability, or nil if not found. +--- @return number|nil number The slot number, or nil if not found. --- --- @usage --- ```lua --- local durability, slot = getDurability("drill") --- if durability then --- print("Durability:", durability) +--- print("Slot:", slot) --- end --- ``` function getDurability(item) @@ -297,7 +354,7 @@ function getDurability(item) local durability = nil if isStarted(QBInv) or isStarted(PSInv) then local itemcheck = Core.Functions.GetPlayerData().items - for k, v in pairs(itemcheck) do + for _, v in pairs(itemcheck) do if v.name == item then if v.slot <= lowestSlot then lowestSlot = v.slot @@ -309,47 +366,86 @@ function getDurability(item) if isStarted(OXInv) then local itemcheck = exports[OXInv]:Search('slots', item) - for k, v in pairs(itemcheck) do + for _, 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 + durability = v.metadata.durability end end end if isStarted(QSInv) then local itemcheck = exports[QSInv]:getUserInventory() - for k, v in pairs(itemcheck) do + for _, v in pairs(itemcheck) do if v.name == item and v.slot <= lowestSlot then lowestSlot = v.slot - durability = itemcheck[k].metadata.durability + durability = v.metadata.durability + end + end + end + + if isStarted(CoreInv) then + local itemcheck = exports[CoreInv]:getInventory() + for _, v in pairs(itemcheck) do + if v.name == item and v.slot <= lowestSlot then + lowestSlot = v.slot + durability = v.metadata.durability + end + end + end + + if isStarted(CodeMInv) then + local itemcheck = exports[CodeMInv]:GetClientPlayerInventory() + for _, v in pairs(itemcheck) do + if v.name == item and v.slot <= lowestSlot then + lowestSlot = v.slot + durability = v.metadata.durability end end end if isStarted(OrigenInv) then local itemcheck = exports[OrigenInv]:getPlayerInventory() - for k, v in pairs(itemcheck) do + for _, v in pairs(itemcheck) do if v.name == item and v.slot <= lowestSlot then lowestSlot = v.slot - durability = itemcheck[k].metadata.durability + durability = v.metadata.durability end end end + + -- For ESX default inventory (es_extended) + if ESX and isStarted(ESXExport) then + local xPlayer = ESX.GetPlayerData() or {} + if xPlayer.inventory then + for _, v in ipairs(xPlayer.inventory) do + if v.name == item then + -- Optionally use a slot field if available; otherwise, use the index + if v.slot and v.slot <= lowestSlot then + lowestSlot = v.slot + end + if v.metadata and v.metadata.durability then + durability = v.metadata.durability + end + end + end + end + end + return durability, lowestSlot end ---- Server event handler to set metadata for an item in a player's inventory. +--- Server event handler to set metadata for an item. --- ---- This event updates the metadata (e.g., durability) of an item in the player's inventory. +--- Updates item metadata (e.g. durability) for the player's inventory based on slot. --- ----@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. +--- @param data table Contains: +--- - item (string): The item name. +--- - slot (number): The inventory slot. +--- - metadata (table): The metadata to set. --- ----@usage +--- @usage --- ```lua --- TriggerServerEvent("script:server:setMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } }) --- ``` @@ -361,171 +457,61 @@ RegisterNetEvent(getScript()..":server:setMetaData", function(data) 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 + elseif isStarted(OXInv) then + exports[OXInv]:SetDurability(src, data.slot, data.metadata.durability) - if isStarted(QSInv) then - exports[QSInv]:SetItemMetadata(source, data.slot, data.metadata) - end + elseif isStarted(QSInv) then + exports[QSInv]:SetItemMetadata(src, data.slot, data.metadata) - 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 + elseif isStarted(CoreInv) then + exports[CoreInv]:setMetadata(src, data.slot, data.metadata) + + elseif isStarted(CodeMInv) then + exports[CodeMInv]:SetItemMetadata(src, data.slot, data.metadata) + + elseif isStarted(OrigenInv) then + exports[OrigenInv]:setMetadata(src, data.slot, data.metadata) end end) ---- Checks if a player has the specified items in their inventory. +------------------------------------------------------------- +-- Random Reward +------------------------------------------------------------- + +--- Grants a random reward from a predefined reward pool if the player is eligible. --- ---- 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. +--- Checks if the item qualifies for a reward, removes the item, then calculates a random reward based on rarity. --- ----@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. +--- @param itemName string The item name to check. --- ---@usage --- ```lua --- getRandomReward("gold_ring") --- ``` -function getRandomReward(itemName) -- Intended for job scripts +function getRandomReward(itemName) if Config.Rewards.RewardPool then local reward = false - if type(Config.Rewards.RewardItem) == "string" then Config.Rewards.RewardItem = { Config.Rewards.RewardItem } end + 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 + 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 + 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'") - + debugPrint("^6Bridge^7: ^3getRandomReward^7: Total Rarity '"..totalRarity.."'") local randomNum = math.random(1, totalRarity) - debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Random Number ^7'^6"..randomNum.."^7'") + debugPrint("^6Bridge^7: ^3getRandomReward^7: Random Number '"..randomNum.."'") local currentRarity = 0 - for i=1, #Config.Rewards.RewardPool do + 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'") @@ -537,24 +523,25 @@ function getRandomReward(itemName) -- Intended for job scripts end end ---- Checks if a player can carry specific items in their inventory. +------------------------------------------------------------- +-- Carry Capacity Check +------------------------------------------------------------- + +--- Checks if a player can carry the specified items based on weight. --- ---- 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. +--- Calculates the current total weight in the player's inventory and determines whether adding the new items would exceed capacity. --- ----@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. +--- @param itemTable table A table where keys are item names and values are required quantities. +--- @param src number The player's source ID. +--- @return table A table mapping each item to a boolean indicating if it can be carried. --- ----@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 +--- @usage +--- local carryCheck = canCarry({ ["health_potion"] = 2, ["mana_potion"] = 3 }, playerId) +--- if carryCheck["health_potion"] and carryCheck["mana_potion"] then +--- -- Player can carry items. --- else ---- -- Inform the player they can't carry all items +--- -- Notify player. --- end ---- ``` function canCarry(itemTable, src) local resultTable = {} if src then @@ -565,16 +552,28 @@ function canCarry(itemTable, src) elseif isStarted(QSInv) then for k, v in pairs(itemTable) do - resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v) + resultTable[k] = exports[QSInv]: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) + resultTable[k] = exports[CoreInv]:canCarry(src, k, v) + end + + elseif isStarted(CodeMInv) then --- This really needs updating, their docs are confusing.. + local items = getPlayerInv(src) + local totalWeight = 0 + if not items then return false end + for _, item in pairs(items) do + totalWeight += (item.weight * item.amount) + end + for k, v in pairs(itemTable) do + local itemInfo = Items[k] + if not itemInfo then + resultTable[k] = true + else + resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight + end end elseif isStarted(OrigenInv) then @@ -583,21 +582,18 @@ function canCarry(itemTable, src) end elseif isStarted(QBInv) or isStarted(PSInv) then - local Player = Core.Functions.GetPlayer(src) - local items = Player.PlayerData.items - local weight, totalWeight = 0, 0 + local items = getPlayerInv(src) + local totalWeight = 0 if not items then return false end - for _, item in pairs(items) do weight += item.weight * item.amount end - - totalWeight = tonumber(weight) - + for _, item in pairs(items) do + totalWeight += (item.weight * item.amount) + end 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 + resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight end end end diff --git a/shared/jobfunctions.lua b/shared/jobfunctions.lua index 2ceaf43..b338a3f 100644 --- a/shared/jobfunctions.lua +++ b/shared/jobfunctions.lua @@ -1,17 +1,32 @@ --- Global variable to track duty status +--[[ + Duty & Interaction Utilities Module + -------------------------------------- + This module provides functions related to: + • Determining boss roles from Jobs and Gangs tables. + • Checking a player's job and duty status. + • Toggling duty state. + • Simulating player interactions such as hand washing, using toilets/urinals, + and teleporting via doors. +]] + +------------------------------------------------------------- +-- Global Duty Status +------------------------------------------------------------- onDuty = false ---- Scans the 'Jobs' and 'Gangs' tables to identify roles classified as Bosses. +------------------------------------------------------------- +-- Boss Role Detection +------------------------------------------------------------- + +--- 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. +--- Iterates through the specified role's grades in the Jobs or Gangs table and returns +--- a table mapping the role to the lowest grade number that qualifies as a boss (isboss or bankAuth). --- ----@param role string The name of the job or gang role to check for boss grades. +--- @param role string The job or gang role to check. +--- @return table table A table with the role mapped to its boss grade number. --- ----@return table table A table containing roles mapped to their respective boss grade numbers. ---- ----@usage +--- @usage --- ```lua --- local bosses = makeBossRoles("police") --- if bosses["police"] then @@ -31,25 +46,28 @@ function makeBossRoles(role) return boss end ---- Checks if the player has a specific job and is on duty. +------------------------------------------------------------- +-- Job & Duty Checks +------------------------------------------------------------- + +--- Checks if the player has a specific job (or gang) 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. +--- Verifies whether the player possesses the specified role. If the role is defined in the Jobs table, +--- it also checks that the player is clocked in (onDuty). If the check fails, a notification is sent. --- ----@param job string The name of the job or gang to check. +--- @param job string The job or gang to check. +--- @return boolean Returns true if the player meets the criteria; false otherwise. --- ----@return boolean Returns `true` if the player has the job (and is on duty if required), otherwise `false`. ---- ----@usage +--- @usage --- ```lua --- if jobCheck("mechanic") then ---- -- Allow access to mechanic-related features +--- -- Allow mechanic features. --- else ---- -- Deny access or notify the player +--- -- Deny access. --- end --- ``` function jobCheck(job) - canDo = true + local canDo = true if Jobs[job] then if not hasJob(job) or not onDuty then triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"]) @@ -66,14 +84,12 @@ 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. +--- Switches the player's duty state between on-duty and off-duty. If using QBcore, +--- it triggers the appropriate server event. Otherwise, it manually toggles the onDuty variable and notifies the player. --- ----@usage +--- @usage --- ```lua ---- toggleDuty() ---- -- Player will receive a notification indicating their new duty status +--- toggleDuty() -- Player receives a notification of their new duty status. --- ``` function toggleDuty() if isStarted(QBExport) or isStarted(QBXExport) then @@ -88,22 +104,24 @@ function toggleDuty() end end +------------------------------------------------------------- +-- Interaction Functions +------------------------------------------------------------- + --- 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. +--- Triggers an animation and a progress bar to simulate hand washing at the specified coordinates. +--- On success, it notifies the player; if cancelled, it sends an error notification. --- ----@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. +--- @param data table A table containing: +--- - coords (vector3): The location where the hand-washing action occurs. --- ----@return void ---- ----@usage +--- @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() +function washHands(data) + local ped = PlayerPedId() lookEnt(data.coords) local cam = createTempCam(ped, data.coords) if progressBar({ @@ -118,22 +136,21 @@ function washHands(data) local ped = PlayerPedId() }) then triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success") else - triggerNotify(nil, Loc[Config.Lan].error["cancel"], 'error') + 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. +--- Manages animations and progress bars for using a urinal or a toilet. If the action is successful, +--- it triggers the appropriate server event (urinal usage) or notifies the player if cancelled. --- ----@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. +--- @param data table A table containing: +--- - urinal (boolean): `true if using a urinal; false for a toilet.` +--- - sitcoords (vector4): `Coordinates and heading for seating when using a toilet.` --- ----@usage +--- @usage --- ```lua --- useToilet({ urinal = true }) --- -- Player uses a urinal with corresponding animations and notifications @@ -154,7 +171,7 @@ function useToilet(data) TriggerServerEvent(getScript().."server:Urinal") else lockInv(false) - triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error') + 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) @@ -167,24 +184,22 @@ function useToilet(data) ClearPedTasks(PlayerPedId()) else lockInv(false) - triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error') + 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. +--- Fades the screen out, moves the player to the target coordinates, sets the player's heading, +--- then fades the screen back in. Commonly used for door interactions or teleportation points. --- ----@param data table A table containing teleportation data. ---- - **telecoords** (`vector4`): The target coordinates and heading for the teleportation. +--- @param data table A table containing: +--- - telecoords (vector4): The target coordinates and heading. --- ----@usage +--- @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) diff --git a/shared/metaHandlers.lua b/shared/metaHandlers.lua index 8d2ae7d..81a0ebf 100644 --- a/shared/metaHandlers.lua +++ b/shared/metaHandlers.lua @@ -1,4 +1,24 @@ +--[[ + Player Metadata Utilities Module + ---------------------------------- + This module provides functions for retrieving and setting metadata for players + across different frameworks (QB, ESX, OXCore). It also registers server callbacks + for getting and setting metadata. +]] +------------------------------------------------------------- +-- Player Retrieval +------------------------------------------------------------- + +--- Retrieves the player object using the active core export. +--- +--- @param source number The server ID of the player. +--- @return table|nil table The player object, or nil if no supported core is detected. +--- +--- @usage +--- ```lua +--- local player = GetPlayer(playerId) +--- ``` function GetPlayer(source) if isStarted(QBExport) then debugPrint("^6Bridge^7: ^3GetPlayer^7() QBExport") @@ -16,9 +36,24 @@ function GetPlayer(source) return nil end --- Get Metadata +------------------------------------------------------------- +-- Metadata Retrieval +------------------------------------------------------------- + +--- Retrieves metadata from a player object. +--- +--- If called client-side (player is nil), it triggers a server callback to retrieve metadata. +--- +--- @param player table|nil The player object; if nil, metadata is retrieved via a server callback. +--- @param key string The metadata key to retrieve. +--- @return any The value of the requested metadata, or nil if not found. +--- +--- @usage +--- ```lua +--- local myMeta = GetMetadata(player, "myKey") +--- ``` function GetMetadata(player, key) - if not player then -- This would be called client side + if not player then debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key) return triggerCallback(getScript()..":server:GetMetadata", key) else @@ -36,56 +71,65 @@ function GetMetadata(player, key) return nil end +-- Register a server callback for retrieving metadata. createCallback(getScript()..":server:GetMetadata", function(source, key) - debugPrint("^6Bridge^7: ^3GetMetadata^7() Callback", source, key) + debugPrint("^6Bridge^7: ^3GetMetadata Callback^7 from source: "..tostring(source)..", key: "..tostring(key)) local player = GetPlayer(source) - local Metadata = {} if not player then - print("Error getting metadata") + print("Error getting metadata: player not found for source "..tostring(source)) return end + if type(key) == "table" then + local Metadata = {} for _, k in ipairs(key) do - Metadata[k] = GetMetadata(player, k).k + Metadata[k] = GetMetadata(player, k) end + return Metadata elseif type(key) == "string" then return GetMetadata(player, key) end - - jsonPrint(Metadata) - return Metadata end) --- Set Metadata +------------------------------------------------------------- +-- Metadata Setting +------------------------------------------------------------- + +--- Sets metadata on a player object. +--- +--- The function updates the player's metadata using the active core export. +--- +--- @param player table The player object. +--- @param key string The metadata key to set. +--- @param value any The new value for the metadata key. +--- +--- @usage +--- ```lua +--- SetMetadata(player, "myKey", "newValue") +--- ``` function SetMetadata(player, key, value) - --if player == nil then -- This would be called client side - -- debugPrint("^6Bridge^7: ^3SetMetadata^7() calling server") - -- triggerCallback(getScript()..":server:SetMetadata", { key, value }) - -- else - debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata") - if isStarted(QBExport) or isStarted(QBXExport) then - debugPrint("^6Bridge^7: ^3SetMetadata^7() QBExport or QBXExport") - player.Functions.SetMetaData(key, value) - elseif isStarted(ESXExport) then - debugPrint("^6Bridge^7: ^3SetMetadata^7() ESXExport") - player.setMeta(key, value) - elseif isStarted(OXCoreExport) then - debugPrint("^6Bridge^7: ^3SetMetadata^7() OXCoreExport") - player.set(key, value) - end - --end + debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata for key: "..key) + if isStarted(QBExport) or isStarted(QBXExport) then + debugPrint("^6Bridge^7: ^3SetMetadata^7() using QBExport/QBXExport") + player.Functions.SetMetaData(key, value) + elseif isStarted(ESXExport) then + debugPrint("^6Bridge^7: ^3SetMetadata^7() using ESXExport") + player.setMeta(key, value) + elseif isStarted(OXCoreExport) then + debugPrint("^6Bridge^7: ^3SetMetadata^7() using OXCoreExport") + player.set(key, value) + end end - +-- Register a server callback for setting metadata. createCallback(getScript()..":server:SetMetadata", function(source, key, value) - print(source, key, value) + debugPrint("SetMetadata callback triggered for source:", source, "key:", key, "value:", value) local player = GetPlayer(source) - --jsonPrint(player) --[[if not player then - print("Error getting metadata") + print("Error setting metadata: player not found for source "..tostring(source)) return false end]] - print("i did it") SetMetadata(player, key, value) + print("Metadata set successfully.", key) return true end) \ No newline at end of file diff --git a/shared/notify.lua b/shared/notify.lua index 3741ff5..b65a2a9 100644 --- a/shared/notify.lua +++ b/shared/notify.lua @@ -1,18 +1,28 @@ --- NOTIFICATIONS -- --- This function is widely used to display notifications to the player, can be used server side or client side -- +--[[ + Notifications Module + ---------------------- + This module provides a unified interface for displaying notifications using various + notification systems. The active system is determined by the Config.System.Notify setting. + + Supported systems include: + • okok + • qb + • ox + • gta (default) + • esx +]] --- 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. +--- Supports multiple notification systems based on Config.System.Notify. Can be triggered from both +--- client and server contexts. --- ----@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. +--- @param title string|nil The notification title (optional for some systems). +--- @param message string The main message content. +--- @param type string The notification type ("success", "error", "info"). +--- @param src number|nil Optional server ID; if provided, the notification is sent to that player. --- ----@usage +--- @usage --- ```lua --- -- Client-side usage without specifying a player (shows to the current player) --- triggerNotify("Success", "You have completed the task!", "success") @@ -21,52 +31,72 @@ --- 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 + 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, { title = title, description = message, type = type or "success" }) + 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 + if not src then + exports["esx_notify"]:Notify(type, 4000, message) + else + TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message) + end + end end +------------------------------------------------------------- +-- ESX Notifications +------------------------------------------------------------- + --- 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. +--- Listens for DisplayESXNotify events and triggers the ESX notification on the client. --- ---- @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. +--- @param type string The notification type. +--- @param title string The notification title. +--- @param text string The notification message. --- --- @usage --- ```lua ---- -- Server-side event trigger ---- TriggerClientEvent(getScript()..":DisplayESXNotify", playerId, "success", "Achievement Unlocked", "You have unlocked a new achievement!") +--- TriggerClientEvent(getScript()..":DisplayESXNotify", playerId, "success", "New achievement unlocked!") --- ``` -RegisterNetEvent(getScript()..":DisplayESXNotify", function(type, title, text) +RegisterNetEvent(getScript()..":DisplayESXNotify", function(type, text) exports["esx_notify"]:Notify(type, 4000, text) end) ---- Displays default GTA-style text notifications. +------------------------------------------------------------- +-- GTA-style Notifications +------------------------------------------------------------- + +--- Displays GTA-style text notifications using native GTA functions. --- ---- 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. +--- Selects an appropriate icon based on the current script (if applicable) and renders the notification. --- ----@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. +--- @param title string The notification title/identifier (used to select an icon). +--- @param text string The notification message. --- ----@usage +--- @usage --- ```lua ---- -- Client-side event trigger --- TriggerEvent(getScript()..":DisplayGTANotify", "taxiname", "Taxi service has arrived.") --- ``` RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text) @@ -81,8 +111,13 @@ RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text) [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) + 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/phones.lua b/shared/phones.lua new file mode 100644 index 0000000..62d9c28 --- /dev/null +++ b/shared/phones.lua @@ -0,0 +1,166 @@ +--[[ + Phone Mails Module + ------------------ + This module handles sending phone mails using different phone systems. + Supported systems include: + - gksphone + - yflip-phone + - qs-smartphone + - qs-smartphone-pro + - roadphone + - lb-phone + - qb-phone + - jpr-phonesystem +]] + +--- Sends a phone mail using the detected phone system. +--- The function iterates through a prioritized list of supported phone systems. +--- Once an active system is found (via `isStarted`), the corresponding mail function is executed. +--- +--- @param data table A table containing the mail data. +--- - subject (string): The email subject. +--- - sender (string): The sender identifier. +--- - message (string): The email body content. +--- - actions (table|nil): Optional action buttons for the email. +--- @usage +--- sendPhoneMail({ +--- subject = "Welcome!", +--- sender = "Admin", +--- message = "Thank you for joining our server.", +--- actions = { +--- { label = "Reply", action = replyFunction } +--- } +--- }) +function sendPhoneMail(data) + -- Define each supported phone system and its corresponding mail-sending function. + local phoneSystems = { + { name = "gksphone", + send = function(mailData) + exports["gksphone"]:SendNewMail(mailData) + end, + }, + { name = "yflip-phone", + send = function(mailData) + TriggerServerEvent(getScript()..":yflip:SendMail", mailData) + end, + }, + { name = "qs-smartphone", + send = function(mailData) + TriggerServerEvent('qs-smartphone:server:sendNewMail', mailData) + end, + }, + { name = "qs-smartphone-pro", + send = function(mailData) + TriggerServerEvent('phone:sendNewMail', mailData) + end, + }, + { name = "roadphone", + send = function(mailData) + -- Convert HTML line breaks to newlines for roadphone. + mailData.message = mailData.message:gsub("%
", "\n") + exports["roadphone"]:sendMail(mailData) + end, + }, + { name = "lb-phone", + send = function(mailData) + -- Convert HTML line breaks to newlines for lb-phone. + mailData.message = mailData.message:gsub("%
", "\n") + TriggerServerEvent(getScript()..":lbphone:SendMail", mailData) + end, + }, + { name = "qb-phone", + send = function(mailData) + TriggerServerEvent('qb-phone:server:sendNewMail', mailData) + end, + }, + { name = "jpr-phonesystem", + send = function(mailData) + TriggerServerEvent(getScript()..":jpr:SendMail", mailData) + end, + }, + } + + local activePhone = nil + -- Check each phone system in order and use the first active one. + for _, phone in ipairs(phoneSystems) do + if isStarted(phone.name) then + activePhone = phone.name + phone.send(data) + break + end + end + + if activePhone then + debugPrint("^6Bridge^7[^3"..activePhone.."^7]: ^2Sending mail to player") + else + print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found") + end +end + +------------------------------------------------------------- +-- Phone System Event Handlers +------------------------------------------------------------- + +--- Handles sending mail for lb-phone. +--- Listens for the `lbphone:SendMail` event and sends an email using lb-phone's API. +--- +--- @event lbphone:SendMail +--- @param data table The mail data. +--- - subject (string): The email subject. +--- - message (string): The email content. +--- - buttons (table|nil): Optional action buttons (mapped from data.actions if present). +RegisterNetEvent(getScript()..":lbphone:SendMail", function(data) + local src = source + local phoneNumber = exports["lb-phone"]:GetEquippedPhoneNumber(src) + local emailAddress = exports["lb-phone"]:GetEmailAddress(phoneNumber) + -- Map actions to buttons if provided. + data.buttons = data.actions or data.buttons + + exports["lb-phone"]:SendMail({ + to = emailAddress, + subject = data.subject, + message = data.message, + actions = data.buttons, + }) +end) + +--- Handles sending mail for yflip-phone. +--- Listens for the `yflip:SendMail` event and sends an email using yflip-phone's API. +--- +--- @event yflip:SendMail +--- @param data table The mail data. +--- - subject (string): The email subject. +--- - sender (string): The sender identifier. +--- - message (string): The email content. +--- - buttons (table|nil): Optional action buttons. +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. +--- Listens for the `jpr:SendMail` event and sends an email using jpr-phonesystem's API. +--- +--- @event jpr:SendMail +--- @param data table The mail data. +--- - subject (string): The email subject. +--- - sender (string): The sender identifier. +--- - message (string): The email content. +--- - buttons (table|nil): Optional action buttons. +RegisterNetEvent(getScript()..":jpr:SendMail", function(data) + local src = source + local Player = Core.Functions.GetPlayer(src) + TriggerEvent('jpr-phonesystem:server:sendEmail', { + Assunto = data.subject, -- Email subject + Conteudo = data.message, -- Email content + Enviado = data.sender, -- Sender information + Destinatario = Player.PlayerData.citizenid, -- Recipient identifier + Event = {}, -- Optional event details + }) +end) \ No newline at end of file diff --git a/shared/playerfunctions.lua b/shared/playerfunctions.lua index 1745b3f..0bd647e 100644 --- a/shared/playerfunctions.lua +++ b/shared/playerfunctions.lua @@ -1,67 +1,52 @@ ---- Locks or unlocks the player's inventory. ---- ---- This function freezes or unfreezes the player's position, sets the inventory busy state, ---- and toggles the ability to use the inventory and hotbar based on the `toggle` parameter. ---- ---- @param toggle boolean `true` to lock the inventory, `false` to unlock. ---- ---- @usage ---- ```lua ---- -- Lock the player's inventory ---- lockInv(true) ---- ---- -- Unlock the player's inventory ---- lockInv(false) ---- ``` -function lockInv(toggle) - FreezeEntityPosition(PlayerPedId(), toggle) - LocalPlayer.state:set("inv_busy", toggle, true) - TriggerEvent('inventory:client:busy:status', toggle) - TriggerEvent('canUseInventoryAndHotbar:toggle', not toggle) -end +--[[ + Player Utility & Server Event Handlers Module + ------------------------------------------------ + This module provides utility functions for: + • Locking/unlocking the player's inventory. + • Instantly turning or gradually turning the player to face a target. + • Handling player needs (thirst and hunger) via server events. + • Charging/funding players (money removal/addition). + • Processing item consumption and applying effects. + • Checking player job/gang roles and retrieving player information. + • Getting active players near a coordinate. +]] ---- Instantly turns an entity to face a specific location or another entity. +------------------------------------------------------------- +-- Player Movement +------------------------------------------------------------- + +--- Instantly turns an entity to face a target (entity or coordinates) without animation. --- ---- This function calculates the heading from the first entity to the second entity or coordinates ---- and sets the entity's heading immediately without any animation. ---- ---- @param ent number|nil The Ped entity to turn. Defaults to the player's Ped (`PlayerPedId()`). ---- @param ent2 number|vector3|nil The target entity or coordinates to face. If a vector, it uses the coordinates. +--- @param ent number|nil The Ped to turn (defaults to player's Ped if nil). +--- @param ent2 number|vector3|nil The target entity or coordinates to face. --- --- @usage --- ```lua ---- -- Make the player instantly face a specific location --- instantLookEnt(nil, vector3(200.0, 300.0, 40.0)) ---- ---- -- Make one entity face another entity --- instantLookEnt(ped1, ped2) --- ``` function instantLookEnt(ent, ent2) - local ent = ent or PlayerPedId() - local p1 = GetEntityCoords(ent, true) - local p2 = type(ent2):find("vector") and ent2 or GetEntityCoords(ent2, true) + local ped = ent or PlayerPedId() + local p1 = GetEntityCoords(ped, true) + local p2 = type(ent2) == "vector3" and ent2 or GetEntityCoords(ent2, true) local dx = p2.x - p1.x local dy = p2.y - p1.y - local heading = GetHeadingFromVector_2d(dx, dy) + debugPrint("^6Bridge^7: ^1Forced ^2Turning Player to^7: '^6"..formatCoord(p2).."^7'") - SetEntityHeading(ent, heading) + SetEntityHeading(ped, heading) end ---- Makes the player Ped look towards a specific entity or coordinates with animation. +--- Makes the player look towards a specific target with an animated turn. --- ---- This function checks if the player is already facing the target. If not, it triggers a turning animation ---- to face the specified entity or coordinates. +--- If the player is not already facing the target (entity or coordinates), a turning animation is triggered. --- ---- @param entity number|vector3|vector4|nil The target entity or coordinates to look at. +--- @param entity number|vector3|vector4|nil The target to look at. --- --- @usage --- ```lua ---- -- Make the player look at a specific location --- lookEnt(vector3(200.0, 300.0, 40.0)) ---- ---- -- Make the player look at another entity --- lookEnt(pedEntity) --- ``` function lookEnt(entity) @@ -86,15 +71,12 @@ function lookEnt(entity) end end ---- Server event handler for handling urinal usage. ---- ---- This event decreases the player's thirst based on a random amount and updates their thirst level. ---- ---- @usage ---- ```lua ---- -- Triggered when a player uses a urinal ---- TriggerServerEvent(getScript()..":server:Urinal") ---- ``` +------------------------------------------------------------- +-- Server Event Handlers for Needs +------------------------------------------------------------- + +--- Server event handler for urinal usage. +--- Decreases player's thirst by a random amount. RegisterNetEvent(getScript()..":server:Urinal", function() local src = source local Player = getPlayer(src) @@ -103,43 +85,27 @@ RegisterNetEvent(getScript()..":server:Urinal", function() setThirst(src, getPlayer(src).thirst - thirst) end) ---- Server event handler for setting player needs. ---- ---- This event updates the player's thirst or hunger based on the provided type and amount. +--- Server event handler for setting player needs (thirst or hunger). --- --- @event ---- @param type string The type of need to set ("thirst" or "hunger"). ---- @param amount number The amount to set the need to. ---- ---- @return void ---- ---- @usage ---- ```lua ---- -- Set the player's thirst level ---- TriggerServerEvent(getScript()..":server:setNeed", "thirst", 50) ---- ---- -- Set the player's hunger level ---- TriggerServerEvent(getScript()..":server:setNeed", "hunger", 75) ---- ``` -RegisterNetEvent(getScript()..":server:setNeed", function(type, amount) +--- @param type string "thirst" or "hunger". +--- @param amount number New value to set. +RegisterNetEvent(getScript()..":server:setNeed", function(needType, amount) local src = source - if type == "thirst" then + if needType == "thirst" then setThirst(src, amount) - elseif type == "hunger" then + elseif needType == "hunger" then setHunger(src, amount) end end) --- Sets the player's thirst level. --- ---- This function updates the player's thirst based on the active inventory system. ---- ---- @param src number The server ID of the player. ---- @param thirst number The new thirst level to set. +--- @param src number The player's server ID. +--- @param thirst number The new thirst level. --- --- @usage --- ```lua ---- -- Set a player's thirst to 80 --- setThirst(playerId, 80) --- ``` function setThirst(src, thirst) @@ -154,14 +120,11 @@ end --- Sets the player's hunger level. --- ---- This function updates the player's hunger based on the active inventory system. ---- ---- @param src number The server ID of the player. ---- @param hunger number The new hunger level to set. +--- @param src number The player's server ID. +--- @param hunger number The new hunger level. --- --- @usage --- ```lua ---- -- Set a player's hunger to 60 --- setHunger(playerId, 60) --- ``` function setHunger(src, hunger) @@ -174,105 +137,103 @@ function setHunger(src, hunger) end end ---- Server event handler for charging a player. +------------------------------------------------------------- +-- Economy Event Handlers +------------------------------------------------------------- + +--- Charges a player by removing money from their account. --- ---- This event removes money from a player based on the specified type ("cash" or "bank"). ---- ---- @event ---- @param cost number The amount of money to charge. ---- @param type string The type of money to charge ("cash" or "bank"). ---- @param newsrc number|nil Optional. The server ID of the player. If `nil`, uses the event source. +--- @param cost number The amount to charge. +--- @param type string "cash" or "bank". +--- @param newsrc number|nil Optional player ID; defaults to event source. --- --- @usage --- ```lua ---- -- Charge a player $100 in cash --- chargePlayer(100, "cash", playerId) ---- ---- -- Charge the source $250 from the bank ---- chargePlayer(250, "bank", src,) --- ``` -function chargePlayer(cost, type, newsrc) +function chargePlayer(cost, moneyType, newsrc) local src = newsrc or source local fundResource = "" - if type == "cash" then + + if moneyType == "cash" then if isStarted(OXInv) then fundResource = OXInv exports[OXInv]:RemoveItem(src, "money", cost) elseif isStarted(QBExport) or isStarted(QBXExport) then fundResource = QBExport Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost) elseif isStarted(ESXExport) then fundResource = ESXExport - local Player = ESX.GetPlayerFromId(src) - Player.removeMoney(cost, "") + ESX.GetPlayerFromId(src).removeMoney(cost, "") end - end - if type == "bank" then + elseif moneyType == "bank" then if isStarted(QBExport) or isStarted(QBXExport) then fundResource = QBExport Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost) elseif isStarted(ESXExport) then fundResource = ESXExport - local Player = ESX.GetPlayerFromId(src) - Player.removeMoney(cost, "") + ESX.GetPlayerFromId(src).removeMoney(cost, "") end end - if fundResource == "" then print("error - check exports.lua") + + if fundResource == "" then + print("Cannot charge player - check starter.lua") else - debugPrint("^6Bridge^7: ^2Charging ^2Player^7: '^6"..cost.."^7'", type, fundResource) + debugPrint("^6Bridge^7: ^2Charging ^2Player^7: '^6"..cost.."^7'", moneyType, fundResource) end end RegisterNetEvent(getScript()..":server:ChargePlayer", chargePlayer) ---- Server event handler for funding a player. +--- Funds a player by adding money to their account. --- ---- This event adds money to a player based on the specified type ("cash" or "bank"). ---- ---- @event ---- @param fund number The amount of money to add. ---- @param type string The type of money to add ("cash" or "bank"). ---- @param newsrc number|nil Optional. The server ID of the player. If `nil`, uses the event source. +--- @param fund number The amount to add. +--- @param type string "cash" or "bank". +--- @param newsrc number|nil Optional player ID; defaults to event source. --- --- @usage --- ```lua ---- -- Add $150 to a player's cash ---- fundPlayer(playerId, 150, "cash") ---- ---- -- Add $300 to the event source's bank account ---- fundPlayer(playerId, 300, "bank") +--- fundPlayer(150, "cash", playerId) --- ``` -function fundPlayer(fund, type, newsrc) +function fundPlayer(fund, moneyType, newsrc) local src = newsrc or source local fundResource = "" - if type == "cash" then - if isStarted(OXInv) then fundResource = OXInv + + if moneyType == "cash" then + if isStarted(OXInv) then + fundResource = OXInv exports[OXInv]:AddItem(src, "money", fund) - elseif isStarted(QBExport) or isStarted(QBXExport) then fundResource = QBExport + elseif isStarted(QBExport) or isStarted(QBXExport) then + fundResource = QBExport Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund) - elseif isStarted(ESXExport) then fundResource = ESXExport - local Player = ESX.GetPlayerFromId(src) - Player.addMoney(fund, "") + elseif isStarted(ESXExport) then + fundResource = ESXExport + PlayESX.GetPlayerFromId(src).addMoney(fund, "") end - end - if type == "bank" then - if isStarted(QBExport) or isStarted(QBXExport) then fundResource = QBExport + elseif moneyType == "bank" then + if isStarted(QBExport) or isStarted(QBXExport) then + fundResource = QBExport Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund) - elseif isStarted(ESXExport) then fundResource = ESXExport - local Player = ESX.GetPlayerFromId(src) - Player.addMoney(fund, "") + elseif isStarted(ESXExport) then + fundResource = ESXExport + ESX.GetPlayerFromId(src).addMoney(fund, "") end end - if fundResource == "" then print("error - check exports.lua") + + if fundResource == "" then + print("Cannot fund player - check starter.lua") else - debugPrint("^6Bridge^7: ^2Funding ^2Player^7: '^2"..fund.."^7'", type, fundResource) + debugPrint("^6Bridge^7: ^2Funding Player: '^2"..fund.."^7'", moneyType, fundResource) end end - RegisterNetEvent(getScript()..":server:FundPlayer", fundPlayer) +------------------------------------------------------------- +-- Item Consumption & Effects +------------------------------------------------------------- + --- Handles successful consumption of an item. --- ---- This function plays a consumption animation, removes the item from the inventory, ---- updates the player's hunger and thirst based on the item consumed, ---- handles alcohol effects, and checks for random rewards. +--- Plays a consumption animation, removes the item, updates player needs, handles alcohol effects, +--- and checks for random rewards. --- ---- @param itemName string The name of the item consumed. ---- @param type string The type/category of the item (e.g., "alcohol"). +--- @param itemName string The name of the consumed item. +--- @param type string The category of the item (e.g., "alcohol"). +--- @param data table Additional data (e.g., hunger and thirst values). --- --- @usage --- ```lua @@ -283,10 +244,12 @@ RegisterNetEvent(getScript()..":server:FundPlayer", fundPlayer) --- ConsumeSuccess("beer", "alcohol") --- ``` function ConsumeSuccess(itemName, type, data) - local hunger = data and data.hunger or Items[itemName].hunger or nil - local thirst = data and data.thirst or Items[itemName].thirst or nil + local hunger = data and data.hunger or Items[itemName].hunger + local thirst = data and data.thirst or Items[itemName].thirst + ExecuteCommand("e c") removeItem(itemName, 1) + if isStarted(ESXExport) then if hunger then TriggerServerEvent(getScript()..":server:setNeed", "hunger", hunger * 10000) @@ -302,7 +265,9 @@ function ConsumeSuccess(itemName, type, data) TriggerServerEvent(getScript()..":server:setNeed", "thirst", Core.Functions.GetPlayerData().metadata["thirst"] + thirst) end end - if type == "alcohol" then alcoholCount += 1 + + if type == "alcohol" then + alcoholCount = (alcoholCount or 0) + 1 if alcoholCount > 1 and alcoholCount < 4 then TriggerEvent("evidence:client:SetStatus", "alcohol", 200) elseif alcoholCount >= 4 then @@ -310,19 +275,20 @@ function ConsumeSuccess(itemName, type, data) AlienEffect() end end - getRandomReward(itemName) -- check if a reward item should be given + + getRandomReward(itemName) end ---- Checks if a player has a specific job and grade. +------------------------------------------------------------- +-- Player Job & Information Utilities +------------------------------------------------------------- + +--- Checks if a player has a specific job or gang (and optionally meets a minimum grade). --- ---- This function verifies whether the player has the specified job and, if a grade is provided, ---- whether the player's grade meets the required level. It supports multiple inventory systems. ---- ---- @param job string The name of the job or gang to check. ---- @param source number|nil Optional. The server ID of the player to check. If `nil`, checks the current player. ---- @param grade number|nil Optional. The minimum grade level required. ---- ---- @return boolean, boolean Returns `true` and `duty status` if the player has the job (and grade if specified), otherwise `false`. +--- @param job string The job or gang name to check. +--- @param source number|nil Optional player source; if nil, checks current player. +--- @param grade number|nil Optional minimum grade level. +--- @return boolean, boolean boolean Returns true and duty status if the check passes; false otherwise. --- --- @usage --- ```lua @@ -338,7 +304,8 @@ end --- -- Allow gang leader actions --- end --- ``` -function hasJob(job, source, grade) local hasJob, duty = false, true +function hasJob(job, source, grade) + local hasJobFlag, duty = false, true if source then local src = tonumber(source) if not src then print(tostring(source).." is not a valid player source") end @@ -348,113 +315,116 @@ function hasJob(job, source, grade) local hasJob, duty = false, true info = ESX.GetPlayerData(src).job Wait(100) end - if info.name == job then hasJob = true end + if info.name == job then hasJobFlag = true end elseif isStarted(OXCoreExport) then local chunk = assert(load(LoadResourceFile('ox_core', ('imports/%s.lua'):format('server')), ('@@ox_core/%s'):format(file))) chunk() - local player = Ox.GetPlayer(tonumber(src)) + local player = Ox.GetPlayer(src) for k, v in pairs(player.getGroups()) do - if k == job then hasJob = true end + if k == job then hasJobFlag = true end end elseif isStarted(QBXExport) then local jobinfo = exports[QBXExport]:GetPlayer(src).PlayerData.job - if jobinfo.name == job then hasJob = true + if jobinfo.name == job then + hasJobFlag = true duty = exports[QBXExport]:GetPlayer(src).PlayerData.job.onduty - if grade and not (grade <= jobinfo.grade.level) then hasJob = false end + if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end end local ganginfo = exports[QBXExport]:GetPlayer(src).PlayerData.gang - if ganginfo.name == job then hasJob = true - if grade and not (grade <= ganginfo.grade.level) then hasJob = false end + if ganginfo.name == job then + hasJobFlag = true + if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end end elseif isStarted(QBExport) and not isStarted(QBXExport) then - if Core.Functions.GetPlayer then -- support older qb-core functions + if Core.Functions.GetPlayer then local player = Core.Functions.GetPlayer(src) if not player then print("Player not found for src: "..src) end local jobinfo = player.PlayerData.job - if jobinfo.name == job then hasJob = true - duty = Core.Functions.GetPlayer(src).PlayerData.job.onduty - if grade and not (grade <= jobinfo.grade.level) then hasJob = false end + if jobinfo.name == job then + hasJobFlag = true + duty = player.PlayerData.job.onduty + if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end end - local ganginfo = Core.Functions.GetPlayer(src).PlayerData.gang - if ganginfo.name == job then hasJob = true - if grade and not (grade <= ganginfo.grade.level) then hasJob = false end + local ganginfo = player.PlayerData.gang + if ganginfo.name == job then + hasJobFlag = true + if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end end - else -- support newer qb-core exports + else local jobinfo = exports[QBExport]:GetPlayer(src).PlayerData.job - if jobinfo.name == job then hasJob = true + if jobinfo.name == job then + hasJobFlag = true duty = exports[QBExport]:GetPlayer(src).PlayerData.job.onduty - if grade and not (grade <= jobinfo.grade.level) then hasJob = false end + if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end end local ganginfo = exports[QBExport]:GetPlayer(src).PlayerData.gang - if ganginfo.name == job then hasJob = true - if grade and not (grade <= ganginfo.grade.level) then hasJob = false end + if ganginfo.name == job then + hasJobFlag = true + if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end end end else - print("^4ERROR^7: ^2No Core detected for hasJob ^7- ^2Check ^3exports^1.^2lua^7") + print("^4ERROR^7: ^2No Core detected for hasJob ^7- ^2Check ^3starter^1.^2lua^7") end else - if isStarted(ESXExport) then - while not ESX do Wait(10) end + -- Client-side check. + if isStarted(ESXExport) and ESX ~= nil then local info = ESX.GetPlayerData().job while not info do info = ESX.GetPlayerData().job Wait(100) end - if info.name == job then hasJob = true end + if info.name == job then hasJobFlag = true end elseif isStarted(OXCoreExport) then for k, v in pairs(exports[OXCoreExport]:GetPlayerData().groups) do - if k == job then hasJob = true end break + if k == job then hasJobFlag = true break end end elseif isStarted(QBXExport) then - local jobinfo = QBX.PlayerData.job - if jobinfo.name == job then hasJob = true - duty = QBX.PlayerData.job.onduty - if grade and not (grade <= jobinfo.grade.level) then hasJob = false end + local info = exports[QBXExport]:GetPlayerData() + if info.job.name == job then + hasJobFlag = true + duty = info.job.onduty + if grade and not (grade <= info.job.grade.level) then hasJobFlag = false end end - local ganginfo = QBX.PlayerData.gang - if ganginfo.name == job then hasJob = true - if grade and not (grade <= ganginfo.grade.level) then hasJob = false end + if info.gang.name == job then + hasJobFlag = true + if grade and not (grade <= info.gang.grade.level) then hasJobFlag = false end end elseif isStarted(QBExport) and not isStarted(QBXExport) then local info = nil - Core.Functions.GetPlayerData(function(PlayerData) - info = PlayerData - end) + Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end) local jobinfo = info.job - if jobinfo.name == job then hasJob = true + if jobinfo.name == job then + hasJobFlag = true duty = jobinfo.onduty - if grade and not (grade <= jobinfo.grade.level) then hasJob = false end + if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end end local ganginfo = info.gang if ganginfo.name == job then - hasJob = true - if grade and not (grade <= ganginfo.grade.level) then hasJob = false end + hasJobFlag = true + if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end end - else - print("^4ERROR^7: ^2No Core detected for hasJob() ^7- ^2Check ^3exports^1.^2lua^7") + print("^4ERROR^7: ^2No Core detected for hasJob() ^7- ^2Check ^3starter^1.^2lua^7") end end - return hasJob, duty + return hasJobFlag, duty end ---- Retrieves basic information about a player. +--- Retrieves basic player information (name, cash, bank, job, etc.) based on the active inventory system. --- ---- This function gathers the player's name, cash balance, and bank balance ---- based on the active inventory system. It can be called server-side or client-side. +--- Can be called server-side (passing a player source) or client-side (for current player). --- ----@param source number|nil Optional. The server ID of the player. If `nil`, retrieves info for the current player. +--- @param source number|nil Optional player server ID. +--- @return table A table containing player details. --- ----@return table table A table containing the player's `name`, `cash`, and `bank` balances. ---- ----@usage +--- @usage --- ```lua --- -- Get information for a specific player --- local playerInfo = getPlayer(playerId) @@ -467,7 +437,8 @@ end function getPlayer(source) local Player = {} debugPrint("^6Bridge^7: ^2Getting ^3Player^2 info^7") - if source then -- If called from server + + if source then local src = tonumber(source) if isStarted(ESXExport) then local info = ESX.GetPlayerFromId(src) @@ -494,13 +465,12 @@ function getPlayer(source) local import = LoadResourceFile('ox_core', file) local chunk = assert(load(import, ('@@ox_core/%s'):format(file))) chunk() - local player = Ox.GetPlayer(tonumber(src)) + local player = Ox.GetPlayer(src) Player = { name = ('%s %s'):format(player.firstName, player.lastName), cash = exports[OXInv]:Search(src, 'count', "money"), bank = 0, } - elseif isStarted(QBXExport) then local info = exports[QBXExport]:GetPlayer(src) Player = { @@ -518,9 +488,8 @@ function getPlayer(source) account = info.PlayerData.charinfo.account, citizenId = info.PlayerData.citizenid, } - elseif isStarted(QBExport) and not isStarted(QBXExport) then - if Core.Functions.GetPlayer ~= nil then -- support older qb-core functions + if Core.Functions.GetPlayer then local info = Core.Functions.GetPlayer(src).PlayerData Player = { firstname = info.charinfo.firstname, @@ -537,9 +506,8 @@ function getPlayer(source) account = info.charinfo.account, citizenId = info.citizenid, } - else - local info = exports[QBExport]:GetPlayer(src).PlayerData -- this was added to new core then removed? + local info = exports[QBExport]:GetPlayer(src).PlayerData Player = { firstname = info.charinfo.firstname, lastname = info.charinfo.lastname, @@ -556,16 +524,15 @@ function getPlayer(source) citizenId = info.citizenid, } end - else - print("^4ERROR^7: ^2No Core detected for getPlayer() ^7- ^2Check ^3exports^1.^2lua^7") + print("^4ERROR^7: ^2No Core detected for getPlayer() - Check exports.lua") end else + -- Client-side: Get current player info. 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 + for k, v in pairs(info.accounts) do if v.name == "money" then cash = v.money end if v.name == "bank" then bank = v.money end end @@ -629,12 +596,22 @@ function getPlayer(source) citizenId = info.citizenid, } else - print("^4ERROR^7: ^2No Core detected for hasJob ^7- ^2Check ^3exports^1.^2lua^7") + print("^4ERROR^7: ^2No Core detected for hasJob ^7- ^2Check ^3starter^1.^2lua^7") end end return Player end +--- Retrieves all active players within a given radius from the specified coordinates. +--- +--- @param coords vector3 The reference coordinates. +--- @param radius number The radius within which to find players. +--- @return table table An array of player IDs. +--- +--- @usage +--- ```lua +--- local nearbyPlayers = GetPlayersFromCoords(vector3(100, 200, 30), 20) +--- ``` function GetPlayersFromCoords(coords, radius) local players = {} for _, playerId in ipairs(GetActivePlayers()) do @@ -642,7 +619,7 @@ function GetPlayersFromCoords(coords, radius) if ped and DoesEntityExist(ped) then local playerCoords = GetEntityCoords(ped) if #(coords - playerCoords) <= radius then - players[#players+1] = playerId + players[#players + 1] = playerId end end end diff --git a/shared/polyZone.lua b/shared/polyZone.lua index bcc07e6..fbe5e42 100644 --- a/shared/polyZone.lua +++ b/shared/polyZone.lua @@ -1,45 +1,61 @@ --- 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, }) ---- +--[[ + PolyZone Management Module + ---------------------------- + This module automatically detects the available polyzone library (ox_lib or PolyZone) + and creates polygonal and circular zones accordingly. It also provides a function to remove + previously created zones. + + Functions: + • createPoly(data) - Creates a polygonal zone. + • createCirclePoly(data) - Creates a circular zone. + • removePolyZone(Location) - Removes a created zone. +]] + +------------------------------------------------------------- +-- Polygonal Zone Creation +------------------------------------------------------------- + --- 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. +--- Automatically checks which polyzone script is active. When using ox_lib, it converts the provided +--- 2D points to 3D (setting a constant z value) and sets a thickness value. For PolyZone, it creates the zone +--- and attaches onEnter and onExit callbacks. --- ----@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. +--- @param data table Zone configuration table with the following keys: +--- - name (string): The zone's identifier. +--- - debug (boolean): Whether debug mode is enabled. +--- - points (table): A list of vec2 points defining the polygon. +--- - onEnter (function): Callback when a player enters the zone. +--- - onExit (function): Callback when a player exits the zone. --- ----@return table|nil table Returns the created zone object or `nil` if creation failed. +--- @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, ---- }) ---- ``` +---```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 + if isStarted(OXLibExport) then debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name) + -- Convert 2D points to 3D with a fixed Z coordinate (e.g., 12.0) for i = 1, #data.points do data.points[i] = vec3(data.points[i].x, data.points[i].y, 12.0) end - data.thickness = 1000 + data.thickness = 1000 -- Set a default thickness value 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") @@ -47,21 +63,25 @@ function createPoly(data) return Location end +------------------------------------------------------------- +-- Circular Zone Creation +------------------------------------------------------------- + --- 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. +--- When using ox_lib, it creates a sphere zone. For PolyZone, it creates a CircleZone and attaches +--- onEnter and onExit callbacks. --- ----@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. +--- @param data table Zone configuration with the following keys: +--- - name (string): The zone's identifier. +--- - coords (vector3): The center of the circle. +--- - radius (number): The radius of the circle. +--- - onEnter (function): Callback when a player enters the zone. +--- - onExit (function): Callback when a player exits the zone. --- ----@return table|nil table Returns the created circular zone object or `nil` if creation failed. +--- @return table|nil table Returns the created circular zone object or nil if creation failed. --- ----@usage +--- @usage --- ```lua --- createCirclePoly({ --- name = 'circleZone', @@ -73,7 +93,7 @@ end --- ``` function createCirclePoly(data) local Location = nil - if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone + if isStarted(OXLibExport) then debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name) Location = lib.zones.sphere(data) elseif isStarted("PolyZone") then @@ -87,26 +107,30 @@ function createCirclePoly(data) end end) else - print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7") + print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3starter^1.^2lua^7") end debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius) return Location end +------------------------------------------------------------- +-- PolyZone Removal Function +------------------------------------------------------------- + --- Removes a previously created polyzone. --- ---- This function detects the active polyzone library (`ox_lib` or `PolyZone`) and removes the specified zone accordingly. +--- Detects the active polyzone library and calls the appropriate removal method. --- --- @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 + if isStarted(OXLibExport) then debugPrint("^6Bridge^7: ^2Removing ^2poly with ^7"..OXLibExport) Location:remove() elseif isStarted("PolyZone") then diff --git a/shared/scaleforms.lua b/shared/scaleforms.lua deleted file mode 100644 index 2fd04bf..0000000 --- a/shared/scaleforms.lua +++ /dev/null @@ -1,61 +0,0 @@ -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 ae581cb..7e2827b 100644 --- a/shared/scaleforms/bigMessageInstance.lua +++ b/shared/scaleforms/bigMessageInstance.lua @@ -1,6 +1,17 @@ -BigMessage = {} +--[[ + BigMessage Module + ----------------- + This module provides a flexible way to display large, attention-grabbing messages + on screen using a Scaleform movie ("MP_BIG_MESSAGE_FREEMODE"). It supports multiple + message types (mission passed, colored shard, old-style, simple shard, rank-up, weapon purchased, + and large multiplayer messages), including customizable transitions and durations. +]] + +local BigMessage = {} BigMessage.__index = BigMessage +--- Creates a new BigMessage instance. +--- @return table table A new BigMessage object. function BigMessage:new() local self = setmetatable({}, BigMessage) self.scaleform = nil @@ -15,6 +26,7 @@ function BigMessage:new() return self end +--- Loads the Scaleform movie if it has not been loaded yet. function BigMessage:Load() if self.scaleform then return end self.scaleform = RequestScaleformMovie("MP_BIG_MESSAGE_FREEMODE") @@ -23,7 +35,8 @@ function BigMessage:Load() end end --- Dispose of the scaleform +--- Disposes of the Scaleform movie. +--- If manualDispose is true, executes a transition before disposing. function BigMessage:Dispose() if not self.scaleform then return end @@ -34,8 +47,8 @@ function BigMessage:Dispose() ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion) EndScaleformMovieMethod() + -- Wait a fraction of the transition duration (in milliseconds) Wait((self.transitionDuration * 0.5) * 1000) - self.manualDispose = false end @@ -46,8 +59,10 @@ function BigMessage:Dispose() self.isDisplaying = false end +--- Updates the display by drawing the Scaleform movie fullscreen. function BigMessage:Update() if not self.scaleform then return end + DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0) if self.manualDispose then return end @@ -60,6 +75,7 @@ function BigMessage:Update() ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion) EndScaleformMovieMethod() self.transitionExecuted = true + -- Extend duration slightly for smooth transition self.duration = self.duration + ((self.transitionDuration * 0.5) * 1000) else self:Dispose() @@ -67,14 +83,20 @@ function BigMessage:Update() end end +--- Sets the transition properties for disposing the message. +--- @param transition string The transition function name (default: "TRANSITION_OUT"). +--- @param duration number The duration for the transition (default: 0.4). +--- @param preventAutoExpansion boolean Whether to prevent auto-expansion (default: true). function BigMessage:SetTransition(transition, duration, preventAutoExpansion) self.transition = transition or "TRANSITION_OUT" self.transitionDuration = duration or 0.4 self.transitionPreventAutoExpansion = preventAutoExpansion or true end +--- Starts a thread to continuously update the HUD until the message is done. function BigMessage:StartUpdate() if self.isDisplaying then return end + self.isDisplaying = true CreateThread(function() while self.isDisplaying do @@ -85,10 +107,13 @@ function BigMessage:StartUpdate() 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. +--- @param msg string The message to display. +--- @param duration number|nil The duration (in milliseconds) to display the message (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose the Scaleform after display (default: false). +--- @usage +--- ```lua +--- BigMessage:ShowMissionPassedMessage("MISSION PASSED", 5000) +--- ``` function BigMessage:ShowMissionPassedMessage(msg, duration, manualDispose) duration = duration or 5000 self:Load() @@ -109,13 +134,12 @@ function BigMessage:ShowMissionPassedMessage(msg, duration, manualDispose) end --- Displays a colored shard message. ---- ---- @param msg string The main message to display. +--- @param msg string The main message. --- @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. +--- @param textColor number The text color index. +--- @param bgColor number The background color index. +--- @param duration number|nil Duration in milliseconds (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose the Scaleform (default: false). function BigMessage:ShowColoredShard(msg, desc, textColor, bgColor, duration, manualDispose) duration = duration or 5000 self:Load() @@ -134,12 +158,9 @@ function BigMessage:ShowColoredShard(msg, desc, textColor, bgColor, duration, ma 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 +--- @param msg string The message. +--- @param duration number|nil Duration in milliseconds (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose (default: false). function BigMessage:ShowOldMessage(msg, duration, manualDispose) duration = duration or 5000 self:Load() @@ -155,13 +176,10 @@ function BigMessage:ShowOldMessage(msg, duration, manualDispose) end --- Displays a simple shard message. ---- ---- @param msg string The main message to display. +--- @param msg string The main message. --- @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 +--- @param duration number|nil Duration in milliseconds (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose (default: false). function BigMessage:ShowSimpleShard(msg, subtitle, duration, manualDispose) duration = duration or 5000 self:Load() @@ -178,12 +196,11 @@ function BigMessage:ShowSimpleShard(msg, subtitle, duration, manualDispose) end --- Displays a rank-up message. ---- ---- @param msg string The main message to display. +--- @param msg string The main message. --- @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. +--- @param duration number|nil Duration in milliseconds (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose (default: false). function BigMessage:ShowRankupMessage(msg, subtitle, rank, duration, manualDispose) duration = duration or 5000 self:Load() @@ -203,12 +220,11 @@ function BigMessage:ShowRankupMessage(msg, subtitle, rank, duration, manualDispo end --- Displays a weapon purchased message. ---- ---- @param bigMessage string The main message to display. +--- @param bigMessage string The main message. --- @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. +--- @param weaponHash number The weapon hash. +--- @param duration number|nil Duration in milliseconds (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose (default: false). function BigMessage:ShowWeaponPurchasedMessage(bigMessage, weaponName, weaponHash, duration, manualDispose) duration = duration or 5000 self:Load() @@ -228,10 +244,9 @@ function BigMessage:ShowWeaponPurchasedMessage(bigMessage, weaponName, weaponHas 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. +--- @param msg string The main message. +--- @param duration number|nil Duration in milliseconds (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose (default: false). function BigMessage:ShowMpMessageLarge(msg, duration, manualDispose) duration = duration or 5000 self:Load() @@ -254,11 +269,10 @@ function BigMessage:ShowMpMessageLarge(msg, duration, manualDispose) end --- Displays a "Wasted" multiplayer message. ---- ---- @param msg string The main message to display. +--- @param msg string The main message. --- @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. +--- @param duration number|nil Duration in milliseconds (default: 5000). +--- @param manualDispose boolean|nil Whether to manually dispose (default: false). function BigMessage:ShowMpWastedMessage(msg, subtitle, duration, manualDispose) duration = duration or 5000 self:Load() @@ -274,4 +288,19 @@ function BigMessage:ShowMpWastedMessage(msg, subtitle, duration, manualDispose) self:StartUpdate() end +--- Starts the update loop for displaying the message. +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 + +-- Create an instance of BigMessage and return it. +BigMessage = BigMessage:new() + return BigMessage \ No newline at end of file diff --git a/shared/scaleforms/countDownHandler.lua b/shared/scaleforms/countDownHandler.lua index 3cb1fc9..6c15049 100644 --- a/shared/scaleforms/countDownHandler.lua +++ b/shared/scaleforms/countDownHandler.lua @@ -1,6 +1,18 @@ +--[[ + CountdownHandler Module + ------------------------- + This module provides a countdown HUD using a Scaleform movie ("COUNTDOWN"). + It handles loading, updating, and disposing of the scaleform, as well as + playing sounds and displaying messages for each countdown tick. + + TriggerNetEvent(getScript()..":startCountdown", 5, 25) +]] + CountdownHandler = {} CountdownHandler.__index = CountdownHandler +--- Creates a new CountdownHandler instance. +--- @return table table A new CountdownHandler object. function CountdownHandler:new() local self = setmetatable({}, CountdownHandler) self.scaleform = nil @@ -9,14 +21,18 @@ function CountdownHandler:new() return self end +--- Loads the "COUNTDOWN" scaleform movie. function CountdownHandler:Load() - if self.scaleform then return end + if self.scaleform then + return + end self.scaleform = RequestScaleformMovie("COUNTDOWN") while not HasScaleformMovieLoaded(self.scaleform) do Wait(0) end end +--- Disposes of the currently loaded scaleform movie. function CountdownHandler:Dispose() if self.scaleform then SetScaleformMovieAsNoLongerNeeded(self.scaleform) @@ -24,15 +40,19 @@ function CountdownHandler:Dispose() end end +--- Updates the HUD by drawing the scaleform movie fullscreen. function CountdownHandler:Update() if self.scaleform then DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0) end end +--- Displays a message on the countdown HUD. +--- @param message string The message to display. function CountdownHandler:ShowMessage(message) local r, g, b, a = self.colour.r, self.colour.g, self.colour.b, self.colour.a + -- Set the message in the scaleform. BeginScaleformMovieMethod(self.scaleform, "SET_MESSAGE") ScaleformMovieMethodAddParamPlayerNameString(message) ScaleformMovieMethodAddParamInt(r) @@ -41,6 +61,7 @@ function CountdownHandler:ShowMessage(message) ScaleformMovieMethodAddParamBool(true) EndScaleformMovieMethod() + -- Trigger a fade effect (optional). BeginScaleformMovieMethod(self.scaleform, "FADE_MP") ScaleformMovieMethodAddParamPlayerNameString(message) ScaleformMovieMethodAddParamInt(r) @@ -49,17 +70,14 @@ function CountdownHandler:ShowMessage(message) 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. ---- +--- Starts the countdown HUD. +--- @param number number|nil The starting number for the countdown (default: 3). +--- @param hudColour number|nil The HUD colour index (default: 18). +--- @return boolean 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 +--- -- When run in an if statement, the script will wait until its finished to continue --- print("Countdown Complete") --- end --- ``` @@ -68,6 +86,7 @@ function CountdownHandler:Start(number, hudColour) number = number or 3 hudColour = hudColour or 18 + -- Get HUD colour using framework function; alternatives could be added here. local r, g, b, a = GetHudColour(hudColour) self.colour = { r = r, g = g, b = b, a = a } @@ -81,18 +100,17 @@ function CountdownHandler:Start(number, hudColour) end end) - -- Begin the countdown + -- Countdown logic 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") + playSound("Go") self:ShowMessage("GO") finished = true @@ -101,14 +119,15 @@ function CountdownHandler:Start(number, hudColour) self:Dispose() finished = true end) + while not finished do Wait(10) end return true end --- Create an instance of CountdownHandler +-- Create a singleton instance of CountdownHandler. CountdownHandler = CountdownHandler:new() --- Optional: Register an event to start the countdown +-- Register an event to start the countdown. RegisterNetEvent(getScript()..":startCountdown", function(number, hudColour) CountdownHandler:Start(number, hudColour) end) diff --git a/shared/scaleforms/debugScaleform.lua b/shared/scaleforms/debugScaleform.lua index b6cc394..52f69f3 100644 --- a/shared/scaleforms/debugScaleform.lua +++ b/shared/scaleforms/debugScaleform.lua @@ -1,41 +1,44 @@ +------------------------------------------------------------- +-- Debug Text Display Functionality +------------------------------------------------------------- ---- Displays debug information on the player's screen. +--- Draws debug text on the screen if debugMode is enabled. --- ---- 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. +--- Calculates a background rectangle based on the number of text lines and renders each line on-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)`. +--- @param textTable table An array of strings to display. +--- @param loc vector2 (Optional) Top-left coordinate for the text box (default: vec2(0.05, 0.65)). --- --- @usage --- ```lua ---- debugScaleForm({ ---- "Player Position: X=123.45 Y=678.90 Z=12.34", ---- "Current Action: Running", ---- }) +---CreateThread(function() +--- while true do +--- debugScaleForm({ +--- "Line 1: Debug info", +--- "Line 2: More info" +--- }) +--- Wait(0) +--- end +---end) --- ``` function debugScaleForm(textTable, loc) if debugMode then - -- Define the display position (top left corner) - local loc = loc or vec2(0.05, 0.65) + 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 + local lineHeight = 0.025 -- Height per line. + local totalHeight = #textTable * lineHeight + local boxPadding = 0.01 -- Padding around the text. + local size = vec2(0.18, totalHeight + boxPadding * 2) + -- Draw background rectangle. DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255) + -- Render each line of text. for i = 1, #textTable do - local textLine = textTable[i] - SetTextScale(0.30, 0.30) - BeginTextCommandDisplayText("STRING") - AddTextComponentSubstringKeyboardDisplay(textLine) - + AddTextComponentSubstringKeyboardDisplay(textTable[i]) EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01) end end -end +end \ No newline at end of file diff --git a/shared/scaleforms/instructionalButtons.lua b/shared/scaleforms/instructionalButtons.lua index e1d7f35..81819db 100644 --- a/shared/scaleforms/instructionalButtons.lua +++ b/shared/scaleforms/instructionalButtons.lua @@ -1,30 +1,45 @@ ---- Creates instructional buttons using the detected polyzone library (ox_lib or PolyZone). +------------------------------------------------------------- +-- Instructional Buttons Functionality +------------------------------------------------------------- + +--- Loads and draws instructional buttons on-screen using a scaleform movie. --- ---- 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. +--- Requests the "instructional_buttons" scaleform, clears previous data, sets clear space, +--- creates data slots for each button option provided in `info`, and then draws the scaleform fullscreen. --- ----@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. +--- @param info table An array of tables, where each table represents a button option: +--- - keys (table): An array of key codes (e.g., {38, 29}) to display. +--- - text (string): The label for the button. --- ----@usage +--- @usage --- ```lua ---- makeInstructionalButtons({ ---- { keys = { 38 }, text = "Interact" }, ---- { keys = { 47 }, text = "Pick Up" }, ---- }) +---CreateThread(function() +--- while true do +--- makeInstructionalButtons({ +--- { keys = {38, 29}, text = "Open Menu" }, +--- { keys = {45}, text = "Close Menu" } +--- }) +--- Wait(0) +--- end +---end) --- ``` function makeInstructionalButtons(info) local build = RequestScaleformMovie("instructional_buttons") while not HasScaleformMovieLoaded(build) do Wait(0) end + -- Draw the scaleform fullscreen (initial draw). DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0) + + -- Clear previous instructions. BeginScaleformMovieMethod(build, "CLEAR_ALL") EndScaleformMovieMethod() + + -- Set clear spacing between buttons. BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE") ScaleformMovieMethodAddParamInt(200) EndScaleformMovieMethod() + -- Add each button option to the scaleform. for i = 1, #info do BeginScaleformMovieMethod(build, "SET_DATA_SLOT") ScaleformMovieMethodAddParamInt(i - 1) @@ -37,8 +52,11 @@ function makeInstructionalButtons(info) EndScaleformMovieMethod() end + -- Draw the instructional buttons. BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS") EndScaleformMovieMethod() + + -- Set a translucent black background. BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR") ScaleformMovieMethodAddParamInt(0) ScaleformMovieMethodAddParamInt(0) @@ -46,5 +64,6 @@ function makeInstructionalButtons(info) ScaleformMovieMethodAddParamInt(80) EndScaleformMovieMethod() + -- Final full-screen draw with full opacity. DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0) end \ No newline at end of file diff --git a/shared/scaleforms/scaleform_basic.lua b/shared/scaleforms/scaleform_basic.lua new file mode 100644 index 0000000..15100c2 --- /dev/null +++ b/shared/scaleforms/scaleform_basic.lua @@ -0,0 +1,242 @@ +--[[ + Instructional Buttons & Debug Text Module + ------------------------------------------- + This module provides functions for: + • Displaying instructional buttons on-screen via a scaleform movie. + • Drawing debug text with a background rectangle when debugMode is enabled. + • Rendering 3D text in the world. + • Displaying help messages and spinners. +]] + +------------------------------------------------------------- +-- Instructional Buttons Functionality +------------------------------------------------------------- + +--- Loads and draws instructional buttons on-screen using a scaleform movie. +--- +--- Requests the "instructional_buttons" scaleform, clears previous data, sets clear space, +--- creates data slots for each button option provided in `info`, and then draws the scaleform fullscreen. +--- +--- @param info table An array of tables, where each table represents a button option: +--- - keys (table): An array of key codes (e.g., {38, 29}) to display. +--- - text (string): The label for the button. +--- +--- @usage +--- ```lua +---CreateThread(function() +--- while true do +--- makeInstructionalButtons({ +--- { keys = {38, 29}, text = "Open Menu" }, +--- { keys = {45}, text = "Close Menu" } +--- }) +--- Wait(0) +--- end +---end) +--- ``` +function makeInstructionalButtons(info) + local build = RequestScaleformMovie("instructional_buttons") + while not HasScaleformMovieLoaded(build) do Wait(0) end + + -- Draw the scaleform fullscreen (initial draw). + DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0) + + -- Clear previous instructions. + BeginScaleformMovieMethod(build, "CLEAR_ALL") + EndScaleformMovieMethod() + + -- Set clear spacing between buttons. + BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE") + ScaleformMovieMethodAddParamInt(200) + EndScaleformMovieMethod() + + -- Add each button option to the scaleform. + 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 + + -- Draw the instructional buttons. + BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS") + EndScaleformMovieMethod() + + -- Set a translucent black background. + BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR") + ScaleformMovieMethodAddParamInt(0) + ScaleformMovieMethodAddParamInt(0) + ScaleformMovieMethodAddParamInt(0) + ScaleformMovieMethodAddParamInt(80) + EndScaleformMovieMethod() + + -- Final full-screen draw with full opacity. + DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0) +end + +------------------------------------------------------------- +-- Debug Text Display Functionality +------------------------------------------------------------- + +--- Draws debug text on the screen if debugMode is enabled. +--- +--- Calculates a background rectangle based on the number of text lines and renders each line on-screen. +--- +--- @param textTable table An array of strings to display. +--- @param loc vector2 (Optional) Top-left coordinate for the text box (default: vec2(0.05, 0.65)). +--- +--- @usage +--- ```lua +---CreateThread(function() +--- while true do +--- debugScaleForm( +--- { +--- "Line 1: Debug info", +--- "Line 2: More info" +--- } +--- ) +--- Wait(0) +--- end +---end) +--- ``` +function debugScaleForm(textTable, loc) + if debugMode then + loc = loc or vec2(0.05, 0.65) + + local lineHeight = 0.025 -- Height per line. + local totalHeight = #textTable * lineHeight + local boxPadding = 0.01 -- Padding around the text. + local size = vec2(0.18, totalHeight + boxPadding * 2) + + -- Draw background rectangle. + DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255) + + -- Render each line of text. + for i = 1, #textTable do + SetTextScale(0.30, 0.30) + BeginTextCommandDisplayText("STRING") + AddTextComponentSubstringKeyboardDisplay(textTable[i]) + EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01) + end + end +end + +------------------------------------------------------------- +-- 3D Text Rendering +------------------------------------------------------------- + +--- Draws 3D text at specified world coordinates. +--- +--- Configures text properties, draws the text, and displays a background rectangle behind it. +--- +--- @param coord table A vector3 with x, y, and z coordinates. +--- @param text string The text to display. +--- @param highlight boolean (Optional) If true, highlights 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 = 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 + +--- Calculates the number of lines and the maximum line length from the given text. +--- +--- @param text string The text to analyze. +--- @return number, number The line count and maximum line length. +--- +--- @usage +--- ```lua +--- local count, maxLen = GetLineCountAndMaxLength("Hello World") +--- ``` +function GetLineCountAndMaxLength(text) + local lineCount, maxLength = 0, 0 + for line in text:gmatch("[^\n]+") do + lineCount += 1 + local lineLength = string.len(line) + if lineLength > maxLength then + maxLength = lineLength + end + end + if lineCount == 0 then lineCount = 1 end + return lineCount, maxLength +end + +------------------------------------------------------------- +-- Additional UI Helpers +------------------------------------------------------------- + +--- Displays a help message on the screen. +--- +--- @param text string The message to display. +--- +--- @usage +--- ```lua +--- 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 should only be called client-side. +--- +--- @usage +--- ```lua +--- stopSpinner() +--- ``` +function stopSpinner() + if not isServer() then + BusyspinnerOff() + end +end \ No newline at end of file diff --git a/shared/scaleforms/timerBars.lua b/shared/scaleforms/timerBars.lua index 1124e79..59f9fea 100644 --- a/shared/scaleforms/timerBars.lua +++ b/shared/scaleforms/timerBars.lua @@ -1,3 +1,21 @@ +--- Creates and displays a timer HUD on the screen. +--- Draws a title (if provided) and a series of timer bars from the supplied data. +--- +--- @param title string|nil Optional title to display at the top of the HUD. +--- @param data table A table of timer bar entries. Each entry should include: +--- - stat (string): The statistic name. +--- - value (string): The value to display. +--- - multi (number|nil): Optional, indicates multiple checkpoints (e.g., progress levels). +--- @param alpha number|nil Optional alpha value (transparency) for the HUD; defaults to 255. +--- +--- @usage +--- ```lua +--- createTimerHud("Timer", { +--- { stat = "Health", value = "85%" }, +--- { stat = "Armor", value = "50%", multi = 2 }, +--- { stat = "Stamina", value = "100%" }, +--- }, 255) +--- ``` function createTimerHud(title, data, alpha) loadTextureDict("timerbars") diff --git a/shared/banking.lua b/shared/societybank.lua similarity index 53% rename from shared/banking.lua rename to shared/societybank.lua index ab348bd..75395c3 100644 --- a/shared/banking.lua +++ b/shared/societybank.lua @@ -1,11 +1,79 @@ +--[[ + Society Banking Module + ------------------------ + This module provides functions to interact with society bank accounts across + different banking systems. Supported systems include: + • qb-banking + • esx_society *testing* + • Renewed-Banking + • fd_banking + • okokBanking +]] -function chargeSociety(society, amount) - local bankScript, newAmount = "", 0 - if isStarted("Renewed-Banking") then +--- Retrieves the current balance of a society's bank account. +--- @param society string The identifier of the society. +--- @return number number The current account balance. +--- @usage +--- ```lua +--- local balance = getSocietyAccount("police") +--- print("Police account balance: $"..balance) +--- ``` +function getSocietyAccount(society) + local bankScript, amount = "", 0 + if isStarted("qb-banking") then + bankScript = "qb-banking" + if not exports["qb-banking"]:GetAccount(society) then + if Jobs[society] then + print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") + exports["qb-banking"]:CreateJobAccount(society, 0) + Wait(150) + elseif Gangs[society] then + print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") + exports["qb-banking"]:CreateGangAccount(society, 0) + Wait(150) + end + end + amount = exports["qb-banking"]:GetAccountBalance(society) + + elseif isStarted("esx_society") then + bankScript = "esx_society" + -- Since esx_society does not have a native client export for retrieving money, + -- we use a server callback to get the final amount. + amount = triggerCallback(getScript() .. ":getESXSocietyAccount", society) or 0 + + elseif isStarted("Renewed-Banking") then bankScript = "Renewed-Banking" - exports['Renewed-Banking']:removeAccountMoney(society, amount) + amount = exports["Renewed-Banking"]:getAccountMoney(society) - elseif isStarted("qb-banking") then + elseif isStarted("fd_banking") then + bankScript = "fd_banking" + amount = exports["fd_banking"]:GetAccount(society) + + elseif isStarted("okokBanking") then + bankScript = "okokBanking" + amount = exports['okokBanking']:GetAccount(society) + end + + if bankScript == "" then + print("^1Error^7: ^3GetSocietyAccount^7: ^2No supported banking script found") + else + debugPrint("^6Bridge^7: ^3"..bankScript.."^7: ^2Retrieved account ^7'^6"..society.."^7' ($"..amount..")") + end + + return amount +end + +--- Deducts funds from a society's bank account. +--- @param society string The identifier of the society. +--- @param amount number The amount of money to remove. +--- @usage +--- ```lua +--- chargeSociety("police", 1000) +--- ``` +function chargeSociety(society, amount) + local bankScript, newAmount = "", 0 + + if isStarted("qb-banking") then bankScript = "qb-banking" if not exports["qb-banking"]:GetAccount(society) then if Jobs[society] then @@ -17,47 +85,73 @@ function chargeSociety(society, amount) end end exports["qb-banking"]:RemoveMoney(society, amount) + elseif isStarted("esx_society") then + bankScript = "esx_society" + TriggerEvent("esx_society:withdrawMoney", society, amount) + + elseif isStarted("Renewed-Banking") then + bankScript = "Renewed-Banking" + exports['Renewed-Banking']:removeAccountMoney(society, amount) elseif isStarted("fd_banking") then bankScript = "fd_banking" exports["fd_banking"]:RemoveMoney(society, amount) + elseif isStarted("okokBanking") then bankScript = "okokBanking" exports['okokBanking']:RemoveMoney(society, amount) end + if bankScript == "" then print("^1Error^7: ^3ChargeSociety^7: ^2No supported banking script found") else newAmount = getSocietyAccount(society) - debugPrint("^6Bridge^7: ^3"..bankScript.."^7: ^2Removing ^7$"..amount.." ^2from account ^7'^6"..society.."^7' ($"..newAmount..")") + debugPrint("^6Bridge^7: ^3"..bankScript.."^7: ^2Removing $"..amount.." from account '^6"..society.."^7' ($"..newAmount..")") end end +--- Adds funds to a society's bank account. +--- @param society string The identifier of the society. +--- @param amount number The amount of money to add. +--- @usage +--- ```lua +--- fundSociety("police", 500) +--- ``` function fundSociety(society, amount) local bankScript, newAmount = "", 0 - if isStarted("Renewed-Banking") then - bankScript = "Renewed-Banking" - exports['Renewed-Banking']:addAccountMoney(society, amount) - newAmount = exports["Renewed-Banking"]:getAccountMoney(society) - elseif isStarted("qb-banking") then + + if isStarted("qb-banking") then bankScript = "qb-banking" - if not exports["qb-banking"]:GetAccount(society) then + if not exports["qb-banking"]:GetAccount(society) then if Jobs[society] then print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") - exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null" + exports["qb-banking"]:CreateJobAccount(society, 0) + Wait(150) elseif Gangs[society] then print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") - exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null" + exports["qb-banking"]:CreateGangAccount(society, 0) + Wait(150) end - end + end exports["qb-banking"]:AddMoney(society, amount) + elseif isStarted("esx_society") then + bankScript = "esx_society" + -- Use the esx_society event to deposit money. + TriggerServerEvent('esx_society:depositMoney', society, amount) + -- Use callback to return the updated balance. + newAmount = triggerCallback(getScript() .. ":getESXSocietyAccount", society) or 0 + + elseif isStarted("Renewed-Banking") then + bankScript = "Renewed-Banking" + exports['Renewed-Banking']:addAccountMoney(society, amount) + newAmount = exports["Renewed-Banking"]:getAccountMoney(society) elseif isStarted("fd_banking") then bankScript = "fd_banking" - exports.fd_banking:AddMoney(society, amount) + exports["fd_banking"]:AddMoney(society, amount) elseif isStarted("okokBanking") then bankScript = "okokBanking" @@ -73,39 +167,12 @@ function fundSociety(society, amount) end end -function getSocietyAccount(society) - local bankScript, amount = "", 0 - if isStarted("Renewed-Banking") then - bankScript = "Renewed-Banking" - amount = exports["Renewed-Banking"]:getAccountMoney(society) - - elseif isStarted("qb-banking") then - bankScript = "qb-banking" - if not exports["qb-banking"]:GetAccount(society) then - if Jobs[society] then - print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") - exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null" - elseif Gangs[society] then - print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") - exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null" - end - end - amount = exports["qb-banking"]:GetAccountBalance(society) - - elseif isStarted("fd_banking") then - bankScript = "fd_banking" - amount = exports["fd_banking"]:GetAccount(society) - - elseif isStarted("okokBanking") then - bankScript = "okokBanking" - amount = exports['okokBanking']:GetAccount(society) - - end - if bankScript == "" then - print("^1Error^7: ^3GetSocietyAccount^7: ^2No supported banking script found") - else - debugPrint("^6Bridge^7: ^3"..bankScript.."^7: ^2Retrieved account ^7'^6"..society.."^7' ($"..amount..")") - end - return amount +-- other +if isStarted("esx_society") then + createCallback(getScript() .. ":getESXSocietyAccount", function(source, society) + -- Example query – adjust table/field names to match your esx_society implementation. + local result = MySQL.scalar.await('SELECT money FROM society_money WHERE society = ?', { society }) + return result or 0 + end) end \ No newline at end of file diff --git a/shared/stashcontrol.lua b/shared/stashcontrol.lua index 7d0a924..dc99009 100644 --- a/shared/stashcontrol.lua +++ b/shared/stashcontrol.lua @@ -1,17 +1,46 @@ +--[[ + Stash Management Module + ------------------------- + This module handles stash-related operations including: + • Retrieving stash items (from server or local cache). + • Checking for required items in stashes. + • Opening stashes using different inventory systems. + • Removing items from stashes. + • Checking if a stash has specific items. +]] + +-- Global variable to hold the current stash (used in callbacks). local stash + +-- If running on the server, create a callback to retrieve stash items. if isServer() then - createCallback(getScript()..':server:GetStashItems', - function(source, stashName) - stash = getStash(stashName) return stash - end) + createCallback(getScript()..':server:GetStashItems', function(source, stashName) + stash = getStash(stashName) + return stash + end) end -local stashCache ={} +-- Local cache for stashes. +local stashCache = {} + +--- Retrieves (or updates) a local stash cache entry with a timeout. +--- When the cache is empty or expired, it triggers a server callback to update the items. +--- +--- @param stashName string The name of the stash. +--- @param stop boolean (Optional) If true, clears the entire stash cache. +--- @return boolean True if items exist in cache (and recheck is skipped), false otherwise. +--- +--- @usage +--- ```lua +--- local cached = GetStashTimeout("playerStash") +--- ``` function GetStashTimeout(stashName, stop) if stop then stashCache = {} return end + + -- Retrieve cache for this stash, or initialize if not present. stash = stashCache[stashName] if not stash then debugPrint("^6Bridge^7: ^2Local Stash ^7'^3"..stashName.."^7'^2 cache ^1not ^2found^7, ^2need to grab from server^7") @@ -19,16 +48,21 @@ function GetStashTimeout(stashName, stop) stash = stashCache[stashName] else debugPrint("^6Bridge^7: ^2Local Stash ^7'^3"..stashName.."^7'^2 cache found^7") + ("^6Bridge^7: ^2Local Stash '^3"..stashName.."^7' cache found") end + + -- If there are already items in cache, skip recheck. if countTable(stashCache[stashName].items) > 0 then debugPrint("^6Bridge^7: '^3"..stashName.." ^2Items found in cache, skipping recheck") return true end + + -- If timeout has expired, update the stash items from the server. if stashCache[stashName].timeout <= 0 then stashCache[stashName].items = triggerCallback(getScript()..':server:GetStashItems', stashName) - stashCache[stashName].timeout = 15000 + stashCache[stashName].timeout = 15000 -- Timeout in milliseconds. CreateThread(function() - while stash.timeout > 0 do + while stashCache[stashName] and stashCache[stashName].timeout > 0 do stashCache[stashName].timeout -= 1000 Wait(1000) end @@ -39,46 +73,93 @@ function GetStashTimeout(stashName, stop) return false end +--- Checks if the specified stashes have the required items. +--- +--- If multiple stashes are provided (as a table), it iterates over each until all required items are found. +--- +--- @param stashes string|table Either a single stash name or a table of stash names. +--- @param itemTable table A table where keys are item names and values are the required amounts. +--- @return boolean, string|nil `boolean, string` true and the stash name if found, otherwise false and nil. +--- +--- @usage +--- ```lua +--- local found, stashName = checkHasItem({"playerStash", "storageStash"}, { iron = 2, wood = 5 }) +--- ``` function checkHasItem(stashes, itemTable) if not stashes then return hasItem(itemTable), nil end + if type(stashes) == "table" then - local succeses = 0 + local successes = 0 local itemCount = countTable(itemTable) - --for _, item in pairs(itemTable) do itemCount += 1 end + -- Iterate over each provided stash name. for _, name in pairs(stashes) do - Wait(10) -- add delay because qb doesn't appreciate multiple callbacks for stashes + Wait(10) -- Delay to avoid multiple callbacks issues. 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") + debugPrint("^6Bridge^7: ^2Checking "..(name and " '^3"..name.."^7'" or "").." ingredients - ^6"..item.."^7") if stashhasItem(stashCache[name].items, item, amount) then - succeses += 1 - if succeses == itemCount then + successes = successes + 1 + if successes == 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") + debugPrint("^6Bridge^7: ^2Checking "..(stashes and " '^3"..stashes.."^7'" or "").." ingredients") GetStashTimeout(stashes) return stashhasItem(stashCache[stashes].items, itemTable), stashes end + return false, nil end +------------------------------------------------------------- +-- Stash Opening Functions +------------------------------------------------------------- --- Stash Items +--- Opens a stash using the active inventory system. +--- +--- Checks for job or gang restrictions before opening the stash. +--- +--- @param data table A table containing stash data: +--- - stash (string): The stash identifier. +--- - label (string): Display label. +--- - maxWeight (number|nil): Maximum weight (default 600000). +--- - slots (number|nil): Number of slots (default 40). +--- - stashOptions (table|nil): Additional options for the stash. +--- - job/gang (string|nil): Restriction for access. +--- - coords (vector3): Coordinates to "look" at. +--- +--- @usage +--- ```lua +--- openStash({ stash = "playerStash", label = "Player Stash", coords = vector3(100, 200, 30) }) +--- ``` function openStash(data) - if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end + 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(CoreInv) then + TriggerServerEvent('core_inventory:server:openInventory', data.stash, 'stash') + elseif isStarted(CodeMInv) then - exports[CodeMInv]:OpenStash(data.stash, StashWeight, 100) + TriggerServerEvent('codem-inventory:server:openstash', data.stash, data.slots, data.maxWeight, data.label) + + elseif isStarted(OrigenInv) then + exports[OrigenInv]:openInventory('stash', data.stash, { label = data.label }) + 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 }) + 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) @@ -86,18 +167,38 @@ function openStash(data) else TriggerEvent("inventory:client:SetCurrentStash", data.stash) TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions) - end + end + lookEnt(data.coords) end +-- Register an event for opening QB stashes. RegisterNetEvent(getScript()..':server:OpenStashQB', function(data) exports[QBInv]:OpenInventory(source, data.stashName, data) end) -function getStash(stashName) local stashResource = "" +------------------------------------------------------------- +-- Stash Retrieval Function +------------------------------------------------------------- + +--- Retrieves stash items from the active inventory system. +--- +--- This function converts the raw stash items into a standardized table using the global Items lookup. +--- +--- @param stashName string The identifier for the stash. +--- @return stashTable table A table of items from the stash. +--- +--- @usage +--- ```lua +--- local items = getStash("playerStash") +--- ``` +function getStash(stashName) + local stashResource = "" if type(stashName) ~= "string" then - return print("Stash name was not a string %s(%s)", stashName, type(stashName)) + print("^6Bridge^7: ^2Stash name was not a string ^3"..stashName.."^7(^3"..type(stashName).."^7)") + return {} end + local stashItems, items = {}, {} if isStarted(OXInv) then stashResource = OXInv stashItems = exports[OXInv]:Inventory(stashName).items @@ -109,14 +210,15 @@ function getStash(stashName) local stashResource = "" stashItems = exports[CoreInv]:getInventory(stashName) elseif isStarted(CodeMInv) then stashResource = CodeMInv - stashItems = exports[CodeMInv]:GetInventoryItems('Stash', stashName) + stashItems = exports[CodeMInv]:GetStashItems(stashName) elseif isStarted(OrigenInv) then stashResource = OrigenInv - stashItems = exports[OrigenInv]:GetStashItems(stashName) + stashItems = exports[OrigenInv]:getInventory(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 @@ -127,8 +229,8 @@ function getStash(stashName) local stashResource = "" 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] = { + local indexNum = #items + 1 -- Fallback index if slot is missing. + items[(item.slot or indexNum)] = { name = itemInfo.name or nil, amount = tonumber(item.amount) or tonumber(item.count), info = item.info or "", @@ -144,16 +246,30 @@ function getStash(stashName) local stashResource = "" } end end - debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for ^7'^6"..stashName.."^7' ^2retrieved^7") + debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for '^6"..stashName.."^7' retrieved") end jsonPrint(items) 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})) +------------------------------------------------------------- +-- Stash Item Removal Function +------------------------------------------------------------- + +--- Removes items from a stash using the active inventory system. +--- +--- Iterates over the provided items and adjusts the stash contents accordingly. +--- +--- @param stashItems table The current stash items. +--- @param stashName string|table The stash identifier (or table of identifiers). +--- @param items table A table of items to remove (keys are item names, values are amounts). +--- +--- @usage +--- ```lua +--- stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 }) +--- ``` +function stashRemoveItem(stashItems, stashName, items) + if isStarted(OXInv) then for k, v in pairs(items) do debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v) @@ -171,19 +287,19 @@ function stashRemoveItem(stashItems, stashName, items) local amount = amount and 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 + 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 @@ -205,8 +321,8 @@ function stashRemoveItem(stashItems, stashName, items) local amount = amount and end end end - debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'") - + exports[CodeMInv]:UpdateStash(stashName, stashItems) + debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3CodeM^2 stash ^7'^6"..stashName.."^7'") elseif isStarted(OrigenInv) then for k, v in pairs(items) do exports[OrigenInv]:RemoveFromStash(stashName, k, v) @@ -228,7 +344,11 @@ function stashRemoveItem(stashItems, stashName, items) local amount = amount and 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) }) + 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 @@ -236,36 +356,56 @@ function stashRemoveItem(stashItems, stashName, items) local amount = amount and 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) }) + 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 + debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v) stashItems[l] = nil else - if Config.System.Debug then - print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v) - end + debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with "..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) }) + debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash '^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") + print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3starter^1.^2lua^7") end end RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem) +------------------------------------------------------------- +-- Stash Item Availability Check +------------------------------------------------------------- + +--- Checks whether a stash has the required amount of specific items. +--- +--- It iterates through the provided items and tallies available quantities. +--- +--- @param stashItems table The items available in the stash. +--- @param items string|table The item name or table of required items (key: item, value: amount). +--- @param amount number (Optional) The required amount (if a single item is provided). +--- @return boolean, table (`boolean, string`) Returns true (and a table with counts) if all items are available; false otherwise. +--- +--- @usage +--- ```lua +--- local hasAll, details = stashhasItem(currentStashItems, { iron = 2, wood = 5 }) +--- ``` function stashhasItem(stashItems, items, amount) - local invs = {OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, QBInv, PSInv} + local invs = { OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, QBInv, PSInv } local foundInv = "" for _, inv in ipairs(invs) do if isStarted(inv) then @@ -274,9 +414,11 @@ function stashhasItem(stashItems, items, amount) end end + -- Ensure items is a table. if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end + local hasTable = {} - for item, amount in pairs(items) do + for item, requiredAmount in pairs(items) do local count = 0 for _, itemData in pairs(stashItems) do if itemData and (itemData.name == item) then @@ -284,11 +426,13 @@ function stashhasItem(stashItems, items, amount) 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) + local debugMsg = string.format("^6Bridge^7: ^3stashHasItem^7[^6%s^7]: %s '%s' ^3%d^7/^3%d^7", foundInv, (count >= requiredAmount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, requiredAmount) debugPrint(debugMsg) - hasTable[item] = { hasItem = (count >= amount), count = count } + hasTable[item] = { hasItem = (count >= requiredAmount), 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 +end diff --git a/shared/targets.lua b/shared/targets.lua index 26cd067..0f85ff1 100644 --- a/shared/targets.lua +++ b/shared/targets.lua @@ -1,9 +1,31 @@ --- This is for experimental targets based on GTA in-world text prompts -- -local TextTargets = {} +--[[ + Experimental GTA In-World Text Prompts Targets Module + ------------------------------------------------------- + This module handles the creation, removal, and management of in-world text targets + for interacting with entities and zones using GTA text prompts. It supports multiple + targeting systems: OX Target, QB Target, or a fallback using DrawText3D. + + Available functionalities: + • createEntityTarget - Creates a target for a specific entity. + • createBoxTarget - Creates a box-shaped zone target. + • createCircleTarget - Creates a circular zone target. + • createModelTarget - Creates a target for specified models. + • removeEntityTarget - Removes a target from an entity. + • removeZoneTarget - Removes a zone target. + + Fallback: If no targeting system is detected (or if disabled via Config.System.DontUseTarget), + the module uses DrawText3D prompts. This is experimental and may not work as expected. +]] + +------------------------------------------------------------- +-- Utility Data & Tables +------------------------------------------------------------- + +-- Mapping of key codes to human-readable key names. 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", + [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", @@ -15,49 +37,63 @@ local Keys = { [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 = {} +-- Tables for storing created targets for the fallback system and zone management. +local TextTargets = {} -- For fallback DrawText3D targets. +local targetEntities = {} -- For entity targets. +local boxTargets = {} -- For box-shaped zone targets. +local circleTargets = {} -- For circular zone targets. + +------------------------------------------------------------- +-- Entity Target Creation +------------------------------------------------------------- --- Creates a target for an entity with specified options and interaction distance. +--- Supports different targeting systems (OX Target, QB Target, or custom DrawText3D) +--- based on the server configuration. --- ---- 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 for which the target is created. +--- @param opts table Array of option tables. Each option should include: +--- - icon (string): The icon to display. +--- - label (string): The text label for the option. +--- - item (string|nil): (Optional) An associated item. +--- - job (string|nil): (Optional) The job required to interact. +--- - gang (string|nil): (Optional) The gang required to interact. +--- - action (function|nil): (Optional) The function executed on selection. +--- @param dist number The interaction distance for the target. --- ----@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 +--- @usage --- ```lua ---- createEntityTarget(entityId, { ---- { icon = "fas fa-car", label = "Open Vehicle", action = openVehicle }, ---- { icon = "fas fa-lock", label = "Lock Vehicle", action = lockVehicle } ---- }, 2.5) +---createEntityTarget(entityId, { +--- { +--- action = function() +--- openStorage() +--- end, +--- icon = "fas fa-box", +--- job = "police", +--- label = "Open Storage", +--- }, +---}, 2.0) --- ``` function createEntityTarget(entity, opts, dist) + -- Store the target entity for later cleanup. targetEntities[#targetEntities + 1] = entity local entityCoords = GetEntityCoords(entity) + + -- Fallback: Use DrawText3D if targeting systems are disabled or unavailable. 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) + debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with DrawText for entity ^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 + -- Check if a target already exists at similar coordinates. + for _, target in pairs(TextTargets) do + if #(target.coords - entityCoords) < 0.01 then existingTarget = target break end end + local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Predefined key codes for options. if existingTarget then - -- Combine options - local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Extend key table as needed + -- Append new options to the existing target. for i = 1, #opts do local key = keyTable[#existingTarget.options + i] opts[i].key = key @@ -65,9 +101,8 @@ function createEntityTarget(entity, opts, dist) existingTarget.options[#existingTarget.options + 1] = opts[i] end else - -- Create new target + -- Create a new target entry. 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 @@ -75,7 +110,7 @@ function createEntityTarget(entity, opts, dist) 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) + debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity) local options = {} for i = 1, #opts do options[i] = { @@ -91,84 +126,95 @@ function createEntityTarget(entity, opts, dist) end exports[OXTargetExport]:addLocalEntity(entity, options) elseif isStarted(QBTargetExport) then - debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..QBTargetExport.." ^7"..entity) + debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..QBTargetExport.." ^2for entity ^7"..entity) local options = { options = opts, distance = dist } exports[QBTargetExport]:AddTargetEntity(entity, options) end end -local boxTargets = {} +------------------------------------------------------------- +-- Box Zone Target Creation +------------------------------------------------------------- --- 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. ---- +--- Supports different targeting systems based on the server configuration. ---@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. +--- - 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. +--- - 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) ---- ``` +---```lua +---createBoxTarget( +--- { +--- 'storageBox', +--- vector3(100.0, 200.0, 30.0), +--- 2.0, +--- 2.0, +--- { +--- name = 'storageBox', +--- heading = 100.0, +--- debugPoly = true, +--- minZ = 27.0 +--- maxZ = 32.0, +--- }, +--- }, +---{ +--- { +--- action = function() +--- openStorage() +--- end, +--- icon = "fas fa-box", +--- job = "police", +--- label = "Open Storage", +--- }, +---}, 2.0) +---``` 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]) + debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^2 for zone ^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 + for _, target in pairs(TextTargets) do + if #(target.coords - data[2]) < 0.01 then existingTarget = target break end end - local keyTable = { 38, 29, 303, 45, 46, 47, 48 } + 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] + 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 + 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 } + 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 ^3Box^2 target with ^6"..OXTargetExport.." ^7"..data[1]) + debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1]) local options = {} for i = 1, #opts do options[i] = { @@ -193,39 +239,38 @@ function createBoxTarget(data, opts, dist) debug = data[5].debugPoly, options = options }) - boxTargets[#boxTargets+1] = target + boxTargets[#boxTargets + 1] = target return target elseif isStarted(QBTargetExport) then - debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^7"..data[1]) + debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^2for zone ^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 + boxTargets[#boxTargets + 1] = target return data[1] end end -local circleTargets = {} +------------------------------------------------------------- +-- Circle Zone Target Creation +------------------------------------------------------------- --- 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. +--- Supports different targeting systems based on server configuration. --- ---@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. +--- - 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. +--- - 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. @@ -243,37 +288,34 @@ local circleTargets = {} --- ``` function createCircleTarget(data, opts, dist) if Config.System.DontUseTarget then - debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6DrawText ^7"..data[1]) + debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6DrawText ^2for zone ^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 + for _, target in pairs(TextTargets) do + if #(target.coords - data[2]) < 0.01 then existingTarget = target break end end + local keyTable = { 38, 29, 303, 45, 46, 47, 48 } 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] + 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 + 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]) + debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1]) local options = {} for i = 1, #opts do options[i] = { @@ -283,7 +325,7 @@ function createCircleTarget(data, opts, dist) 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 + return distance < dist end } end @@ -293,45 +335,46 @@ function createCircleTarget(data, opts, dist) debug = data[4].debugPoly, options = options }) - circleTargets[#circleTargets+1] = target + circleTargets[#circleTargets + 1] = target return target elseif isStarted(QBTargetExport) then - debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6"..QBTargetExport.." ^7"..data[1]) + debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..QBTargetExport.." ^2for zone ^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 + circleTargets[#circleTargets + 1] = target return data[1] end end -local targetEntities = {} +------------------------------------------------------------- +-- Model Target Creation +------------------------------------------------------------- ---- Creates a target for an entity with specified options and interaction distance. +--- Creates a target for models with specified options and interaction distance. +--- Supports different targeting systems (OX Target, QB Target) based on server configuration. --- ---- 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 models table Array of model identifiers. +--- @param opts table Array of option tables (same structure as in createEntityTarget). +--- @param dist number The interaction distance for the target. --- ----@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 +--- @usage --- ```lua ---- createEntityTarget(entityId, { ---- { icon = "fas fa-car", label = "Open Vehicle", action = openVehicle }, ---- { icon = "fas fa-lock", label = "Lock Vehicle", action = lockVehicle } ---- }, 2.5) ---- ``` +---createModelTarget( +---{ model1, model2 }, +---{ +--- { +--- action = function() +--- openStorage() +--- end, +--- icon = "fas fa-box", +--- job = "police", +--- label = "Open Storage", +--- }, +---}, 2.0) +---``` function createModelTarget(models, opts, dist) if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then - -- + -- Fallback for model targets is not implemented. elseif isStarted(OXTargetExport) then debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..OXTargetExport) local options = {} @@ -355,28 +398,32 @@ function createModelTarget(models, opts, dist) end end +------------------------------------------------------------- +-- Target Removal Functions +------------------------------------------------------------- - --- 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 +--- ```lua --- 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 + 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 @@ -385,54 +432,60 @@ end --- 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 + 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 * +------------------------------------------------------------- +-- Fallback: DrawText3D Targets (Experimental) +------------------------------------------------------------- + +-- If no targeting system is detected and this is a client script, use DrawText3D for targets. 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 camRotation = GetGameplayCamRot(2) -- Camera rotation (degrees) + local camForwardVector = RotationToDirection(camRotation) -- Convert rotation to direction 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 + -- Identify the closest target in front of the camera. + for _, target in pairs(TextTargets) do + local dist = #(pedCoords - target.coords) + local vecToTarget = target.coords - camCoords 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 -- Threshold for facing target. - local isFacingTarget = dot > 0.5 -- Adjust threshold as needed - - if dist <= v.dist and isFacingTarget then + if dist <= target.dist and isFacingTarget then if dist < closestDist then closestDist = dist - closestTarget = v + closestTarget = target 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 + -- Render the DrawText3D targets and listen for key presses. + for _, target in pairs(TextTargets) do + local isClosest = (target == closestTarget) + if #(pedCoords - target.coords) <= target.dist then + for i = 1, #target.options do + if IsControlJustPressed(0, target.options[i].key) and isClosest then + if target.options[i].onSelect then target.options[i].onSelect() end + if target.options[i].action then target.options[i].action() end end end - DrawText3D(vec3(v.coords.x, v.coords.y, v.coords.z + 0.7), concatenateText(v.buttontext), isClosest) + DrawText3D(vec3(target.coords.x, target.coords.y, target.coords.z + 0.7), concatenateText(target.buttontext), isClosest) end end Wait(0) @@ -440,18 +493,34 @@ if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStar end) end --- If the current loaded script is stopped, automatically remove targets -- +------------------------------------------------------------- +-- Cleanup on Resource Stop +------------------------------------------------------------- + +-- When the current resource stops, remove all targets. onResourceStop(function() + -- Remove entity targets. 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 + if isStarted(OXTargetExport) then + exports[OXTargetExport]:removeLocalEntity(targetEntities[i], nil) + elseif isStarted(QBTargetExport) then + exports[QBTargetExport]:RemoveTargetEntity(targetEntities[i]) + end end + -- Remove box zone targets. 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 + if isStarted(OXTargetExport) then + exports[OXTargetExport]:removeZone(boxTargets[i], true) + elseif isStarted(QBTargetExport) then + exports[QBTargetExport]:RemoveZone(boxTargets[i].name) + end end + -- Remove circle zone targets. 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 + 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 dacc044..49c068e 100644 --- a/shared/vehicles.lua +++ b/shared/vehicles.lua @@ -1,22 +1,29 @@ --- Get Vehicle Info -- -local lastCar = nil -local carInfo = {} +--[[ + Vehicle Info & Properties Module + ---------------------------------- + This module provides utilities for: + - Retrieving vehicle information from a Vehicles table. + - Getting and setting vehicle properties using the active framework. + - Comparing vehicle property differences. + - Synchronizing vehicle properties across clients. + - Managing network control of vehicles. + - Finding the closest vehicle to a given position. +]] ---- Searches the 'Vehicles' table for a specific vehicle's 'name', 'price', and 'class'. +-- Cached vehicle info to avoid unnecessary re-searches. +local lastCar, carInfo = nil, {} + +--- Searches the 'Vehicles' table for a specific vehicle's details. +--- If the vehicle differs from the last searched, it retrieves its model and updates the carInfo table. +--- The table includes the vehicle's name, price, and class information. --- ---- 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 A table containing the vehicle's details or nil if the vehicle is invalid. --- ----@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 +--- @usage --- ```lua --- local info = searchCar(vehicleEntity) ---- print(info.name, info.price, info.class) +--- print(info.name, info.price, info.class.name, info.class.index) --- ``` function searchCar(vehicle) if lastCar ~= vehicle then -- If same car, use previous info @@ -78,27 +85,26 @@ function searchCar(vehicle) end end --- Vehicle Properties -- +------------------------------------------------------------- +-- Vehicle Properties Functions +------------------------------------------------------------- ---- 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. +--- Retrieves the properties of a given vehicle using the active framework. --- --- @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. +--- @return table|nil table A table containing the vehicle's properties or nil if invalid. --- --- @usage --- ```lua --- local props = getVehicleProperties(vehicleEntity) --- if props then ---- -- Manipulate vehicle properties +--- -- Use vehicle properties --- end --- ``` function getVehicleProperties(vehicle) + if not vehicle then return nil end + 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]") @@ -109,25 +115,22 @@ function getVehicleProperties(vehicle) return properties end ---- Sets the properties of a given vehicle. +--- Sets the properties of a given vehicle if changes are detected. +--- It compares the current properties with the new ones and applies the update using the active framework. --- ---- 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 new properties to apply. --- ----@param vehicle number The entity ID of the vehicle. ----@param props table The properties to set on the vehicle. ---- ----@usage +--- @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)) + print("Unable to set vehicle properties for '"..vehicle.."' (entity does not exist)") 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]") @@ -140,16 +143,13 @@ function setVehicleProperties(vehicle, props) end --- Checks for differences between the current and new vehicle properties. +--- Compares properties using JSON encoding for deep comparison and logs differences. --- ---- 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. +--- @return boolean `true` if differences are found; `false` otherwise. --- ----@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 +--- @usage --- ```lua --- if checkDifferences(vehicleEntity, newProperties) then --- setVehicleProperties(vehicleEntity, newProperties) @@ -158,43 +158,41 @@ end function checkDifferences(vehicle, newProps) local oldProps = getVehicleProperties(vehicle) debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7") - local allow = false + local differencesFound = false + for k in pairs(oldProps) do if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then - allow = true + differencesFound = 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 + + return differencesFound end ---- Handles setting vehicle properties received from the server. +------------------------------------------------------------- +-- Vehicle Properties Synchronization +------------------------------------------------------------- + +--- Event handler for setting vehicle properties received from the server. +--- Listens for the `ox:setVehicleProperties` event and applies the properties. --- ---- 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) +--- @event `getScript()..ox:setVehicleProperties` +--- @param netId number The network ID of the vehicle. +--- @param props table The new vehicle 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. +--- Handles state bag changes for updating vehicle properties. +--- When the state bag changes, the new properties are applied to the vehicle. --- ---- 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 +--- @param bagName string The state bag's name. +--- @param key string The key that changed. +--- @param value table The new state value. AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagName, _, value) if not value or not GetEntityFromStateBagName then return end local entity = GetEntityFromStateBagName(bagName) @@ -208,8 +206,10 @@ AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagN end end) ---- Pushes a vehicle to other players by syncing it. ---- +------------------------------------------------------------- +-- Vehicle Control Functions +------------------------------------------------------------- + --- 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. --- @@ -222,6 +222,7 @@ end) function pushVehicle(entity) SetVehicleModKit(entity, 0) if entity ~= 0 and DoesEntityExist(entity) then + -- Request network control if not already controlled. if not NetworkHasControlOfEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.") NetworkRequestControlOfEntity(entity) @@ -231,11 +232,13 @@ function pushVehicle(entity) timeout = timeout - 100 end if NetworkHasControlOfEntity(entity) then - debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.") + debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network now has control of the entity^7.") end end + + -- Set as mission entity if not already set. if not IsEntityAMissionEntity(entity) then - debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' &2entity^7.") + 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 @@ -249,41 +252,47 @@ function pushVehicle(entity) end end +--- Finds the closest vehicle to the specified coordinates. +--- The function uses different APIs based on whether a source is provided. +--- +--- @param coords table|vector3 (Optional) The reference coordinates. If nil, uses the player's position. +--- @param src boolean (Optional) If true, uses GetPlayerPed(source) and GetAllVehicles. +--- @return number|number closestVehicle|closestDistance The closest vehicle entity and its distance. +--- +--- @usage +--- ```lua +--- local closestVeh, distance = getClosestVehicle({ x = 100, y = 200, z = 30 }, true) +--- ``` function getClosestVehicle(coords, src) - if src then - local ped = GetPlayerPed(source) - local vehicles = GetAllVehicles() - local closestDistance, closestVehicle = -1, -1 - if coords then coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords end - if not coords then coords = GetEntityCoords(ped) end - for i = 1, #vehicles do - local vehicleCoords = GetEntityCoords(vehicles[i]) - local distance = #(vehicleCoords - coords) - if closestDistance == -1 or closestDistance > distance then - closestVehicle = vehicles[i] - closestDistance = distance - end - end - return closestVehicle, closestDistance - else - local ped = PlayerPedId() - local vehicles = GetGamePool('CVehicle') - local closestDistance = -1 - local closestVehicle = -1 - if coords then - coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords - else - coords = GetEntityCoords(ped) - end - for i = 1, #vehicles, 1 do - local vehicleCoords = GetEntityCoords(vehicles[i]) - local distance = #(vehicleCoords - coords) + local ped, vehicles, closestDistance, closestVehicle - if closestDistance == -1 or closestDistance > distance then - closestVehicle = vehicles[i] - closestDistance = distance - end - end - return closestVehicle, closestDistance + if src then + ped = GetPlayerPed(src) + vehicles = GetAllVehicles() + else + ped = PlayerPedId() + vehicles = GetGamePool('CVehicle') end + + local closestDistance, closestVehicle = -1, -1 + + if coords then + if type(coords) == 'table' then + coords = vec3(coords.x, coords.y, coords.z) + end + else + coords = GetEntityCoords(ped) + end + + for i = 1, #vehicles, 1 do + local vehicleCoords = GetEntityCoords(vehicles[i]) + local distance = #(vehicleCoords - coords) + + if closestDistance == -1 or distance < closestDistance then + closestDistance = distance + closestVehicle = vehicles[i] + end + end + + return closestVehicle, closestDistance end \ No newline at end of file diff --git a/shared/wrapperfunctions.lua b/shared/wrapperfunctions.lua index 3794291..8eef846 100644 --- a/shared/wrapperfunctions.lua +++ b/shared/wrapperfunctions.lua @@ -1,166 +1,21 @@ --- 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. +--- This function supports multiple command systems (OXLib, qb-core, ESX Legacy). --- --- @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. +--- - 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: +--- ```lua --- registerCommand("greet", { --- "Greets the player", --- { name = "name", help = "Name of the player to greet" }, ---- function(source, args) print("Hello, " .. args[1] .. "!") end, +--- function(source, args) print("Hello, "..args[1].."!") end, --- nil, --- "admin" --- }) @@ -170,10 +25,10 @@ function registerCommand(command, options) 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) + debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7"..QBExport, command) + Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil) elseif isStarted(ESXExport) then - debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7 ESX Legacy", command) + debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7ESX Legacy", command) ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError) options[4](xPlayer.source, args, showError) end, false, { help = options[1] }) @@ -181,19 +36,24 @@ function registerCommand(command, options) end --- Registers a stash with the active inventory system. +--- Supports either OXInv or QSInv. --- ---- 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. ---- +--- @param name string Unique stash identifier. +--- @param label string Display name for the stash. +--- @param slots number|nil (Optional) Number of slots (default 50). +--- @param weight number|nil (Optional) Maximum weight (default 4000000). +--- @param owner string|nil (Optional) Owner identifier for personal stashes. +--- @param coords table|nil (Optional) Coordinates for the stash location. --- @usage --- ```lua ---- registerStash("playerStash", "Player Stash", 100, 8000000, "player123", { x = 100.0, y = 200.0, z = 30.0 }) +--- 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 @@ -201,19 +61,25 @@ function registerStash(name, label, slots, weight, owner, coords) 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) + exports[QSInv]:RegisterStash(nil, name, slots or 50, weight or 4000000) + + --elseif isStarted(CoreInv) then + -- debugPrint("^6Bridge^7: ^2Registering ^3CoreInv ^2Stash^7:", name, label) + -- exports[CoreInv]:openHolder(nil, name, 'stash', nil, nil, false, nil) + + elseif isStarted(OrigenInv) then + debugPrint("^6Bridge^7: ^2Registering ^3OrigenInv ^2Stash^7:", name, label) + exports["origen_inventory"]:registerStash(name, label, slots or 50, weight or 4000000) end end --- Registers a shop with the active inventory system. +--- Supports either OXInv or QBInv (with QBInvNew flag). --- ---- 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. ---- +--- @param name string Unique shop identifier. +--- @param label string Display name for the shop. +--- @param items table List of available shop items. +--- @param society string|nil (Optional) Society identifier for shared shops. --- @usage --- ```lua --- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons") @@ -221,13 +87,11 @@ end 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, - } - ) + 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) exports[QBInv]:CreateShop({ @@ -240,25 +104,22 @@ function registerShop(name, label, items, society) end end --- Server-Side Event Registration - if isServer() then --- Registers an event to create an OX stash from the server. + --- When triggered, it calls registerStash with the provided parameters. --- - --- @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. - --- + --- @event server:makeOXStash + --- @param name string Unique stash identifier. + --- @param label string Display name for the stash. + --- @param slots number|nil (Optional) Number of slots. + --- @param weight number|nil (Optional) Maximum weight. + --- @param owner string|nil (Optional) Owner identifier. + --- @param coords table|nil (Optional) Stash coordinates. --- @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 +end diff --git a/starter.lua b/starter.lua index 996143e..b1d38ed 100644 --- a/starter.lua +++ b/starter.lua @@ -39,6 +39,7 @@ for _, v in pairs({ -- This is a specific load order 'duifunctions.lua', -- Native Scaleforms + 'scaleforms/scaleform_basic.lua', 'scaleforms/bigMessageInstance.lua', 'scaleforms/countDownHandler.lua', 'scaleforms/debugScaleform.lua', @@ -56,11 +57,13 @@ for _, v in pairs({ -- This is a specific load order 'wrapperfunctions.lua', 'polyZone.lua', + 'inventories.lua', 'itemcontrol.lua', 'playerfunctions.lua', 'metaHandlers.lua', 'jobfunctions.lua', - 'banking.lua', + 'societybank.lua', + 'phones.lua', -- Interactions 'targets.lua', @@ -78,7 +81,9 @@ for _, v in pairs({ -- This is a specific load order 'scaleEntity.lua', 'vehicles.lua', 'effects.lua', - 'versioncheck.lua' + + -- Do version check last + '_versioncheck.lua' }) do if debugMode then --print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")