Beta: Fixes for existing scripts + feature for jim-crafting

This commit is contained in:
Jim Shield
2025-03-06 23:49:07 +00:00
committed by GitHub
parent 8eb98bff29
commit 8dd2b7ae9a
8 changed files with 312 additions and 31 deletions

View File

@@ -157,6 +157,7 @@ function openMenu(Menu, data)
end
if not Menu[k].header then Menu[k].header = " " end
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
end
exports[QBMenuExport]:openMenu(Menu)

View File

@@ -40,76 +40,184 @@ local CraftLock = false
--- })
--- ```
function craftingMenu(data)
-- Prevent opening the menu if crafting is locked.
if CraftLock then return end
-- If a job or gang restriction exists and the player doesn't pass the job check, exit early.
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
-- Display a temporary "thinking" notification/menu depending on the configured system.
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
-- Normalize stash name: if stashTable is provided, assign it to stashName.
if data.stashTable then data.stashName = data.stashTable end
-- Initialize an empty menu table and a flag for job verification.
local Menu, hasjob = {}, false
-- Get the list of recipes from the provided data.
local Recipes = data.craftable.Recipes
local craftingLevel = data.craftable.craftingLevel
local craftedItems = data.craftable.craftedItems
-- Create a temporary table to collect required item amounts for each recipe.
local tempCarryTable = {}
for i = 1, #Recipes do
-- Iterate over each key in the current recipe.
for k in pairs(Recipes[i]) do
-- Ignore meta keys: "amount", "metadata", "job", and "gang".
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
-- Record the required amount for this ingredient (default to 1 if not specified).
tempCarryTable[k] = Recipes[i].amount or 1
end
end
end
-- Trigger a server callback to check if the player can carry the required items.
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
-- Process each recipe to build the menu entries.
for i = 1, #Recipes do
-- Ensure the recipe has an "amount" field; default to 1 if missing.
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
-- Loop through each key-value pair in the recipe.
for k, v in pairs(Recipes[i]) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
-- Skip meta keys that are not ingredients.
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,
}
if not excludeKeys[k] then
-- Check job requirements if specified for the recipe.
if Recipes[i].job then
for l, b in pairs(Recipes[i].job) do
-- hasJob returns true if the player meets the job criteria.
hasjob = hasJob(l, nil, b)
if hasjob == true then break end
end
else hasjob = true end
local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or nil)
else
hasjob = true
end
-- Initialize variables for menu display text, disable flag, and any metadata.
local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil)
if hasjob then
-- Build tables for ingredient details.
local itemTable = {}
local metaTable = {}
-- Iterate over the ingredients for the current key.
for l, b in pairs(Recipes[i][tostring(k)]) do
-- Append item label and quantity to the settext string.
-- Use a line break (br) if settext is not empty.
settext = settext..(settext ~= "" and br or "")..(Items[l] and Items[l].label or "error - "..l)..(b > 1 and " x"..b or "")
-- Populate the metaTable with item labels and their amounts.
metaTable[Items[l] and Items[l].label or "error - "..l] = b
-- Build a simple table of items required.
itemTable[l] = b
Wait(0)
Wait(0) -- Yield to avoid freezing the game.
end
-- Wait until the server callback (canCarryTable) has returned.
while not canCarryTable do Wait(0) end
-- Determine if the recipe should be disabled by checking if the player has the required items.
disable = not checkHasItem(data.stashName, itemTable)
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 "")
-- Construct the header text for this menu item using metadata or default item label.
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 "")
-- Append an emoji to indicate carry status:
-- If not disabled and the player cannot carry the item, append 📦,
-- otherwise append ✔️ if they can carry it.
-- if jim-crafting and its a blueprint item that has/hasnt been crafting prefix with ✨ to represent its a new item
if not disable then
if not canCarryTable[k] then setheader = setheader .. " 📦"
else setheader = setheader .. " ✔️" end
elseif not canCarryTable[k] then setheader = setheader .. " 📦" end
if not canCarryTable[k] then
setheader = setheader .. " 📦"
else
setheader = setheader .. " ✔️"
end
elseif not canCarryTable[k] then
setheader = setheader .. " 📦"
end
if Recipes[i]["hasCrafted"] ~= nil then
if craftedItems[k] == nil then
setheader = ""..setheader
end
end
-- Add the constructed menu item into the Menu table.
Menu[#Menu + 1] = {
-- Show an arrow if the item is enabled and can be carried.
arrow = not disable and canCarryTable[k],
disable = isStarted(QBMenuExport) and disable and not canCarryTable[k],
-- Disable the menu item based on the state of QBMenuExport and carry-check.
isMenuHeader = disable or not canCarryTable[k],
-- Set icon and image for the menu item (using metadata image if available).
icon = invImg((metadata and metadata.image) or tostring(k)),
image = invImg((metadata and metadata.image) or tostring(k)),
-- Final header text, appending ❌ if disabled or cannot be carried.
header = setheader..((disable or not canCarryTable[k]) and "" or ""),
txt = isStarted(QBMenuExport) and settext or nil,
--metadata = debugMode and Recipes[i]["metadata"] or nil,
-- Set description text if QBMenuExport is started.
txt = (isStarted(QBMenuExport) or disable) and settext or nil,
-- Attach the metadata table containing ingredient details.
metadata = metaTable,
-- Define the onSelect function to trigger crafting actions if the item is selectable.
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 = Recipes[i]["metadata"] }
if Config.Crafting.MultiCraft then multiCraft(transdata) else makeItem(transdata) end
-- Build transaction data with details needed for crafting.
local transdata = {
item = k,
craft = data.craftable.Recipes[i],
craftable = data.craftable,
coords = data.coords,
stashName = data.stashName,
onBack = data.onBack,
metadata = metadata
}
-- Call multiCraft or makeItem based on configuration.
if Config.Crafting.MultiCraft then
multiCraft(transdata)
else
makeItem(transdata)
end
end) or nil),
}
end
end
Wait(0)
Wait(0) -- Yield within the loop to maintain responsiveness.
end
end
openMenu(Menu, { header = data.craftable.Header, onBack = data.onBack or nil, canClose = true, onExit = function() end, })
-- Open the final crafting menu with the built Menu table and provided header/onBack configuration.
openMenu(Menu, {
header = data.craftable.Header,
headertxt = data.craftable.Headertxt,
onBack = data.onBack or nil,
canClose = true,
onExit = function() end,
})
-- Trigger an action (likely camera or player focus) to look at the specified coordinates.
lookEnt(data.coords)
end
--- Opens a menu for selecting the quantity to craft.
---
--- This function presents the player with options to craft multiple quantities of an item, based on `Config.Crafting.MultiCraftAmounts`.
@@ -159,7 +267,17 @@ function multiCraft(data)
header = "Craft - x"..k * data.craft.amount,
txt = settext,
onSelect = function ()
makeItem({item = data.item, craft = data.craft, craftable = data.craftable, amount = k, coords = data.coords, stashName = stashname, stashTable = data.stashName, onBack = data.onBack, metadata = data.metadata })
makeItem({
item = data.item,
craft = data.craft,
craftable = data.craftable,
amount = k,
coords = data.coords,
stashName = stashname,
stashTable = data.stashName,
onBack = data.onBack,
metadata = data.metadata
})
end,
}
end
@@ -199,20 +317,38 @@ function makeItem(data)
CraftLock = true
if data.stashTable then data.stashName = data.stashTable end
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 a"
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 amount = data.amount and (data.amount ~= 1) and data.amount or 1
local metadata = data.metadata or nil
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
local canReturn = true
local crafted, crafting = true, true
local cam = createTempCam(PlayerPedId(), data.coords)
startTempCam(cam)
for i = 1, amount do
countTable(data.craft)
for k, v in pairs(data.craft) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
local excludeKeys = {
amount = true,
info = true,
metadata = true,
description = true,
job = true,
gang = true,
oneUse = true,
slot = true,
blueprintRef = true,
craftingLevel = true,
craftedItems = true,
hasCrafted = true,
}
if not excludeKeys[k] then
if type(v) == "table" then
for l, b in pairs(v) do
if crafting and progressBar({
@@ -248,6 +384,20 @@ function makeItem(data)
icon = data.item,
}) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata)
if data.craft["hasCrafted"] ~= nil then
debugPrint("hasCrafted Found, marking '"..data.item.."' as crafted for player")
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems )
--SetMetadata(nil, "craftedItems", data.craftable.craftedItems)
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
-- If recipe is removed it doesn't try to open menu again, it was causing blank menus for some reason
end
else
crafting = false
break
@@ -262,7 +412,7 @@ function makeItem(data)
stopTempCam()
CraftLock = false
lockInv(false)
craftingMenu(data)
if canReturn then craftingMenu(data) end
ClearPedTasks(PlayerPedId())
end
@@ -299,11 +449,11 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
else
if craftable then
for k, v in pairs(craftable[ItemMake] or {}) do
TriggerEvent(getScript()..":server:toggleItem", false, tostring(k), v, src)
removeItem(tostring(k), v, src)
end
end
end
TriggerEvent(getScript()..":server:toggleItem", true, ItemMake, amount, src, metadata)
addItem(ItemMake, amount, metadata, src)
--if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
end)
@@ -423,7 +573,7 @@ RegisterNetEvent(getScript().."Sellitems", function(data)
local src = source
local hasItems, hasTable = hasItem(data.item, 1, src)
if hasItems then
TriggerEvent(getScript()..":server:toggleItem", false, data.item, hasTable[data.item].count, src)
removeItem(data.item, hasTable[data.item].count, src)
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)

View File

@@ -17,7 +17,7 @@
--- end
--- ```
function isStarted(script)
return GetResourceState(script):find("start")
return GetResourceState(script):find("start") ~= nil
end
local scriptName = nil

