2025-03-08 13:44:05 +00:00
|
|
|
--[[
|
|
|
|
|
Crafting, Selling, and Shop Module
|
|
|
|
|
-----------------------------------
|
|
|
|
|
This module provides functions for opening crafting menus, handling multi-crafting,
|
|
|
|
|
performing the crafting process (with animations and progress bars), selling items,
|
|
|
|
|
and opening shop interfaces. It integrates with various inventory and menu systems,
|
|
|
|
|
and uses server callbacks to check item carry capacity.
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Global Variables
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
CraftLock = false
|
|
|
|
|
|
2025-03-26 21:43:48 +00:00
|
|
|
-- helper filter table for crafting menus
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Crafting Menu
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
--- Opens the crafting menu based on provided data.
|
|
|
|
|
--- Checks job restrictions, builds the recipe menu, and opens the menu.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @param data table Crafting menu configuration containing:
|
|
|
|
|
--- - craftable (`table`) Table with Header, Recipes, Anims, and (optionally) craftedItems.
|
|
|
|
|
--- - coords (`vector3`) The coordinates where the crafting menu is being opened.
|
|
|
|
|
--- - stashTable|stashName (`string\table`) Name(s) of the stash for checking item availability.
|
|
|
|
|
--- - job|gang (`string`) Job or gang requirements.
|
|
|
|
|
--- - onBack (optional): Function to call when returning.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @usage
|
2025-03-01 12:58:43 +00:00
|
|
|
--- ```lua
|
2025-04-22 19:16:31 +01:00
|
|
|
--- craftingMenu({
|
|
|
|
|
--- craftable = {
|
|
|
|
|
--- Header = "Weapon Crafting",
|
|
|
|
|
--- Recipes = {
|
|
|
|
|
--- [1] = {
|
|
|
|
|
--- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
|
|
|
|
|
--- amount = 1,
|
|
|
|
|
--- },
|
|
|
|
|
--- -- More recipes...
|
|
|
|
|
--- },
|
|
|
|
|
--- Anims = {
|
|
|
|
|
--- animDict = "amb@prop_human_parking_meter@male@idle_a",
|
|
|
|
|
--- anim = "idle_a",
|
|
|
|
|
--- },
|
|
|
|
|
--- },
|
|
|
|
|
--- coords = vector3(100.0, 200.0, 300.0),
|
|
|
|
|
--- stashTable = "crafting_stash",
|
|
|
|
|
--- job = "mechanic",
|
|
|
|
|
--- onBack = function() print("Returning to previous menu") end,
|
|
|
|
|
--- })
|
2025-03-01 12:58:43 +00:00
|
|
|
function craftingMenu(data)
|
|
|
|
|
if CraftLock then return end
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-- Job or gang check; exit if not authorized.
|
2025-03-01 12:58:43 +00:00
|
|
|
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-- Display a temporary "thinking" notification.
|
2025-03-01 12:58:43 +00:00
|
|
|
if Config.System.Menu == "jim" then
|
|
|
|
|
triggerNotify(nil, "Thinking", "info")
|
|
|
|
|
else
|
|
|
|
|
openMenu({ { header = "Thinking...", icon = "fas fa-hourglass-end", isMenuHeader = true } }, { header = "Crafting Menu" } )
|
|
|
|
|
end
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-- Normalize stash name.
|
2025-03-07 13:30:17 +00:00
|
|
|
data.stashName = data.stashTable or data.stashName
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
local Menu = {}
|
2025-03-01 12:58:43 +00:00
|
|
|
local Recipes = data.craftable.Recipes
|
2025-03-07 13:30:17 +00:00
|
|
|
local craftedItems = {}
|
2025-03-01 12:58:43 +00:00
|
|
|
local tempCarryTable = {}
|
2025-03-08 13:44:05 +00:00
|
|
|
|
|
|
|
|
-- Build a table of all required ingredients (default quantity is 1).
|
2025-03-01 12:58:43 +00:00
|
|
|
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
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-- Check if the player can carry the required items (server callback).
|
2025-03-01 12:58:43 +00:00
|
|
|
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
|
2025-06-14 14:44:16 +01:00
|
|
|
|
2025-07-04 15:17:56 +01:00
|
|
|
local usingStash = data.stashName ~= nil
|
|
|
|
|
|
|
|
|
|
Menu[#Menu+1] = {
|
|
|
|
|
icon = usingStash and "fas fa-boxes-stacked" or "fas fa-person",
|
|
|
|
|
header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"),
|
|
|
|
|
disabled = true,
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-- Process each recipe to create menu entries.
|
2025-03-01 12:58:43 +00:00
|
|
|
for i = 1, #Recipes do
|
|
|
|
|
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
|
2025-03-08 13:44:05 +00:00
|
|
|
for k, _ in pairs(Recipes[i]) do
|
2025-03-06 23:49:07 +00:00
|
|
|
if not excludeKeys[k] then
|
2025-03-08 13:44:05 +00:00
|
|
|
local hasjob = true
|
2025-03-01 12:58:43 +00:00
|
|
|
if Recipes[i].job then
|
|
|
|
|
for l, b in pairs(Recipes[i].job) do
|
|
|
|
|
hasjob = hasJob(l, nil, b)
|
2025-03-08 13:44:05 +00:00
|
|
|
if hasjob then break end
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
2025-03-06 23:49:07 +00:00
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
if hasjob then
|
2025-03-08 13:44:05 +00:00
|
|
|
local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil)
|
2025-03-01 12:58:43 +00:00
|
|
|
local itemTable = {}
|
|
|
|
|
local metaTable = {}
|
2025-03-08 13:44:05 +00:00
|
|
|
-- Build ingredient details.
|
2025-03-01 12:58:43 +00:00
|
|
|
for l, b in pairs(Recipes[i][tostring(k)]) do
|
2025-06-14 14:44:16 +01:00
|
|
|
local label = Items[l] and Items[l].label or "error - "..l
|
|
|
|
|
local hasItem = checkStashItem(data.stashName, { [l] = b })
|
|
|
|
|
local missingMark = not hasItem and " ❌" or " "
|
2025-06-19 22:18:53 +01:00
|
|
|
settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark
|
2025-06-14 14:44:16 +01:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
metaTable[Items[l] and Items[l].label or "error - "..l] = b
|
|
|
|
|
itemTable[l] = b
|
|
|
|
|
end
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
while not canCarryTable do Wait(0) end
|
2025-04-22 19:16:31 +01:00
|
|
|
disable = not checkStashItem(data.stashName, itemTable)
|
2025-03-08 13:44:05 +00:00
|
|
|
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k))
|
|
|
|
|
..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-06-14 14:44:16 +01:00
|
|
|
local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️"
|
2025-05-05 12:24:38 +01:00
|
|
|
local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "✨ " or ""
|
|
|
|
|
setheader = isNew .. setheader .. statusEmoji
|
2025-03-08 13:44:05 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
Menu[#Menu + 1] = {
|
2025-06-14 14:44:16 +01:00
|
|
|
arrow = isOx() and (not disable and canCarryTable[k]),
|
2025-03-06 23:49:07 +00:00
|
|
|
isMenuHeader = disable or not canCarryTable[k],
|
2025-03-01 12:58:43 +00:00
|
|
|
icon = invImg((metadata and metadata.image) or tostring(k)),
|
|
|
|
|
image = invImg((metadata and metadata.image) or tostring(k)),
|
2025-05-05 12:24:38 +01:00
|
|
|
header = setheader,
|
2025-06-14 14:44:16 +01:00
|
|
|
txt = settext or nil,
|
2025-03-01 12:58:43 +00:00
|
|
|
metadata = metaTable,
|
2025-03-08 13:44:05 +00:00
|
|
|
onSelect = (not disable and canCarryTable[k]) and function()
|
2025-03-06 23:49:07 +00:00
|
|
|
local transdata = {
|
|
|
|
|
item = k,
|
|
|
|
|
craft = data.craftable.Recipes[i],
|
|
|
|
|
craftable = data.craftable,
|
|
|
|
|
coords = data.coords,
|
|
|
|
|
stashName = data.stashName,
|
|
|
|
|
onBack = data.onBack,
|
2025-03-08 13:44:05 +00:00
|
|
|
metadata = metadata,
|
2025-03-06 23:49:07 +00:00
|
|
|
}
|
|
|
|
|
if Config.Crafting.MultiCraft then
|
|
|
|
|
multiCraft(transdata)
|
|
|
|
|
else
|
|
|
|
|
makeItem(transdata)
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
end or nil,
|
2025-03-01 12:58:43 +00:00
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-05-05 12:24:38 +01:00
|
|
|
--Wait(0)
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
end
|
2025-03-06 23:49:07 +00:00
|
|
|
|
|
|
|
|
openMenu(Menu, {
|
|
|
|
|
header = data.craftable.Header,
|
|
|
|
|
headertxt = data.craftable.Headertxt,
|
|
|
|
|
onBack = data.onBack or nil,
|
|
|
|
|
canClose = true,
|
2025-03-08 20:55:05 +00:00
|
|
|
onExit = data.onExit or (function() end),
|
2025-03-06 23:49:07 +00:00
|
|
|
})
|
2025-03-01 12:58:43 +00:00
|
|
|
lookEnt(data.coords)
|
|
|
|
|
end
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Multi-Craft Menu
|
|
|
|
|
-------------------------------------------------------------
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
--- Opens a menu for selecting the quantity to craft.
|
|
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Presents the player with multiple crafting quantities based on Config.Crafting.MultiCraftAmounts.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @param data table Crafting configuration containing:
|
|
|
|
|
--- - item `string`) The item to craft.
|
|
|
|
|
--- - craft (`table`) The crafting recipe.
|
|
|
|
|
--- - craftable (`table`) Crafting options.
|
|
|
|
|
--- - coords (`vector3`) where crafting occurs.
|
|
|
|
|
--- - stashName (`string`) The stash name(s) for item availability.
|
|
|
|
|
--- - onBack (`function`) Callback when returning.
|
|
|
|
|
--- - metadata (`table`) (optional): Metadata for the crafted item.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @usage
|
2025-03-01 12:58:43 +00:00
|
|
|
--- ```lua
|
|
|
|
|
--- multiCraft({
|
|
|
|
|
--- item = "weapon_pistol",
|
|
|
|
|
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
|
|
|
|
|
--- craftable = craftingOptions,
|
2025-03-08 13:44:05 +00:00
|
|
|
--- coords = vector3(100,200,300),
|
2025-03-01 12:58:43 +00:00
|
|
|
--- stashName = "crafting_stash",
|
|
|
|
|
--- onBack = function() craftingMenu(data) end,
|
|
|
|
|
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
|
|
|
|
|
--- })
|
|
|
|
|
--- ```
|
|
|
|
|
function multiCraft(data)
|
2025-03-26 21:43:48 +00:00
|
|
|
local max = 0
|
|
|
|
|
local stashName = nil
|
2025-05-05 12:24:38 +01:00
|
|
|
local maxCreation = 100
|
|
|
|
|
|
|
|
|
|
-- Generate item table to check against stash or inventory
|
|
|
|
|
-- takes into account the ingredients needed for multiple items
|
|
|
|
|
local multiItemTable = {}
|
|
|
|
|
|
|
|
|
|
for i = 1, maxCreation do
|
|
|
|
|
multiItemTable[i] = {}
|
2025-03-01 12:58:43 +00:00
|
|
|
for l, b in pairs(data.craft[data.item]) do
|
2025-05-05 12:24:38 +01:00
|
|
|
multiItemTable[i][l] = (b * i)
|
2025-03-26 21:43:48 +00:00
|
|
|
end
|
2025-05-05 12:24:38 +01:00
|
|
|
end
|
2025-03-26 21:43:48 +00:00
|
|
|
|
2025-05-05 12:24:38 +01:00
|
|
|
for i = 1, maxCreation do
|
|
|
|
|
-- if its received a stash name, check if the items are in the stash
|
2025-03-26 21:43:48 +00:00
|
|
|
if data.stashName then
|
2025-05-05 12:24:38 +01:00
|
|
|
local hasItems, stashname = checkStashItem(data.stashName, multiItemTable[i])
|
2025-03-26 21:43:48 +00:00
|
|
|
if hasItems == true then
|
|
|
|
|
max += 1
|
|
|
|
|
stashName = stashname
|
|
|
|
|
else
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
else
|
2025-05-05 12:24:38 +01:00
|
|
|
-- if not check the players inventory for the items
|
|
|
|
|
local has, _ = hasItem(multiItemTable[i], nil, nil)
|
2025-03-26 21:43:48 +00:00
|
|
|
if has then
|
|
|
|
|
max += 1
|
|
|
|
|
else
|
|
|
|
|
break
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
|
2025-05-06 00:17:07 +01:00
|
|
|
local carryMax = triggerCallback(getScript()..":server:getMaxCarryCraft", {
|
|
|
|
|
item = data.item,
|
|
|
|
|
max = max
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
local dialog = createInput(data.craftable.Header..(Config.System.Menu == "qb" and ": "..br.."How many to craft? "..br.."Max: "..carryMax or ""), {
|
2025-03-26 21:43:48 +00:00
|
|
|
((Config.System.Menu == "ox") and {
|
|
|
|
|
type = "slider",
|
2025-05-06 00:17:07 +01:00
|
|
|
label = "How many to craft? "..br.."Max: "..carryMax,
|
2025-03-26 21:43:48 +00:00
|
|
|
required = true,
|
|
|
|
|
default = 1,
|
|
|
|
|
min = 1,
|
2025-05-06 00:17:07 +01:00
|
|
|
max = carryMax
|
2025-03-26 21:43:48 +00:00
|
|
|
}) or nil,
|
|
|
|
|
((Config.System.Menu == "qb") and {
|
|
|
|
|
type = "number",
|
|
|
|
|
name = "amount",
|
2025-05-06 00:17:07 +01:00
|
|
|
isRequired = true,
|
2025-03-26 21:43:48 +00:00
|
|
|
default = 1,
|
|
|
|
|
}) or nil,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if dialog then
|
|
|
|
|
if Config.System.Menu == "ox" then
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
if Config.System.Menu == "qb" then
|
2025-05-05 22:52:46 +01:00
|
|
|
dialog["amount"] = tonumber(dialog["amount"])
|
2025-05-06 00:17:07 +01:00
|
|
|
if dialog["amount"] > carryMax or dialog["amount"] < 1 or dialog["amount"] == nil or dialog["amount"] == "" then
|
2025-03-26 21:43:48 +00:00
|
|
|
triggerNotify(nil, "Invalid Amount", "error")
|
|
|
|
|
craftingMenu(data)
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
makeItem({
|
|
|
|
|
item = data.item,
|
|
|
|
|
craft = data.craft,
|
|
|
|
|
craftable = data.craftable,
|
|
|
|
|
amount = dialog["amount"] or dialog[1],
|
|
|
|
|
coords = data.coords,
|
|
|
|
|
stashName = stashName or nil,
|
|
|
|
|
onBack = data.onBack,
|
|
|
|
|
metadata = data.metadata,
|
|
|
|
|
})
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Crafting Process
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
--- Initiates the crafting process for a specified item.
|
|
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Plays crafting animations, shows progress bars, removes ingredients, and triggers item creation.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @param data table Crafting configuration containing:
|
|
|
|
|
--- - item `string`) The item to craft.
|
|
|
|
|
--- - craft (`table`) The crafting recipe.
|
|
|
|
|
--- - craftable (`table`) Crafting options.
|
|
|
|
|
--- - amount (`number`) (optional): Quantity to craft (default 1).
|
|
|
|
|
--- - coords (`vector3`) where crafting occurs.
|
|
|
|
|
--- - stashName (`string`) The stash name(s) for item availability.
|
|
|
|
|
--- - onBack (`function`) Callback when returning.
|
|
|
|
|
--- - metadata (`table`) (optional): Metadata for the crafted item.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @usage
|
2025-03-01 12:58:43 +00:00
|
|
|
--- ```lua
|
|
|
|
|
--- makeItem({
|
|
|
|
|
--- item = "weapon_pistol",
|
|
|
|
|
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
|
|
|
|
|
--- craftable = craftingOptions,
|
|
|
|
|
--- amount = 2,
|
2025-03-08 13:44:05 +00:00
|
|
|
--- coords = vector3(100,200,300),
|
2025-03-01 12:58:43 +00:00
|
|
|
--- stashName = "crafting_stash",
|
|
|
|
|
--- onBack = function() craftingMenu(data) end,
|
|
|
|
|
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
|
|
|
|
|
--- })
|
|
|
|
|
--- ```
|
|
|
|
|
function makeItem(data)
|
|
|
|
|
if CraftLock then return end
|
|
|
|
|
CraftLock = true
|
2025-03-08 13:44:05 +00:00
|
|
|
data.stashName = data.stashTable or data.stashName
|
|
|
|
|
|
|
|
|
|
local bartime = (data.craftable.progressBar and data.craftable.progressBar.time) or 5000
|
|
|
|
|
local bartext = (data.craftable.progressBar and data.craftable.progressBar.label)
|
|
|
|
|
or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"])
|
|
|
|
|
or "Making "
|
|
|
|
|
local animDict = (data.craftable.Anims and data.craftable.Anims.animDict) or "amb@prop_human_parking_meter@male@idle_a"
|
|
|
|
|
local anim = (data.craftable.Anims and data.craftable.Anims.anim) or "idle_a"
|
|
|
|
|
local craftAmount = (data.amount and data.amount ~= 1) and data.amount or 1
|
2025-03-01 12:58:43 +00:00
|
|
|
local metadata = data.metadata or nil
|
|
|
|
|
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
|
2025-03-06 23:49:07 +00:00
|
|
|
local canReturn = true
|
|
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
local crafted, crafting = true, true
|
|
|
|
|
local cam = createTempCam(PlayerPedId(), data.coords)
|
|
|
|
|
startTempCam(cam)
|
2025-03-08 13:44:05 +00:00
|
|
|
for i = 1, craftAmount do
|
2025-03-01 12:58:43 +00:00
|
|
|
for k, v in pairs(data.craft) do
|
2025-03-06 23:49:07 +00:00
|
|
|
if not excludeKeys[k] then
|
2025-03-01 12:58:43 +00:00
|
|
|
if type(v) == "table" then
|
|
|
|
|
for l, b in pairs(v) do
|
2025-05-06 00:17:07 +01:00
|
|
|
if isInventoryOpen() then
|
|
|
|
|
print("^1Error^7: ^2Inventory is open, you tried to break things")
|
|
|
|
|
crafted, crafting = false, false
|
2025-06-03 16:56:56 +01:00
|
|
|
stopTempCam()
|
|
|
|
|
ClearPedTasks(PlayerPedId())
|
|
|
|
|
if canReturn then craftingMenu(data) end
|
|
|
|
|
CraftLock = false
|
2025-05-06 00:17:07 +01:00
|
|
|
return
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
if crafting and progressBar({
|
|
|
|
|
label = "Using "..b.." "..Items[l].label,
|
|
|
|
|
time = 1000,
|
|
|
|
|
cancel = true,
|
|
|
|
|
dict = 'pickup_object',
|
|
|
|
|
anim = "putdown_low",
|
2025-05-06 00:17:07 +01:00
|
|
|
flag = 49,
|
2025-03-01 12:58:43 +00:00
|
|
|
icon = l,
|
|
|
|
|
}) then
|
2025-03-08 13:44:05 +00:00
|
|
|
TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b)
|
2025-03-01 12:58:43 +00:00
|
|
|
else
|
|
|
|
|
crafted, crafting = false, false
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
Wait(200)
|
|
|
|
|
end
|
2025-05-06 00:17:07 +01:00
|
|
|
if isInventoryOpen() then
|
2025-06-03 16:56:56 +01:00
|
|
|
print("^1Error^7: ^2Inventory is open, you tried to break things")
|
|
|
|
|
crafted, crafting = false, false
|
|
|
|
|
stopTempCam()
|
|
|
|
|
ClearPedTasks(PlayerPedId())
|
|
|
|
|
if canReturn then craftingMenu(data) end
|
|
|
|
|
CraftLock = false
|
2025-05-06 00:17:07 +01:00
|
|
|
return
|
|
|
|
|
end
|
2025-06-03 16:56:56 +01:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
if crafted then
|
|
|
|
|
local craftProp = nil
|
|
|
|
|
if prop then
|
2025-03-08 13:44:05 +00:00
|
|
|
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)
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
2025-04-01 14:15:50 +01:00
|
|
|
if data.sound then
|
|
|
|
|
local s = data.sound
|
|
|
|
|
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
|
|
|
|
|
end
|
2025-05-06 00:17:07 +01:00
|
|
|
if isInventoryOpen() then
|
2025-05-13 21:57:19 +01:00
|
|
|
--print("^1Error^7: ^2Inventory is open, you tried to break things")
|
2025-05-06 00:17:07 +01:00
|
|
|
crafted, crafting = false, false
|
2025-05-13 21:57:19 +01:00
|
|
|
crafted, crafting, CraftLock = false, false, false
|
2025-05-06 00:17:07 +01:00
|
|
|
return
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
if crafting and progressBar({
|
|
|
|
|
label = bartext..((metadata and metadata.label) or Items[data.item].label),
|
|
|
|
|
time = bartime,
|
|
|
|
|
cancel = true,
|
|
|
|
|
dict = animDict,
|
|
|
|
|
anim = anim,
|
|
|
|
|
flag = 49,
|
|
|
|
|
icon = data.item,
|
2025-05-01 18:07:39 +01:00
|
|
|
request = true,
|
2025-03-01 12:58:43 +00:00
|
|
|
}) then
|
2025-05-01 18:07:39 +01:00
|
|
|
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
|
|
|
|
|
currentToken = nil -- clear client cached token
|
2025-03-07 13:30:17 +00:00
|
|
|
CreateThread(function()
|
|
|
|
|
if data.craft["hasCrafted"] ~= nil then
|
2025-06-20 18:03:08 +01:00
|
|
|
debugPrint("^5Bridge^7: ^3hasCrafted ^2Found^7, ^2marking ^7'^4"..data.item.."^7' ^2as crafted for player^7")
|
2025-03-07 13:30:17 +00:00
|
|
|
data.craftable.craftedItems[data.item] = true
|
2025-03-08 13:44:05 +00:00
|
|
|
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems)
|
2025-03-07 13:30:17 +00:00
|
|
|
end
|
|
|
|
|
Wait(100)
|
|
|
|
|
if data.craft["exp"] ~= nil then
|
|
|
|
|
craftingLevel += data.craft["exp"].give
|
|
|
|
|
jsonPrint(data.craft["exp"])
|
2025-06-20 18:03:08 +01:00
|
|
|
debugPrint("^6Bridge^7: ^3EXP ^2found^7, ^2giving exp for ^7'^4"..data.item.."^7'")
|
2025-03-07 13:30:17 +00:00
|
|
|
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel)
|
|
|
|
|
end
|
|
|
|
|
end)
|
2025-03-06 23:49:07 +00:00
|
|
|
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
|
2025-04-01 14:15:50 +01:00
|
|
|
if data.sound then
|
|
|
|
|
StopSound(data.sound.soundId)
|
|
|
|
|
end
|
|
|
|
|
if data.requiredItemfunc then
|
|
|
|
|
data.requiredItemfunc()
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
else
|
|
|
|
|
crafting = false
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
if craftProp then destroyProp(craftProp) end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
Wait(500)
|
|
|
|
|
end
|
|
|
|
|
stopTempCam()
|
|
|
|
|
CraftLock = false
|
2025-03-06 23:49:07 +00:00
|
|
|
if canReturn then craftingMenu(data) end
|
2025-03-01 12:58:43 +00:00
|
|
|
ClearPedTasks(PlayerPedId())
|
|
|
|
|
end
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Server Event Handler: Crafted Item
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
--- Server event handler for giving the crafted item to the player.
|
|
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Removes required ingredients from the player's inventory or stash,
|
|
|
|
|
--- then adds the crafted item to their inventory.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
|
|
|
|
--- @param ItemMake string The item being crafted.
|
|
|
|
|
--- @param craftable table The crafting recipe and details.
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @param stashName string|table The stash name(s) to remove ingredients from.
|
2025-03-01 12:58:43 +00:00
|
|
|
--- @param metadata table (optional) Metadata for the crafted item.
|
2025-03-26 21:43:48 +00:00
|
|
|
--- @usage
|
2025-05-01 18:07:39 +01:00
|
|
|
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata, token)
|
2025-03-08 13:44:05 +00:00
|
|
|
local src = source
|
2025-06-21 16:32:49 +01:00
|
|
|
-- debugPrint("^6Bridge^7: ^4"..getScript().."^7:^4Crafting^7:^4GetItem ^2invoked by^7: "..GetInvokingResource())
|
2025-05-01 18:07:39 +01:00
|
|
|
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
|
2025-06-20 18:03:08 +01:00
|
|
|
debugPrint("^1ERROR^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
|
2025-05-01 18:07:39 +01:00
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
2025-05-05 11:44:51 +01:00
|
|
|
if not checkToken(src, token, "item", ItemMake) then
|
|
|
|
|
return
|
2025-05-01 18:07:39 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
local hasItems, hasTable = hasItem(ItemMake, 1, src)
|
2025-03-01 12:58:43 +00:00
|
|
|
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 _, b in pairs(stashItems or {}) do
|
2025-05-12 13:44:37 +01:00
|
|
|
if k == b.name then
|
|
|
|
|
itemRemove[k] = v
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
stashItems = getStash(stashName)
|
|
|
|
|
for k, v in pairs(craftable[ItemMake] or {}) do
|
|
|
|
|
for _, b in pairs(stashItems or {}) do
|
2025-05-12 13:44:37 +01:00
|
|
|
if k == b.name then
|
|
|
|
|
itemRemove[k] = v
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
stashRemoveItem(stashItems, stashName, itemRemove)
|
|
|
|
|
else
|
|
|
|
|
if craftable then
|
|
|
|
|
for k, v in pairs(craftable[ItemMake] or {}) do
|
2025-03-06 23:49:07 +00:00
|
|
|
removeItem(tostring(k), v, src)
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
addItem(ItemMake, craftable.amount or 1, metadata, src)
|
|
|
|
|
-- Optionally, add experience here:
|
2025-03-26 21:43:48 +00:00
|
|
|
-- for example:
|
2025-03-08 13:44:05 +00:00
|
|
|
-- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
|
2025-03-26 21:43:48 +00:00
|
|
|
end)
|