diff --git a/shared/contextmenus.lua b/shared/contextmenus.lua index 82e64af..d6a35cc 100644 --- a/shared/contextmenus.lua +++ b/shared/contextmenus.lua @@ -157,6 +157,7 @@ function openMenu(Menu, data) end if not Menu[k].header then Menu[k].header = " " end if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end + Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable end exports[QBMenuExport]:openMenu(Menu) diff --git a/shared/crafting.lua b/shared/crafting.lua index 71cc8e1..c02a0de 100644 --- a/shared/crafting.lua +++ b/shared/crafting.lua @@ -40,76 +40,184 @@ local CraftLock = false --- }) --- ``` 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. 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. 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. if data.stashTable then data.stashName = data.stashTable end + + -- 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 Recipes = data.craftable.Recipes + local craftingLevel = data.craftable.craftingLevel + local craftedItems = data.craftable.craftedItems + + -- Create a temporary table to collect required item amounts for each recipe. local tempCarryTable = {} for i = 1, #Recipes do + -- Iterate over each key in the current recipe. for k in pairs(Recipes[i]) do + -- 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. local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable) + + -- Process each recipe to build the 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 - if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then + -- Skip meta keys that are not ingredients. + 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, + } + + if not excludeKeys[k] then + + -- Check job requirements if specified for the recipe. 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 end - else hasjob = true end - local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or nil) + 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 itemTable = {} local metaTable = {} + + -- Iterate over the ingredients for the current key. 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) + Wait(0) -- Yield to avoid freezing the game. 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 .. " 📦" - else setheader = setheader .. " ✔️" end - elseif not canCarryTable[k] then setheader = setheader .. " 📦" end + if not canCarryTable[k] then + setheader = setheader .. " 📦" + else + setheader = setheader .. " ✔️" + end + elseif not canCarryTable[k] then + setheader = setheader .. " 📦" + end + if Recipes[i]["hasCrafted"] ~= nil then + if craftedItems[k] == nil then + setheader = "✨ "..setheader + end + 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 = isStarted(QBMenuExport) and disable and not 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 ""), - txt = isStarted(QBMenuExport) and settext or nil, - --metadata = debugMode and Recipes[i]["metadata"] or nil, + -- 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() - local transdata = { item = k, craft = data.craftable.Recipes[i], craftable = data.craftable, coords = data.coords, stashName = data.stashName, onBack = data.onBack, metadata = Recipes[i]["metadata"] } - if Config.Crafting.MultiCraft then multiCraft(transdata) else makeItem(transdata) end + -- Build transaction data with details needed for crafting. + local transdata = { + item = k, + craft = data.craftable.Recipes[i], + craftable = data.craftable, + coords = data.coords, + stashName = data.stashName, + onBack = data.onBack, + metadata = metadata + } + -- Call multiCraft or makeItem based on configuration. + if Config.Crafting.MultiCraft then + multiCraft(transdata) + else + makeItem(transdata) + end end) or nil), } end end - Wait(0) + Wait(0) -- Yield within the loop to maintain responsiveness. end end - openMenu(Menu, { header = data.craftable.Header, onBack = data.onBack or nil, canClose = true, onExit = function() 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, + onBack = data.onBack or nil, + canClose = true, + onExit = function() end, + }) + + -- Trigger an action (likely camera or player focus) to look at the specified coordinates. lookEnt(data.coords) end + --- Opens a menu for selecting the quantity to craft. --- --- This function presents the player with options to craft multiple quantities of an item, based on `Config.Crafting.MultiCraftAmounts`. @@ -159,7 +267,17 @@ function multiCraft(data) header = "Craft - x"..k * data.craft.amount, txt = settext, onSelect = function () - makeItem({item = data.item, craft = data.craft, craftable = data.craftable, amount = k, coords = data.coords, stashName = stashname, stashTable = data.stashName, onBack = data.onBack, metadata = data.metadata }) + makeItem({ + item = data.item, + craft = data.craft, + craftable = data.craftable, + amount = k, + coords = data.coords, + stashName = stashname, + stashTable = data.stashName, + onBack = data.onBack, + metadata = data.metadata + }) end, } end @@ -199,20 +317,38 @@ function makeItem(data) CraftLock = true if data.stashTable then data.stashName = data.stashTable end local bartime = data.craftable.progressBar and data.craftable.progressBar.time or 5000 - local bartext = (data.craftable.progressBar and data.craftable.progressBar.label) or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"]) or "Making a" + local 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 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 k, v in pairs(data.craft) do - if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then + 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, + } + + if not excludeKeys[k] then if type(v) == "table" then for l, b in pairs(v) do if crafting and progressBar({ @@ -248,6 +384,20 @@ function makeItem(data) icon = data.item, }) then TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata) + 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 ) + --SetMetadata(nil, "craftedItems", data.craftable.craftedItems) + end + if data.craftable.Recipes[1].oneUse == true then + removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot) + 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 break @@ -262,7 +412,7 @@ function makeItem(data) stopTempCam() CraftLock = false lockInv(false) - craftingMenu(data) + if canReturn then craftingMenu(data) end ClearPedTasks(PlayerPedId()) end @@ -299,11 +449,11 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, else if craftable then for k, v in pairs(craftable[ItemMake] or {}) do - TriggerEvent(getScript()..":server:toggleItem", false, tostring(k), v, src) + removeItem(tostring(k), v, src) end end end - TriggerEvent(getScript()..":server:toggleItem", true, ItemMake, amount, src, metadata) + addItem(ItemMake, amount, metadata, src) --if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end end) @@ -423,7 +573,7 @@ RegisterNetEvent(getScript().."Sellitems", function(data) local src = source local hasItems, hasTable = hasItem(data.item, 1, src) if hasItems then - TriggerEvent(getScript()..":server:toggleItem", false, data.item, hasTable[data.item].count, src) + removeItem(data.item, hasTable[data.item].count, src) TriggerEvent(getScript()..":server:FundPlayer", (hasTable[data.item].count * data.price), "cash", src) else triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src) diff --git a/shared/helpers.lua b/shared/helpers.lua index 38a8c54..067d1f2 100644 --- a/shared/helpers.lua +++ b/shared/helpers.lua @@ -17,7 +17,7 @@ --- end --- ``` function isStarted(script) - return GetResourceState(script):find("start") + return GetResourceState(script):find("start") ~= nil end local scriptName = nil diff --git a/shared/itemcontrol.lua b/shared/itemcontrol.lua index e8a2620..0e24c78 100644 --- a/shared/itemcontrol.lua +++ b/shared/itemcontrol.lua @@ -95,12 +95,12 @@ end --- ```lua --- removeItem("health_potion", 1) --- ``` -function removeItem(item, amount, src) +function removeItem(item, amount, src, slot) if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist") return end if src then - TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, info) + TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, nil, slot) else - TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, info) + TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, nil, slot) end end @@ -119,7 +119,7 @@ end --- ```lua --- TriggerServerEvent("script:server:toggleItem", true, "health_potion", 1) --- ``` -RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info) +RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info, slot) if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." ^7'^3"..item.."^7'^2 but it doesn't exist") return end local src = newsrc or source local addremove = (tostring(give) == "true" and "addItem" or "removeItem") @@ -153,7 +153,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, elseif isStarted(QBInv) then 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") diff --git a/shared/metaHandlers.lua b/shared/metaHandlers.lua new file mode 100644 index 0000000..81b115d --- /dev/null +++ b/shared/metaHandlers.lua @@ -0,0 +1,90 @@ + +function GetPlayer(source) + if isStarted(QBExport) then + debugPrint("^6Debug^7: ^3GetPlayer^7() QBExport") + return exports[QBExport]:GetCoreObject().Functions.GetPlayer(source) + elseif isStarted(QBXExport) then + debugPrint("^6Debug^7: ^3GetPlayer^7() QBOXExport") + return exports[QBXExport]:GetCoreObject().Functions.GetPlayer(source) + elseif isStarted(ESXExport) then + debugPrint("^6Debug^7: ^3GetPlayer^7() ESXExport") + return exports[ESXExport]:GetPlayerFromId(source) + elseif isStarted(OXCoreExport) then + debugPrint("^6Debug^7: ^3GetPlayer^7() OXCoreExport") + return exports[OXCoreExport]:GetPlayer(source) + end + return nil +end + +-- Get Metadata +function GetMetadata(player, key) + if not player then -- This would be called client side + debugPrint("^6Debug^7: ^3GetMetadata^7() calling server") + return triggerCallback(getScript()..":server:GetMetadata", key) + else + if isStarted(QBExport) or isStarted(QBXExport) then + debugPrint("^6Debug^7: ^3GetMetadata^7() QBExport or QBXExport") + return player.PlayerData.metadata[key] + elseif isStarted(ESXExport) then + debugPrint("^6Debug^7: ^3GetMetadata^7() ESXExport") + return player.getMeta(key) + elseif isStarted(OXCoreExport) then + debugPrint("^6Debug^7: ^3GetMetadata^7() OXCoreExport") + return player.get(key) + end + end + return nil +end + +createCallback(getScript()..":server:GetMetadata", function(source, key) + debugPrint("^6Debug^7: ^3GetMetadata^7() Callback", source) + local player = GetPlayer(source) + local Metadata = {} + if not player then + print("Error getting metadata") + return + end + if type(key) == "table" then + for _, k in ipairs(key) do + Metadata[k] = GetMetadata(player, k).k + end + elseif type(key) == "string" then + return GetMetadata(player, key) + end + jsonPrint(Metadata) + return Metadata +end) + +-- Set Metadata +function SetMetadata(player, key, value) + --if player == nil then -- This would be called client side + -- debugPrint("^6Debug^7: ^3SetMetadata^7() calling server") + -- triggerCallback(getScript()..":server:SetMetadata", { key, value }) + -- else + debugPrint("^6Debug^7: ^3SetMetadata^7() setting metadata") + if isStarted(QBExport) or isStarted(QBXExport) then + debugPrint("^6Debug^7: ^3SetMetadata^7() QBExport or QBXExport") + player.Functions.SetMetaData(key, value) + elseif isStarted(ESXExport) then + debugPrint("^6Debug^7: ^3SetMetadata^7() ESXExport") + player.setMeta(key, value) + elseif isStarted(OXCoreExport) then + debugPrint("^6Debug^7: ^3SetMetadata^7() OXCoreExport") + player.set(key, value) + end + --end +end + + +createCallback(getScript()..":server:SetMetadata", function(source, key, value) + print(source, key, value) + local player = GetPlayer(source) + --jsonPrint(player) + --[[if not player then + print("Error getting metadata") + return false + end]] + print("i did it") + SetMetadata(player, key, value) + return true +end) \ No newline at end of file diff --git a/shared/targets.lua b/shared/targets.lua index 9206b5c..26cd067 100644 --- a/shared/targets.lua +++ b/shared/targets.lua @@ -263,7 +263,7 @@ function createCircleTarget(data, opts, dist) end else -- Create new target - local tempText = "" + local tempText = {} local keyTable = { 38, 29, 303, 45, 46, 47, 48 } for i = 1, #opts do opts[i].key = keyTable[i] diff --git a/shared/vehicles.lua b/shared/vehicles.lua index 2afcf6d..dacc044 100644 --- a/shared/vehicles.lua +++ b/shared/vehicles.lua @@ -248,3 +248,42 @@ function pushVehicle(entity) end end end + +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) + + if closestDistance == -1 or closestDistance > distance then + closestVehicle = vehicles[i] + closestDistance = distance + end + end + return closestVehicle, closestDistance + end +end \ No newline at end of file diff --git a/starter.lua b/starter.lua index 5b9236a..0197760 100644 --- a/starter.lua +++ b/starter.lua @@ -58,6 +58,7 @@ for _, v in pairs({ -- This is a specific load order 'polyZone.lua', 'itemcontrol.lua', 'playerfunctions.lua', + 'metaHandlers.lua', 'jobfunctions.lua', -- Interactions @@ -79,11 +80,11 @@ for _, v in pairs({ -- This is a specific load order 'versioncheck.lua' }) do if debugMode then - print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...") + --print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...") end local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v))) fileLoader() if debugMode then - print("^5Success^7: ^2loaded file^7: 'jim_bridge/shared/"..v.."'!") + print("^5Success^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7") end end \ No newline at end of file