View File

@@ -95,12 +95,12 @@ end
--- ```lua
--- removeItem("health_potion", 1)
--- ```
function removeItem(item, amount, src)
function removeItem(item, amount, src, slot)
if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist") return end
if src then
TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, info)
TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, nil, slot)
else
TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, info)
TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, nil, slot)
end
end
@@ -119,7 +119,7 @@ end
--- ```lua
--- TriggerServerEvent("script:server:toggleItem", true, "health_potion", 1)
--- ```
RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info)
RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info, slot)
if not Items[item] then print("^6Debug^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." ^7'^3"..item.."^7'^2 but it doesn't exist") return end
local src = newsrc or source
local addremove = (tostring(give) == "true" and "addItem" or "removeItem")
@@ -153,7 +153,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
elseif isStarted(QBInv) then
while remamount > 0 do
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then
remamount -= 1
else
print("^1Error removing "..data.item.." Amount left to remove: "..remamount.."^7")

90
shared/metaHandlers.lua Normal file
View File

@@ -0,0 +1,90 @@
function GetPlayer(source)
if isStarted(QBExport) then
debugPrint("^6Debug^7: ^3GetPlayer^7() QBExport")
return exports[QBExport]:GetCoreObject().Functions.GetPlayer(source)
elseif isStarted(QBXExport) then
debugPrint("^6Debug^7: ^3GetPlayer^7() QBOXExport")
return exports[QBXExport]:GetCoreObject().Functions.GetPlayer(source)
elseif isStarted(ESXExport) then
debugPrint("^6Debug^7: ^3GetPlayer^7() ESXExport")
return exports[ESXExport]:GetPlayerFromId(source)
elseif isStarted(OXCoreExport) then
debugPrint("^6Debug^7: ^3GetPlayer^7() OXCoreExport")
return exports[OXCoreExport]:GetPlayer(source)
end
return nil
end
-- Get Metadata
function GetMetadata(player, key)
if not player then -- This would be called client side
debugPrint("^6Debug^7: ^3GetMetadata^7() calling server")
return triggerCallback(getScript()..":server:GetMetadata", key)
else
if isStarted(QBExport) or isStarted(QBXExport) then
debugPrint("^6Debug^7: ^3GetMetadata^7() QBExport or QBXExport")
return player.PlayerData.metadata[key]
elseif isStarted(ESXExport) then
debugPrint("^6Debug^7: ^3GetMetadata^7() ESXExport")
return player.getMeta(key)
elseif isStarted(OXCoreExport) then
debugPrint("^6Debug^7: ^3GetMetadata^7() OXCoreExport")
return player.get(key)
end
end
return nil
end
createCallback(getScript()..":server:GetMetadata", function(source, key)
debugPrint("^6Debug^7: ^3GetMetadata^7() Callback", source)
local player = GetPlayer(source)
local Metadata = {}
if not player then
print("Error getting metadata")
return
end
if type(key) == "table" then
for _, k in ipairs(key) do
Metadata[k] = GetMetadata(player, k).k
end
elseif type(key) == "string" then
return GetMetadata(player, key)
end
jsonPrint(Metadata)
return Metadata
end)
-- Set Metadata
function SetMetadata(player, key, value)
--if player == nil then -- This would be called client side
-- debugPrint("^6Debug^7: ^3SetMetadata^7() calling server")
-- triggerCallback(getScript()..":server:SetMetadata", { key, value })
-- else
debugPrint("^6Debug^7: ^3SetMetadata^7() setting metadata")
if isStarted(QBExport) or isStarted(QBXExport) then
debugPrint("^6Debug^7: ^3SetMetadata^7() QBExport or QBXExport")
player.Functions.SetMetaData(key, value)
elseif isStarted(ESXExport) then
debugPrint("^6Debug^7: ^3SetMetadata^7() ESXExport")
player.setMeta(key, value)
elseif isStarted(OXCoreExport) then
debugPrint("^6Debug^7: ^3SetMetadata^7() OXCoreExport")
player.set(key, value)
end
--end
end
createCallback(getScript()..":server:SetMetadata", function(source, key, value)
print(source, key, value)
local player = GetPlayer(source)
--jsonPrint(player)
--[[if not player then
print("Error getting metadata")
return false
end]]
print("i did it")
SetMetadata(player, key, value)
return true
end)

View File

@@ -263,7 +263,7 @@ function createCircleTarget(data, opts, dist)
end
else
-- Create new target
local tempText = ""
local tempText = {}
local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
for i = 1, #opts do
opts[i].key = keyTable[i]

View File

@@ -248,3 +248,42 @@ function pushVehicle(entity)
end
end
end
function getClosestVehicle(coords, src)
if src then
local ped = GetPlayerPed(source)
local vehicles = GetAllVehicles()
local closestDistance, closestVehicle = -1, -1
if coords then coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords end
if not coords then coords = GetEntityCoords(ped) end
for i = 1, #vehicles do
local vehicleCoords = GetEntityCoords(vehicles[i])
local distance = #(vehicleCoords - coords)
if closestDistance == -1 or closestDistance > distance then
closestVehicle = vehicles[i]
closestDistance = distance
end
end
return closestVehicle, closestDistance
else
local ped = PlayerPedId()
local vehicles = GetGamePool('CVehicle')
local closestDistance = -1
local closestVehicle = -1
if coords then
coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords
else
coords = GetEntityCoords(ped)
end
for i = 1, #vehicles, 1 do
local vehicleCoords = GetEntityCoords(vehicles[i])
local distance = #(vehicleCoords - coords)
if closestDistance == -1 or closestDistance > distance then
closestVehicle = vehicles[i]
closestDistance = distance
end
end
return closestVehicle, closestDistance
end
end

View File

@@ -58,6 +58,7 @@ for _, v in pairs({ -- This is a specific load order
'polyZone.lua',
'itemcontrol.lua',
'playerfunctions.lua',
'metaHandlers.lua',
'jobfunctions.lua',
-- Interactions
@@ -79,11 +80,11 @@ for _, v in pairs({ -- This is a specific load order
'versioncheck.lua'
}) do
if debugMode then
print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
--print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
end
local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v)))
fileLoader()
if debugMode then
print("^5Success^7: ^2loaded file^7: 'jim_bridge/shared/"..v.."'!")
print("^5Success^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7")
end
end