Compare commits

...

4 Commits

Author SHA1 Message Date
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
4 changed files with 238 additions and 197 deletions

View File

@@ -1,6 +1,6 @@
name "Jim_Bridge" name "Jim_Bridge"
author "Jimathy" author "Jimathy"
version "2.1.03" version "2.1.04"
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,9 +41,14 @@ local excludeKeys = {
--- craftable = { --- craftable = {
--- Header = "Weapon Crafting", --- Header = "Weapon Crafting",
--- Recipes = { --- Recipes = {
--- [1] = { --- weapon_pistol = {
--- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, --- id = 1,
--- amount = 1, --- ingredients = {
--- steel = 5, plastic = 5,
--- },
--- info = {
--- amount = 1,
--- },
--- }, --- },
--- -- More recipes... --- -- More recipes...
--- }, --- },
@@ -56,6 +62,7 @@ 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
@@ -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
-- 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
id += 1
if doesItemExist(l) then
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
-- Convert to array
local RecipesArray = {}
for k, v in pairs(data.craftable.Recipes) do
RecipesArray[v.id] = { [k] = v }
end
local Menu = {} local Menu = {}
local Recipes = data.craftable.Recipes local Recipes = data.craftable.Recipes
local craftedItems = {} local craftedItems = {}
local tempCarryTable = {}
-- Build a table of all required ingredients (default quantity is 1). local tempCarryTable = {}
for i = 1, #Recipes do -- Build a temporary table of all required ingredients (default quantity is 1).
for k in pairs(Recipes[i]) do for i = 1, #RecipesArray do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then for k, v in pairs(RecipesArray[i]) do
tempCarryTable[k] = Recipes[i].amount or 1 if doesItemExist(k) then
if not v.info.amount then v.info.amount = 1 end
tempCarryTable[k] = tempCarryTable[k] and (tempCarryTable[k] < v.info.amount) or v.info.amount
end end
end end
end end
@@ -97,75 +137,89 @@ function craftingMenu(data)
disabled = true, disabled = true,
} }
-- Process each recipe to create menu entries. for i = 1, #RecipesArray do
for i = 1, #Recipes do local item = ""
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end for k in pairs(RecipesArray[i]) do
for k, _ 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 break
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
end end
--Wait(0) end
local Recipe = RecipesArray[i][item]
-- Job Check
local hasGroup = true
if Recipe.info.job then
for l, b in pairs(Recipe.info.job) do
hasGroup = hasJob(l, nil, b)
if hasGroup then goto skipcheck end
end
end
if Recipe.info.gang then
for l, b in pairs(Recipe.info.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.info.metadata or Recipe.info.info or nil)
local itemTable = {}
local metaTable = {}
-- Build ingredient details.
for l, b in pairs(Recipe.ingredients) 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.info.amount > 1 and " x"..Recipe.info.amount or "")
local statusEmoji = disable and " " or not canCarryTable[item] and " 📦" or " ✔️"
local isNew = (Recipe.info.hasCrafted ~= nil and craftedItems[item] == nil) and "" or ""
setheader = isNew .. setheader .. statusEmoji
-- Build menu option using info
Menu[#Menu + 1] = {
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 = RecipesArray[i][item],
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 end
end end
-- open context menu
openMenu(Menu, { openMenu(Menu, {
header = data.craftable.Header, header = data.craftable.Header,
headertxt = data.craftable.Headertxt, headertxt = data.craftable.Headertxt,
@@ -216,7 +270,7 @@ function multiCraft(data)
for i = 1, maxCreation do for i = 1, maxCreation do
multiItemTable[i] = {} multiItemTable[i] = {}
for l, b in pairs(data.craft[data.item]) do for l, b in pairs(data.craft.ingredients) do
multiItemTable[i][l] = (b * i) multiItemTable[i][l] = (b * i)
end end
end end
@@ -337,112 +391,62 @@ 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
jsonPrint(prop)
local canReturn = true local canReturn = true
local crafted, crafting = true, true local crafted, crafting = true, true
local cam = createTempCam(Ped, 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 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.ingredients) do
if not excludeKeys[k] and type(v) == "table" then if isInventoryOpen() then
for l, b in pairs(v) do print("^1Error^7: ^2Inventory is open, you tried to break things")
if isInventoryOpen() then stopCam(0)
print("^1Error^7: ^2Inventory is open, you tried to break things") ClearPedTasks(Ped)
stopTempCam() if canReturn then craftingMenu(data) end
ClearPedTasks(Ped) CraftLock = false
if canReturn then craftingMenu(data) end return
CraftLock = false
return
end
if crafting and progressBar({
label = "Using "..b.." "..getItemLabel(l),
time = 800,
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
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 end
end
end
if not crafted then if not crafted then
stopTempCam() stopCam(0)
ClearPedTasks(Ped) ClearPedTasks(Ped)
if canReturn then craftingMenu(data) end if canReturn then craftingMenu(data) end
CraftLock = false CraftLock = false
return 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, 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 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.craftable.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
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,
@@ -455,31 +459,73 @@ 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.info.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.info.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", Ped, "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 if data.requiredItemfunc then
data.requiredItemfunc() data.requiredItemfunc()
end end
else else
break break
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.info.amount = craftAmount
for k, v in pairs(data.craft.ingredients) do
-- multiply igredient requirement in sent crafting table for removal
data.craft.ingredients[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.info.hasCrafted ~= nil then
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft.info.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
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
end end
end end
if craftProp then destroyProp(craftProp) end
Wait(500) --Wait(500)
stopTempCam() stopCam(0)
CraftLock = false CraftLock = false
if canReturn then craftingMenu(data) end if canReturn then craftingMenu(data) end
ClearPedTasks(Ped) ClearPedTasks(Ped)
@@ -512,14 +558,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.ingredients) 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
@@ -529,7 +574,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.ingredients) 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
@@ -540,12 +585,12 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
stashRemoveItem(stashItems, stashName, itemRemove) stashRemoveItem(stashItems, stashName, itemRemove)
else else
if craftable then if craftable then
for k, v in pairs(craftable[ItemMake] or {}) do for k, v in pairs(craftable.ingredients or {}) do
removeItem(tostring(k), v, src) removeItem(tostring(k), v, src)
end end
end end
end end
addItem(ItemMake, craftable.amount or 1, metadata, src) addItem(ItemMake, craftable.info.amount or 1, metadata, src)
-- Optionally, add experience here: -- Optionally, add experience here:
-- for example: -- for example:
-- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end -- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end

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)

View File

@@ -1,13 +1,9 @@
2.1.03 2.1.04
- Fix hunger and thirst math (add instead of set) - Crafting fixes (prop/sound/remove ingredients)
- Fix cleanup of targets qb-targets on unload - "SingleProgress" is now one progressbar
- Add support for new jim-shops exploit protection - Add ropeControl.lua - to help create and edit custom ropes/wires
- Add full support for esx_society (fixes esx bossmenu errors) - Support for new crafting recipe style
- Fix possible issue with qb-core /command registering - create setStress() multiframework function
- 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