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
|
|
|
|
|
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- 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-03-08 13:44:05 +00:00
|
|
|
---craftingMenu({
|
2025-03-01 12:58:43 +00:00
|
|
|
--- 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),
|
2025-03-08 13:44:05 +00:00
|
|
|
---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
|
2025-03-07 13:30:17 +00:00
|
|
|
if k == "hasCrafted" and not data.craftable.craftedItems then
|
|
|
|
|
craftedItems = GetMetadata(nil, "craftedItems") or {}
|
|
|
|
|
data.craftable.craftedItems = craftedItems
|
|
|
|
|
end
|
2025-03-01 12:58:43 +00:00
|
|
|
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-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
|
|
|
local excludeKeys = {
|
2025-03-08 13:44:05 +00:00
|
|
|
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-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
|
|
|
|
|
settext = settext..(settext ~= "" and br or "")..(Items[l] and Items[l].label or "error - "..l)..(b > 1 and " x"..b or "")
|
|
|
|
|
metaTable[Items[l] and Items[l].label or "error - "..l] = b
|
|
|
|
|
itemTable[l] = b
|
2025-03-08 13:44:05 +00:00
|
|
|
Wait(0)
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
2025-03-06 23:49:07 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
while not canCarryTable do Wait(0) end
|
|
|
|
|
disable = not checkHasItem(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-03-01 12:58:43 +00:00
|
|
|
if not disable then
|
2025-03-06 23:49:07 +00:00
|
|
|
if not canCarryTable[k] then
|
2025-03-08 13:44:05 +00:00
|
|
|
setheader = setheader.." 📦"
|
2025-03-06 23:49:07 +00:00
|
|
|
else
|
2025-03-08 13:44:05 +00:00
|
|
|
setheader = setheader.." ✔️"
|
2025-03-06 23:49:07 +00:00
|
|
|
end
|
|
|
|
|
elseif not canCarryTable[k] then
|
2025-03-08 13:44:05 +00:00
|
|
|
setheader = setheader.." 📦"
|
2025-03-06 23:49:07 +00:00
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
if Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil then
|
|
|
|
|
setheader = "✨ "..setheader
|
2025-03-06 23:49:07 +00:00
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
Menu[#Menu + 1] = {
|
|
|
|
|
arrow = 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)),
|
|
|
|
|
header = setheader..((disable or not canCarryTable[k]) and " ❌" or ""),
|
2025-03-06 23:49:07 +00:00
|
|
|
txt = (isStarted(QBMenuExport) or disable) and 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-03-08 13:44:05 +00: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)
|
|
|
|
|
local Menu = {}
|
2025-03-08 13:44:05 +00:00
|
|
|
local amounts = Config.Crafting.MultiCraftAmounts
|
2025-03-01 12:58:43 +00:00
|
|
|
local metadata = data.metadata or nil
|
2025-03-08 13:44:05 +00:00
|
|
|
|
|
|
|
|
-- Header for the multi-craft menu.
|
|
|
|
|
Menu[#Menu + 1] = {
|
2025-03-01 12:58:43 +00:00
|
|
|
isMenuHeader = true,
|
|
|
|
|
icon = invImg(metadata and metadata.image or data.item),
|
|
|
|
|
header = metadata and metadata.label or Items[data.item].label,
|
|
|
|
|
}
|
2025-03-08 13:44:05 +00:00
|
|
|
|
|
|
|
|
for k in pairsByKeys(amounts) do
|
2025-03-01 12:58:43 +00:00
|
|
|
local settext = ""
|
|
|
|
|
local itemTable = {}
|
|
|
|
|
for l, b in pairs(data.craft[data.item]) do
|
|
|
|
|
itemTable[l] = (b * k)
|
2025-03-08 13:44:05 +00:00
|
|
|
settext = settext..(settext ~= "" and br or "")..Items[l].label..(b * k > 1 and " x"..b * k or "")
|
2025-03-01 12:58:43 +00:00
|
|
|
Wait(0)
|
|
|
|
|
end
|
|
|
|
|
local disable, stashname = checkHasItem(data.stashName, itemTable)
|
|
|
|
|
Menu[#Menu + 1] = {
|
|
|
|
|
isMenuHeader = not disable,
|
|
|
|
|
arrow = disable,
|
2025-03-08 13:44:05 +00:00
|
|
|
header = "Craft - x"..(k * data.craft.amount),
|
2025-03-01 12:58:43 +00:00
|
|
|
txt = settext,
|
2025-03-08 13:44:05 +00:00
|
|
|
onSelect = function()
|
2025-03-06 23:49:07 +00:00
|
|
|
makeItem({
|
|
|
|
|
item = data.item,
|
|
|
|
|
craft = data.craft,
|
|
|
|
|
craftable = data.craftable,
|
|
|
|
|
amount = k,
|
|
|
|
|
coords = data.coords,
|
|
|
|
|
stashName = stashname,
|
|
|
|
|
stashTable = data.stashName,
|
|
|
|
|
onBack = data.onBack,
|
2025-03-08 13:44:05 +00:00
|
|
|
metadata = data.metadata,
|
2025-03-06 23:49:07 +00:00
|
|
|
})
|
2025-03-01 12:58:43 +00:00
|
|
|
end,
|
|
|
|
|
}
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
|
|
|
|
|
openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) 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
|
|
|
local excludeKeys = {
|
2025-03-08 13:44:05 +00:00
|
|
|
amount = true, info = true, metadata = true, description = 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-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
|
|
|
|
|
if crafting and progressBar({
|
|
|
|
|
label = "Using "..b.." "..Items[l].label,
|
|
|
|
|
time = 1000,
|
|
|
|
|
cancel = true,
|
|
|
|
|
dict = 'pickup_object',
|
|
|
|
|
anim = "putdown_low",
|
|
|
|
|
flag = 48,
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
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,
|
|
|
|
|
}) then
|
|
|
|
|
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata)
|
2025-03-07 13:30:17 +00:00
|
|
|
CreateThread(function()
|
|
|
|
|
if data.craft["hasCrafted"] ~= nil then
|
|
|
|
|
debugPrint("hasCrafted Found, marking '"..data.item.."' as crafted for player")
|
|
|
|
|
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-03-08 13:44:05 +00:00
|
|
|
debugPrint("exp found, giving exp for '"..data.item.."'")
|
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-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
|
|
|
|
|
lockInv(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.
|
|
|
|
|
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata)
|
2025-03-08 13:44:05 +00:00
|
|
|
local src = source
|
|
|
|
|
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
|
|
|
|
|
if k == b.name then itemRemove[k] = v end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
stashItems = getStash(stashName)
|
|
|
|
|
for k, v in pairs(craftable[ItemMake] or {}) do
|
|
|
|
|
for _, b in pairs(stashItems or {}) do
|
|
|
|
|
if k == b.name then itemRemove[k] = v end
|
|
|
|
|
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:
|
|
|
|
|
-- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
|
2025-03-01 12:58:43 +00:00
|
|
|
end)
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Selling Menu and Animation
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
--- Opens a selling menu with available items and prices.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @param data table Contains selling menu data:
|
|
|
|
|
--- - sellTable (`table`) Table with Header and Items (item names and prices).
|
|
|
|
|
--- - ped (optional) (`number`) Ped entity involved.
|
|
|
|
|
--- - onBack (optional) (`function`) Callback for returning.
|
|
|
|
|
--- @usage
|
2025-03-01 12:58:43 +00:00
|
|
|
--- ```lua
|
|
|
|
|
--- sellMenu({
|
|
|
|
|
--- sellTable = {
|
|
|
|
|
--- Header = "Sell Items",
|
|
|
|
|
--- Items = {
|
|
|
|
|
--- ["gold_ring"] = 100,
|
|
|
|
|
--- ["diamond"] = 500,
|
|
|
|
|
--- },
|
|
|
|
|
--- },
|
|
|
|
|
--- ped = pedEntity,
|
|
|
|
|
--- onBack = function() print("Returning to previous menu") end,
|
|
|
|
|
--- })
|
|
|
|
|
--- ```
|
|
|
|
|
function sellMenu(data)
|
|
|
|
|
local origData = data
|
|
|
|
|
local Menu = {}
|
|
|
|
|
if data.sellTable.Items then
|
|
|
|
|
local itemList = {}
|
|
|
|
|
for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
|
|
|
|
|
local _, hasTable = hasItem(itemList)
|
|
|
|
|
for k, v in pairsByKeys(data.sellTable.Items) do
|
2025-03-08 13:44:05 +00:00
|
|
|
Menu[#Menu + 1] = {
|
2025-03-01 12:58:43 +00:00
|
|
|
isMenuHeader = not hasTable[k].hasItem,
|
|
|
|
|
icon = invImg(k),
|
2025-03-08 13:44:05 +00:00
|
|
|
header = Items[k].label..(hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
|
2025-03-01 12:58:43 +00:00
|
|
|
txt = Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"],
|
|
|
|
|
onSelect = function()
|
|
|
|
|
sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
|
|
|
|
|
end,
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
for k, v in pairsByKeys(data.sellTable) do
|
|
|
|
|
if type(v) == "table" then
|
2025-03-08 13:44:05 +00:00
|
|
|
Menu[#Menu + 1] = {
|
2025-03-01 12:58:43 +00:00
|
|
|
arrow = true,
|
|
|
|
|
header = k,
|
|
|
|
|
txt = "Amount of items: "..countTable(v.Items),
|
|
|
|
|
onSelect = function()
|
|
|
|
|
v.onBack = function() sellMenu(origData) end
|
|
|
|
|
v.sellTable = data.sellTable[k]
|
|
|
|
|
sellMenu(v)
|
|
|
|
|
end,
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
openMenu(Menu, {
|
|
|
|
|
header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items),
|
|
|
|
|
headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items) or "",
|
|
|
|
|
canClose = true,
|
|
|
|
|
onBack = data.onBack,
|
|
|
|
|
})
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Plays the selling animation and processes the sale transaction.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Checks if the player has the item, plays animations, triggers the server event for selling,
|
|
|
|
|
--- and then calls the onBack callback if provided.
|
2025-03-01 12:58:43 +00:00
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- @param data table Contains:
|
|
|
|
|
--- `- item: The item to sell.
|
|
|
|
|
--- `- price: Price per item.
|
|
|
|
|
--- `- ped (optional): Ped entity involved.
|
|
|
|
|
--- `- onBack (optional): Callback to call on completion.
|
2025-03-01 12:58:43 +00:00
|
|
|
---@usage
|
|
|
|
|
--- ```lua
|
|
|
|
|
--- sellAnim({
|
|
|
|
|
--- item = "gold_ring",
|
|
|
|
|
--- price = 100,
|
|
|
|
|
--- ped = pedEntity,
|
|
|
|
|
--- onBack = function() sellMenu(data) end,
|
|
|
|
|
--- })
|
|
|
|
|
--- ```
|
|
|
|
|
function sellAnim(data)
|
|
|
|
|
if not hasItem(data.item, 1) then
|
|
|
|
|
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error")
|
|
|
|
|
return
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
|
|
|
|
|
-- Remove any attached clipboard objects.
|
|
|
|
|
for _, obj in pairs(GetGamePool('CObject')) do
|
|
|
|
|
for _, model in pairs({ `p_cs_clipboard` }) do
|
|
|
|
|
if GetEntityModel(obj) == model and IsEntityAttachedToEntity(data.ped, obj) then
|
|
|
|
|
DeleteObject(obj)
|
|
|
|
|
DetachEntity(obj, 0, 0)
|
|
|
|
|
SetEntityAsMissionEntity(obj, true, true)
|
|
|
|
|
Wait(100)
|
|
|
|
|
DeleteEntity(obj)
|
2025-03-01 12:58:43 +00:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
TriggerServerEvent(getScript().."Sellitems", data)
|
|
|
|
|
lookEnt(data.ped)
|
|
|
|
|
local dict = "mp_common"
|
|
|
|
|
playAnim(dict, "givetake2_a", 0.3, 2)
|
|
|
|
|
playAnim(dict, "givetake2_b", 0.3, 2, data.ped)
|
|
|
|
|
Wait(2000)
|
|
|
|
|
StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5)
|
|
|
|
|
StopAnimTask(data.ped, dict, "givetake2_b", 0.5)
|
|
|
|
|
if data.onBack then data.onBack() end
|
|
|
|
|
end
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Server event handler for processing item sales.
|
|
|
|
|
--- Removes sold items from inventory and funds the player based on the sale.
|
2025-03-01 12:58:43 +00:00
|
|
|
RegisterNetEvent(getScript().."Sellitems", function(data)
|
|
|
|
|
local src = source
|
|
|
|
|
local hasItems, hasTable = hasItem(data.item, 1, src)
|
|
|
|
|
if hasItems then
|
2025-03-06 23:49:07 +00:00
|
|
|
removeItem(data.item, hasTable[data.item].count, src)
|
2025-03-01 12:58:43 +00:00
|
|
|
TriggerEvent(getScript()..":server:FundPlayer", (hasTable[data.item].count * data.price), "cash", src)
|
|
|
|
|
else
|
|
|
|
|
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src)
|
|
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Shop Interface
|
|
|
|
|
-------------------------------------------------------------
|
|
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
--- Opens a shop interface for the player.
|
|
|
|
|
---
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Checks job/gang restrictions, then uses the active inventory system to open the shop.
|
|
|
|
|
--- @param data table Contains:
|
|
|
|
|
--- - shop (`string`) The shop identifier.
|
|
|
|
|
--- - items (`table`) The items available in the shop.
|
|
|
|
|
--- - coords (`vector3`) where the shop is located.
|
|
|
|
|
--- - job/gang (optional) (`string`) Job or gang requirements.
|
2025-03-01 12:58:43 +00:00
|
|
|
---@usage
|
|
|
|
|
--- ```lua
|
|
|
|
|
--- openShop({
|
|
|
|
|
--- shop = "weapon_shop",
|
|
|
|
|
--- items = weaponShopItems,
|
|
|
|
|
--- coords = vector3(100.0, 200.0, 300.0),
|
|
|
|
|
--- job = "police",
|
|
|
|
|
--- })
|
|
|
|
|
--- ```
|
|
|
|
|
function openShop(data)
|
|
|
|
|
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
|
2025-03-08 13:44:05 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
if isStarted(OXInv) then
|
|
|
|
|
exports[OXInv]:openInventory('shop', { type = data.shop })
|
2025-03-08 13:44:05 +00:00
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
elseif isStarted(QBInv) then
|
|
|
|
|
if QBInvNew then
|
2025-03-08 13:44:05 +00:00
|
|
|
TriggerServerEvent(getScript()..':server:OpenShopNewQB', data.shop)
|
2025-03-01 12:58:43 +00:00
|
|
|
else
|
|
|
|
|
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
|
|
|
|
|
end
|
2025-03-08 13:44:05 +00:00
|
|
|
|
|
|
|
|
--elseif isStarted(OrigenInv) then -- Needs testing, not sure if i did this right
|
|
|
|
|
-- exports[OrigenInv]:openInventory('shop', data.shop, data.items)
|
|
|
|
|
|
2025-03-01 12:58:43 +00:00
|
|
|
else
|
|
|
|
|
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
|
|
|
|
|
end
|
|
|
|
|
lookEnt(data.coords)
|
|
|
|
|
end
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
--- Server event handler for opening a shop using the new QB inventory system.
|
2025-03-01 12:58:43 +00:00
|
|
|
RegisterNetEvent(getScript()..':server:OpenShopNewQB', function(data)
|
|
|
|
|
exports[QBInv]:OpenShop(source, data)
|
|
|
|
|
end)
|
|
|
|
|
|
2025-03-08 13:44:05 +00:00
|
|
|
-------------------------------------------------------------
|
|
|
|
|
-- Server Callback Registration
|
|
|
|
|
-------------------------------------------------------------
|
2025-03-01 12:58:43 +00:00
|
|
|
if isServer() then
|
|
|
|
|
createCallback(getScript()..':server:canCarry', function(source, itemTable) local result = canCarry(itemTable, source) return result end)
|
2025-02-21 13:47:15 +00:00
|
|
|
end
|