Merge branch 'jimathy:main' into main

This commit is contained in:
oosayeroo
2025-10-03 13:42:13 +01:00
committed by GitHub
9 changed files with 715 additions and 221 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

255
shared/make/ropeControl.lua Normal file
View File

@@ -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

View File

@@ -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()