diff --git a/fxmanifest.lua b/fxmanifest.lua index 41fb921..74bbf3a 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,6 +1,6 @@ name "Jim_Bridge" author "Jimathy" -version "2.1.03" +version "2.1.05" description "Framework Bridge By Jimathy" fx_version "cerulean" rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.' diff --git a/shared/crafting.lua b/shared/crafting.lua index 0aaa24d..e618619 100644 --- a/shared/crafting.lua +++ b/shared/crafting.lua @@ -17,7 +17,8 @@ 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, anim = true, time = true, + hasCrafted = true, exp = true, anim = true, time = true, id = true, + ingredients = true, } ------------------------------------------------------------- @@ -40,9 +41,14 @@ local excludeKeys = { --- craftable = { --- Header = "Weapon Crafting", --- Recipes = { ---- [1] = { ---- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, ---- amount = 1, +--- weapon_pistol = { +--- id = 1, +--- ingredients = { +--- steel = 5, plastic = 5, +--- }, +--- info = { +--- amount = 1, +--- }, --- }, --- -- More recipes... --- }, @@ -56,9 +62,10 @@ local excludeKeys = { --- job = "mechanic", --- onBack = function() print("Returning to previous menu") end, --- }) +--- ``` function craftingMenu(data) if CraftLock then return end - + local data = cloneTable(data) -- 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 @@ -72,16 +79,49 @@ function craftingMenu(data) -- Normalize stash name. data.stashName = data.stashTable or data.stashName - local Menu = {} - local Recipes = data.craftable.Recipes - local craftedItems = {} - local tempCarryTable = {} + -- Wrapper to convert old style crafting recipes to be handled by the menu properly + --if data.craftable.Recipes[1] then -- assume old style crafting table + -- local compatTable = {} + -- local id = 0 + -- for k, v in ipairs(data.craftable.Recipes) do + -- local Recipe = v + -- for l, b in pairs(Recipe) do + -- if doesItemExist(l) then + -- id += 1 + -- compatTable[l] = { + -- ingredients = b, + -- id = id, + -- info = { + -- amount = Recipe.amount or 1, + -- metadata = Recipe.metadata or nil, + -- job = Recipe.job or nil, + -- gang = Recipe.gang or nil, + -- hasCrafted = Recipe.hasCrafted or nil, + -- }, + -- } + -- end + -- end + -- end + -- data.craftable.Recipes = compatTable + --end - -- Build a table of all required ingredients (default quantity is 1). + -- Convert to array + --local RecipesArray = {} + --for k, v in pairs(data.craftable.Recipes) do + -- RecipesArray[v.id] = { [k] = v } + --end + + local Menu = {} + local Recipes = cloneTable(data.craftable.Recipes) + local craftedItems = {} + + local tempCarryTable = {} + -- Build a temporary table of all required ingredients (default quantity is 1). for i = 1, #Recipes do - for k in pairs(Recipes[i]) do - if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then - tempCarryTable[k] = Recipes[i].amount or 1 + for k, v in pairs(Recipes[i]) do + if not excludeKeys[k] then + if not Recipes[i].amount then Recipes[i].amount = 1 end + tempCarryTable[k] = tempCarryTable[k] and (tempCarryTable[k] < Recipes[i].amount) or Recipes[i].amount end end end @@ -96,76 +136,91 @@ function craftingMenu(data) header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"), disabled = true, } - - -- Process each recipe to create menu entries. for i = 1, #Recipes do - if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end - for k, _ in pairs(Recipes[i]) do + local menuId = #Menu+1 + local item = "" + local Recipe = {} + for k, v in pairs(Recipes[i]) do if not excludeKeys[k] then - local hasjob = true - if Recipes[i].job then - for l, b in pairs(Recipes[i].job) do - hasjob = hasJob(l, nil, b) - if hasjob then break end - end - end - if hasjob then - local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil) - local itemTable = {} - local metaTable = {} - -- Build ingredient details. - for l, b in pairs(Recipes[i][tostring(k)]) do - local label = getItemLabel(l) - local hasItem = checkStashItem(data.stashName, { [l] = b }) - local missingMark = not hasItem and " ❌" or " " - settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark - - metaTable[label] = b - itemTable[l] = b - end - - while not canCarryTable do - Wait(10) - end - disable = not checkStashItem(data.stashName, itemTable) - setheader = ((metadata and metadata.label) or getItemLabel(k)) - ..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "") - - local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️" - local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "✨ " or "" - setheader = isNew .. setheader .. statusEmoji - - Menu[#Menu + 1] = { - arrow = isOx() and (not disable and canCarryTable[k]), - isMenuHeader = disable or not canCarryTable[k], - icon = invImg((metadata and metadata.image) or tostring(k)), - image = invImg((metadata and metadata.image) or tostring(k)), - header = setheader, - txt = settext or nil, - metadata = metaTable, - onSelect = (not disable and canCarryTable[k]) and function() - local transdata = { - item = k, - craft = data.craftable.Recipes[i], - craftable = data.craftable, - coords = data.coords, - stashName = data.stashName, - onBack = data.onBack, - metadata = metadata, - } - if Config.Crafting.MultiCraft then - multiCraft(transdata) - else - makeItem(transdata) - end - end or nil, - } - end + item = k + Recipe = Recipes[i] + Recipe.amount = Recipe.amount or 1 + break end - --Wait(0) + end + + -- Job Check + local hasGroup = true + if Recipe.job then + for l, b in pairs(Recipe.job) do + hasGroup = hasJob(l, nil, b) + if hasGroup then goto skipcheck end + end + end + if Recipe.gang then + for l, b in pairs(Recipe.gang) do + hasGroup = hasJob(l, nil, b) + if hasGroup then goto skipcheck end + end + end + ::skipcheck:: + + -- if has group requirement, continue + if hasGroup then + local setheader, settext, disable, metadata = "", "", false, (Recipe.metadata or Recipe.info or nil) + local itemTable = {} + local metaTable = {} + -- Build ingredient details. + for l, b in pairs(Recipe[item]) do + local label = getItemLabel(l) + local hasItem = checkStashItem(data.stashName, { [l] = b }) + local missingMark = not hasItem and " ❌" or " " + settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark + + metaTable[label] = b + itemTable[l] = b + end + + -- Make sure "canCarryTable" exists + while not canCarryTable do Wait(10) end + disable = not checkStashItem(data.stashName, itemTable) + setheader = ((metadata and metadata.label) or getItemLabel(item)) + ..(Recipe.amount > 1 and " x"..Recipe.amount or "") + + local statusEmoji = disable and " " or not canCarryTable[item] and " 📦" or " ✔️" + local isNew = (Recipe.hasCrafted ~= nil and craftedItems[item] == nil) and "✨ " or "" + setheader = isNew .. setheader .. statusEmoji + + -- Build menu option using info + Menu[menuId] = { + arrow = isOx() and (not disable and canCarryTable[item]), + isMenuHeader = disable or not canCarryTable[item], + icon = invImg((metadata and metadata.image) or item), + image = invImg((metadata and metadata.image) or item), + header = setheader, + txt = settext or nil, + metadata = metaTable, + onSelect = (not disable and canCarryTable[item]) and function() + local transdata = { + item = item, + craft = Recipe, + craftable = data.craftable, + coords = data.coords, + amount = Recipe.amount, + stashName = data.stashName, + onBack = data.onBack, + metadata = metadata, + } + if Config.Crafting.MultiCraft then + multiCraft(transdata) + else + makeItem(transdata) + end + end or nil, + } end end - + -- open context menu openMenu(Menu, { header = data.craftable.Header, headertxt = data.craftable.Headertxt, @@ -322,7 +377,11 @@ end --- }) --- ``` -function makeItem(data) +function makeItem(origData) + + local data = cloneTable(origData) + + local Ped = PlayerPedId() if CraftLock then return end CraftLock = true data.stashName = data.stashTable or data.stashName @@ -336,106 +395,58 @@ function makeItem(data) 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) + local cam = createCam(Ped, data.coords.xyz) + startCam(cam, 5000) -- Calculate total bartime if SingleProgress is enabled - local totalBartime = bartime * craftAmount + local totalBartime = (bartime * craftAmount) - -- Run ingredient check and usage separately first - for i = 1, craftAmount do - for k, v in pairs(data.craft) do - if not excludeKeys[k] and type(v) == "table" then - for l, b in pairs(v) do - if isInventoryOpen() then - print("^1Error^7: ^2Inventory is open, you tried to break things") - stopTempCam() - ClearPedTasks(PlayerPedId()) - if canReturn then craftingMenu(data) end - CraftLock = false - return - end - if crafting and progressBar({ - label = "Using "..b.." "..getItemLabel(l), - time = 1000, - cancel = true, - dict = 'pickup_object', - anim = "putdown_low", - flag = 49, - icon = l, - }) then - TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b) - else - crafted, crafting = false, false - break - end - Wait(200) - end - end - end + local craftProp = nil + if prop then + craftProp = makeProp({ prop = prop.model, coords = GetEntityCoords(PlayerPedId()), true, true }) + AttachEntityToEntity(craftProp, Ped, GetPedBoneIndex(Ped, 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 not crafted then - stopTempCam() - ClearPedTasks(PlayerPedId()) - if canReturn then craftingMenu(data) end - CraftLock = false - return + if data.sound then + local s = data.sound + PlaySoundFromEntity(s.soundId, s.audioName, Ped, s.audioRef, true, 0) end - - -- Handle SingleProgress option - if Config.Crafting.SingleProgress then - local craftProp = nil - if prop then - 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 data.sound then - local s = data.sound - PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0) - end - if crafting and progressBar({ - label = bartext..((metadata and metadata.label) or getItemLabel(data.item)).." x"..craftAmount, - time = totalBartime, - cancel = true, - dict = animDict, - anim = anim, - flag = 49, - icon = data.item, - request = true, - }) then - data.craft.amount = craftAmount - TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken) - currentToken = nil -- clear client cached token - -- handle metadata and experience in a single go - if data.craft["hasCrafted"] ~= nil then - data.craftable.craftedItems[data.item] = true - triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems) - end - if data.craft["exp"] ~= nil then - craftingLevel += data.craft["exp"].give * craftAmount - triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel) - 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 - end - if data.sound then - StopSound(data.sound.soundId) - end - if data.requiredItemfunc then - data.requiredItemfunc() - end - end - if craftProp then destroyProp(craftProp) end - else - -- Run the original loop for multiple progress bars + if not Config.Crafting.SingleProgress then -- if SingleProgress is disabled, dont do ingredient progressbars + -- Run ingredient check and usage separately first for i = 1, craftAmount do + for k, v in pairs(data.craft[data.item]) do + if isInventoryOpen() then + print("^1Error^7: ^2Inventory is open, you tried to break things") + stopCam(0) + ClearPedTasks(Ped) + if canReturn then craftingMenu(origData) end + CraftLock = false + return + end + if crafting and progressBar({ + label = "Using "..v.." "..getItemLabel(k), + time = 800, + cancel = true, + dict = 'pickup_object', + anim = "putdown_low", + flag = 49, + icon = k, + }) then + TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[k], "use", v) + else + crafted, crafting = false, false + break + end + Wait(200) + end + + if not crafted then + goto finishEarly + end + if crafting and progressBar({ label = bartext..((metadata and metadata.label) or getItemLabel(data.item)), time = bartime, @@ -448,34 +459,75 @@ function makeItem(data) }) then TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken) currentToken = nil - if data.craft["hasCrafted"] ~= nil then + if data.craft.hasCrafted ~= nil then data.craftable.craftedItems[data.item] = true triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems) end - if data.craft["exp"] ~= nil then - craftingLevel += data.craft["exp"].give + if data.craft.exp ~= nil then + craftingLevel += data.craft.exp.give triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel) 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) + PlaySoundFromEntity(breakId, "Drill_Pin_Break", Ped, "DLC_HEIST_FLEECA_SOUNDSET", 1, 0) canReturn = false end - if data.requiredItemfunc then - data.requiredItemfunc() - end else break end + + end + else + if crafting and progressBar({ + label = bartext..((metadata and metadata.label) or getItemLabel(data.item)).." x"..craftAmount, + time = totalBartime, + cancel = true, + dict = animDict, + anim = anim, + flag = 49, + icon = data.item, + request = true, + }) then + data.craft.amount *= craftAmount + for k, v in pairs(data.craft[data.item]) do + -- multiply igredient requirement in sent crafting table for removal + data.craft[data.item][k] = (v * craftAmount) + end + TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken) + currentToken = nil -- clear client cached token + -- handle metadata and experience in a single go + if data.craft.hasCrafted ~= nil then + data.craftable.craftedItems[data.item] = true + triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems) + end + if data.craft.exp ~= nil then + craftingLevel += data.craft["exp"].give * craftAmount + triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel) + end + --if data.craft.Recipes[1].oneUse == true then + -- removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot) + -- local breakId = GetSoundId() + -- PlaySoundFromEntity(breakId, "Drill_Pin_Break", Ped, "DLC_HEIST_FLEECA_SOUNDSET", 1, 0) + -- canReturn = false + --end end end + ::finishEarly:: + if craftProp then destroyProp(craftProp) end - Wait(500) - stopTempCam() + if data.sound then + StopSound(data.sound.soundId) + end + if data.requiredItemfunc then + data.requiredItemfunc() + end + + --Wait(500) + stopCam(0) CraftLock = false - if canReturn then craftingMenu(data) end - ClearPedTasks(PlayerPedId()) + if canReturn then craftingMenu(origData) end + ClearPedTasks(Ped) end @@ -505,14 +557,13 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, return end - local hasItems, hasTable = hasItem(ItemMake, 1, src) if stashName then local itemRemove = {} if type(stashName) == "table" then for _, name in pairs(stashName) do stashItems = getStash(name) - for k, v in pairs(craftable[ItemMake] or {}) do + for k, v in pairs(craftable[ItemMake]) do for _, b in pairs(stashItems or {}) do if k == b.name then itemRemove[k] = v @@ -522,7 +573,7 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, end else stashItems = getStash(stashName) - for k, v in pairs(craftable[ItemMake] or {}) do + for k, v in pairs(craftable[ItemMake]) do for _, b in pairs(stashItems or {}) do if k == b.name then itemRemove[k] = v @@ -532,8 +583,8 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, end stashRemoveItem(stashItems, stashName, itemRemove) else - if craftable then - for k, v in pairs(craftable[ItemMake] or {}) do + if craftable[ItemMake] then + for k, v in pairs(craftable[ItemMake]) do removeItem(tostring(k), v, src) end end diff --git a/shared/helpers.lua b/shared/helpers.lua index f5c17bc..f26f4d6 100644 --- a/shared/helpers.lua +++ b/shared/helpers.lua @@ -651,6 +651,29 @@ function lookEnt(entity) end end +-- Function to clone tables, to use when referencing tables that need to be hard set +function cloneTable(obj, opts, seen) + if type(obj) ~= "table" then return obj end + seen = seen or {} + if seen[obj] then return seen[obj] end + + local copy = {} + seen[obj] = copy + + -- Copy entries + for k, v in pairs(obj) do + local k2 = (opts and opts.copy_keys) and cloneTable(k, opts, seen) or k + copy[k2] = cloneTable(v, opts, seen) + end + + -- Preserve metatable unless told not to + if not (opts and opts.strip_meta) then + local mt = getmetatable(obj) + if mt ~= nil then setmetatable(copy, mt) end + end + + return copy +end ------------------------------------------------------------- -- Material and Prop Functions diff --git a/shared/itemcontrol.lua b/shared/itemcontrol.lua index 698c59d..85a2c41 100644 --- a/shared/itemcontrol.lua +++ b/shared/itemcontrol.lua @@ -1147,7 +1147,7 @@ local InvFunc = { function(itemTable, src) local resultTable = {} for k, v in pairs(itemTable) do - resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v) + resultTable[k] = exports[RSGInv]:CanCarryItem(src, k, v) end return resultTable end, @@ -1301,6 +1301,20 @@ function hasItem(items, amount, src) end end end + + -- Fallback to default hasItem function + if not hasTable or not next(hasTable) then + for item, amt in pairs(items) do + local count = 0 + for _, itemData in pairs(grabInv) do + if itemData and itemData.name == item then + count += (itemData.amount or itemData.count or 1) + end + end + hasTable[item] = { hasItem = count >= amt, count = count } + end + end + for _, v in pairs(hasTable) do if not v.hasItem then return false, hasTable @@ -1324,17 +1338,35 @@ end --- ``` function getPlayerInv(src) local grabInv = nil - local foundInv = "" for i = 1, #InvFunc do local inv = InvFunc[i] if isStarted(inv.invName) then - foundInv = inv.invName grabInv = inv.getPlayerInv(src) break end end - if foundInv == "" then + + -- Fallback to framework functions + if not grabInv then + if isStarted(QBExport) then + if src then + grabInv = Core.Functions.GetPlayer(src).PlayerData.items + else + grabInv = Core.Functions.GetPlayerData().items + end + elseif isStarted(ESXExport) then + if src then + local xPlayer = ESX.GetPlayerFromId(src) + grabInv = xPlayer and xPlayer.getInventory() or {} + else + local xPlayer = ESX.GetPlayerData() + grabInv = xPlayer and xPlayer.inventory or {} + end + end + end + + if grabInv == nil then print("^4ERROR^7: ^2No Supported Inventory detected ^7- ^2Check ^3starter^1.^2lua^7") end return grabInv, foundInv @@ -1423,6 +1455,17 @@ function createUseableItem(item, funct) return end end + + -- Fallback to framework functiosn + if isStarted(QBExport) then + debugPrint("^6Bridge^7: ^2Registering ^3UsableItem^2 with ^4"..QBExport.."^7:", item) + Core.Functions.CreateUseableItem(item, funct) + elseif isStarted(ESXExport) then + debugPrint("^6Bridge^7: ^2Registering ^3UsableItem^2 with ^4"..ESXExport.."^7:", item) + while not ESX do Wait(0) end + ESX.RegisterUsableItem(item, funct) + end + debugPrint("^4ERROR^7: No supported framework detected for registering usable item: ^3"..item.."^7") else print("^1ERROR^7: ^1Tried to make item usable but it didn't exist^7: "..item) @@ -1448,7 +1491,7 @@ end --- addItem("health_potion", 2, { quality = "high" }) --- ``` function addItem(item, amount, info, src) - if not Items[item] then + if not doesItemExist(item) then print("^6Bridge^7: ^1Error^7 - ^2Tried to give ^7'^3"..item.."^7'^2 but it doesn't exist") return end @@ -1477,7 +1520,7 @@ end --- removeItem("health_potion", 1) --- ``` function removeItem(item, amount, src, slot) - if not Items[item] then + if not doesItemExist(item) then print("^6Bridge^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist") return end @@ -1568,7 +1611,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, end else - debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..getItemLabel(item).."("..item..") x"..(amount or 1)) + debugPrint("^6Bridge^7: ^3"..action.."^7[^6"..invName.."^7] Player(^3"..src.."^7) "..getItemLabel(item).."("..item..") x"..(amount or 1)) end end else @@ -1593,7 +1636,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd) end else - debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..getItemLabel(item).."("..item..") x"..(amount or 1)) + debugPrint("^6Bridge^7: ^3"..action.."^7[^6"..invName.."^7] Player(^3"..src.."^7) "..getItemLabel(item).."("..item..") x"..(amount or 1)) end end end) @@ -1725,9 +1768,22 @@ RegisterNetEvent(getScript()..":server:setItemMetaData", function(data, src) local inv = InvFunc[i] if isStarted(inv.invName) then inv.setItemMetadata(data, src) - break + return end end + + -- Fallback functions + if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions + local Player = Core.Functions.GetPlayer(src) + Player.PlayerData.items[data.slot].info = data.metadata + if data.metadata.durability then + Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability + end + Player.Functions.SetInventory(Player.PlayerData.items) + + elseif ESX and isStarted(ESXExport) then -- if esx then use core functions + --? + end end) ------------------------------------------------------------- @@ -1804,6 +1860,22 @@ function canCarry(itemTable, src) return inv.canCarry(itemTable, src) end end + + -- Fallback to default canCarry function + 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 return resultTable end @@ -1855,6 +1927,11 @@ function getMaxInvWeight() break end end + + -- Fallback function + if weight == 0 then + weight = InventoryWeight + end return weight end @@ -1877,6 +1954,13 @@ function getCurrentInvWeight(src) end end + -- fallback function + if weight == 0 then + local itemcheck = getPlayerInv(src) + for _, v in pairs(itemcheck) do + weight += ((v.weight * v.amount) or 0) + end + end return weight end @@ -2185,7 +2269,6 @@ end --- local items = getStash("playerStash") --- ``` function getStash(stashName) - local stashResource = "" if stashName == "" or type(stashName) ~= "string" then print("^6Bridge^7: ^2Stash name was not a string ^3"..stashName.."^7(^3"..type(stashName).."^7)") return {} @@ -2201,6 +2284,19 @@ function getStash(stashName) end end + -- Fallback to sql checks if no supported inventory found + if not stashItems or not next(stashItems) then + if isStarted(QBExport) or isStarted(QBXExport) then + local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName }) + if result then + stashItems = json.decode(result) + end + elseif ESX and isStarted(ESXExport) then -- if esx then use core functions + --? + end + + end + if stashItems then for _, item in pairs(stashItems) do local itemInfo = Items[item.name:lower()] @@ -2224,7 +2320,7 @@ function getStash(stashName) end debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for '^6"..stashName.."^7' retrieved") end - jsonPrint(items) + --jsonPrint(items) return items end @@ -2260,7 +2356,28 @@ function stashRemoveItem(stashItems, stashName, items) return end end - + if isStarted(QBExport) then + local stashItems = getStash(stashName[1]) + 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 + stashItems[l] = nil + else + debugPrint("^6Bridge^7: ^2Removing ^3"..PSInv.." ^2Stash item^7:", k, v) + stashItems[l].amount -= v + end + end + end + end + debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QBCORE^2 stash ^7'^6"..stashName[1].."^7'") + MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { + ['stash'] = stashName[1], + ['items'] = json.encode(stashItems) + }) + elseif isStarted(ESXExport) then + --? + end print("^4ERROR^7: ^2No supported Inventory detected ^7- ^2Check ^3starter^1.^2lua^7") end RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem) @@ -2390,17 +2507,17 @@ if isServer() then end RegisterNetEvent(getScript()..":openGrabBox", function(data) - if isStarted(OXInv) then - return - end - local id = "" - if data.metadata then - id = data.metadata.id - elseif data.info then - id = data.info.id - end + local Ped = PlayerPedId() + local id = data.metadata and data.metadata.id or data.info and data.info.id or "" + + if isStarted(OXInv) then + -- this SHOLD be handled within ox_inventory if set up right + return + end + openStash({ stash = id, + coords = GetEntityCoords(Ped) }) end) @@ -2646,10 +2763,17 @@ function openShop(data) if isStarted(inv.invName) then inv.openShop(data.shop, data.items.label, data.items) lookEnt(data.coords) - break + return end end + if isStarted(QBExport) then + TriggerServerEvent(getScript()..':server:openServerShop', data.shop) + TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items) + elseif isStarted(ESXExport) then + --? + end + end RegisterNetEvent(getScript()..':server:openServerShop', function(shopName) diff --git a/shared/make/ropeControl.lua b/shared/make/ropeControl.lua new file mode 100644 index 0000000..29c3f7c --- /dev/null +++ b/shared/make/ropeControl.lua @@ -0,0 +1,255 @@ +if not isServer() then + local RopeCache = { + nextId = 1, + byId = {}, + byHandle = {}, + } + + function loadRopeTextures() + RopeLoadTextures() + local tries = 0 + while not RopeAreTexturesLoaded() and tries < 200 do + Wait(0) + tries += 1 + end + return RopeAreTexturesLoaded() + end + + function maybeUnloadTextures() + local anyLeft = false + local all = GetAllRopes() + if all and type(all) == "table" then + for _, _ in ipairs(all) do anyLeft = true break end + end + if not anyLeft then + RopeUnloadTextures() + end + end + + function registerRope(ropeHandle, startCoords, endCoords, opts) + if not ropeHandle or ropeHandle == 0 then return nil end + local id = RopeCache.nextId + RopeCache.nextId = id + 1 + + local rec = { + handle = ropeHandle, + startCoords = startCoords, + endCoords = endCoords, + type = opts.ropeType, + rate = opts.lengthChangeRate, + breakable = opts.breakable, + createdAt = GetGameTimer() + } + RopeCache.byId[id] = rec + RopeCache.byHandle[ropeHandle] = id + debugPrint("^5Debug^7: 2Registered Rope^7'^3"..id.."^7' - '^3"..ropeHandle.."^7'") + return id + end + + function resolve(id) + if type(id) ~= "number" then return nil, "rope id must be a number" end + local rec = RopeCache.byId[id] + if not rec then return nil, "rope id not found" end + if not DoesRopeExist(rec.handle) then + -- stale; clean mapping + RopeCache.byHandle[rec.handle] = nil + RopeCache.byId[id] = nil + return nil, "rope no longer exists" + end + return rec + end + + --- Create a rope pinned between two coordinates. + --- @param startCoords vector3|table start coord + --- @param endCoords vector3|table end coord + --- @param opts table|nil { ropeType=0..7, breakable=false, collision=true, lockFromFront=false, lengthChangeRate=1.0, timeMultiplier=1.0, slack=0.0, preset=nil } + --- @return integer ropeId + function ropeCreateLine(startCoords, endCoords, opts) + + startCoords = type(startCoords) == "number" and GetEntityCoords(startCoords) or startCoords + endCoords = type(endCoords) == "number" and GetEntityCoords(endCoords) or endCoords + + opts = opts or {} + + local length = #(startCoords - endCoords) + local mid = (startCoords + endCoords) * 0.5 + + local ropeType = opts.ropeType or 1 -- 0..7 (see ropedata.xml types) + local initLength = length + (opts.slack or 0.0) + local maxLength = initLength -- allow droop equal to init + local minLength = 0.0 + local lengthChangeRate = opts.lengthChangeRate or 1.0 + local collisionOn = (opts.collision ~= false) + local lockFromFront = (opts.lockFromFront == true) + local timeMultiplier = opts.timeMultiplier or 1.0 + local breakable = (opts.breakable == true) + + local rope = AddRope(mid.x, mid.y, mid.z, 0.0, 0.0, 0.0, maxLength, ropeType, initLength, minLength, lengthChangeRate, false, collisionOn, lockFromFront, timeMultiplier, breakable, 0) + + if not rope or rope == 0 or not DoesRopeExist(rope) then + debugPrint("^1AddRope failed") + return nil + end + + -- Optional preset (e.g., "ropeFamily3") if provided. + if opts.preset then + -- Best-effort; if invalid it just won't change. + pcall(function() LoadRopeData(rope, tostring(opts.preset)) end) + end + + ActivatePhysics(rope) + + -- Pin both ends to the given coords. + local count = GetRopeVertexCount(rope) + if count and count >= 2 then + PinRopeVertex(rope, 0, startCoords.x, startCoords.y, startCoords.z) + PinRopeVertex(rope, count - 1, endCoords.x, endCoords.y, endCoords.z) + end + + local id = registerRope(rope, startCoords, endCoords, { + ropeType = ropeType, + lengthChangeRate = lengthChangeRate, + breakable = breakable + }) + + return id + end + + --- Delete and unregister a rope. + --- @param id integer + function ropeDelete(id) + local rec, err = resolve(id) + if not rec then return false, err end + local rope = rec.handle + if DoesRopeExist(rope) then + DeleteRope(rope) + end + RopeCache.byHandle[rope] = nil + RopeCache.byId[id] = nil + debugPrint(("deleted rope id=%s"):format(id)) + maybeUnloadTextures() + return true + end + + --- Force rope to a specific length (instant snap). + --- @param id integer + --- @param newLength number + function ropeSetLength(id, newLength) + local rec, err = resolve(id) + if not rec then return false, err end + RopeForceLength(rec.handle, newLength) + return true + end + + --- Smoothly wind the rope (shorten over time). + --- @param id integer + function ropeStartWinding(id) + local rec, err = resolve(id) + if not rec then return false, err end + StartRopeWinding(rec.handle) + return true + end + + --- Stop winding (if winding). + function ropeStopWinding(id) + local rec, err = resolve(id) + if not rec then return false, err end + StopRopeWinding(rec.handle) + return true + end + + --- Smoothly unwind the rope from the front (lengthen over time). + function ropeStartUnwinding(id) + local rec, err = resolve(id) + if not rec then return false, err end + StartRopeUnwindingFront(rec.handle) + return true + end + + --- Stop unwinding (if unwinding). + function ropeStopUnwinding(id) + local rec, err = resolve(id) + if not rec then return false, err end + StopRopeUnwindingFront(rec.handle) + return true + end + + --- Change the wind speed (length change rate). + --- @param id integer + --- @param rate number (units per sec; try 0.5..5.0) + function ropeSetRate(id, rate) + local rec, err = resolve(id) + if not rec then return false, err end + SetRopeLengthChangeRate(rec.handle, rate) + rec.rate = rate + return true + end + + --- Move end endpoint to a new coordinate (re-pins the vertex). + --- @param id integer + --- @param pos vector3|table + function ropeMoveEnd(id, pos) + local rec, err = resolve(id) + if not rec then return false, err end + pos = type(pos) == "number" and GetEntityCoords(pos) or pos + local rope = rec.handle + local count = GetRopeVertexCount(rope) + if not count or count < 2 then return false, "invalid vertex count" end + PinRopeVertex(rope, count - 1, pos.x, pos.y, pos.z) + rec.endCoord = pos + return true + end + + --- Move start endpoint to a new coordinate (re-pins the vertex). + --- @param id integer + --- @param pos vector3|table + function ropeMoveStart(id, pos) + local rec, err = resolve(id) + if not rec then return false, err end + pos = type(pos) == "number" and GetEntityCoords(pos) or pos + local rope = rec.handle + local count = GetRopeVertexCount(rope) + if not count or count < 2 then return false, "invalid vertex count" end + PinRopeVertex(rope, 0, pos.x, pos.y, pos.z) + rec.startCoord = pos + return true + end + + --- Re-pin both endpoints in one call. + function ropeSetEnds(ropeId, startCoord, endCoord) + startCoord = type(startCoord) == "number" and GetEntityCoords(startCoord) or startCoord + endCoord = type(endCoord) == "number" and GetEntityCoords(endCoord) or endCoord + local rec, err = resolve(ropeId) + if not rec then return false, err end + local rope = rec.handle + local count = GetRopeVertexCount(rope) + if not count or count < 2 then + return false, "invalid vertex count" + end + PinRopeVertex(rope, 0, startCoord.x, startCoord.y, startCoord.z) + PinRopeVertex(rope, count - 1, endCoord.x, endCoord.y, endCoord.z) + rec.startCoord = startCoord + rec.endCoord = endCoord + return true + end + + function ropeExists(id) + local rec = RopeCache.byId[id] + return rec and DoesRopeExist(rec.handle) or false + end + + onResourceStart(function() + loadRopeTextures() + end) + + onResourceStop(function() + for id, rec in pairs(RopeCache.byId) do + if rec.handle and DoesRopeExist(rec.handle) then + DeleteRope(rec.handle) + end + RopeCache.byHandle[rec.handle] = nil + RopeCache.byId[id] = nil + end + maybeUnloadTextures() + end) +end \ No newline at end of file diff --git a/shared/playerfunctions.lua b/shared/playerfunctions.lua index 1c50c52..8cc5615 100644 --- a/shared/playerfunctions.lua +++ b/shared/playerfunctions.lua @@ -29,6 +29,14 @@ local hungerThirstFunc = { Player.Functions.SetMetaData('hunger', newHunger) TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst) end, + setStress = + function(src, stress) + local Player = Core.Functions.GetPlayer(src) + local newStress = Player.PlayerData.metadata.stress + stress + newStress = (newStress >= 100.0 and 100.0)or (newStress <= 0 and 0) or newStress + Player.Functions.SetMetaData('stress', newStress) + TriggerClientEvent('hud:client:UpdateStress', src, newStress) + end, }, { framework = QBExport, @@ -48,6 +56,14 @@ local hungerThirstFunc = { Player.Functions.SetMetaData('hunger', newHunger) TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst) end, + setStress = + function(src, stress) + local Player = Core.Functions.GetPlayer(src) + local newStress = Player.PlayerData.metadata.stress + stress + newStress = (newStress >= 100.0 and 100.0)or (newStress <= 0 and 0) or newStress + Player.Functions.SetMetaData('stress', newStress) + TriggerClientEvent('hud:client:UpdateStress', src, newStress) + end, }, { framework = RSGExport, @@ -67,6 +83,14 @@ local hungerThirstFunc = { Player.Functions.SetMetaData('hunger', newHunger) TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst) end, + setStress = + function(src, stress) + local Player = Core.Functions.GetPlayer(src) + local newStress = Player.PlayerData.metadata.stress + stress + newStress = (newStress >= 100.0 and 100.0) or (newStress <= 0 and 0) or newStress + Player.Functions.SetMetaData('stress', newStress) + TriggerClientEvent('hud:client:UpdateStress', src, newStress) + end, }, { framework = ESXExport, @@ -78,6 +102,10 @@ local hungerThirstFunc = { function(src, hunger) TriggerClientEvent('esx_status:add', src, 'hunger', tonumber(hunger) * 10000) end, + setStress = + function(src, stress) + TriggerClientEvent("esx_status:add", src, "stress", tonumber(stress) * 10000) + end, }, } @@ -126,6 +154,26 @@ function setHunger(src, hunger) end end +--- Sets the player's stress level. +--- +--- @param src number The player's server ID. +--- @param stress number The new stress level. +--- +--- @usage +--- ```lua +--- setStress(playerId, 60) +--- ``` +function setStress(src, stress) + for i = 1, #hungerThirstFunc do + local framework = hungerThirstFunc[i] + if isStarted(framework.framework) then + debugPrint("^4Debug^7: ^2Adding ^3"..framework.framework.." ^2stress^7: ^3"..stress.." ^2to player^7: ^3"..src.."^7") + framework.setStress(src, stress) + return + end + end +end + --- Server event handler for urinal usage. --- Decreases player's thirst by a random amount. RegisterNetEvent(getScript()..":server:Urinal", function() diff --git a/starter.lua b/starter.lua index 5a89cdd..1607f15 100644 --- a/starter.lua +++ b/starter.lua @@ -176,6 +176,7 @@ for _, v in pairs({ -- This is a specific load order 'scaleEntity.lua', 'vehicles.lua', 'effects.lua', + 'make/ropeControl.lua', --'warmenu.lua', '_authToken.lua', diff --git a/ui_modules/progressbar.lua b/ui_modules/progressbar.lua index 80a2947..2b8e29f 100644 --- a/ui_modules/progressbar.lua +++ b/ui_modules/progressbar.lua @@ -231,7 +231,6 @@ function gtaProgressBar(data) while result == nil do Wait(10) end inProgress = false - print(tostring(result)) -- Cleanup animations/tasks if data.dict then stopAnim(data.dict, data.anim, ped) @@ -240,7 +239,6 @@ function gtaProgressBar(data) ClearPedTasks(ped) end - -- Cleanup FreezeEntityPosition(ped, false) diff --git a/version.txt b/version.txt index c5bf465..a3e3140 100644 --- a/version.txt +++ b/version.txt @@ -1,13 +1,7 @@ -2.1.03 +2.1.05 -- Fix hunger and thirst math (add instead of set) -- Fix cleanup of targets qb-targets on unload -- Add support for new jim-shops exploit protection -- Add full support for esx_society (fixes esx bossmenu errors) -- Fix possible issue with qb-core /command registering -- Make ox_lib progressbars return false correctly -- Fix ox_inv players current weight checks -- User alternate function for inventory retreival for CodeM Inv -- Rework _loaders.lua tobe more organised/optimizied +- Fix "true/false" print from built-in gta progressbars +- Revert Crafting system to previous style (I was too quick to do this) +- Fix singleprogress multiplying crafting recipes https://github.com/jimathy/jim_bridge/releases/latest \ No newline at end of file