Compare commits

..

25 Commits

Author SHA1 Message Date
Jim Shield
7c47de5dc0 Version Bump
`2.0.19` - `2.0.20`
2025-07-06 19:16:01 +01:00
Jim Shield
c1ab2b3505 Support only one progressBar when crafting 2025-07-06 19:13:10 +01:00
Jim Shield
1665ab41de fix multicraft menu for lation_ui 2025-07-06 19:01:10 +01:00
Jim Shield
6d397b59bf fix onExit support for lation_ui 2025-07-05 20:52:08 +01:00
Jim Shield
9dfbcbe878 fix typo breaking tgiann CanCarryItem export check 2025-07-05 13:24:19 +01:00
Jim Shield
ca0970b650 Add fallback config support for QBInv inventory
Enhanced the inventory config loader to support both old and new config file structures for QBInv by introducing a fallback mechanism. The loader now attempts multiple config paths to improve compatibility with different versions.
2025-07-05 11:11:51 +01:00
Jim Shield
02218a4902 enhance dosItemExist() and add getItemLabel() 2025-07-05 00:33:26 +01:00
Jim Shield
a900044306 Fix hang when using jpr-inventory
The script was randomly looping the config file check, if it hit qb-inventory first it would end the check and break the script, changed it to skip instead and fixed it
2025-07-04 23:44:37 +01:00
Jim Shield
7382a534a0 I THINK jim_bridge now fully supports lation_ui 2025-07-04 18:56:19 +01:00
Jim Shield
3b76b15bb4 Add starting support for lation_ui
Added support for:
- Menus
- Draw Text
- Notifications
- Skillchecks
- ProgressBar

TODO:
- Input dialogs
2025-07-04 15:20:02 +01:00
Jim Shield
f2eda0fef6 Add indicator of ingredient items check location 2025-07-04 15:17:56 +01:00
Jim Shield
61c1cce814 Increase cache export timeout to 2 minutes 2025-07-04 15:16:53 +01:00
Jim Shield
0f1660e3ed remove debug print for ox_inv 2025-07-01 13:21:59 +01:00
Jim Shield
e5b55e49b7 Add missing functions for playing animations
Forgot to include animation functions for progressbar ui_module when porting it over
2025-07-01 13:07:36 +01:00
Jim Shield
1ff01526bc Add larger timeouts for caching 2025-06-30 23:19:23 +01:00
Jim Shield
1ed227080b Add missing "Open Wheel" to searchCar() function 2025-06-30 23:18:43 +01:00
Jim Shield
b07c089946 Version Bump
`2.0.18` - `2.0.19`
2025-06-28 20:08:51 +01:00
Jim Shield
d603067c77 Remove files i was testing with (forgot) 2025-06-28 20:04:50 +01:00
Jim Shield
5a97b8f83a Fix script break stop if config file not found 2025-06-28 11:22:12 +01:00
Jim Shield
d0a09871c7 Fix for if using QBOX without ox_inventory
Realised that if using older (and custom) versions of qbox, the item list can't be retrieved through `GetCoreObject()`

This fixes that and also (if needed) grabs the item lists from inventories directly if needed
2025-06-27 13:35:28 +01:00
Jim Shield
e87aa8d74d Fix server lag when loading scripts
This was my bad, set up functions that requested the cache from server, didn't realise it was duplicating it and spamming it

This moves the requests to a separate file and uses an export instead

Also hopefully fixes ox_lib images being an arse
2025-06-26 23:15:33 +01:00
Jim Shield
e204e6153d remove debug prints
oops
2025-06-26 20:08:31 +01:00
Jim Shield
5c69f8dd28 add new function hasFreeInventorySlots()
using the function to grab slot info, this can now check if you have enough inventory slots for items to be given, allowing it to check before TRYING to add them
2025-06-26 20:04:32 +01:00
Jim Shield
f3fa309dbf Revamp framework caching
People were reporting issues with scripts claiming items didn't exist,  I believe this was due to their servers having slot starts (eg. alot of clothes or vehicles being loaded)

I've rewritten it to fully delay loading until everything is correctly cached and let the scripts continue. Also hopefully reducing memory footprint for it too.

---
Also slipped in an `experimental` function that grabs inventory config files to check their set InventoryWeight and InventorySlots to automatically apply it for other functions
---
2025-06-26 20:03:12 +01:00
Jim Shield
9d74e7cb46 Update setMetadata server event
Before it was JUST for durability but fixed it to work for all item metadata
2025-06-26 19:57:03 +01:00
18 changed files with 760 additions and 353 deletions

View File

