Compare commits

...

18 Commits

Author SHA1 Message Date
Jim Shield
45f390c177 Update playerfunctions.lua 2025-10-04 22:10:39 +01:00
Jim Shield
803c612cbb Change stress to take away instead of increase 2025-10-04 19:12:50 +01:00
Jim Shield
63bcb86ac1 ESX Fallback functions are very basic.
There aren't many native functions for what the script can do, if anyone has any better ideas

- go to `itemcontrol.lua`
- search `ESX` and tell me what could be done better/right
2025-10-04 01:31:14 +01:00
Jim Shield
0bc3c0f9b4 Update itemcontrol.lua 2025-10-03 17:44:29 +01:00
Jim Shield
672dd7684b Fix ESX authtoken callback saying possible exploit 2025-10-03 13:51:15 +01:00
Jim Shield
c040390d1f Start working on inventory fallback functions
NEEDS TESTING

A lot of ESX stuff not done yet
2025-10-03 12:22:05 +01:00
Jim Shield
26b1f36029 Version Bump
`2.1.04` - `2.1.05`
2025-10-02 12:24:27 +01:00
Jim Shield
331dc94e2f Revert Crafting
I realised in trying to increase readability and optimize, I broke crafting recipes that supported different multiple recipes in the same list

eg. Mining has 3 recipes in the smelter for `goldingot`

This reverts the system back to the previous recipe style but keeps all the fixes/features from the latest updates
2025-10-02 12:20:58 +01:00
Jim Shield
d1dab48acc Fix singleprogress multiplying crafting recipes
Adds a `cloneTable()` function to make a "deep copy" of the crafting recipes when calculating the multiple amounts to be taken when multicrafting
2025-10-02 00:14:13 +01:00
Jim Shield
7e595df143 Remove gta progressbar print from testing 2025-10-01 17:04:59 +01:00
Jim Shield
d791a2e09f quick fix converting old crafting style 2025-10-01 11:42:47 +01:00
Jim Shield
7738bb4fca Version Bump
`2.1.03` - `2.1.04`
2025-09-30 23:39:55 +01:00
Jim Shield
3ad65810c3 Rework crafting.lua
Changes:
- Crafting prop not spawning
- Crafting "sound" not playing
- `SingleProgress` is now "one progressbar"

I'm working on refactoring crafting table's layout style too

I'm updating my scripts crafting recipes to a new format, this commit basically makes use of it and makes it slightly easier to edit later if needed

It *should* increase readability and users ability to edit these recipes

# "old style" crafting recipes should still be compatible thanks to a wrapper I added so you shouldn't notice a difference.
2025-09-28 14:03:23 +01:00
Jim Shield
45eeaf8f0d update a couple debug prints 2025-09-28 13:48:17 +01:00
Jim Shield
37f7eb1116 add testing code for new crafting table style 2025-09-28 03:22:12 +01:00
Jim Shield
145e3aa156 add new module ropeControl.lua
**Needs work** but adds the ability to easily create ropes/wires from one coord to another
2025-09-27 13:01:09 +01:00
Jim Shield
bb176addbf Create setStress function
This allows per framework stress functions to be set, including ESX

For ESX the math is done in this function as their values are insanely high instead of 0 - 100
2025-09-27 12:35:56 +01:00
Jim Shield
178040348e Rework SingleProgress slightly
Realised it wasn't doing the math to take the correct amount of required items, so this fixes it

also if `SingleProgress` is enabled, it won't show the "Ingredient Progress bars" any more
2025-09-27 12:34:46 +01:00
10 changed files with 771 additions and 227 deletions

View File

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

View File

