Compare commits

...

12 Commits

Author SHA1 Message Date
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
9 changed files with 580 additions and 210 deletions

View File

@@ -1,6 +1,6 @@
name "Jim_Bridge" name "Jim_Bridge"
author "Jimathy" author "Jimathy"
version "2.1.03" version "2.1.05"
description "Framework Bridge By Jimathy" description "Framework Bridge By Jimathy"
fx_version "cerulean" 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.' 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

@@ -17,7 +17,8 @@ local excludeKeys = {
amount = true, metadata = true, description = true, info = true, amount = true, metadata = true, description = true, info = true,
job = true, gang = true, oneUse = true, slot = true, job = true, gang = true, oneUse = true, slot = true,
blueprintRef = true, craftingLevel = true, craftedItems = 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,10 +41,15 @@ local excludeKeys = {
--- craftable = { --- craftable = {
--- Header = "Weapon Crafting", --- Header = "Weapon Crafting",
--- Recipes = { --- Recipes = {
--- [1] = { --- weapon_pistol = {
--- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, --- id = 1,
--- ingredients = {
--- steel = 5, plastic = 5,
--- },
--- info = {
--- amount = 1, --- amount = 1,
--- }, --- },
--- },
--- -- More recipes... --- -- More recipes...
--- }, --- },
--- Anims = { --- Anims = {
@@ -56,9 +62,10 @@ local excludeKeys = {
--- job = "mechanic", --- job = "mechanic",
--- onBack = function() print("Returning to previous menu") end, --- onBack = function() print("Returning to previous menu") end,
--- }) --- })
--- ```
function craftingMenu(data) function craftingMenu(data)
if CraftLock then return end if CraftLock then return end
local data = cloneTable(data)
-- Job or gang check; exit if not authorized. -- 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 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. -- Normalize stash name.
data.stashName = data.stashTable or data.stashName data.stashName = data.stashTable or data.stashName
local Menu = {} -- Wrapper to convert old style crafting recipes to be handled by the menu properly
local Recipes = data.craftable.Recipes --if data.craftable.Recipes[1] then -- assume old style crafting table
local craftedItems = {} -- local compatTable = {}
local tempCarryTable = {} -- 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 i = 1, #Recipes do
for k in pairs(Recipes[i]) do for k, v in pairs(Recipes[i]) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then if not excludeKeys[k] then
tempCarryTable[k] = Recipes[i].amount or 1 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 end
end end
@@ -96,25 +136,42 @@ function craftingMenu(data)
header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"), header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"),
disabled = true, disabled = true,
} }
-- Process each recipe to create menu entries.
for i = 1, #Recipes do for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end local menuId = #Menu+1
for k, _ in pairs(Recipes[i]) do local item = ""
local Recipe = {}
for k, v in pairs(Recipes[i]) do
if not excludeKeys[k] then if not excludeKeys[k] then
local hasjob = true item = k
if Recipes[i].job then Recipe = Recipes[i]
for l, b in pairs(Recipes[i].job) do Recipe.amount = Recipe.amount or 1
hasjob = hasJob(l, nil, b) break
if hasjob then break end
end end
end end
if hasjob then
local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil) -- 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 itemTable = {}
local metaTable = {} local metaTable = {}
-- Build ingredient details. -- Build ingredient details.
for l, b in pairs(Recipes[i][tostring(k)]) do for l, b in pairs(Recipe[item]) do
local label = getItemLabel(l) local label = getItemLabel(l)
local hasItem = checkStashItem(data.stashName, { [l] = b }) local hasItem = checkStashItem(data.stashName, { [l] = b })
local missingMark = not hasItem and "" or " " local missingMark = not hasItem and "" or " "
@@ -124,31 +181,32 @@ function craftingMenu(data)
itemTable[l] = b itemTable[l] = b
end end
while not canCarryTable do -- Make sure "canCarryTable" exists
Wait(10) while not canCarryTable do Wait(10) end
end
disable = not checkStashItem(data.stashName, itemTable) disable = not checkStashItem(data.stashName, itemTable)
setheader = ((metadata and metadata.label) or getItemLabel(k)) setheader = ((metadata and metadata.label) or getItemLabel(item))
..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "") ..(Recipe.amount > 1 and " x"..Recipe.amount or "")
local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️" local statusEmoji = disable and " " or not canCarryTable[item] and " 📦" or " ✔️"
local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or "" local isNew = (Recipe.hasCrafted ~= nil and craftedItems[item] == nil) and "" or ""
setheader = isNew .. setheader .. statusEmoji setheader = isNew .. setheader .. statusEmoji
Menu[#Menu + 1] = { -- Build menu option using info
arrow = isOx() and (not disable and canCarryTable[k]), Menu[menuId] = {
isMenuHeader = disable or not canCarryTable[k], arrow = isOx() and (not disable and canCarryTable[item]),
icon = invImg((metadata and metadata.image) or tostring(k)), isMenuHeader = disable or not canCarryTable[item],
image = invImg((metadata and metadata.image) or tostring(k)), icon = invImg((metadata and metadata.image) or item),
image = invImg((metadata and metadata.image) or item),
header = setheader, header = setheader,
txt = settext or nil, txt = settext or nil,
metadata = metaTable, metadata = metaTable,
onSelect = (not disable and canCarryTable[k]) and function() onSelect = (not disable and canCarryTable[item]) and function()
local transdata = { local transdata = {
item = k, item = item,
craft = data.craftable.Recipes[i], craft = Recipe,
craftable = data.craftable, craftable = data.craftable,
coords = data.coords, coords = data.coords,
amount = Recipe.amount,
stashName = data.stashName, stashName = data.stashName,
onBack = data.onBack, onBack = data.onBack,
metadata = metadata, metadata = metadata,
@@ -162,10 +220,7 @@ function craftingMenu(data)
} }
end end
end end
--Wait(0) -- open context menu
end
end
openMenu(Menu, { openMenu(Menu, {
header = data.craftable.Header, header = data.craftable.Header,
headertxt = data.craftable.Headertxt, 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 if CraftLock then return end
CraftLock = true CraftLock = true
data.stashName = data.stashTable or data.stashName 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 craftAmount = (data.amount and data.amount ~= 1) and data.amount or 1
local metadata = data.metadata or nil local metadata = data.metadata or nil
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
local canReturn = true local canReturn = true
local crafted, crafting = true, true local crafted, crafting = true, true
local cam = createTempCam(PlayerPedId(), data.coords) local cam = createCam(Ped, data.coords.xyz)
startTempCam(cam) startCam(cam, 5000)
-- Calculate total bartime if SingleProgress is enabled -- Calculate total bartime if SingleProgress is enabled
local totalBartime = bartime * craftAmount local totalBartime = (bartime * craftAmount)
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 data.sound then
local s = data.sound
PlaySoundFromEntity(s.soundId, s.audioName, Ped, s.audioRef, true, 0)
end
if not Config.Crafting.SingleProgress then -- if SingleProgress is disabled, dont do ingredient progressbars
-- Run ingredient check and usage separately first -- Run ingredient check and usage separately first
for i = 1, craftAmount do for i = 1, craftAmount do
for k, v in pairs(data.craft) do for k, v in pairs(data.craft[data.item]) do
if not excludeKeys[k] and type(v) == "table" then
for l, b in pairs(v) do
if isInventoryOpen() then if isInventoryOpen() then
print("^1Error^7: ^2Inventory is open, you tried to break things") print("^1Error^7: ^2Inventory is open, you tried to break things")
stopTempCam() stopCam(0)
ClearPedTasks(PlayerPedId()) ClearPedTasks(Ped)
if canReturn then craftingMenu(data) end if canReturn then craftingMenu(origData) end
CraftLock = false CraftLock = false
return return
end end
if crafting and progressBar({ if crafting and progressBar({
label = "Using "..b.." "..getItemLabel(l), label = "Using "..v.." "..getItemLabel(k),
time = 1000, time = 800,
cancel = true, cancel = true,
dict = 'pickup_object', dict = 'pickup_object',
anim = "putdown_low", anim = "putdown_low",
flag = 49, flag = 49,
icon = l, icon = k,
}) then }) then
TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b) TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[k], "use", v)
else else
crafted, crafting = false, false crafted, crafting = false, false
break break
end end
Wait(200) Wait(200)
end end
end
end
end
if not crafted then if not crafted then
stopTempCam() goto finishEarly
ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(data) end
CraftLock = false
return
end 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
for i = 1, craftAmount do
if crafting and progressBar({ if crafting and progressBar({
label = bartext..((metadata and metadata.label) or getItemLabel(data.item)), label = bartext..((metadata and metadata.label) or getItemLabel(data.item)),
time = bartime, time = bartime,
@@ -448,34 +459,75 @@ function makeItem(data)
}) then }) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken) TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil currentToken = nil
if data.craft["hasCrafted"] ~= nil then if data.craft.hasCrafted ~= nil then
data.craftable.craftedItems[data.item] = true data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems) triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end end
if data.craft["exp"] ~= nil then if data.craft.exp ~= nil then
craftingLevel += data.craft["exp"].give craftingLevel += data.craft.exp.give
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel) triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end end
if data.craftable.Recipes[1].oneUse == true then if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot) removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId() 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 canReturn = false
end end
if data.requiredItemfunc then
data.requiredItemfunc()
end
else else
break break
end end
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
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then
data.requiredItemfunc()
end end
Wait(500) --Wait(500)
stopTempCam() stopCam(0)
CraftLock = false CraftLock = false
if canReturn then craftingMenu(data) end if canReturn then craftingMenu(origData) end
ClearPedTasks(PlayerPedId()) ClearPedTasks(Ped)
end end
@@ -505,14 +557,13 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
return return
end end
local hasItems, hasTable = hasItem(ItemMake, 1, src) local hasItems, hasTable = hasItem(ItemMake, 1, src)
if stashName then if stashName then
local itemRemove = {} local itemRemove = {}
if type(stashName) == "table" then if type(stashName) == "table" then
for _, name in pairs(stashName) do for _, name in pairs(stashName) do
stashItems = getStash(name) 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 for _, b in pairs(stashItems or {}) do
if k == b.name then if k == b.name then
itemRemove[k] = v itemRemove[k] = v
@@ -522,7 +573,7 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
end end
else else
stashItems = getStash(stashName) 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 for _, b in pairs(stashItems or {}) do
if k == b.name then if k == b.name then
itemRemove[k] = v itemRemove[k] = v
@@ -532,8 +583,8 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
end end
stashRemoveItem(stashItems, stashName, itemRemove) stashRemoveItem(stashItems, stashName, itemRemove)
else else
if craftable then if craftable[ItemMake] then
for k, v in pairs(craftable[ItemMake] or {}) do for k, v in pairs(craftable[ItemMake]) do
removeItem(tostring(k), v, src) removeItem(tostring(k), v, src)
end end
end end

View File

@@ -651,6 +651,29 @@ function lookEnt(entity)
end end
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 -- Material and Prop Functions

View File

@@ -1568,7 +1568,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
end end
else 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 end
else else
@@ -1593,7 +1593,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd) ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd)
end end
else 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 end
end) end)
@@ -2390,17 +2390,17 @@ if isServer() then
end end
RegisterNetEvent(getScript()..":openGrabBox", function(data) RegisterNetEvent(getScript()..":openGrabBox", function(data)
local Ped = PlayerPedId()
local id = data.metadata and data.metadata.id or data.info and data.info.id or ""
if isStarted(OXInv) then if isStarted(OXInv) then
return return
end end
local id = ""
if data.metadata then
id = data.metadata.id
elseif data.info then
id = data.info.id
end
openStash({ openStash({
stash = id, stash = id,
coords = GetEntityCoords(Ped)
}) })
end) end)

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) Player.Functions.SetMetaData('hunger', newHunger)
TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst) TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst)
end, 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, { framework = QBExport,
@@ -48,6 +56,14 @@ local hungerThirstFunc = {
Player.Functions.SetMetaData('hunger', newHunger) Player.Functions.SetMetaData('hunger', newHunger)
TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst) TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst)
end, 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, { framework = RSGExport,
@@ -67,6 +83,14 @@ local hungerThirstFunc = {
Player.Functions.SetMetaData('hunger', newHunger) Player.Functions.SetMetaData('hunger', newHunger)
TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst) TriggerClientEvent("hud:client:UpdateNeeds", src, newHunger, Player.PlayerData.metadata.thirst)
end, 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, { framework = ESXExport,
@@ -78,6 +102,10 @@ local hungerThirstFunc = {
function(src, hunger) function(src, hunger)
TriggerClientEvent('esx_status:add', src, 'hunger', tonumber(hunger) * 10000) TriggerClientEvent('esx_status:add', src, 'hunger', tonumber(hunger) * 10000)
end, 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
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. --- Server event handler for urinal usage.
--- Decreases player's thirst by a random amount. --- Decreases player's thirst by a random amount.
RegisterNetEvent(getScript()..":server:Urinal", function() RegisterNetEvent(getScript()..":server:Urinal", function()

View File

@@ -176,6 +176,7 @@ for _, v in pairs({ -- This is a specific load order
'scaleEntity.lua', 'scaleEntity.lua',
'vehicles.lua', 'vehicles.lua',
'effects.lua', 'effects.lua',
'make/ropeControl.lua',
--'warmenu.lua', --'warmenu.lua',
'_authToken.lua', '_authToken.lua',

View File

@@ -231,7 +231,6 @@ function gtaProgressBar(data)
while result == nil do Wait(10) end while result == nil do Wait(10) end
inProgress = false inProgress = false
print(tostring(result))
-- Cleanup animations/tasks -- Cleanup animations/tasks
if data.dict then if data.dict then
stopAnim(data.dict, data.anim, ped) stopAnim(data.dict, data.anim, ped)
@@ -240,7 +239,6 @@ function gtaProgressBar(data)
ClearPedTasks(ped) ClearPedTasks(ped)
end end
-- Cleanup -- Cleanup
FreezeEntityPosition(ped, false) 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 "true/false" print from built-in gta progressbars
- Fix cleanup of targets qb-targets on unload - Revert Crafting system to previous style (I was too quick to do this)
- Add support for new jim-shops exploit protection - Fix singleprogress multiplying crafting recipes
- 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
https://github.com/jimathy/jim_bridge/releases/latest https://github.com/jimathy/jim_bridge/releases/latest