@@ -53,11 +53,11 @@ setr jim_DisableDebug true
setr jim_DisableEventDebug true
# Force the default setting for what framework scripts should be used
setr jim_menuScript qb # qb, ox, gta, jim
setr jim_notifyScript gta # qb, ox, gta, esx, okok, red
setr jim_drawTextScript qb # qb, ox, gta, esx
setr jim_progressBarScript qb # qb, ox, gta, esx
setr jim_skillCheckScript qb # qb, ox, gta
setr jim_menuScript qb # qb, ox, gta, lation
setr jim_notifyScript gta # qb, ox, gta, esx, okok, red, lation
setr jim_drawTextScript qb # qb, ox, gta, esx, lation
setr jim_progressBarScript qb # qb, ox, gta, esx, lation
setr jim_skillCheckScript qb # qb, ox, gta, lation
setr jim_dontUseTarget false # Set to true to disable target systems and use draw text 3d
```

32
clientFrameworkCache.lua Normal file
View File

@@ -0,0 +1,32 @@
if _G.__bridge_cache_loaded then return end
_G.__bridge_cache_loaded = true
_G.BridgeCache = {
hasCache = false,
data = nil,
queue = {},
}
RegisterNetEvent("jim_bridge:receiveCache", function(data)
if _G.BridgeCache.hasCache then return end
_G.BridgeCache.data = data
_G.BridgeCache.hasCache = true
for _, cb in ipairs(_G.BridgeCache.queue) do
cb(data)
end
_G.BridgeCache.queue = nil
end)
-- Only send the request ONCE per client session
TriggerServerEvent("jim_bridge:requestCache")
-- Exported async function
function GetBridgeCache(callback)
if _G.BridgeCache.hasCache then
callback(_G.BridgeCache.data)
else
table.insert(_G.BridgeCache.queue, callback)
end
end
exports("GetBridgeCache", GetBridgeCache)

View File

@@ -35,10 +35,23 @@ local Exports = {
}
-- Prevent reloading if cache is already initialized
if _G.__jimBridgeDataCache then return end
_G.__jimBridgeDataCache = {}
local cache = _G.__jimBridgeDataCache
local cache = {
Items = {},
Vehicles = {},
Jobs = {},
Gangs = {},
}
local cacheReady = false
local timers = {}
local function startTimer(label)
timers[label] = GetGameTimer()
end
local function endTimer(label)
timers[label] = GetGameTimer() - (timers[label] or GetGameTimer())
timers[label] = timers[label] / 1000
end
startTimer("Cache")
-- Helper function to check if resource exists in server (instead of if it is already started)
local function checkExists(resourceName)
return GetResourceState(resourceName):find("start") or GetResourceState(resourceName):find("stopped")
@@ -62,7 +75,6 @@ if checkExists(Exports.ESXExport) then
end
-- Initialize variables for caching
local Items, Vehicles, Jobs, Gangs, Core = {}, {}, {}, {}, nil
local itemResource, jobResource, vehResource = "N/A", "N/A", "N/A"
-- Print just to announce it knows the exports/scripts exist in the server
@@ -76,6 +88,7 @@ end
---- Load Items -----
---------------------
-- Items initialization based on detected inventory system
startTimer("Items")
if checkExists(Exports.OXInv) then
-- Wait for OX Inventory to start if it's not already started
while GetResourceState(Exports.OXInv) ~= "started" do Wait(100) end
@@ -85,22 +98,15 @@ if checkExists(Exports.OXInv) then
return exports[Exports.OXInv]:Items()
end)
if success and result then
Items = result
end
if Items == nil or not next(Items) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.OXInv.." ^7Items ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Items = {} -- Fallback to an empty table
cache.Items = result
end
-- Get Weapon info and duplicate them if they are uppercase
-- (duplicate incase anything checks for the uppercase version)
for k, v in pairs(Items) do
for k, v in pairs(cache.Items) do
if type(k) == "string" then
if k:find("WEAPON") then
Items[k:lower()] = Items[k]
cache.Items[k:lower()] = cache.Items[k]
end
else
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
@@ -109,27 +115,48 @@ if checkExists(Exports.OXInv) then
end
end
elseif checkExists(Exports.QBXExport) then
while GetResourceState(Exports.QBXExport) ~= "started" do Wait(100) end
itemResource = Exports.QBXExport
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
-- If this is nil, they need to update to qbx_core 1.23.0+
if not cache.Items then
-- if their inventory doesn't allow that (they refuse to update their butchered core replacement):
if GetResourceState(Exports.QSInv):find("start") then
itemResource = Exports.QSInv
cache.Items = exports[Exports.QSInv]:GetItemList()
elseif GetResourceState(Exports.OrigenInv):find("start") then
itemResource = Exports.OrigenInv
cache.Items = exports[Exports.OrigenInv]:Items()
elseif GetResourceState(Exports.CodeMInv):find("start") then
itemResource = Exports.CodeMInv
cache.Items = exports[Exports.CodeMInv]:GetItemList()
elseif GetResourceState(Exports.CoreInv):find("start") then
itemResource = Exports.CoreInv
cache.Items = exports[Exports.CoreInv]:getItemsList()
elseif GetResourceState(Exports.TgiannInv):find("start") then
itemResource = Exports.TgiannInv
cache.Items = exports[Exports.TgiannInv]:Items()
end
end
elseif checkExists(Exports.QBExport) then
while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end
itemResource = Exports.QBExport
Core = exports[Exports.QBExport]:GetCoreObject()
Items = Core.Shared.Items
if Items == nil or not next(Items) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.QBExport.." ^7Items ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Items = {} -- Fallback to an empty table
end
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
elseif checkExists(Exports.ESXExport) then
itemResource = Exports.ESXExport
if GetResourceState(Exports.QSInv):find("start") then
Items = exports[Exports.QSInv]:GetItemList()
cache.Items = exports[Exports.QSInv]:GetItemList()
else
Items = ESX.GetItems()
while not next(Items) do
Items = ESX.GetItems()
cache.Items = ESX.GetItems()
while not next(cache.Items) do
cache.Items = ESX.GetItems()
Wait(1000)
end
end
@@ -137,37 +164,28 @@ elseif checkExists(Exports.ESXExport) then
elseif checkExists(Exports.RSGExport) then
while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end
itemResource = Exports.RSGExport
Core = exports[Exports.RSGExport]:GetCoreObject()
Items = Core.Shared.Items
if Items == nil or not next(Items) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.RSGExport.." ^7Items ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Items = {} -- Fallback to an empty table
end
cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
end
endTimer("Items")
---------------------
--- Load Vehicles ---
---------------------
startTimer("Vehicles")
-- Vehicle loading depending on framework
if checkExists(Exports.QBXExport) or checkExists(Exports.QBExport) then
if checkExists(Exports.QBXExport) then
vehResource = Exports.QBXExport
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
elseif checkExists(Exports.QBExport)then
vehResource = Exports.QBExport
Core = Core or exports[Exports.QBExport]:GetCoreObject()
Vehicles = Core.Shared.Vehicles
if Vehicles == nil or not next(Vehicles) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.QBExport.." ^7Vehicles ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Vehicles = {} -- Fallback to an empty table
end
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
elseif checkExists(Exports.OXCoreExport) then
vehResource = Exports.OXCoreExport
Vehicles = {}
cache.Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
Vehicles[k] = {
cache.Vehicles[k] = {
model = k, hash = GetHashKey(k),
price = v.price,
name = v.name,
@@ -178,9 +196,8 @@ elseif checkExists(Exports.OXCoreExport) then
elseif checkExists(Exports.ESXExport) then
vehResource = Exports.ESXExport
while not MySQL do Wait(1000) end
Vehicles = {}
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
Vehicles[v.model] = {
cache.Vehicles[v.model] = {
model = v.model,
hash = GetHashKey(v.model),
price = v.price,
@@ -190,35 +207,26 @@ elseif checkExists(Exports.ESXExport) then
elseif checkExists(Exports.RSGExport) then
vehResource = Exports.RSGExport
Core = Core or exports[Exports.RSGExport]:GetCoreObject()
Vehicles = Core.Shared.Vehicles
if Vehicles == nil or not next(Vehicles) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find shared ^7Vehicles ^1table^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Vehicles = {} -- Fallback to an empty table
end
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
end
endTimer("Vehicles")
---------------------
----- Load Jobs -----
---------------------
startTimer("Jobs")
-- Jobs loading based on framework
if checkExists(Exports.QBXExport) then
jobResource = Exports.QBXExport
Core = Core or exports[Exports.QBXExport]:GetCoreObject()
Jobs, Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
if Jobs == nil or not next(Jobs) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find shared ^7Jobs ^1table^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Jobs = {} -- Fallback to an empty table
end
cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
elseif checkExists(Exports.QBExport) then
jobResource = Exports.QBExport
cache.Jobs, cache.Gangs = exports[Exports.QBExport]:GetCoreObject().Shared.Jobs, exports[Exports.QBExport]:GetCoreObject().Shared.Gangs
elseif checkExists(Exports.OXCoreExport) then
jobResource = Exports.OXCoreExport
Jobs = {}
while not MySQL do Wait(1000) end
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
@@ -228,34 +236,29 @@ elseif checkExists(Exports.OXCoreExport) then
gradeMap[grade.group][grade.grade] = { name = grade.label }
end
for _, job in pairs(tempJobs) do
Jobs[job.name] = {
cache.Jobs[job.name] = {
label = job.label,
grades = gradeMap[job.name] or {}
}
end
Gangs = Jobs
elseif checkExists(Exports.QBExport) then
jobResource = Exports.QBExport
Core = Core or exports[Exports.QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
cache.Gangs = cache.Jobs
elseif checkExists(Exports.ESXExport) then
jobResource = Exports.ESXExport
ESX = exports[Exports.ESXExport]:getSharedObject()
Jobs = ESX.GetJobs()
while not next(Jobs) do
cache.Jobs = ESX.GetJobs()
while not next(cache.Jobs) do
Wait(100)
Jobs = ESX.GetJobs()
cache.Jobs = ESX.GetJobs()
end
for Role, Grades in pairs(Jobs) do
for Role, Grades in pairs(cache.Jobs) do
-- Check for if user has added grades
if Grades.grades == nil or not next(Grades.grades) then
goto continue
end
for grade, info in pairs(Grades.grades) do
if info.label and info.label:find("[Bb]oss") then
Jobs[Role].grades[grade].isBoss = true
cache.Jobs[Role].grades[grade].isBoss = true
goto continue
end
end
@@ -268,64 +271,184 @@ elseif checkExists(Exports.ESXExport) then
end
if highestGrade then
Jobs[Role].grades[tostring(highestGrade)].isBoss = true
cache.Jobs[Role].grades[tostring(highestGrade)].isBoss = true
end
::continue::
end
Gangs = Jobs
cache.Gangs = cache.Jobs
elseif checkExists(Exports.RSGExport) then
jobResource = Exports.RSGExport
Core = Core or exports[Exports.RSGExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
if Jobs == nil or not next(Jobs) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find shared ^7Jobs ^1table^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Jobs = {} -- Fallback to an empty table
end
cache.Jobs, cache.Gangs = exports[Exports.RSGExport]:GetCoreObject().Shared.Jobs, exports[Exports.RSGExport]:GetCoreObject().Shared.Gangs
end
endTimer("Jobs")
-- Save to global cache
cache.Items = Items
cache.Vehicles = Vehicles
cache.Jobs = Jobs
cache.Gangs = Gangs
-- Fallback if nil or empty
if cache.Items == nil or not next(cache.Items) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..itemResource.." ^7Items ^1list^7, ^1possible error in that file or is it empty^7?")
print("^1--------------------------------------------^7")
cache.Items = {} -- Fallback to an empty table
end
if cache.Vehicles == nil or not next(cache.Vehicles) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..vehResource.." ^7Vehicles ^1list^7, ^1possible error in that file or is it empty^7?")
print("^1--------------------------------------------^7")
cache.Vehicles = {} -- Fallback to an empty table
end
if cache.Jobs == nil or not next(cache.Jobs) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..jobResource.." ^7job ^1list^7, ^1possible error in that file or is it empty^7?")
print("^1--------------------------------------------^7")
cache.Jobs = {} -- Fallback to an empty table
end
-- Auto Detection of Inventory Weight -- **EXPERIMENTAL**
-- Forcefully load the the specified config file from inventory scripts
-- This allows to get information required for certain functions that need to detect how much space is left in a players inventory
-- This is born from too many tickets of me needing to explain that they need to change "InventoryWeight" to match their inv setting
startTimer("InvWeight")
startTimer("InvSlots")
local function getInventoryConfig(resource, data)
if data.convars then
return function(path)
local key = path[1]
local convar = key == "MaxWeight" and data.convars.weight or key == "MaxSlots" and data.convars.slots
return convar and GetConvarInt(convar.key, convar.default) or nil, "Unsupported convar path: " .. table.concat(path, ".")
end
end
local content = LoadResourceFile(resource, data.file)
if not content then return nil, "Failed to load file" end
local env = {
GetConvar = GetConvar, vector3 = vector3, Citizen = Citizen,
GetResourceState = GetResourceState, exports = exports,
DependencyCheck = DependencyCheck or function() return nil end,
}
local fn, err = load(content, '@'..data.file, 't', env)
if not fn then return nil, "Failed to compile config: " .. err end
if not pcall(fn) then return nil, "Error executing config file" end
local cfg = env.Config or env.config
if not cfg then return nil, "Config table not found" end
return function(path)
local ref = cfg
for _, k in ipairs(path) do
if type(ref) ~= "table" then return nil, "Path invalid at: " .. tostring(k) end
ref = ref[k]
end
return ref
end
end
-- Inventory table
local invWeightTable = {
[Exports.OXInv] = { convars = {
weight = { key = "inventory:weight", default = 30000 },
slots = { key = "inventory:slots", default = 40 }
}},
[Exports.QBInv] = {
fallback = {
{ file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } }, -- old version
{ file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } }, -- new version
}
},
[Exports.JPRInv] = { file = "configs/main_config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
[Exports.PSInv] = { file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
[Exports.QSInv] = { file = "config/config.lua", path = { "InventoryWeight", "weight" }, slotPath = { "InventoryWeight", "slots" } },
[Exports.TgiannInv] = { file = "configs/config.lua", path = { "slotsMaxWeights", "player", "maxWeight" }, slotPath = { "slotsMaxWeights", "player", "slots" } },
[Exports.CodeMInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
[Exports.RSGInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
--[Exports.OrigenInv] = { file = "config.lua", path = { "MaxWeight" } },
}
-- Run config detection
local invResource = ""
for script, data in pairs(invWeightTable) do
if checkExists(script) then
if script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end
local attempts = data.fallback or { data }
local lookup, used, err
for _, option in ipairs(attempts) do
local try, e = getInventoryConfig(script, option)
if try then
lookup = try
used = option
break
end
err = e
end
if not lookup then
print(("^1ERROR^7: ^1Config loader failed from ^5%s^7: ^1%s^7"):format(script, err or "unknown"))
break
end
local function resolve(label, path)
local val, perr = lookup(path)
if val then
cache["Inventory"..label] = val
return
end
local warnType = label == "Weight" and "^1ERROR" or "^3WARNING"
print(("%s^7: ^1Failed to get ^7Inventory%s ^1from ^5%s^7: ^1%s^7"):format(warnType, label, script, perr or "unknown"))
end
resolve("Weight", used.path or { "MaxWeight" })
resolve("Slots", used.slotPath or { "MaxSlots" })
invResource = script:gsub("-", "^7-^4"):gsub("_", "^7_^4")
break
end
::skip::
end
endTimer("InvWeight")
endTimer("InvSlots")
CreateThread(function()
local counts = {
Items = 0,
Vehicles = 0,
Jobs = 0,
Gangs = 0,
Items = 0, Vehicles = 0, Jobs = 0, Gangs = 0,
}
for k, v in pairs(cache) do
for count in pairs(v) do
counts[k] += 1
end
if type(v) ~= "number" then for count in pairs(v) do counts[k] += 1 end end
end
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Items).."^2 Items from ^7"..itemResource)
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Vehicles).."^2 Vehicles from ^7"..vehResource)
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Jobs).."^2 Jobs from ^7"..jobResource)
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Gangs).."^2 Gangs from ^7"..jobResource)
if cache.InventoryWeight then
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventoryWeight^7: ^3"..cache.InventoryWeight.."^7 (^3"..(cache.InventoryWeight / 1000).."kg^7) ^7("..timers["InvWeight"].."s)")
end
if cache.InventorySlots then
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7("..timers["InvSlots"].."s)")
end
print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7("..timers["Items"].."s)")
print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7("..timers["Vehicles"].."s)")
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7("..timers["Jobs"].."s)")
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7("..timers["Jobs"].."s)")
endTimer("Cache")
print("^6FrameworkCache^7: ^2Cache Ready ^7("..timers["Cache"].."s)")
cacheReady = true
end)
RegisterNetEvent("jim_bridge:requestCache", function()
local src = source
TriggerClientEvent("jim_bridge:receiveCache", src, _G.__jimBridgeDataCache)
TriggerClientEvent("jim_bridge:receiveCache", src, cache)
end)
exports("GetSharedData", function()
-- Wait for data to be ready before returning it
local timeout = GetGameTimer() + 5000
while (
not _G.__jimBridgeDataCache or
(not _G.__jimBridgeDataCache.Items or next(_G.__jimBridgeDataCache.Items) == nil) or
(not _G.__jimBridgeDataCache.Vehicles or next(_G.__jimBridgeDataCache.Vehicles) == nil) or
(not _G.__jimBridgeDataCache.Jobs or next(_G.__jimBridgeDataCache.Jobs) == nil)
) and GetGameTimer() < timeout do
Wait(50)
-- Wait indefinitely (with periodic checks) until cacheReady is true
local timeout = GetGameTimer() + 20000 -- 20 second failsafe timeout
while not cacheReady and GetGameTimer() < timeout do
Wait(100)
end
return _G.__jimBridgeDataCache
if not cacheReady then
print("^1ERROR^7: jim_bridge cache timed out waiting for data.")
return nil -- Signal clearly if cache never became ready
end
return cache
end)

View File

@@ -1,6 +1,6 @@
name "Jim_Bridge"
author "Jimathy"
version "2.0.18"
version "2.0.20"
description "Framework Bridge By Jimathy"
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.'
@@ -18,9 +18,10 @@ files {
-- Version checker
server_scripts {
'frameworkCache.lua',
'_versioncheck.lua'
'_versioncheck.lua',
}
client_scripts {
'ui_modules/*.lua'
'clientFrameworkCache.lua',
'ui_modules/*.lua',
}

View File

@@ -197,22 +197,23 @@ function waitForSharedLoad()
local timeout = 100000 -- 10 seconds in milliseconds
local startTime = GetGameTimer()
local loaded = true
--Wait(1000)
local count = {}
local loop = 0
while ((not Jobs or not next(Jobs)) and (not Items or not next(Items)) and (not Vehicles or not next(Vehicles))) and (GetGameTimer() - startTime) < timeout do
if not messageShown then
while ((not Jobs or not next(Jobs)) or
(not Items or not next(Items)) or
(not Vehicles or not next(Vehicles))
) and ((GetGameTimer() - startTime) < timeout) do
if loop >= 3 and not messageShown then
if (not Jobs or not next(Jobs)) then
debugPrint("^4Debug^7: ^2Waiting for Jobs to be loaded")
print("^4Debug^7: ^2Waiting for ^7Jobs^2 to be loaded")
end
if (not Items or not next(Items)) then
debugPrint("^4Debug^7: ^2Waiting for Items to be loaded")
print("^4Debug^7: ^2Waiting for ^7Items^2 to be loaded")
end
if (not Vehicles or not next(Vehicles)) then
debugPrint("^4Debug^7: ^2Waiting for Vehicles to be loaded")
print("^4Debug^7: ^2Waiting for ^7Vehicles^2 to be loaded")
end
messageShown = true
end
messageShown = true
--print((GetGameTimer() - startTime) < timeout)
Wait(1000)
if Jobs and Items and Vehicles then

View File

@@ -47,48 +47,7 @@
--- })
--- ```
function openMenu(Menu, data)
if Config.System.Menu == "jim" then
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
header = " ",
txt = "Return",
params = {
isAction = true,
event = data.onBack,
},
})
elseif data.canClose then
table.insert(Menu, 1, {
icon = "fas fa-circle-xmark",
header = " ",
txt = "Close",
params = {
isAction = true,
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
},
})
end
if data.header ~= nil then
local tempMenu = {}
for k, v in pairs(Menu) do tempMenu[k + 1] = v end
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
Menu = tempMenu
end
for k in pairs(Menu) do
if not Menu[k].params or not Menu[k].params.event then
Menu[k].params = {
isAction = true,
event = Menu[k].onSelect or function() end,
}
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
TriggerEvent("jim_bridge:client:openMenu", Menu)
elseif Config.System.Menu == "ox" then
if Config.System.Menu == "ox" then
local index = nil
if data.onBack and not data.onSelected then
table.insert(Menu, 1, {
@@ -286,11 +245,57 @@ function openMenu(Menu, data)
function(data, menu)
menu.close()
end)
elseif Config.System.Menu == "lation" then
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
onSelect = data.onBack,
header = "Return",
})
end
for k in pairs(Menu) do
if data.onSelected and Menu[k].arrow then
Menu[k].icon = "fas fa-angle-right"
end
-- If no title, use header or txt as title/label.
if not Menu[k].title then
if Menu[k].header ~= nil and Menu[k].header ~= "" then
Menu[k].title = Menu[k].header
Menu[k].label = Menu[k].header
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
else
Menu[k].title = Menu[k].txt
Menu[k].label = Menu[k].txt
end
end
-- Copy parameters from 'params' if available.
if Menu[k].params then
Menu[k].event = Menu[k].params.event
Menu[k].args = Menu[k].params.args or {}
end
if Menu[k].isMenuHeader then
Menu[k].readOnly = true
end
end
exports.lation_ui:registerMenu({
id = 'menu',
title = data.header,
onExit = data.onExit and data.onExit or nil,
subtitle = (data.headertxt and data.headertxt or ""),
options = Menu,
})
-- Show menu
exports.lation_ui:showMenu('menu')
end
end
--- A line break constant used for menu header formatting.
br = (Config.System.Menu == "ox" or Config.System.Menu == "gta") and "\n" or "<br>"
br = (Config.System.Menu == "ox" or Config.System.Menu == "gta" or Config.System.Menu == "lation") and "\n" or "<br>"
--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes.
--- @return boolean boolean True if using ox or gta menus, otherwise false.