@@ -7,14 +7,22 @@ AuthEvent = nil
currentToken = nil
if isServer() then
local excludeRes = {
[Exports.QBExport] = true,
[Exports.ESXExport] = true,
[Exports.VorpExport] = true,
}
local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen()
validTokens = validTokens or {}
createCallback(AuthEvent, function(source)
local src = source
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
--debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= Exports.QBExport and GetInvokingResource() ~= Exports.VorpExport then
local invokingRes = GetInvokingResource()
debugPrint(invokingRes)
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return ""
end
@@ -47,9 +55,9 @@ if isServer() then
createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source
--debugPrint(GetInvokingResource())
local invokingRes = GetInvokingResource()
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= Exports.QBExport and GetInvokingResource() ~= Exports.VorpExport then
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7")
return ""
end

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,32 @@ function hasItem(items, amount, src)
end
end
end
-- Fallback to default hasItem function
if not hasTable or not next(hasTable) then
if isStarted(ESXExport) then
for item, amt in pairs(items) do
local count = 0
for itemName, amount in pairs(grabInv) do
if itemName and itemName == item then
count += (amount or 1)
end
end
hasTable[item] = { hasItem = count >= amt, count = count }
end
else
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
end
for _, v in pairs(hasTable) do
if not v.hasItem then
return false, hasTable
@@ -1334,12 +1360,41 @@ function 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
grabInv = triggerCallback(getScript()..":GetESXInv")
end
end
--jsonPrint(grabInv)
end
if grabInv == nil then
print("^4ERROR^7: ^2No Supported Inventory detected ^7- ^2Check ^3starter^1.^2lua^7")
end
return grabInv, foundInv
end
if isServer() then
createCallback(getScript()..":GetESXInv", function(source)
local src = source
local xPlayer = ESX.GetPlayerFromId(src)
local inv = xPlayer.getInventory()
return xPlayer.getInventory(src)
end)
end
function isInventoryOpen()
return IsNuiFocused()
@@ -1423,6 +1478,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 +1514,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 +1543,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
@@ -1513,14 +1579,21 @@ end
RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info, slot, token)
local excludeRes = {
[QBExport] = true,
[ESXExport] = true,
[VorpExport] = true,
}
local invokingRes = GetInvokingResource()
--debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return
end
if not Items[item] then
print("^6Bridge^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." '^3"..item.."^7' but it doesn't exist")
if not doesItemExist(item) then
print("^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." '^3"..item.."^7' but it doesn't exist")
return
end
@@ -1568,7 +1641,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 +1666,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 +1798,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 +1890,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 +1957,11 @@ function getMaxInvWeight()
break
end
end
-- Fallback function
if weight == 0 then
weight = InventoryWeight
end
return weight
end
@@ -1877,6 +1984,19 @@ function getCurrentInvWeight(src)
end
end
-- fallback function
if weight == 0 then
local itemcheck = getPlayerInv(src)
if isStarted(ESXExport) then
for _, amount in pairs(itemcheck) do
weight += (amount or 0)
end
else
for _, v in pairs(itemcheck) do
weight += ((v.weight * (v.amount or v.count)) or 0)
end
end
end
return weight
end
@@ -2185,7 +2305,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 +2320,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 +2356,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
@@ -2261,10 +2393,37 @@ function stashRemoveItem(stashItems, stashName, items)
end
end
-- Fallback to core functions
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)
function stashAddItem(stashItems, stashName, items)
-- wip
end
-------------------------------------------------------------
-- Stash Item Availability Check
-------------------------------------------------------------
@@ -2390,17 +2549,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 +2805,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()
@@ -139,10 +187,10 @@ end)
--- Server event handler for setting player needs (thirst or hunger).
---
--- @event
--- @param type string "thirst" or "hunger".
--- @param needType string "thirst" or "hunger".
--- @param amount number New value to set.
RegisterNetEvent(getScript()..":server:setNeed", function(needType, amount)
print("^1Deprecated function ':server:setNeed' called, please update your scripts^7")
print("^1Deprecated function ':server:setNeed' called, please update the scripts that use it^7")
-- local src = source
-- if needType == "thirst" then
-- setThirst(src, amount)

View File

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

View File

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

View File

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