View File

@@ -1,4 +1,4 @@
Items, Vehicles, Jobs, Gangs, Core = {}, nil, nil, nil, nil
Items, Vehicles, Jobs, Gangs = nil, nil, nil, nil
-- Shared Exports Initialization
Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv
@@ -33,80 +33,72 @@ end
if IsDuplicityVersion() then
CreateThread(function()
local cache = nil
local timeout = GetGameTimer() + 5000 -- 5 seconds max wait
local cache = nil
local timeout = GetGameTimer() + 120000 -- 2 minutes max wait (had to up this from 5 seconds because of slow servers)
-- Wait until jim_bridge is started and export is available
while not cache and GetGameTimer() < timeout do
if GetResourceState("jim_bridge"):find("start") then
local success, result = pcall(function()
return exports["jim_bridge"]:GetSharedData()
end)
if success and result then
cache = result
--print(json.encode(cache, {indent = true}))
end
-- Wait until jim_bridge is started and export is available
while not cache and GetGameTimer() < timeout do
if GetResourceState("jim_bridge"):find("start") then
local success, result = pcall(function()
return exports["jim_bridge"]:GetSharedData()
end)
if success and result then
cache = result
end
Wait(100)
end
Wait(100)
end
if not cache then
print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
return
end
if not cache then
print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
return
end
Items = cache.Items
Vehicles = cache.Vehicles
Jobs = cache.Jobs
Gangs = cache.Gangs
Items = cache.Items
Vehicles = cache.Vehicles
Jobs = cache.Jobs
Gangs = cache.Gangs
InventoryWeight = cache.InventoryWeight or InventoryWeight
InventorySlots = cache.InventorySlots or 40
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
end)
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
--print(countTable(Items), countTable(Vehicles), countTable(Jobs))
else
local hasCache = false
-- 🔹 Client Side: Request from server
_G.__jimBridgeDataCache = {}
RegisterNetEvent("jim_bridge:receiveCache", function(data)
if not hasCache then
_G.__jimBridgeDataCache = data
hasCache = true
else
return
end
end)
TriggerServerEvent("jim_bridge:requestCache")
CreateThread(function()
while not _G.__jimBridgeDataCache or not next(_G.__jimBridgeDataCache) do Wait(50) end
local cache = _G.__jimBridgeDataCache
-- Shared bridge cache - prevent duplicate listeners
exports["jim_bridge"]:GetBridgeCache(function(cache)
Items = cache.Items or {}
Vehicles = cache.Vehicles or {}
Jobs = cache.Jobs or {}
Gangs = cache.Gangs or {}
InventoryWeight = cache.InventoryWeight or InventoryWeight
InventorySlots = cache.InventorySlots or 50
if isStarted(ESXExport) then
for _, v in pairs(Vehicles) do
Vehicles[v.model] = {
model = v.model,
hash = v.hash,
price = v.price,
name = v.name,
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
}
end
end
if isStarted(OXInv) then
for k, v in pairsByKeys(Items) do
local tempInfo = exports[OXInv]:Items(k)
if tempInfo and tempInfo.client then
Items[k].image = (tempInfo.client and tempInfo.client.image) and tempInfo.client.image:gsub("nui://"..OXInv.."/web/images/", "") or k..".png"
Items[k].hunger = tempInfo.client and tempInfo.client.hunger
Items[k].thirst = tempInfo.client and tempInfo.client.thirst
CreateThread(function()
if isStarted(ESXExport) then
for _, v in pairs(Vehicles) do
Vehicles[v.model] = {
model = v.model,
hash = v.hash,
price = v.price,
name = v.name,
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
}
end
end
end
if isStarted(OXInv) then
for k, v in pairs(Items) do
local tempInfo = exports[OXInv]:Items(k)
Items[k].image = k..".png" -- Set default image
if tempInfo and tempInfo.client then -- if client info, check for hard set image
Items[k].image = (tempInfo.client.image) and tempInfo.client.image:gsub("nui://"..OXInv.."/web/images/", "") or Items[k].image
Items[k].hunger = tempInfo.client.hunger
Items[k].thirst = tempInfo.client.thirst
end
end
end
end)
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
end)
end

View File

@@ -89,6 +89,14 @@ function craftingMenu(data)
-- Check if the player can carry the required items (server callback).
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
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,
}
-- Process each recipe to create menu entries.
for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
@@ -238,7 +246,7 @@ function multiCraft(data)
})
local dialog = createInput(data.craftable.Header..(Config.System.Menu == "qb" and ": "..br.."How many to craft? "..br.."Max: "..carryMax or ""), {
((Config.System.Menu == "ox") and {
((Config.System.Menu == "ox" or Config.System.Menu == "lation") and {
type = "slider",
label = "How many to craft? "..br.."Max: "..carryMax,
required = true,
@@ -311,6 +319,7 @@ end
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- })
--- ```
function makeItem(data)
if CraftLock then return end
CraftLock = true
@@ -330,117 +339,144 @@ function makeItem(data)
local crafted, crafting = true, true
local cam = createTempCam(PlayerPedId(), data.coords)
startTempCam(cam)
-- Calculate total bartime if SingleProgress is enabled
local totalBartime = bartime * craftAmount
-- Run ingredient check and usage separately first
for i = 1, craftAmount do
for k, v in pairs(data.craft) do
if not excludeKeys[k] then
if type(v) == "table" then
for l, b in pairs(v) do
if isInventoryOpen() then
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
return
end
if crafting and progressBar({
label = "Using "..b.." "..Items[l].label,
time = 1000,
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
if not excludeKeys[k] and type(v) == "table" then
for l, b in pairs(v) do
if isInventoryOpen() then
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
return
end
if crafted then
local craftProp = nil
if prop then
craftProp = makeProp({ prop = prop.model, coords = vec4(0, 0, 0, 0), true, true })
AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true)
end
if data.sound then
local s = data.sound
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end
if isInventoryOpen() then
--print("^1Error^7: ^2Inventory is open, you tried to break things")
crafted, crafting = false, false
crafted, crafting, CraftLock = false, false, false
return
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,
request = true,
}) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil -- clear client cached token
CreateThread(function()
if data.craft["hasCrafted"] ~= nil then
debugPrint("^5Bridge^7: ^3hasCrafted ^2Found^7, ^2marking ^7'^4"..data.item.."^7' ^2as crafted for player^7")
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems)
end
Wait(100)
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give
jsonPrint(data.craft["exp"])
debugPrint("^6Bridge^7: ^3EXP ^2found^7, ^2giving exp for ^7'^4"..data.item.."^7'")
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel)
end
end)
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false
end
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
else
crafting = false
break
end
if craftProp then destroyProp(craftProp) end
if crafting and progressBar({
label = "Using "..b.." "..Items[l].label,
time = 1000,
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
Wait(500)
end
if not crafted then
stopTempCam()
ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(data) end
CraftLock = false
return
end
-- Handle SingleProgress option
if Config.Crafting.SingleProgress then
local craftProp = nil
if prop then
craftProp = makeProp({ prop = prop.model, coords = vec4(0, 0, 0, 0), true, true })
AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true)
end
if data.sound then
local s = data.sound
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label).." x"..craftAmount,
time = totalBartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
request = true,
}) then
data.craft.amount = craftAmount
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil -- clear client cached token
-- handle metadata and experience in a single go
if data.craft["hasCrafted"] ~= nil then
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give * craftAmount
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel)
end
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false
end
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
end
if craftProp then destroyProp(craftProp) end
else
-- Run the original loop for multiple progress bars
for i = 1, craftAmount do
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label),
time = bartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
request = true,
}) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil
if data.craft["hasCrafted"] ~= nil then
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel)
end
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
else
break
end
end
end
Wait(500)
stopTempCam()
CraftLock = false
if canReturn then craftingMenu(data) end
ClearPedTasks(PlayerPedId())
end
-------------------------------------------------------------
-- Server Event Handler: Crafted Item
-------------------------------------------------------------

View File

@@ -35,11 +35,26 @@ function drawText(image, input, style, oxStyleTable)
elseif Config.System.drawText == "ox" then
-- Append newline spacing to each input line.
local inputnum = countTable(input)
for k, v in pairs(input) do
input[k] = v.." \n"
input[k] = v..(inputnum ~= k and " \n" or "")
end
lib.showTextUI(table.concat(input), { icon = (image and radarTable[image] or image) or nil, position = 'left-center', style = oxStyleTable })
elseif Config.System.drawText == "lation" then
local inputnum = countTable(input)
for k, v in pairs(input) do
input[k] = v..(inputnum ~= k and " \n" or "")
end
exports.lation_ui:showText({
--title = " ",
description = table.concat(input),
keybind = nil,
icon = (image and radarTable[image] or image) or nil,
iconColor = '#3B82F6',
position = 'center-left'
})
elseif Config.System.drawText == "gta" then
-- Concatenate input lines and apply GTA style formatting.
for i = 1, #input do
@@ -88,6 +103,8 @@ function hideText()
exports[QBExport]:HideText()
elseif Config.System.drawText == "ox" then
lib.hideTextUI()
elseif Config.System.drawText == "lation" then
exports.lation_ui:hideText()
elseif Config.System.drawText == "gta" then
ClearAllHelpMessages()
elseif Config.System.drawText == "esx" then

View File

@@ -138,6 +138,98 @@ function createInput(title, opts)
)
return dialog
elseif Config.System.Menu == "lation" then
for i in pairs(opts) do
currentNum += 1
if opts[i] == nil then currentNum -= 1 goto skip end
if opts[i].type == "radio" then
for k in pairs(opts[i].options) do
opts[i].options[k].label = opts[i].options[k].text
end
options[currentNum] = {
type = "select",
required = opts[i].isRequired,
label = opts[i].label or opts[i].text,
name = opts[i].name,
default = opts[i].default or opts[i].options[1].value,
options = opts[i].options,
}
end
if opts[i].type == "number" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label or opts[i].text,
description = opts[i].txt and " - "..opts[i].txt or "",
required = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
}
end
if opts[i].type == "text" then
options[currentNum] = {
type = "input",
label = opts[i].label or opts[i].text,
description = (opts[i].txt and " - "..opts[i].txt or ""),
placeholder = opts[i].default,
required = opts[i].isRequired,
}
end
if opts[i].type == "select" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label or opts[i].text,
description = opts[i].txt and " - "..opts[i].txt or "",
required = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
if opts[i].type == "checkbox" then
for k in pairs(opts[i].options) do
if options[currentNum] then currentNum += 1 end
options[currentNum] = {
type = opts[i].type,
label = opts[i].options[k].text..(opts[i].txt and " - "..opts[i].txt or ""),
name = opts[i].options[k].value,
}
end
end
if opts[i].type == "color" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
required = opts[i].isRequired,
format = opts[i].format,
default = opts[i].default,
}
end
if opts[i].type == "slider" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
required = opts[i].required,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
::skip::
end
local dialog = exports.lation_ui:input({
title = title,
submitText = "Accept",
options = options
})
return dialog
elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(opts),
title,

View File

@@ -275,7 +275,6 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
else
local amountToAdd = amount or 1
if isStarted(OXInv) then invName = OXInv
jsonPrint(info)
exports[OXInv]:AddItem(src, item, amountToAdd, info, slot)
elseif isStarted(QSInv) then invName = QSInv
@@ -516,17 +515,19 @@ end
--- ```lua
--- TriggerServerEvent("script:server:setMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } })
--- ```
RegisterNetEvent(getScript()..":server:setMetaData", function(data)
local src = source
RegisterNetEvent(getScript()..":server:setMetaData", function(data, src)
local src = src or source
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) or isStarted(JPRInv) then
debugPrint(src, data.item, 1, data.slot)
--debugPrint(src, data.item, 1, data.slot)
local Player = Core.Functions.GetPlayer(src)
Player.PlayerData.items[data.slot].info = data.metadata
Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability
if data.metadata.durability then
Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability
end
Player.Functions.SetInventory(Player.PlayerData.items)
elseif isStarted(OXInv) then
exports[OXInv]:SetDurability(src, data.slot, data.metadata.durability)
exports[OXInv]:SetMetadata(src, data.slot, data.metadata)
elseif isStarted(QSInv) then
exports[QSInv]:SetItemMetadata(src, data.slot, data.metadata)
@@ -653,7 +654,7 @@ function canCarry(itemTable, src)
elseif isStarted(TgiannInv) then
for k, v in pairs(itemTable) do
resultTable[k] = exports[TgiannInv]:CanCarryItem(source, k, v)
resultTable[k] = exports[TgiannInv]:CanCarryItem(src, k, v)
end
elseif isStarted(JPRInv) then
@@ -868,6 +869,14 @@ function getCurrentInvWeight()
return weight
end
function hasFreeInventorySlots(slots, src)
local inv = getPlayerInv(src)
if (countTable(inv) + slots) <= InventorySlots then
return true
end
return false
end
-------------------------------------------------------------
-- Check Item Existance - return boolean(true,false)
-------------------------------------------------------------
@@ -882,9 +891,28 @@ end
--- if doesItemExist(item) then print("item exists") end
--- ```
function doesItemExist(item)
if not item or item == "" then return false end
if not item or item == "" then
return false
end
if not Items or not next(Items) then
return item.." (Missing)"
end
if Items[item] ~= nil then
return true
return Items[item]
end
return false
end
function getItemLabel(item)
if not item or item == "" then
return ""
end
if not Items or not next(Items) then
return item.." (Missing)"
end
if Items[item] ~= nil then
return Items[item].label
end
return item.." (Missing)"
end

View File

@@ -135,6 +135,34 @@ function progressBar(data)
end
})
elseif Config.System.ProgressBar == "lation" then
if exports.lation_ui:progressBar({
label = data.label,
description = nil,
duration = debugMode and 1000 or data.time,
icon = data.icon,
useWhileDead = data.dead or false,
disable = {
combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
},
anim = {
dict = data.dict,
clip = data.anim,
},
prop = {
model = data.prop and data.prop.model,
pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
bone = data.prop and (data.prop.bone or 0)
}
}) then
result = true
else
result = true
end
elseif Config.System.ProgressBar == "red" then
-- Currently only uses jim-redui if you choose this option
if exports["jim_bridge"]:redProgressBar({
@@ -204,6 +232,8 @@ function stopProgressBar()
exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "lation" then
exports.lation_ui:cancelProgress()
elseif Config.System.ProgressBar == "gta" or Config.System.ProgressBar == "red" then
exports["jim_bridge"]:stopProgressBar()
end

View File

@@ -62,6 +62,20 @@ function triggerNotify(title, message, type, src)
else
TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message)
end
elseif Config.System.Notify == "lation" then
if not src then
exports.lation_ui:notify({
title = title,
message = message,
type = type or "success",
})
else
TriggerClientEvent("lation_ui:notify", src, {
title = title,
message = message,
type = type or "success",
})
end
elseif Config.System.Notify == "red" then
if isStarted("jim-redui") then

View File

@@ -32,6 +32,25 @@ function skillCheck(data)
elseif Config.System.skillCheck == "gta" then
exports.jim_bridge:skillCheck()
elseif Config.System.skillCheck == "lation" then
local Skillbar = exports.lation_ui:skillCheck("",
{
"easy",
"easy",
"easy"
},
{
"1",
"2",
"3",
"4"
})
if Skillbar then
result = true
else
result = false
end
else
result = true
end

View File

@@ -53,6 +53,7 @@ function searchCar(vehicle)
"Military", --20
"Commercial", --21
"Trains", --22
"Open Wheel", --23
}
if Vehicles then
for k, v in pairs(Vehicles) do

View File

@@ -225,7 +225,7 @@ if isStarted(PSInv) then
return true
end
if versionCompare(GetResourceMetadata("ps-inventory", "version", 0), "1.0.6") then
if versionCompare(GetResourceMetadata(PSInv, "version", 0), "1.0.6") then
print("^6Bridge^7: ^4"..PSInv.."^2 Version above ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1true^7")
QBInvNew = true
else
@@ -233,4 +233,4 @@ if isStarted(PSInv) then
QBInvNew = false
end
end
end
end

View File

@@ -6,6 +6,25 @@ local function loadTextureDict(dict)
end
end
local function loadAnimDict(animDict)
if not DoesAnimDictExist(animDict) then
print("^6Bridge^7: ^1ERROR^7: ^2Anim Dictionary^7 - '^6"..animDict.."^7' ^2does not exist in server") return
else
while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end
end
end
local function playAnim(animDict, animName, duration, flag, ped, speed)
loadAnimDict(animDict)
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
end
local function stopAnim(animDict, animName, ped)
StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
unloadAnimDict(animDict)
end
function redProgressBar(data)
local ped = PlayerPedId()

View File

@@ -1,17 +1,14 @@
2.0.18
2.0.20
- Fix ox_inv client item caching (images)
- Add better checks if errors in ox_inv items and add more prominent error messages all around
- Tidy up several debugPrints
- Move AuthEvents to new file
- Fix clearStash error with PSInv and JPRInv
- Add extra trigger event for PSInv when opening stash (hopefully fix missing items for some)
- Fix onPlayerUnloaded() not triggering
- Add support for esx_society in openBossMenu()
- Added support for okokbilling to billPlayer()
- Rework progressBar and ui_modules folder
- Rework isAnimal.lua to be more automated and smarter
- Rework/Fix auto detection of QBInvNew
- Add new functions to get players current Inventory Weight
- Add Full support for lation_ui
- Add "Open Wheel" to searchCar()
- Increase timeout for cache timer from 5 seconds to 2 minutes
- Fix GTA progressbar erroring on playAnim()
- Add Igredient location indicator when crafting
- Fix JPRInv checks breaking early when caching framework info
- Enhance doesItemExist() and add getItemLabel() function
- Add Support for old qb-inventory config loading
- Fix typo in cancarry when using Tgiann Inv
- Add support for single progressBar when crafting
https://github.com/jimathy/jim_bridge