Compare commits

..

93 Commits

Author SHA1 Message Date
Jim Shield
880d2b0233 Version Bump
`2.0.20` - `2.0.21`
2025-07-14 11:29:29 +01:00
Jim Shield
5865d2eeb2 increase authToken timeout from 5 sec to 1 min 2025-07-13 17:41:06 +01:00
Jim Shield
528735abd1 fix anim flag on lation_ui progressbars 2025-07-12 22:24:28 +01:00
Jim Shield
d4df995c71 fix lation_ui progressbars returning true when cancelled 2025-07-12 13:05:10 +01:00
Jim Shield
3b13fa663e Merge pull request #49 from derTweaky/patch-1 2025-07-12 11:31:16 +01:00
derTweaky
e973c64ffd Update makePed.lua
Typo fix
2025-07-12 12:27:31 +02:00
Jim Shield
6ecf10c084 fix inventory config loading if vector4 is present 2025-07-11 21:16:45 +01:00
Jim Shield
79e765fbb0 Stop crafting trying to check blank stash names
Skimmed through the stash checking and editing functions and added more "security" for handling nil or blank stash names
2025-07-11 13:33:44 +01:00
Jim Shield
d4a08d5e2b Rewrite waitForSharedLoad() to fix issues 2025-07-10 22:41:28 +01:00
Jim Shield
eeb3f40018 fix break when trying to fade ents too far away 2025-07-10 22:16:26 +01:00
Jim Shield
e6b4c1d13c Add fade in animations for entity spawning 2025-07-09 15:10:53 +01:00
Jim Shield
6ecbc3392a make breaktool() smarter and add useToolDegrade() 2025-07-09 15:09:41 +01:00
Jim Shield
7f10c394de make cache timeout optional 2025-07-09 15:09:14 +01:00
Jim Shield
fae099fd93 fix progressbar looking for missing function 2025-07-08 11:20:52 +01:00
Jim Shield
64d43b54e9 fix getPlayer() checking users not logged in yet 2025-07-08 00:48:39 +01:00
Jim Shield
d4abdbc93f fix typo stopping mysql cache loading on esx 2025-07-07 17:56:23 +01:00
Jim Shield
b595453efe Rename metahandler functions
Realised I named the player and item metadata functions too closely and made it confusing, this separates them better
2025-07-07 17:47:04 +01:00
Jim Shield
d255034c02 Increase timer again on cache timeout
Also moved around the cache timers to be a bit more accurate (timers start when the cache creation is triggered)
2025-07-07 02:11:36 +01:00
Jim Shield
d6fd2cc28c Fix toggleDuty() for ESX 2025-07-07 00:09:34 +01:00
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
Jim Shield
fea72bba2e hotfix ox_lib progressbars stopping movement 2025-06-23 12:54:24 +01:00
Jim Shield
b364f14260 Version Bump
`2.0.17` - `2.0.18`
2025-06-22 22:16:17 +01:00
Jim Shield
8077ec4f46 Add functions to try and get players inv weights 2025-06-22 20:03:12 +01:00
Jim Shield
e5277574df Rework Auto detection of QBInvNew
It's messy but hopefully it will work as expected
2025-06-22 17:45:33 +01:00
Jim Shield
e0563dd4ce Add extra triggerevent for ps-inventory
They keep changing their event names 🤷‍♀️
added a duplicate with `ps-` at the start in hopes this triggers things correctly when opening a stash
2025-06-22 17:45:04 +01:00
Jim Shield
1987cf3df3 Fix IsAnimal checks getting confused 2025-06-22 16:51:19 +01:00
Jim Shield
9f5145c3b2 Update progressBars.lua 2025-06-22 16:38:02 +01:00
Jim Shield
5921cf8adc ProgressBar default variable fixes 2025-06-22 16:14:32 +01:00
Jim Shield
f154b45076 Enhance built in ui_modules to support RedM 2025-06-21 22:34:03 +01:00
Jim Shield
2c41272c36 Remove ClearStash export call for ps-inv 2025-06-21 18:28:50 +01:00
McKlean
57d4beafed Commit[crafting]: commented print causing errors
- debugPrint was causing crafting to error
2025-06-21 16:32:49 +01:00
McKlean
bdb526bd1e Commit[Billing]: added okok Billing
- Added OKOK's CreateInvoice as a choice for the billing
2025-06-21 14:52:09 +01:00
Jim Shield
bb9dbe248f Update callback.lua 2025-06-21 13:56:49 +01:00
Jim Shield
caf2e7fbed add support for esx_society to openBossMenu() 2025-06-21 13:29:42 +01:00
Jim Shield
d0b8fb16d6 fix for if ox_inv can't find an item when trying to get image 2025-06-21 13:29:01 +01:00
Jim Shield
2e57e66095 Fix confusion with old and new inv shop exports 2025-06-20 23:33:44 +01:00
Jim Shield
05fc856e86 small tidy up 2025-06-20 21:53:44 +01:00
Jim Shield
74e6a679e2 Tidy up onPlayerLoaded for all frameworks 2025-06-20 21:32:08 +01:00
Jim Shield
17db5aa31a Fix QBCore OnPlayerUnload event not triggering 2025-06-20 21:12:53 +01:00
Jim Shield
387c97de71 Fix jpr erroring when clearStash is triggered 2025-06-20 20:56:45 +01:00
Jim Shield
de45491c8c move exploit protection to new file
Moves Auth Token stuff to separate file because it doesn't really need to be in `itemcontrol.lua`

Helps me keep track of it better too

Also added a "cooldown" to workaround people's multicharacter scripts claiming the players loaded more than once and breaking it
2025-06-20 20:55:49 +01:00
Jim Shield
4da4cbfc15 Tidy up and organise several debugPrints better 2025-06-20 18:03:08 +01:00
Jim Shield
9fbf2f2211 Make frameworkCache.lua smurter
Add better checks for if theres errors in ox_inv items and add more prominent error messages all around
2025-06-20 17:47:54 +01:00
Jim Shield
41b2965743 Merge pull request #47 from oosayeroo/main 2025-06-20 13:35:45 +01:00
Jim Shield
a02c11d3cc Update crafting.lua 2025-06-19 22:18:53 +01:00
Jim Shield
c888aa5ecf Fix ox_inv item client info (images) caching 2025-06-19 22:15:49 +01:00
McKlean
30de9a2d9c Version Bump 2025-06-18 20:52:04 +01:00
oosayeroo
804327f3aa Merge branch 'jimathy:main' into main 2025-06-18 19:30:56 +01:00
oosayeroo
ba59418b6d return values (boolean)
- added return values on following functions
:fundPlayer
:chargePlayer

- added check for Config.System in Notify.lua
2025-06-18 19:30:21 +01:00
Jim Shield
f39678002f Fix progressbars getting stuck on ox_lib 2025-06-18 12:02:03 +01:00
McKlean
3e402cae3d Commit[progressBar]: ox progress options
- Added ability to choose circle/bar type
- Added ability for props when using progbar
2025-06-16 19:02:46 +01:00
Jim Shield
b4aa1b3eda Version Bump
`2.0.15 - 2.0.16`
2025-06-16 11:05:46 +01:00
Jim Shield
0f9bab00ef add openBossmenu() function 2025-06-16 00:10:32 +01:00
Jim Shield
9d8706e8c6 Tidy up setThirst() and setHunger() functions
added debug print and fixed non-esx functions setting needs wrong
2025-06-14 22:32:58 +01:00
Jim Shield
6ba1e14888 Change crafting menu slightly to make it more readable 2025-06-14 14:44:16 +01:00
Jim Shield
0454f8c0a9 Add full support for JPR-Inventory
Thanks to JPR for providing access to make this possible

https://jpresources.tebex.io/package/5284060
2025-06-13 23:25:32 +01:00
Jim Shield
e17d8f2b93 Merge pull request #46 from oosayeroo/main 2025-06-13 18:43:21 +01:00
oosayeroo
b44dfa91e5 Merge branch 'jimathy:main' into main 2025-06-11 18:44:49 +01:00
oosayeroo
51d4c88694 light fix (in case of missing Crafting Var)
- added check for missing Config.Crafting variables

- reduces errors in scripts if not added to config
2025-06-11 18:43:31 +01:00
Jim Shield
f5f502875e Comment out a couple debug prints for now 2025-06-11 16:30:51 +01:00
Jim Shield
9c769f4e8e Update frameworkCache.lua 2025-06-11 16:30:29 +01:00
Jim Shield
87cdd5502e Change QBInvNew export because i forgor
I forgot that it needed to check the client side too for the change, but I set it to check for a server only export

got lucky and realised `HasItem` is an export on both client and server
2025-06-10 21:47:12 +01:00
Jim Shield
68d713a8dc Add warnings for QB if theres an error in shared files
If `jim_bridge` attempts to find shared files, and it returns `nil` it will print an error message stating it couldn't find them.

So..people can stop blaming me for my script not working when its an error in their vehicles.lua
2025-06-10 18:29:10 +01:00
Jim Shield
dd34f8aab1 Add auto checkers for QBInvNew
I had to add a checker for if old qbinv was being used or new qbinv

This was made into a variable, but this update makes the script auto set it if it can't find an export. Automating the process
2025-06-10 18:26:57 +01:00
Jim Shield
4a3fb8db4d Add "support" for CodeM shops
This is a tricky one to support

They handle their shops inside their script with no way to add one from the outside

They give the ability to trigger a shop from outside the script, but not create one

This just adds that trigger for now
2025-06-07 19:50:34 +01:00
Jim Shield
59733de08e support for npwd_qbx_mail addon for sending mail 2025-06-07 19:48:18 +01:00
Jim Shield
2b7a1c2e6d Start working on billPlayer() function
The intention here is to try and add multiple "billing" script functions, so the user can pre-set it for scripts that use `jim_bridge`

Currently only handles `jim-payments`
2025-06-07 18:14:53 +01:00
Jim Shield
5db9e88bb9 add function checkExportExists()
Workaround function made to detect if new QB Inventory functions are available

This can be used in other situations like checking if a script is working correctly or not and the export has been registered
2025-06-07 18:13:06 +01:00
Jim Shield
a5b1f750dd Fix weapons caching for ox_inv
I completely overlooked the fact that when ox_inv shows weapon info, it does it with the upper case version `WEAPON_PISTOL` while my scripts were looking for `weapon_pistol`

making my scripts think they didn't exist

This fixes that
2025-06-06 19:36:12 +01:00
37 changed files with 2228 additions and 873 deletions

View File

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

@@ -21,12 +21,12 @@ This module provides access and control over player metadata, which is useful fo
print("Player stress level:", stress) print("Player stress level:", stress)
``` ```
- **SetMetadata(player, key, value)** - **setPlayerMetadata(player, key, value)**
⚠️ Server side only ⚠️ Server side only
- Updates or assigns a value to a specific metadata key for a player. - Updates or assigns a value to a specific metadata key for a player.
- The function updates the player's metadata using the active core export. - The function updates the player's metadata using the active core export.
- **Example:** - **Example:**
```lua ```lua
SetMetadata(player, "stress", 0) setPlayerMetadata(player, "stress", 0)
``` ```

View File

@@ -1,10 +1,11 @@
--[[ --[[
Cached Resource Initialization Module Cached Resource Initialization Module
-------------------------------------- --------------------------------------
This module initializes shared data (Items, Vehicles, Jobs, Gangs) Initializes shared resource data (Items, Vehicles, Jobs, Gangs) once and caches it
only once and stores it in _G.__jimBridgeDataCache for reuse across scripts. to improve performance and reduce redundant data fetching across scripts.
]] ]]
-- Define resource names used in different frameworks
local Exports = { local Exports = {
QBExport = "qb-core", QBExport = "qb-core",
QBXExport = "qbx_core", QBXExport = "qbx_core",
@@ -19,6 +20,7 @@ local Exports = {
CodeMInv = "codem-inventory", CodeMInv = "codem-inventory",
OrigenInv = "origen_inventory", OrigenInv = "origen_inventory",
TgiannInv = "tgiann-inventory", TgiannInv = "tgiann-inventory",
JPRInv = "jpr-inventory",
OXLibExport = "ox_lib", OXLibExport = "ox_lib",
@@ -32,17 +34,35 @@ local Exports = {
RSGInv = "rsg-inventory" RSGInv = "rsg-inventory"
} }
-- Ensure cache only runs once -- Prevent reloading if cache is already initialized
if _G.__jimBridgeDataCache then return end local cache = {
_G.__jimBridgeDataCache = {} Items = {},
local cache = _G.__jimBridgeDataCache Vehicles = {},
Jobs = {},
Gangs = {},
}
local cacheReady = false
local timers = {}
local function startTimer(label)
timers[label] = GetGameTimer()
end
function checkExists(resourceName) local function endTimer(label)
timers[label] = GetGameTimer() - (timers[label] or GetGameTimer())
timers[label] = "("..(timers[label] / 1000).."s)"
end
startTimer("Cache") startTimer("Items") startTimer("Vehicles") startTimer("Jobs") startTimer("InvWeight") startTimer("InvSlots")
-- 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") return GetResourceState(resourceName):find("start") or GetResourceState(resourceName):find("stopped")
end end
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua'))) -- Ensure oxmysql resource is loaded
fileLoader() if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) then
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua')))
fileLoader()
end
if checkExists(Exports.OXCoreExport) then if checkExists(Exports.OXCoreExport) then
-- Detected OX_Core in server, wait for it to be started if needed -- Detected OX_Core in server, wait for it to be started if needed
while GetResourceState(Exports.OXCoreExport) ~= "started" do Wait(100) end while GetResourceState(Exports.OXCoreExport) ~= "started" do Wait(100) end
@@ -56,9 +76,8 @@ if checkExists(Exports.ESXExport) then
fileLoader() fileLoader()
end end
-- Init variables -- Initialize variables for caching
local Items, Vehicles, Jobs, Gangs, Core = nil, nil, nil, nil, nil local itemResource, jobResource, vehResource = "N/A", "N/A", "N/A"
local itemResource, jobResource, vehResource = "", "", ""
-- Print just to announce it knows the exports/scripts exist in the server -- Print just to announce it knows the exports/scripts exist in the server
for _, v in pairs(Exports) do for _, v in pairs(Exports) do
@@ -70,51 +89,75 @@ end
--------------------- ---------------------
---- Load Items ----- ---- Load Items -----
--------------------- ---------------------
-- Items initialization based on detected inventory system
if checkExists(Exports.OXInv) then if checkExists(Exports.OXInv) then
-- Wait for OX Inventory to start if it's not already started -- Wait for OX Inventory to start if it's not already started
while GetResourceState(Exports.OXInv) ~= "started" do Wait(100) end while GetResourceState(Exports.OXInv) ~= "started" do Wait(100) end
itemResource = Exports.OXInv itemResource = Exports.OXInv
Items = exports[Exports.OXInv]:Items()
-- IF QBX-Core is running, merge items from there local success, result = pcall(function()
if checkExists(Exports.QBExport)then return exports[Exports.OXInv]:Items()
local tempWeapons = exports[Exports.QBExport]:GetCoreObject().Shared.Weapons end)
for k, v in pairs(tempWeapons) do if success and result then
local info = exports[Exports.OXInv]:Items(v.name) cache.Items = result
local weight = info and info.weight or 0 end
if not Items[v.name] then
Items[v.name] = { -- Get Weapon info and duplicate them if they are uppercase
name = v.name, -- (duplicate incase anything checks for the uppercase version)
label = v.label, for k, v in pairs(cache.Items) do
type = "weapon", if type(k) == "string" then
ammotype = v.ammotype or "AMMO_PISTOL", if k:find("WEAPON") then
weight = weight, cache.Items[k:lower()] = cache.Items[k]
image = v.image or (v.name..".png"),
description = v.label or "",
}
end end
else
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
print("^1Possible Issue found^7:")
print(json.encode(Items[k], {indent = true}))
end end
end end
-- tidy info into something jim_bridge can use
for k, v in pairs(Items) do elseif checkExists(Exports.QBXExport) then
Items[k].image = (v.client and v.client.image) and v.client.image:gsub("nui://"..Exports.OXInv.."/web/images/", "") or k..".png" while GetResourceState(Exports.QBXExport) ~= "started" do Wait(100) end
Items[k].hunger = v.client and v.client.hunger itemResource = Exports.QBXExport
Items[k].thirst = v.client and v.client.thirst 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 end
elseif checkExists(Exports.QBExport) then elseif checkExists(Exports.QBExport) then
while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end
itemResource = Exports.QBExport itemResource = Exports.QBExport
Core = exports[Exports.QBExport]:GetCoreObject() cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
Items = Core.Shared.Items
elseif checkExists(Exports.ESXExport) then elseif checkExists(Exports.ESXExport) then
itemResource = Exports.ESXExport itemResource = Exports.ESXExport
if GetResourceState(Exports.QSInv):find("start") then if GetResourceState(Exports.QSInv):find("start") then
Items = exports[Exports.QSInv]:GetItemList() cache.Items = exports[Exports.QSInv]:GetItemList()
else else
Items = ESX.GetItems() cache.Items = ESX.GetItems()
while not next(Items) do while not next(cache.Items) do
Items = ESX.GetItems() cache.Items = ESX.GetItems()
Wait(1000) Wait(1000)
end end
end end
@@ -122,23 +165,27 @@ elseif checkExists(Exports.ESXExport) then
elseif checkExists(Exports.RSGExport) then elseif checkExists(Exports.RSGExport) then
while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end
itemResource = Exports.RSGExport itemResource = Exports.RSGExport
Core = exports[Exports.RSGExport]:GetCoreObject() cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
Items = Core.Shared.Items
end end
endTimer("Items")
--------------------- ---------------------
--- Load Vehicles --- --- Load Vehicles ---
--------------------- ---------------------
if checkExists(Exports.QBXExport) or checkExists(Exports.QBExport) then -- Vehicle loading depending on framework
if checkExists(Exports.QBXExport) then
vehResource = Exports.QBXExport
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
elseif checkExists(Exports.QBExport)then
vehResource = Exports.QBExport vehResource = Exports.QBExport
Core = Core or exports[Exports.QBExport]:GetCoreObject() cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
Vehicles = Core.Shared.Vehicles
elseif checkExists(Exports.OXCoreExport) then elseif checkExists(Exports.OXCoreExport) then
vehResource = Exports.OXCoreExport vehResource = Exports.OXCoreExport
Vehicles = {} cache.Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do for k, v in pairs(Ox.GetVehicleData()) do
Vehicles[k] = { cache.Vehicles[k] = {
model = k, hash = GetHashKey(k), model = k, hash = GetHashKey(k),
price = v.price, price = v.price,
name = v.name, name = v.name,
@@ -149,9 +196,8 @@ elseif checkExists(Exports.OXCoreExport) then
elseif checkExists(Exports.ESXExport) then elseif checkExists(Exports.ESXExport) then
vehResource = Exports.ESXExport vehResource = Exports.ESXExport
while not MySQL do Wait(1000) end while not MySQL do Wait(1000) end
Vehicles = {}
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
Vehicles[v.model] = { cache.Vehicles[v.model] = {
model = v.model, model = v.model,
hash = GetHashKey(v.model), hash = GetHashKey(v.model),
price = v.price, price = v.price,
@@ -161,21 +207,25 @@ elseif checkExists(Exports.ESXExport) then
elseif checkExists(Exports.RSGExport) then elseif checkExists(Exports.RSGExport) then
vehResource = Exports.RSGExport vehResource = Exports.RSGExport
Core = Core or exports[Exports.RSGExport]:GetCoreObject() cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
Vehicles = Core.Shared.Vehicles
end end
endTimer("Vehicles")
--------------------- ---------------------
----- Load Jobs ----- ----- Load Jobs -----
--------------------- ---------------------
-- Jobs loading based on framework
if checkExists(Exports.QBXExport) then if checkExists(Exports.QBXExport) then
jobResource = Exports.QBXExport jobResource = Exports.QBXExport
Core = Core or exports[Exports.QBXExport]:GetCoreObject() cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
Jobs, 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 elseif checkExists(Exports.OXCoreExport) then
jobResource = Exports.OXCoreExport jobResource = Exports.OXCoreExport
Jobs = {}
while not MySQL do Wait(1000) end while not MySQL do Wait(1000) end
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`') local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`') local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
@@ -185,34 +235,29 @@ elseif checkExists(Exports.OXCoreExport) then
gradeMap[grade.group][grade.grade] = { name = grade.label } gradeMap[grade.group][grade.grade] = { name = grade.label }
end end
for _, job in pairs(tempJobs) do for _, job in pairs(tempJobs) do
Jobs[job.name] = { cache.Jobs[job.name] = {
label = job.label, label = job.label,
grades = gradeMap[job.name] or {} grades = gradeMap[job.name] or {}
} }
end end
Gangs = Jobs cache.Gangs = cache.Jobs
elseif checkExists(Exports.QBExport) then
jobResource = Exports.QBExport
Core = Core or exports[Exports.QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
elseif checkExists(Exports.ESXExport) then elseif checkExists(Exports.ESXExport) then
jobResource = Exports.ESXExport jobResource = Exports.ESXExport
ESX = exports[Exports.ESXExport]:getSharedObject() ESX = exports[Exports.ESXExport]:getSharedObject()
Jobs = ESX.GetJobs() cache.Jobs = ESX.GetJobs()
while not next(Jobs) do while not next(cache.Jobs) do
Wait(100) Wait(100)
Jobs = ESX.GetJobs() cache.Jobs = ESX.GetJobs()
end end
for Role, Grades in pairs(Jobs) do for Role, Grades in pairs(cache.Jobs) do
-- Check for if user has added grades -- Check for if user has added grades
if Grades.grades == nil or not next(Grades.grades) then if Grades.grades == nil or not next(Grades.grades) then
goto continue goto continue
end end
for grade, info in pairs(Grades.grades) do for grade, info in pairs(Grades.grades) do
if info.label and info.label:find("[Bb]oss") then 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 goto continue
end end
end end
@@ -225,61 +270,190 @@ elseif checkExists(Exports.ESXExport) then
end end
if highestGrade then if highestGrade then
print("found boss for", Role) cache.Jobs[Role].grades[tostring(highestGrade)].isBoss = true
Jobs[Role].grades[tostring(highestGrade)].isBoss = true
end end
::continue:: ::continue::
end end
Gangs = Jobs cache.Gangs = cache.Jobs
elseif checkExists(Exports.RSGExport) then elseif checkExists(Exports.RSGExport) then
jobResource = Exports.RSGExport jobResource = Exports.RSGExport
Core = Core or exports[Exports.RSGExport]:GetCoreObject() cache.Jobs, cache.Gangs = exports[Exports.RSGExport]:GetCoreObject().Shared.Jobs, exports[Exports.RSGExport]:GetCoreObject().Shared.Gangs
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end
endTimer("Jobs")
-- 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 end
-- Save to global cache -- Auto Detection of Inventory Weight -- **EXPERIMENTAL**
cache.Items = Items -- Forcefully load the the specified config file from inventory scripts
cache.Vehicles = Vehicles -- This allows to get information required for certain functions that need to detect how much space is left in a players inventory
cache.Jobs = Jobs -- This is born from too many tickets of me needing to explain that they need to change "InventoryWeight" to match their inv setting
cache.Gangs = Gangs 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,
vector4 = vector4,
Citizen = Citizen,
GetResourceState = GetResourceState,
exports = exports,
DependencyCheck = function() return nil end,
}
local fn, err = load(content, '@'..data.file, 't', env)
--local success, err = xpcall(fn, function(e)
-- print(debug.traceback("Error while executing qs-inventory config:\n" .. tostring(e), 2))
--end)
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
while GetResourceState(script) ~= "started" do Wait(100) print("Waiting for script start") 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() CreateThread(function()
local counts = { local counts = {
Items = 0, Items = 0, Vehicles = 0, Jobs = 0, Gangs = 0,
Vehicles = 0,
Jobs = 0,
Gangs = 0,
} }
for k, v in pairs(cache) do for k, v in pairs(cache) do
for count in pairs(v) do if type(v) ~= "number" then for count in pairs(v) do counts[k] += 1 end end
counts[k] += 1
end
end end
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Items).."^2 Items from ^7"..itemResource) if cache.InventoryWeight then
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Vehicles).."^2 Vehicles from ^7"..vehResource) print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventoryWeight^7: ^3"..cache.InventoryWeight.."^7 (^3"..(cache.InventoryWeight / 1000).."kg^7) ^7"..timers["InvWeight"])
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Jobs).."^2 Jobs from ^7"..jobResource) end
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Gangs).."^2 Gangs from ^7"..jobResource) if cache.InventorySlots then
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7"..timers["InvSlots"])
end
print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7"..timers["Items"])
print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7"..timers["Vehicles"])
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7"..timers["Jobs"])
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7"..timers["Jobs"])
endTimer("Cache")
print("^6FrameworkCache^7: ^2Cache Ready ^7"..timers["Cache"])
cacheReady = true
end) end)
--print(json.encode(cache.Items , { indent = true}))
RegisterNetEvent("jim_bridge:requestCache", function() RegisterNetEvent("jim_bridge:requestCache", function()
local src = source local src = source
TriggerClientEvent("jim_bridge:receiveCache", src, _G.__jimBridgeDataCache) TriggerClientEvent("jim_bridge:receiveCache", src, cache)
end) end)
exports("GetSharedData", function() exports("GetSharedData", function()
-- Wait for data to be ready before returning it -- Wait indefinitely (with periodic checks) until cacheReady is true
local timeout = GetGameTimer() + 5000 local timeout = GetGameTimer() + 20000 -- 20 second failsafe timeout
while ( while not cacheReady and GetGameTimer() < timeout do
not _G.__jimBridgeDataCache or Wait(100)
(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)
end 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) end)

View File

@@ -1,6 +1,6 @@
name "Jim_Bridge" name "Jim_Bridge"
author "Jimathy" author "Jimathy"
version "2.0.15" version "2.0.21"
description "Framework Bridge By Jimathy" description "Framework Bridge By Jimathy"
fx_version "cerulean" fx_version "cerulean"
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.' rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
@@ -18,9 +18,10 @@ files {
-- Version checker -- Version checker
server_scripts { server_scripts {
'frameworkCache.lua', 'frameworkCache.lua',
'_versioncheck.lua' '_versioncheck.lua',
} }
client_scripts { client_scripts {
'ui_modules/*.lua' 'clientFrameworkCache.lua',
'ui_modules/*.lua',
} }

118
shared/_authToken.lua Normal file
View File

@@ -0,0 +1,118 @@
-------------------------------------------------------------
-- Exploit Auth System
-------------------------------------------------------------
AuthEvent = nil
currentToken = nil
if isServer() then
local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen()
validTokens = validTokens or {}
createCallback(AuthEvent, function(source)
local src = source
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
--debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return ""
end
debugPrint("^1Auth^7:^2 Player Source^7: "..src.." ^2requested new token^7:", token)
validTokens[src] = token
timeOutAuth(validTokens[src], src) -- Give script 10 seconds, then clear token
return token
end)
function timeOutAuth(token, src)
local token = token
SetTimeout(10000, function()
if token == validTokens[src] then
print("^1--------------------------------------------^7")
print("^7Clearing token for player ^1"..src.."^7", token)
print("^7This shouldn't happen unless a token has been called by a player or script and it hasn't been used")
print("^1--------------------------------------------^7")
end
end)
end
RegisterNetEvent(getScript()..":clearAuthToken", function()
local src = source
debugPrint("^1Auth^7: ^2Manually removing token for Player Source^7:", src, validTokens[src])
validTokens[src] = nil
end)
receivedEvent = {}
authCooldown = {}
createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7")
return ""
end
if authCooldown[src] then
debugPrint("^1Auth^7: ^3Cooldown active^7 for Player ^1"..src.."^7, ignoring additional auth request")
return AuthEvent
end
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent)
authCooldown[src] = true
SetTimeout(60000, function() -- 1 minute cooldown
authCooldown[src] = nil
end)
if not receivedEvent[src] then
receivedEvent[src] = true
return AuthEvent
else
print("^1Auth^7: ^1Player ^7"..src.." ^1tried to request auth token more than once^7")
return ""
end
end)
RegisterNetEvent(getScript()..":clearAuthEventRequest", function()
local src = source
debugPrint("^1Auth^7: ^2Manually clearing Auth Event for Player Source^7:", src, AuthEvent)
receivedEvent[src] = nil
end)
-- Multiuse function to check if the generated client token is valid
function checkToken(src, token, genType, name)
if token == nil then
debugPrint("^1Auth^7: ^1No token recieved^7")
if genType == "stash" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1without an auth token^7")
elseif genType == "item" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1without an auth token^7")
end
return false
else
debugPrint("^1Auth^7: ^2Auth token received^7, ^2checking against server cache^7..")
if token ~= validTokens[src] then
debugPrint("^1Auth^7: ^1Tokens don't match! ^7", token, validTokens[src])
if genType == "stash" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1with an incorrect auth token^7")
elseif genType == "item" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1with an incorrect auth token^7")
end
return false
else
debugPrint("^1Auth^7: ^2Client and Server Auth tokens match^7!", token, validTokens[src])
validTokens[src] = nil
return true
end
end
end
else
onPlayerLoaded(function()
debugPrint("^1Auth^7: ^2Requesting ^3Auth Event^7")
AuthEvent = triggerCallback(getScript()..":callback:GetAuthEvent")
end, true)
onPlayerUnload(function()
debugPrint("^1Auth^7: ^2Clearing Auth Event^7")
TriggerServerEvent(getScript()..":clearAuthEventRequest")
end, true)
end

View File

@@ -47,22 +47,21 @@ function onPlayerLoaded(func, onStart)
if isStarted(QBExport) or isStarted(QBXExport) then if isStarted(QBExport) or isStarted(QBXExport) then
onPlayerFramework = QBExport onPlayerFramework = QBExport
AddEventHandler('QBCore:Client:OnPlayerLoaded', tempFunc) RegisterNetEvent('QBCore:Client:OnPlayerLoaded', tempFunc)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
onPlayerFramework = ESXExport onPlayerFramework = ESXExport
AddEventHandler('esx:playerLoaded', function() RegisterNetEvent('esx:playerLoaded', function()
if waitForSharedLoad() then if waitForSharedLoad() then
if isStarted(ESXExport) then Wait(1000) end
tempFunc() tempFunc()
end end
end end
) )
elseif isStarted(OXCoreExport) then elseif isStarted(OXCoreExport) then
onPlayerFramework = OXCoreExport onPlayerFramework = OXCoreExport
AddEventHandler('ox:playerLoaded', tempFunc) RegisterNetEvent('ox:playerLoaded', tempFunc)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
onPlayerFramework = RSGExport onPlayerFramework = RSGExport
AddEventHandler('RSGCore:Client:OnPlayerLoaded', tempFunc) RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
end end
if onPlayerFramework ~= "" then if onPlayerFramework ~= "" then
@@ -83,12 +82,15 @@ end
--- end) --- end)
--- ``` --- ```
function onPlayerUnload(func) function onPlayerUnload(func)
AddEventHandler('QBCore:Client:OnPlayerUnload', function() func() end) debugPrint("^6Bridge^7: ^2Registering ^3onPlayerUnload^7()")
AddEventHandler('ox:playerLogout', function() func() end) RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() func() end)
AddEventHandler('RSGCore:Client:OnPlayerUnload', function() func() end)
RegisterNetEvent('ox:playerLogout', function() func() end)
RegisterNetEvent('RSGCore:Client:OnPlayerUnload', function() func() end)
RegisterNetEvent('esx:onPlayerLogout', function() func() end)
--AddEventHandler('esx:playerLogout', function() func() end)
-- ^ Only server side for now, need a way to send it to client if not already available
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -115,7 +117,6 @@ function onResourceStart(func, thisScript)
debugPrint("^6Bridge^7: ^2Shared Load Detected^7.") debugPrint("^6Bridge^7: ^2Shared Load Detected^7.")
hasPrinted = true hasPrinted = true
end end
if isStarted(ESXExport) then Wait(10000) end
func() func()
end end
end end
@@ -192,38 +193,35 @@ end
local messageShown = false local messageShown = false
function waitForSharedLoad() function waitForSharedLoad()
local timeout = 100000 -- 10 seconds in milliseconds local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
local startTime = GetGameTimer()
local loaded = true
--Wait(1000)
local count = {}
local loop = 0 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 (timeout and GetGameTimer() < timeout) do
if loop >= 3 and not messageShown then
if (not Jobs or not next(Jobs)) 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 end
if (not Items or not next(Items)) then 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 end
if (not Vehicles or not next(Vehicles)) then 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 end
messageShown = true
end end
messageShown = true
--print((GetGameTimer() - startTime) < timeout)
Wait(1000) Wait(1000)
if Jobs and Items and Vehicles then
--print("^6Bridge^7: ^2Jobs, Items, and Vehicles Loaded^7.")
loaded = true
break
end
loop += 1 loop += 1
end end
if not loaded then
if Jobs and Items and Vehicles then
debugPrint("^6Bridge^7: ^2Jobs, Items, and Vehicles Loaded^7.")
return true
else
print("^4Error^7: ^1Timeout reached while waiting for shared load^7.") print("^4Error^7: ^1Timeout reached while waiting for shared load^7.")
return false return false
else
return true
end end
end end

View File

@@ -24,8 +24,8 @@
--- ``` --- ```
function createCallback(callbackName, funct) function createCallback(callbackName, funct)
if isServer() then if isServer() then
debugPrint("^6Bridge^7: ^3Registering callback^7:", callbackName)
if isStarted(OXLibExport) then if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..OXLibExport.." ^3Callback^7:", callbackName)
lib.callback.register(callbackName, funct) lib.callback.register(callbackName, funct)
else else
local adaptedFunction = function(source, cb, ...) local adaptedFunction = function(source, cb, ...)
@@ -34,12 +34,14 @@ function createCallback(callbackName, funct)
end end
if isStarted(QBExport) then if isStarted(QBExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
Core = Core or exports[QBExport]:GetCoreObject() Core = Core or exports[QBExport]:GetCoreObject()
Core.Functions.CreateCallback(callbackName, adaptedFunction) Core.Functions.CreateCallback(callbackName, adaptedFunction)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
ESX.RegisterServerCallback(callbackName, adaptedFunction) ESX.RegisterServerCallback(callbackName, adaptedFunction)
else else
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to register callback with", callbackName) print("^1ERROR^7: ^1Can't find any supported framework to register callback with^7: "..callbackName)
end end
end end
end end
@@ -64,7 +66,7 @@ end
--- ``` --- ```
function triggerCallback(callbackName, ...) function triggerCallback(callbackName, ...)
local result = nil local result = nil
debugPrint("^6Bridge^7: ^3Triggering callback^7:", callbackName) debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName)
if isStarted(OXLibExport) then if isStarted(OXLibExport) then
result = lib.callback.await(callbackName, false, ...) result = lib.callback.await(callbackName, false, ...)
elseif isStarted(QBExport) then elseif isStarted(QBExport) then

View File

@@ -47,48 +47,7 @@
--- }) --- })
--- ``` --- ```
function openMenu(Menu, data) function openMenu(Menu, data)
if Config.System.Menu == "jim" then if Config.System.Menu == "ox" 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
local index = nil local index = nil
if data.onBack and not data.onSelected then if data.onBack and not data.onSelected then
table.insert(Menu, 1, { table.insert(Menu, 1, {
@@ -286,11 +245,57 @@ function openMenu(Menu, data)
function(data, menu) function(data, menu)
menu.close() menu.close()
end) 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
end end
--- A line break constant used for menu header formatting. --- 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. --- 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. --- @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 -- Shared Exports Initialization
Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv
@@ -10,7 +10,7 @@ OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
Exports.ESXExport or "", Exports.ESXExport or "",
Exports.OXCoreExport or "" Exports.OXCoreExport or ""
OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv = OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
Exports.OXInv or "", Exports.OXInv or "",
Exports.QBInv or "", Exports.QBInv or "",
Exports.PSInv or "", Exports.PSInv or "",
@@ -18,7 +18,8 @@ OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv =
Exports.CoreInv or "", Exports.CoreInv or "",
Exports.CodeMInv or "", Exports.CodeMInv or "",
Exports.OrigenInv or "", Exports.OrigenInv or "",
Exports.TgiannInv or "" Exports.TgiannInv or "",
Exports.JPRInv or ""
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or "" RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
QBMenuExport = Exports.QBMenuExport or "" QBMenuExport = Exports.QBMenuExport or ""
@@ -32,71 +33,72 @@ end
if IsDuplicityVersion() then if IsDuplicityVersion() then
CreateThread(function() local cache = nil
local cache = nil local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
local timeout = GetGameTimer() + 5000 -- 5 seconds max wait
-- Wait until jim_bridge is started and export is available -- Wait until jim_bridge is started and export is available
while not cache and GetGameTimer() < timeout do while not cache and (timeout and GetGameTimer() < timeout) do
if GetResourceState("jim_bridge"):find("start") then if GetResourceState("jim_bridge"):find("start") then
local success, result = pcall(function() local success, result = pcall(function()
return exports["jim_bridge"]:GetSharedData() return exports["jim_bridge"]:GetSharedData()
end) end)
if success and result then if success and result then
cache = result cache = result
--print(json.encode(cache, {indent = true}))
end
end end
Wait(100)
end end
Wait(100)
end
if not cache then if timeout and not cache then
print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.") print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
return return
end end
Items = cache.Items Items = cache.Items
Vehicles = cache.Vehicles Vehicles = cache.Vehicles
Jobs = cache.Jobs Jobs = cache.Jobs
Gangs = cache.Gangs Gangs = cache.Gangs
InventoryWeight = cache.InventoryWeight or InventoryWeight
InventorySlots = cache.InventorySlots or 40
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.") debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
end) --print(countTable(Items), countTable(Vehicles), countTable(Jobs))
else else
local hasCache = false -- Shared bridge cache - prevent duplicate listeners
-- 🔹 Client Side: Request from server exports["jim_bridge"]:GetBridgeCache(function(cache)
_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
Items = cache.Items or {} Items = cache.Items or {}
Vehicles = cache.Vehicles or {} Vehicles = cache.Vehicles or {}
Jobs = cache.Jobs or {} Jobs = cache.Jobs or {}
Gangs = cache.Gangs or {} Gangs = cache.Gangs or {}
InventoryWeight = cache.InventoryWeight or InventoryWeight
InventorySlots = cache.InventorySlots or 50
if isStarted(ESXExport) then CreateThread(function()
for _, v in pairs(Vehicles) do if isStarted(ESXExport) then
Vehicles[v.model] = { for _, v in pairs(Vehicles) do
model = v.model, Vehicles[v.model] = {
hash = v.hash, model = v.model,
price = v.price, hash = v.hash,
name = v.name, price = v.price,
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper) name = v.name,
} brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
}
end
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)
end end

View File

@@ -88,6 +88,15 @@ function craftingMenu(data)
-- Check if the player can carry the required items (server callback). -- Check if the player can carry the required items (server callback).
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable) local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
local usingStash = data.stashName ~= nil and data.stashName ~= ""
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. -- Process each recipe to create menu entries.
for i = 1, #Recipes do for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
@@ -106,10 +115,13 @@ function craftingMenu(data)
local metaTable = {} local metaTable = {}
-- Build ingredient details. -- Build ingredient details.
for l, b in pairs(Recipes[i][tostring(k)]) do 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 "") local label = Items[l] and Items[l].label or "error - "..l
local hasItem = checkStashItem(data.stashName, { [l] = b })
local missingMark = not hasItem and "" or " "
settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark
metaTable[Items[l] and Items[l].label or "error - "..l] = b metaTable[Items[l] and Items[l].label or "error - "..l] = b
itemTable[l] = b itemTable[l] = b
--Wait(0)
end end
while not canCarryTable do Wait(0) end while not canCarryTable do Wait(0) end
@@ -117,17 +129,17 @@ function craftingMenu(data)
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k)) 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 "") ..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️" local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️"
local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or "" local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or ""
setheader = isNew .. setheader .. statusEmoji setheader = isNew .. setheader .. statusEmoji
Menu[#Menu + 1] = { Menu[#Menu + 1] = {
arrow = not disable and canCarryTable[k], arrow = isOx() and (not disable and canCarryTable[k]),
isMenuHeader = disable or not canCarryTable[k], isMenuHeader = disable or not canCarryTable[k],
icon = invImg((metadata and metadata.image) or tostring(k)), icon = invImg((metadata and metadata.image) or tostring(k)),
image = invImg((metadata and metadata.image) or tostring(k)), image = invImg((metadata and metadata.image) or tostring(k)),
header = setheader, header = setheader,
txt = (isStarted(QBMenuExport) or disable) and settext or nil, txt = settext or nil,
metadata = metaTable, metadata = metaTable,
onSelect = (not disable and canCarryTable[k]) and function() onSelect = (not disable and canCarryTable[k]) and function()
local transdata = { local transdata = {
@@ -234,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 ""), { 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", type = "slider",
label = "How many to craft? "..br.."Max: "..carryMax, label = "How many to craft? "..br.."Max: "..carryMax,
required = true, required = true,
@@ -307,6 +319,7 @@ end
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" }, --- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- }) --- })
--- ``` --- ```
function makeItem(data) function makeItem(data)
if CraftLock then return end if CraftLock then return end
CraftLock = true CraftLock = true
@@ -326,117 +339,144 @@ function makeItem(data)
local crafted, crafting = true, true local crafted, crafting = true, true
local cam = createTempCam(PlayerPedId(), data.coords) local cam = createTempCam(PlayerPedId(), data.coords)
startTempCam(cam) 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 i = 1, craftAmount do
for k, v in pairs(data.craft) do for k, v in pairs(data.craft) do
if not excludeKeys[k] then if not excludeKeys[k] and type(v) == "table" then
if type(v) == "table" then for l, b in pairs(v) do
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 isInventoryOpen() then if isInventoryOpen() then
print("^1Error^7: ^2Inventory is open, you tried to break things") print("^1Error^7: ^2Inventory is open, you tried to break things")
crafted, crafting = false, false
stopTempCam() stopTempCam()
ClearPedTasks(PlayerPedId()) ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(data) end if canReturn then craftingMenu(data) end
CraftLock = false CraftLock = false
return return
end end
if crafting and progressBar({
if crafted then label = "Using "..b.." "..Items[l].label,
local craftProp = nil time = 1000,
if prop then cancel = true,
craftProp = makeProp({ prop = prop.model, coords = vec4(0, 0, 0, 0), true, true }) dict = 'pickup_object',
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) anim = "putdown_low",
end flag = 49,
if data.sound then icon = l,
local s = data.sound }) then
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0) TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b)
end else
if isInventoryOpen() then crafted, crafting = false, false
--print("^1Error^7: ^2Inventory is open, you tried to break things") break
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("hasCrafted Found, marking '"..data.item.."' as crafted for player")
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("exp found, giving exp for '"..data.item.."'")
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
end end
Wait(200)
end end
end end
end end
Wait(500)
end 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:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give * craftAmount
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", 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:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", 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() stopTempCam()
CraftLock = false CraftLock = false
if canReturn then craftingMenu(data) end if canReturn then craftingMenu(data) end
ClearPedTasks(PlayerPedId()) ClearPedTasks(PlayerPedId())
end end
------------------------------------------------------------- -------------------------------------------------------------
-- Server Event Handler: Crafted Item -- Server Event Handler: Crafted Item
------------------------------------------------------------- -------------------------------------------------------------
@@ -453,9 +493,9 @@ end
--- @usage --- @usage
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata, token) RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata, token)
local src = source local src = source
debugPrint(GetInvokingResource()) -- debugPrint("^6Bridge^7: ^4"..getScript().."^7:^4Crafting^7:^4GetItem ^2invoked by^7: "..GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7") debugPrint("^1ERROR^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return return
end end

View File

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

View File

@@ -56,6 +56,25 @@ function isServer()
return IsDuplicityVersion() return IsDuplicityVersion()
end end
function checkExportExists(resource, export)
if not resource or not export then return false end
local exists = false
local ok, result = pcall(function()
exists = type(exports[resource][export]) == "function"
end)
if ok and exists then
debugPrint(("^3Export Check^7: ^5exports^7['^5%s^7']:^5%s^7() ^2Exists^7"):format(resource, export))
return true
else
debugPrint(("^3Export Check^7: ^5exports^7['^5%s^7']:^5%s^7() ^1Doesn^7'^1t Exist^7"):format(resource, export))
return false
end
end
------------------------------------------------------------- -------------------------------------------------------------
-- Debugging and JSON Utilities -- Debugging and JSON Utilities
------------------------------------------------------------- -------------------------------------------------------------
@@ -474,7 +493,7 @@ end
--- end --- end
--- ``` --- ```
function ensureNetToVeh(vehNetID) function ensureNetToVeh(vehNetID)
debugPrint("^6Bridge^7: ^3ensureNetToVeh^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..vehNetID.."^7)") --debugPrint("^6Bridge^7: ^3ensureNetToVeh^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..vehNetID.."^7)")
local timeout = 100 local timeout = 100
while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do
timeout -= 1 timeout -= 1
@@ -502,7 +521,7 @@ end
--- end --- end
--- ``` --- ```
function ensureNetToEnt(entNetID) function ensureNetToEnt(entNetID)
debugPrint("^6Bridge^7: ^3ensureNetToEnt^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..entNetID.."^7)") --debugPrint("^6Bridge^7: ^3ensureNetToEnt^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..entNetID.."^7)")
local timeout = 100 local timeout = 100
while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do
timeout -= 1 timeout -= 1

View File

@@ -138,6 +138,98 @@ function createInput(title, opts)
) )
return dialog 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 elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(opts), WarMenu.CreateMenu(tostring(opts),
title, title,

View File

@@ -50,7 +50,7 @@ function hasItem(items, amount, src)
local count = 0 local count = 0
for _, itemData in pairs(grabInv) do for _, itemData in pairs(grabInv) do
jsonPrint(itemData) --jsonPrint(itemData)
if itemData and itemData.name == item then if itemData and itemData.name == item then
count += (itemData.amount or itemData.count or 1) count += (itemData.amount or itemData.count or 1)
end end
@@ -138,6 +138,14 @@ function getPlayerInv(src)
grabInv = exports[TgiannInv]:GetPlayerItems() grabInv = exports[TgiannInv]:GetPlayerItems()
end end
elseif isStarted(JPRInv) then
foundInv = JPRInv
if src then
grabInv = Core.Functions.GetPlayer(src).PlayerData.items
else
grabInv = Core.Functions.GetPlayerData().items
end
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
foundInv = QBInv foundInv = QBInv
if src then if src then
@@ -148,8 +156,11 @@ function getPlayerInv(src)
elseif isStarted(PSInv) then elseif isStarted(PSInv) then
foundInv = PSInv foundInv = PSInv
if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items if src then
else grabInv = Core.Functions.GetPlayerData().items end grabInv = Core.Functions.GetPlayer(src).PlayerData.items
else
grabInv = Core.Functions.GetPlayerData().items
end
elseif ESX and isStarted(ESXExport) then elseif ESX and isStarted(ESXExport) then
foundInv = ESX foundInv = ESX
@@ -179,41 +190,6 @@ function isInventoryOpen()
return IsNuiFocused() return IsNuiFocused()
--if isStarted(OXInv) then
-- return LocalPlayer.state.invBusy
--elseif isStarted(QSInv) then
-- return exports[QSInv]:inInventory()
--elseif isStarted(OrigenInv) then
-- return exports[OrigenInv]:IsInventoryOpen()
--elseif isStarted(CoreInv) then
-- return exports[CoreInv]:isInventoryOpen()
--elseif isStarted(CodeMInv) then
-- return false
-- -- CodeM doesn't have a function to check if the inventory is open
-- -- No idea what it uses, so it just skips the check
--elseif isStarted(TgiannInv) then
-- return IsNuiFocused()
--elseif isStarted(QBInv) then
-- return LocalPlayer.state.inv_busy
--elseif isStarted(PSInv) then
-- return LocalPlayer.state.inv_busy
--elseif ESX and isStarted(ESXExport) then
-- return false
--elseif isStarted(RSGInv) then
-- return LocalPlayer.state.inv_busy
--end
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -248,12 +224,14 @@ function invImg(item)
elseif isStarted(OrigenInv) then elseif isStarted(OrigenInv) then
imgLink = "nui://"..OrigenInv.."/html/img/"..(Items[item].image or "") imgLink = "nui://"..OrigenInv.."/html/img/"..(Items[item].image or "")
elseif isStarted(JPRInv) then
imgLink = "nui://"..JPRInv.."/html/images/"..(Items[item].image or "")
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
imgLink = "nui://"..QBInv.."/html/images/"..(Items[item].image or "") imgLink = "nui://"..QBInv.."/html/images/"..(Items[item].image or "")
elseif isStarted(PSInv) then elseif isStarted(PSInv) then
imgLink = "nui://"..PSInv.."/html/images/"..(Items[item].image or "") imgLink = "nui://"..PSInv.."/html/images/"..(Items[item].image or "")
elseif isStarted(TgiannInv) then elseif isStarted(TgiannInv) then
imgLink = "nui://inventory_images/images/"..(Items[item].image or "") imgLink = "nui://inventory_images/images/"..(Items[item].image or "")

View File

@@ -5,7 +5,7 @@
or other type (e.g., coyote). It uses predefined model hashes stored in the AnimalPeds table. or other type (e.g., coyote). It uses predefined model hashes stored in the AnimalPeds table.
Global Flags: Global Flags:
- isCat, isDog, isBigDog, isSmallDog, isCoyote, isAnimal: Booleans to track the player's - isCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal: Booleans to track the player's
current animal classification. current animal classification.
When running client-side (not on the server), the module checks the player's Ped after they load. When running client-side (not on the server), the module checks the player's Ped after they load.
@@ -26,13 +26,13 @@
]] ]]
-- Global animal classification flags. -- Global animal classification flags.
isCat, isDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false
if not isServer() then if not isServer() then
onPlayerLoaded(function() onPlayerLoaded(function()
Wait(2000) Wait(1000)
-- Reset classification flags -- Reset classification flags
isCat, isDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false
-- Check if the player's Ped is an animal. -- Check if the player's Ped is an animal.
isPedAnimal() isPedAnimal()
if isAnimal then if isAnimal then
@@ -41,18 +41,18 @@ if not isServer() then
-- Determine if the Ped is a cat: -- Determine if the Ped is a cat:
-- Also treat 'ft-raccoon' as a cat unless it is 'ft-sphynx' -- Also treat 'ft-raccoon' as a cat unless it is 'ft-sphynx'
isCat = (isCat(ped) or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`) isPedCat = (isCat(ped) == true or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`)
-- Determine if the Ped is a dog and whether it's big: -- Determine if the Ped is a dog and whether it's big:
isDog, isBigDog = isDog(ped) isPedDog, isBigDog = isDog(ped)
isSmallDog = not isBigDog isSmallDog = not isBigDog
if isDog and pedModel == `a_c_coyote` then isDog = false end if isPedDog and pedModel == `a_c_coyote` then isPedDog = false end
-- Determine if the Ped is a coyote (special case): -- Determine if the Ped is a coyote (special case):
isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`) isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`)
-- Special override: if model is 'ft-capmonkey2', treat as a dog. -- Special override: if model is 'ft-capmonkey2', treat as a dog.
if pedModel == `ft-capmonkey2` then isDog = true end if pedModel == `ft-capmonkey2` then isPedDog = true end
end end
end, true) end, true)
@@ -79,7 +79,7 @@ if not isServer() then
for animalModelHash, _ in pairs(animalCategory) do for animalModelHash, _ in pairs(animalCategory) do
if PedModel == animalModelHash then if PedModel == animalModelHash then
isAnimal = true isAnimal = true
debugPrint("^6Bridge^7: ^2Ped is Animal") debugPrint("^4isAnimal^7: ^2Ped is ^4Animal")
return true return true
end end
end end
@@ -109,6 +109,7 @@ if not isServer() then
local PedModel = GetEntityModel(ped or PlayerPedId()) local PedModel = GetEntityModel(ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.CatPeds) do for modelHash, _ in pairs(AnimalPeds.CatPeds) do
if PedModel == modelHash then if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Cat")
return true return true
end end
end end
@@ -126,8 +127,8 @@ if not isServer() then
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- local isDog, isBigDog = isDog() --- local isPedDog, isBigDog = isDog()
--- if isDog then --- if isPedDog then
--- if isBigDog then --- if isBigDog then
--- print("Player is a big dog!") --- print("Player is a big dog!")
--- else --- else
@@ -151,11 +152,13 @@ if not isServer() then
local PedModel = GetEntityModel(ped or PlayerPedId()) local PedModel = GetEntityModel(ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.BigDogs) do for modelHash, _ in pairs(AnimalPeds.BigDogs) do
if PedModel == modelHash then if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Dog")
return true, true return true, true
end end
end end
for modelHash, _ in pairs(AnimalPeds.SmallDogs) do for modelHash, _ in pairs(AnimalPeds.SmallDogs) do
if PedModel == modelHash then if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Small Dog")
return true, false return true, false
end end
end end

View File

@@ -30,25 +30,34 @@ validTokens = {}
--- end) --- end)
--- ``` --- ```
function createUseableItem(item, funct) function createUseableItem(item, funct)
if isStarted(ESXExport) then if doesItemExist(item) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7es_extended^7", item) local itemResource = ""
while not ESX do Wait(0) end if isStarted(ESXExport) then
ESX.RegisterUsableItem(item, funct) itemResource = ESXExport
while not ESX do Wait(0) end
ESX.RegisterUsableItem(item, funct)
elseif isStarted(QBExport) and not isStarted(QBXExport) then elseif isStarted(QBExport) and not isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qb-core^7", item) itemResource = QBExport
Core.Functions.CreateUseableItem(item, funct) Core.Functions.CreateUseableItem(item, funct)
elseif isStarted(QBXExport) then elseif isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qbx_core^7", item) itemResource = QBXExport
exports[QBXExport]:CreateUseableItem(item, funct) exports[QBXExport]:CreateUseableItem(item, funct)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^1rsg-core^7", item) itemResource = RSGExport
Core.Functions.CreateUseableItem(item, funct) Core.Functions.CreateUseableItem(item, funct)
end
if itemResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^3UsableItem^2 with ^4"..itemResource.."^7:", item)
else
debugPrint("^4ERROR^7: No supported framework detected for registering usable item: ^3"..item.."^7")
end
else else
print("^4ERROR^7: No supported framework detected for registering usable item: ^3"..item.."^7") print("^1ERROR^7: ^1Tried to make item usable but it didn't exist^7: "..item)
end end
end end
@@ -186,6 +195,20 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
invName = TgiannInv invName = TgiannInv
exports[TgiannInv]:RemoveItem(src, item, remamount) exports[TgiannInv]:RemoveItem(src, item, remamount)
elseif isStarted(JPRInv) then
invName = JPRInv
while remamount > 0 do
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then
remamount -= 1
else
print("^1Error removing "..item.." Amount left: "..remamount)
break
end
end
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
invName = QBInv invName = QBInv
while remamount > 0 do while remamount > 0 do
@@ -196,7 +219,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
break break
end end
end end
if Config.Crafting.showItemBox then if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "remove", amount or 1) TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end end
@@ -210,7 +233,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
break break
end end
end end
if Config.Crafting.showItemBox then if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1) TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end end
@@ -223,7 +246,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
break break
end end
end end
if Config.Crafting.showItemBox then if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("rsg-inventory:client:ItemBox", src, Items[item], "remove", amount or 1) TriggerClientEvent("rsg-inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end end
@@ -252,7 +275,6 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
else else
local amountToAdd = amount or 1 local amountToAdd = amount or 1
if isStarted(OXInv) then invName = OXInv if isStarted(OXInv) then invName = OXInv
jsonPrint(info)
exports[OXInv]:AddItem(src, item, amountToAdd, info, slot) exports[OXInv]:AddItem(src, item, amountToAdd, info, slot)
elseif isStarted(QSInv) then invName = QSInv elseif isStarted(QSInv) then invName = QSInv
@@ -270,6 +292,13 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
elseif isStarted(TgiannInv) then invName = TgiannInv elseif isStarted(TgiannInv) then invName = TgiannInv
exports[TgiannInv]:AddItem(src, item, amountToAdd, slot, info) exports[TgiannInv]:AddItem(src, item, amountToAdd, slot, info)
elseif isStarted(JPRInv) then invName = JPRInv
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("ps-inventory:client:ItemBox", src, Items[item], "add", amountToAdd)
end
end
elseif isStarted(QBInv) then invName = QBInv elseif isStarted(QBInv) then invName = QBInv
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then
TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "add", amountToAdd) TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "add", amountToAdd)
@@ -277,7 +306,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
elseif isStarted(PSInv) then invName = PSInv elseif isStarted(PSInv) then invName = PSInv
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then
if Config.Crafting.showItemBox then if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("ps-inventory:client:ItemBox", src, Items[item], "add", amountToAdd) TriggerClientEvent("ps-inventory:client:ItemBox", src, Items[item], "add", amountToAdd)
end end
end end
@@ -347,130 +376,73 @@ end
--- ```lua --- ```lua
--- breakTool({ item = "drill", damage = 10 }) --- breakTool({ item = "drill", damage = 10 })
--- ``` --- ```
function breakTool(data) -- WIP function breakTool(data)
local durability, slot = getDurability(data.item) local metadata, slot = getItemMetadata(data.item)
if not durability then durability = 100 end metadata = metadata or {}
durability -= data.damage if not metadata.durability then metadata.durability = 100 end
if durability <= 0 then metadata.durability -= data.damage
removeItem(data.item, 1) if metadata.durability <= 0 then
removeItem(data.item, 1, nil, slot)
local breakId = GetSoundId() local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0) PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
else else
TriggerServerEvent(getScript()..":server:setMetaData", { item = data.item, slot = slot, metadata = { durability = durability } }) TriggerServerEvent(getScript()..":server:setItemMetaData", { item = data.item, slot = slot, metadata = metadata })
end end
end end
--- Retrieves the durability and slot number of an item in a player's inventory.
--- Reduces the uses of a tool by a specified damage amount.
--- ---
--- Searches through the player's inventory for the specified item. --- If uses reaches zero or below, the tool is removed and a break sound is played.
--- ---
--- @param item string The item name. --- @param data table Contains:
--- @return number|nil number The durability, or nil if not found. --- - item (string): The tool's name.
--- @return number|nil number The slot number, or nil if not found. --- - damage (number): The damage % to apply.
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- local durability, slot = getDurability("drill") --- useToolDegrade({ item = "drill", maxUse = 10 })
--- if durability then
--- print("Durability:", durability)
--- print("Slot:", slot)
--- end
--- ``` --- ```
function getDurability(item) function useToolDegrade(data) -- WIP
local metadata, slot = getItemMetadata(data.item)
metadata = metadata or {}
if not metadata["Uses Left"] then metadata["Uses Left"] = data.maxUse end
metadata["Uses Left"] -= 1
if metadata["Uses Left"] <= 0 then
removeItem(data.item, 1, nil, slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
else
TriggerServerEvent(getScript()..":server:setItemMetaData", { item = data.item, slot = slot, metadata = metadata })
end
end
function getItemMetadata(item, slot, src)
local lowestSlot = 100 local lowestSlot = 100
local durability = nil local chosenSlot = slot
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) then local metadata = {}
local itemcheck = Core.Functions.GetPlayerData().items
local itemcheck = getPlayerInv(src)
if chosenSlot then
for k, v in pairs(itemcheck) do
if v.name == item and v.slot == chosenSlot then
lowestSlot = v.slot
metadata = itemcheck[k].info or itemcheck[k].metadata or {}
debugPrint("^6Bridge^7: ^2Found metadata for item ^3"..item.." ^2in slot ^3"..lowestSlot.."^7")
end
end
else
for k, v in pairs(itemcheck) do for k, v in pairs(itemcheck) do
if v.name == item then if v.name == item then
if v.slot <= lowestSlot then if v.slot <= lowestSlot then
lowestSlot = v.slot lowestSlot = v.slot
durability = itemcheck[k].info and itemcheck[k].info.durability or nil metadata = itemcheck[k].info or itemcheck[k].metadata or {}
end end
end end
end end
end end
return metadata, lowestSlot
if isStarted(OXInv) then
local itemcheck = exports[OXInv]:Search('slots', item)
for k, v in pairs(itemcheck) do
if v.slot <= lowestSlot then
debugPrint(v.slot, itemcheck[k].metadata.durability)
lowestSlot = v.slot
durability = v.metadata and v.metadata.durability or nil
end
end
end
if isStarted(QSInv) then
local itemcheck = exports[QSInv]:getUserInventory()
for _, v in pairs(itemcheck) do
if v.name == item and v.slot <= lowestSlot then
lowestSlot = v.slot
durability = v.info and v.info.durability or nil
end
end
end
if isStarted(CoreInv) then
local itemcheck = exports[CoreInv]:getInventory()
for _, v in pairs(itemcheck) do
if v.name == item and v.slot <= lowestSlot then
lowestSlot = v.slot
durability = v.metadata and v.metadata.durability or nil
end
end
end
if isStarted(CodeMInv) then
local itemcheck = exports[CodeMInv]:GetClientPlayerInventory()
for _, v in pairs(itemcheck) do
if v.name == item and tonumber(v.slot) <= lowestSlot then
lowestSlot = tonumber(v.slot)
durability = v.info and v.info.durability or nil
end
end
end
if isStarted(OrigenInv) then
local itemcheck = exports[OrigenInv]:getPlayerInventory()
for _, v in pairs(itemcheck) do
if v.name == item and v.slot <= lowestSlot then
lowestSlot = v.slot
durability = v.metadata and v.metadata.durability or nil
end
end
end
if isStarted(TgiannInv) then
local itemcheck = exports[TgiannInv]:GetPlayerItems()
for _, v in pairs(itemcheck) do
if v.name == item and v.slot <= lowestSlot then
lowestSlot = v.slot
durability = v.metadata and v.metadata.durability or nil
end
end
end
-- For ESX default inventory (es_extended)
if ESX and isStarted(ESXExport) then
local xPlayer = ESX.GetPlayerData() or {}
if xPlayer.inventory then
for _, v in ipairs(xPlayer.inventory) do
if v.name == item then
-- Optionally use a slot field if available; otherwise, use the index
if v.slot and v.slot <= lowestSlot then
lowestSlot = v.slot
end
if v.metadata and v.metadata.durability then
durability = v.metadata.durability
end
end
end
end
end
return durability, lowestSlot
end end
--- Server event handler to set metadata for an item. --- Server event handler to set metadata for an item.
@@ -484,19 +456,21 @@ end
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- TriggerServerEvent("script:server:setMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } }) --- TriggerServerEvent("script:server:setItemMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } })
--- ``` --- ```
RegisterNetEvent(getScript()..":server:setMetaData", function(data) RegisterNetEvent(getScript()..":server:setItemMetaData", function(data, src)
local src = source local src = src or source
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) then 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) local Player = Core.Functions.GetPlayer(src)
Player.PlayerData.items[data.slot].info = data.metadata 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) Player.Functions.SetInventory(Player.PlayerData.items)
elseif isStarted(OXInv) then 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 elseif isStarted(QSInv) then
exports[QSInv]:SetItemMetadata(src, data.slot, data.metadata) exports[QSInv]:SetItemMetadata(src, data.slot, data.metadata)
@@ -623,7 +597,23 @@ function canCarry(itemTable, src)
elseif isStarted(TgiannInv) then elseif isStarted(TgiannInv) then
for k, v in pairs(itemTable) do 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
local items = getPlayerInv(src)
local totalWeight = 0
if not items then return false end
for _, item in pairs(items) do
totalWeight += (item.weight * item.amount)
end
for k, v in pairs(itemTable) do
local itemInfo = Items[k]
if not itemInfo then
resultTable[k] = true
else
resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight
end
end end
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
@@ -671,8 +661,6 @@ end
------------------------------------------------------------- -------------------------------------------------------------
-- Server Callback Registration -- Server Callback Registration
------------------------------------------------------------- -------------------------------------------------------------
AuthEvent = nil
currentToken = nil
if isServer() then if isServer() then
createCallback(getScript()..":server:canCarry", function(source, itemTable) createCallback(getScript()..":server:canCarry", function(source, itemTable)
local result = canCarry(itemTable, source) local result = canCarry(itemTable, source)
@@ -698,103 +686,138 @@ if isServer() then
end end
return maxCanCarry return maxCanCarry
end) end)
end
local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen() function getMaxInvWeight()
validTokens = validTokens or {} local weight = 0
if isStarted(QBInv) then
createCallback(AuthEvent, function(source) if checkExportExists(QBInv, "GetMaxWeight") then
local src = source return exports[QBInv]:GetMaxWeight()
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here else
--debugPrint(GetInvokingResource()) return InventoryWeight
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then end
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7") end
return "" if isStarted(PSInv) then
if checkExportExists(PSInv, "GetMaxWeight") then
return exports[PSInv]:GetMaxWeight()
else
return InventoryWeight
end
end
if isStarted(RSGInv) then
if checkExportExists(RSGInv, "GetMaxWeight") then
return exports[RSGInv]:GetMaxWeight()
else
return InventoryWeight
end
end
if isStarted(JPRInv) then
if checkExportExists(JPRInv, "GetMaxWeight") then
return exports[JPRInv]:GetMaxWeight()
else
return InventoryWeight
end end
debugPrint("^1Auth^7:^2 Player Source^7: "..src.." ^2requested new token^7:", token)
validTokens[src] = token
timeOutAuth(validTokens[src], src) -- Give script 10 seconds, then clear token
return token
end)
function timeOutAuth(token, src)
local token = token
SetTimeout(10000, function()
if token == validTokens[src] then
print("^1--------------------------------------------^7")
print("^7Clearing token for player ^1"..src.."^7", token)
print("^7This shouldn't happen unless a token has been called by a player or script and it hasn't been used")
print("^1--------------------------------------------^7")
end
end)
end end
RegisterNetEvent(getScript()..":clearAuthToken", function() if isStarted(OXInv) then
local src = source return exports[OXInv]:GetPlayerMaxWeight()
debugPrint("^1Auth^7: ^2Manually removing token for Player Source^7:", src, validTokens[src]) end
validTokens[src] = nil
end)
receivedEvent = {} if isStarted(QSInv) then
createCallback(getScript()..":callback:GetAuthEvent", function(source) return InventoryWeight
local src = source end
--debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then if isStarted(CoreInv) then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7") return InventoryWeight
return "" end
if isStarted(CodeMInv) then
return InventoryWeight
end
if isStarted(OrigenInv) then
return InventoryWeight
end
if isStarted(TgiannInv) then
return InventoryWeight
end
-- For ESX default inventory (es_extended)
if ESX and isStarted(ESXExport) then
return InventoryWeight
end
return InventoryWeight
end
function getCurrentInvWeight()
local weight = 0
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) or isStarted(JPRInv) then
local itemcheck = Core.Functions.GetPlayerData().items
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end end
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent) end
if not receivedEvent[src] then
receivedEvent[src] = true if isStarted(OXInv) then
return AuthEvent weight = exports[OXInv]:GetPlayerWeight()
else end
print("^1Auth^7: ^1Player ^7"..src.." ^1tried to request auth token more than once^7")
return "" if isStarted(QSInv) then
local itemcheck = exports[QSInv]:getUserInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end end
end) end
RegisterNetEvent(getScript()..":clearAuthEventRequest", function() if isStarted(CoreInv) then
local src = source local itemcheck = exports[CoreInv]:getInventory()
debugPrint("^1Auth^7: ^2Manually clearing Auth Event for Player Source^7:", src, AuthEvent) for _, v in pairs(itemcheck) do
receivedEvent[src] = nil weight += ((v.weight * v.amount) or 0)
end) end
end
-- Multiuse function to check if the generated client token is valid if isStarted(CodeMInv) then
function checkToken(src, token, genType, name) local itemcheck = exports[CodeMInv]:GetClientPlayerInventory()
if token == nil then for _, v in pairs(itemcheck) do
debugPrint("^1Auth^7: ^1No token recieved^7") weight += ((v.weight * v.amount) or 0)
if genType == "stash" then end
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1without an auth token^7") end
elseif genType == "item" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1without an auth token^7") if isStarted(OrigenInv) then
end local itemcheck = exports[OrigenInv]:getPlayerInventory()
return false for _, v in pairs(itemcheck) do
else weight += ((v.weight * v.amount) or 0)
debugPrint("^1Auth^7: ^2Auth token received^7, ^2checking against server cache^7..") end
if token ~= validTokens[src] then end
debugPrint("^1Auth^7: ^1Tokens don't match! ^7", token, validTokens[src])
if genType == "stash" then if isStarted(TgiannInv) then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1with an incorrect auth token^7") local itemcheck = exports[TgiannInv]:GetPlayerItems()
elseif genType == "item" then for _, v in pairs(itemcheck) do
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1with an incorrect auth token^7") weight += ((v.weight * v.amount) or 0)
end end
return false end
else
debugPrint("^1Auth^7: ^2Client and Server Auth tokens match^7!", token, validTokens[src]) -- For ESX default inventory (es_extended)
validTokens[src] = nil if ESX and isStarted(ESXExport) then
return true local xPlayer = ESX.GetPlayerData() or {}
if xPlayer.inventory then
for _, v in pairs(xPlayer.inventory) do
weight += ((v.weight * v.amount) or 0)
end end
end end
end end
else
onPlayerLoaded(function()
debugPrint("^1Auth^7: ^2Requesting ^3Auth Event^7")
AuthEvent = triggerCallback(getScript()..":callback:GetAuthEvent")
end, true)
onPlayerUnload(function() return weight
debugPrint("^1Auth^7: ^2Clearing Auth Event^7") end
TriggerServerEvent(getScript()..":clearAuthEventRequest")
end, true) function hasFreeInventorySlots(slots, src)
local inv = getPlayerInv(src)
if (countTable(inv) + slots) <= InventorySlots then
return true
end
return false
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -811,9 +834,28 @@ end
--- if doesItemExist(item) then print("item exists") end --- if doesItemExist(item) then print("item exists") end
--- ``` --- ```
function doesItemExist(item) 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 if Items[item] ~= nil then
return true return Items[item]
end end
return false 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 end

View File

@@ -104,16 +104,31 @@ function toggleDuty()
TriggerServerEvent("QBCore:ToggleDuty") TriggerServerEvent("QBCore:ToggleDuty")
Wait(100) Wait(100)
onDuty = getPlayer().onDuty onDuty = getPlayer().onDuty
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
TriggerServerEvent("RSGCore:ToggleDuty") TriggerServerEvent("RSGCore:ToggleDuty")
Wait(100) Wait(100)
onDuty = getPlayer().onDuty onDuty = getPlayer().onDuty
else
elseif isStarted(ESXExport) then
local tempJob = ESX.GetPlayerData().job
tempJob.onDuty = not onDuty
ESX.SetPlayerData("job", tempJob)
onDuty = getPlayer().onDuty
if onDuty then if onDuty then
triggerNotify(nil, "Now on duty", "success") triggerNotify(nil, "Now on duty", "success")
else else
triggerNotify(nil, "Now off duty", "success") triggerNotify(nil, "Now off duty", "success")
end end
else
onDuty = not onDuty
if onDuty then
triggerNotify(nil, "Now on duty", "success")
else
triggerNotify(nil, "Now off duty", "success")
end
end end
end end
@@ -231,3 +246,19 @@ function useDoor(data)
DoScreenFadeIn(1000) DoScreenFadeIn(1000)
Wait(100) Wait(100)
end end
function openBossMenu(isGang, group)
if isStarted("qb-management") then
if isGang then
TriggerEvent("qb-gangmenu:client:OpenMenu")
else
TriggerEvent("qb-bossmenu:client:OpenMenu")
end
elseif isStarted("qbx_management") then
exports["qbx_management"]:OpenBossMenu(isGang and "gang" or "job")
elseif isStarted("esx_society") then
TriggerEvent('esx_society:openBossMenu', group, function() end, { wash = false })
end
end

View File

@@ -187,7 +187,7 @@ end
--- ``` --- ```
function playAnim(animDict, animName, duration, flag, ped, speed) function playAnim(animDict, animName, duration, flag, ped, speed)
loadAnimDict(animDict) loadAnimDict(animDict)
debugPrint("Attempting to make player play anim", animDict, animName) debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName, flag)
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) 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 end
@@ -204,7 +204,7 @@ end
--- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId()) --- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
--- ``` --- ```
function stopAnim(animDict, animName, ped) function stopAnim(animDict, animName, ped)
debugPrint("Stopping anim for "..(ped or PlayerPedId())) debugPrint("^6Bridge^7: ^3stopAnim^7() ^2Triggered^7: ", animDict, animName)
StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5) StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5) StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
unloadAnimDict(animDict) unloadAnimDict(animDict)
@@ -238,4 +238,25 @@ function playGameSound(audioBank, soundSet, soundRef, coords, synced, range)
PlaySoundFromEntity(soundId, soundRef, coords, soundSet, synced, 1.0) PlaySoundFromEntity(soundId, soundRef, coords, soundSet, synced, 1.0)
end end
ReleaseScriptAudioBank(audioBank) ReleaseScriptAudioBank(audioBank)
end
-- Experimental, add fade in for spawned entities
function fadeInEnt(ent, duration)
if not DoesEntityExist(ent) then return end
duration = duration or 500 -- in ms
local fadeSteps = 20
local stepTime = duration / fadeSteps
SetEntityAlpha(ent, 0, false)
SetEntityVisible(ent, true, false)
SetEntityLocallyInvisible(ent) -- sometimes helps prevent "pop-in" in early frames
for i = 1, fadeSteps do
Wait(stepTime)
local newAlpha = math.floor((i / fadeSteps) * 255)
SetEntityAlpha(ent, newAlpha, false)
end
-- Restore full visibility
ResetEntityAlpha(ent)
end end

View File

@@ -62,7 +62,7 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced)
model = data.model model = data.model
loadModel(data.model) loadModel(data.model)
ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false) ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
SetEntityAlpha(ped, 0, false)
-- Inheritance -- Inheritance
SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false) SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)
@@ -116,6 +116,7 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced)
loadModel(model) loadModel(model)
if gameName == "rdr3" then if gameName == "rdr3" then
ped = CreatePed(model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false) ped = CreatePed(model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
SetEntityAlpha(ped, 0, false)
SetEntityVisible(ped, 1) -- SetEntityVisible SetEntityVisible(ped, 1) -- SetEntityVisible
SetEntityAlpha(ped, 255, false) -- SetEntityAlpha SetEntityAlpha(ped, 255, false) -- SetEntityAlpha
SetRandomOutfitVariation(ped, true) -- Invisible without SetRandomOutfitVariation(ped, true) -- Invisible without
@@ -141,6 +142,10 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced)
end end
unloadModel(model) unloadModel(model)
Peds[keyGen()..keyGen()] = ped Peds[keyGen()..keyGen()] = ped
CreateThread(function()
fadeInEnt(ped)
end)
return ped return ped
end end
@@ -243,4 +248,4 @@ onResourceStop(function()
for k in pairs(Peds) do for k in pairs(Peds) do
DeletePed(Peds[k]) DeletePed(Peds[k])
end end
end, true) end, true)

View File

@@ -23,12 +23,16 @@ local Props = {}
function makeProp(data, freeze, synced) function makeProp(data, freeze, synced)
loadModel(data.prop) loadModel(data.prop)
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false) local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
SetEntityAlpha(veh, 0, false)
SetEntityHeading(prop, (data.coords.w or 0) + 180.0) SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
FreezeEntityPosition(prop, freeze or false) FreezeEntityPosition(prop, freeze or false)
debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords)) debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords))
SetModelAsNoLongerNeeded(data.prop) SetModelAsNoLongerNeeded(data.prop)
Props[keyGen()..keyGen()] = prop Props[keyGen()..keyGen()] = prop
CreateThread(function()
fadeInEnt(prop)
end)
return prop return prop
end end

View File

@@ -18,18 +18,22 @@ function makeVeh(model, coords)
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false)
SetVehicleHasBeenOwnedByPlayer(veh, true) SetVehicleHasBeenOwnedByPlayer(veh, true)
if gameName ~= "rdr3" then if gameName ~= "rdr3" then
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true) SetEntityAlpha(veh, 0, false)
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
Wait(100) Wait(100)
SetVehicleNeedsToBeHotwired(veh, false) SetVehicleNeedsToBeHotwired(veh, false)
SetVehRadioStation(veh, 'OFF') SetVehRadioStation(veh, 'OFF')
SetVehicleFuelLevel(veh, 100.0) SetVehicleFuelLevel(veh, 100.0)
SetVehicleModKit(veh, 0) SetVehicleModKit(veh, 0)
end end
SetVehicleOnGroundProperly(veh) SetVehicleOnGroundProperly(veh)
debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords)) debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords))
unloadModel(model) unloadModel(model)
Vehicles[#Vehicles + 1] = veh Vehicles[#Vehicles + 1] = veh
CreateThread(function()
fadeInEnt(veh)
end)
return veh return veh
end end
@@ -54,14 +58,14 @@ function makeDistVehicle(data, radius, onEnter, onExit)
onEnter = function() onEnter = function()
vehicle = makeVeh(data.model, data.coords) vehicle = makeVeh(data.model, data.coords)
if onEnter then if onEnter then
debugPrint("makeDistVehicle onEnter running") debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onEnter^7() ^2running^7")
onEnter(vehicle) onEnter(vehicle)
end end
end, end,
onExit = function() onExit = function()
deleteVehicle(vehicle) deleteVehicle(vehicle)
if onExit then if onExit then
debugPrint("makeDistVehicle onExit running") debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onExit^7() ^2running^7")
onExit(vehicle) onExit(vehicle)
end end
end, end,

View File

@@ -1,4 +1,5 @@
local inProgress = false local inProgress = false
local storedPID = nil
--- Displays a progress bar using the configured progress bar system. --- Displays a progress bar using the configured progress bar system.
--- ---
@@ -43,11 +44,12 @@ function progressBar(data)
local result = nil local result = nil
if data.cam then startTempCam(data.cam) end if data.cam then startTempCam(data.cam) end
if Config.System.ProgressBar == "ox" then if Config.System.ProgressBar == "ox" then
if exports[OXLibExport]:progressBar({ local options = {
duration = debugMode and 1000 or data.time, duration = debugMode and 1000 or data.time,
label = data.label, label = data.label,
position = data.position or "bottom",
useWhileDead = data.dead or false, useWhileDead = data.dead or false,
canCancel = data.cancel and data.cancel or true, canCancel = data.cancel or true,
anim = { anim = {
dict = data.dict, dict = data.dict,
clip = data.anim, clip = data.anim,
@@ -55,12 +57,41 @@ function progressBar(data)
scenario = data.task scenario = data.task
}, },
disable = { disable = {
combat = true combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
mouse = data.mouse or false
}, },
}) then }
result = true if data.prop and data.prop.model then
else options.prop = {
result = false model = data.prop.model,
pos = data.prop.pos or vec3(0, 0, 0),
rot = data.prop.rot or vec3(0, 0, 0),
bone = data.prop.bone or 0
}
end
if data.propTwo and data.propTwo.model then
options.propTwo = {
model = data.propTwo.model,
pos = data.propTwo.pos or vec3(0, 0, 0),
rot = data.propTwo.rot or vec3(0, 0, 0),
bone = data.propTwo.bone or 0
}
end
if data.progressType == "circle" then
if exports[OXLibExport]:progressCircle(options) then
result = true
else
result = false
end
end
if not data.progressType or data.progressType == "bar" then
if exports[OXLibExport]:progressBar(options) then
result = true
else
result = false
end
end end
elseif Config.System.ProgressBar == "qb" then elseif Config.System.ProgressBar == "qb" then
@@ -69,8 +100,19 @@ function progressBar(data)
debugMode and 1000 or data.time, debugMode and 1000 or data.time,
data.dead or false, data.dead or false,
data.cancel or true, data.cancel or true,
{ disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true }, {
{ animDict = data.dict, anim = data.anim, flags = data.flag or 32, task = data.task }, {}, {}, disableMovement = data.disableMovement or false,
disableCarMovement = data.disableMovement or false,
disableMouse = data.disableMouse or false,
disableCombat = data.disableCombat or true,
},
{
animDict = data.dict,
anim = data.anim,
flags = data.flag or 32,
task = data.task
},
{}, {},
function() function()
result = true result = true
end, function() end, function()
@@ -93,16 +135,46 @@ function progressBar(data)
end 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,
flag = data.flag
},
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 = false
end
elseif Config.System.ProgressBar == "red" then elseif Config.System.ProgressBar == "red" then
-- Currently only uses jim-redui if you choose this option -- Currently only uses jim-redui if you choose this option
if exports["jim-redui"]:progressBar({ if exports["jim_bridge"]:redProgressBar({
label = data.label, label = data.label,
time = debugMode and 1000 or data.time, time = debugMode and 1000 or data.time,
dict = data.dict, dict = data.dict,
anim = data.anim, anim = data.anim,
flag = data.flag or 32, flag = data.flag or 32,
task = data.task, task = data.task,
cancel = true, disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then }) then
result = true result = true
else else
@@ -110,76 +182,26 @@ function progressBar(data)
end end
elseif Config.System.ProgressBar == "gta" then elseif Config.System.ProgressBar == "gta" then
loadTextureDict("timerbars") if exports["jim_bridge"]:gtaProgressBar({
if inProgress then return false end label = data.label,
inProgress = true time = debugMode and 1000 or data.time,
local wait = debugMode and 1000 or data.time dict = data.dict,
local endTime = GetGameTimer() + wait anim = data.anim,
local ped = PlayerPedId() flag = data.flag or 32,
task = data.task,
-- Setup Animation/Task if specified disableMovement = data.disableMovement or false,
if data.dict then cancel = data.cancel or true,
playAnim(data.dict, data.anim, -1, data.flag or 32) }) then
elseif data.task then result = true
TaskStartScenarioInPlace(ped, data.task, -1, true) else
result = false
end end
-- Progress bar rendering loop
CreateThread(function()
while GetGameTimer() < endTime and inProgress do
Wait(0)
local elapsed = GetGameTimer()
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
-- Convert to segmented progress (assuming 5 segments here)
local segments = 5 -- Number of segments in the bar
local segmentProgress = {}
local progressPerSegment = 100 / segments
for i = 1, segments do
local segmentStart = (i - 1) * progressPerSegment
local segmentEnd = i * progressPerSegment
if percentage >= segmentEnd then
segmentProgress[i] = 100
elseif percentage <= segmentStart then
segmentProgress[i] = 0
else
segmentProgress[i] = ((percentage - segmentStart) / progressPerSegment) * 100
end
end
percentage = percentage >= 100 and 100 or percentage
-- Draw your segmented progress bar
ShowGTAProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress
DisablePlayerFiring(ped, true)
DisableControlAction(0, 25, true) -- Disable aim
DisableControlAction(0, 21, true) -- Disable sprint
DisableControlAction(0, 30, true) -- Disable move left/right
DisableControlAction(0, 31, true) -- Disable move forward/back
DisableControlAction(0, 36, true) -- Disable stealth
if data.cancel and (IsControlJustReleased(0, 202) or IsControlJustReleased(0, 177) or IsControlJustReleased(0, 73)) then
inProgress = false
end
end
end)
-- Wait for completion or cancel
while GetGameTimer() < endTime and inProgress do
Wait(100)
end
-- Cleanup animations/tasks
if data.dict then stopAnim(data.dict, data.anim, ped) end
ClearPedTasks(ped)
result = inProgress
inProgress = false
end end
while result == nil do Wait(10) end while result == nil do
Wait(10)
end
-- Cleanup -- Cleanup
FreezeEntityPosition(ped, false) FreezeEntityPosition(ped, false)
@@ -203,51 +225,6 @@ function progressBar(data)
return result return result
end end
function ShowGTAProgressBar(currentProg, title, level)
local loc = vec2(0.37, 0.90)
local size = vec2(0.3, 0.03)
-- Draw background box
DrawSprite("timerbars", "all_black_bg", loc.x +0.028, loc.y-0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255)
DrawSprite("timerbars", "all_black_bg", loc.x +0.170, loc.y-0.01, 0.15, 0.07, 180.0, 255, 255, 255, 255)
SetTextFont(0)
SetTextProportional(1)
SetTextScale(0.35, 0.35)
SetTextColour(255, 255, 255, 255)
SetTextDropshadow(0, 0, 0, 0, 255)
SetTextDropShadow()
SetTextOutline()
SetTextEntry("STRING")
AddTextComponentString(title)
DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034) -- Adjust text position
SetTextFont(0)
SetTextProportional(1)
SetTextScale(0.35, 0.25)
SetTextColour(255, 255, 255, 255)
SetTextEntry("STRING")
AddTextComponentString(level)
DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030) -- Right-aligned additional text
local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each)
local gap = segmentWidth / #currentProg -- Smaller gap between segments
for i = 1, #currentProg do
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
local fillPercentage = currentProg[i]
local progressBarWidth = segmentWidth * (fillPercentage / 100)
-- Semi-transparent background for each segment
DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255)
-- Filling progress for each segment
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 93, 182, 229, 255) -- Blue progress
end
end
end
--- Stops the current progress bar. --- Stops the current progress bar.
--- ---
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup. --- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
@@ -256,15 +233,16 @@ function stopProgressBar()
exports[OXLibExport]:cancelProgress() exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel") TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "gta" then elseif Config.System.ProgressBar == "lation" then
inProgress = false exports.lation_ui:cancelProgress()
elseif Config.System.ProgressBar == "gta" or Config.System.ProgressBar == "red" then
exports["jim_bridge"]:stopProgressBar()
end end
end end
-- System to handle sending/sharing progress bars between players -- -- System to handle sending/sharing progress bars between players --
-- For example, healing someone -- -- For example, healing someone --
local storedPID = nil
--- Server event handler for starting a shared progress bar. --- Server event handler for starting a shared progress bar.
--- This event is triggered when a player wants to start a progress bar on another player. --- This event is triggered when a player wants to start a progress bar on another player.

View File

@@ -58,9 +58,9 @@ end
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- local myMeta = GetMetadata(player, "myKey") --- local myMeta = getPlayerMetadata(player, "myKey")
--- ``` --- ```
function GetMetadata(player, key) function getPlayerMetadata(player, key)
if not player then if not player then
debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key) debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key)
return triggerCallback(getScript()..":server:GetMetadata", key) return triggerCallback(getScript()..":server:GetMetadata", key)
@@ -98,11 +98,11 @@ createCallback(getScript()..":server:GetMetadata", function(source, key)
if type(key) == "table" then if type(key) == "table" then
local Metadata = {} local Metadata = {}
for _, k in ipairs(key) do for _, k in ipairs(key) do
Metadata[k] = GetMetadata(player, k) Metadata[k] = getPlayerMetadata(player, k)
end end
return Metadata return Metadata
elseif type(key) == "string" then elseif type(key) == "string" then
return GetMetadata(player, key) return getPlayerMetadata(player, key)
end end
end) end)
@@ -120,9 +120,9 @@ end)
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- SetMetadata(player, "myKey", "newValue") --- setPlayerMetadata(player, "myKey", "newValue")
--- ``` --- ```
function SetMetadata(player, key, value) function setPlayerMetadata(player, key, value)
debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata for key: "..key) debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata for key: "..key)
if isStarted(QBExport) or isStarted(QBXExport) then if isStarted(QBExport) or isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using QBExport/QBXExport") debugPrint("^6Bridge^7: ^3SetMetadata^7() using QBExport/QBXExport")
@@ -144,14 +144,14 @@ function SetMetadata(player, key, value)
end end
-- Register a server callback for setting metadata. -- Register a server callback for setting metadata.
createCallback(getScript()..":server:SetMetadata", function(source, key, value) createCallback(getScript()..":server:setPlayerMetadata", function(source, key, value)
debugPrint("SetMetadata callback triggered for source:", source, "key:", key, "value:", value) debugPrint("SetMetadata callback triggered for source:", source, "key:", key, "value:", value)
local player = GetPlayer(source) local player = GetPlayer(source)
--[[if not player then --[[if not player then
print("Error setting metadata: player not found for source "..tostring(source)) print("Error setting metadata: player not found for source "..tostring(source))
return false return false
end]] end]]
SetMetadata(player, key, value) setPlayerMetadata(player, key, value)
print("Metadata set successfully.", key) print("Metadata set successfully.", key)
return true return true
end) end)

View File

@@ -31,6 +31,7 @@
--- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId) --- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId)
--- ``` --- ```
function triggerNotify(title, message, type, src) function triggerNotify(title, message, type, src)
if not Config.System or not Config.System.Notify then debugPrint("Notify triggered but not set up") return end
if Config.System.Notify == "okok" then if Config.System.Notify == "okok" then
if not src then if not src then
TriggerEvent('okokNotify:Alert', title, message, 6000, type) TriggerEvent('okokNotify:Alert', title, message, 6000, type)
@@ -61,6 +62,20 @@ function triggerNotify(title, message, type, src)
else else
TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message) TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message)
end 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 elseif Config.System.Notify == "red" then
if isStarted("jim-redui") then if isStarted("jim-redui") then

View File

@@ -73,6 +73,11 @@ function sendPhoneMail(data)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData) TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end, end,
}, },
{ name = "npwd_qbx_mail",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
{ name = "jpr-phonesystem", { name = "jpr-phonesystem",
send = function(mailData) send = function(mailData)
TriggerServerEvent(getScript()..":jpr:SendMail", mailData) TriggerServerEvent(getScript()..":jpr:SendMail", mailData)
@@ -85,23 +90,125 @@ function sendPhoneMail(data)
}, },
} }
local activePhone = nil
-- Check each phone system in order and use the first active one. -- Check each phone system in order and use the first active one.
for _, phone in ipairs(phoneSystems) do for _, phone in ipairs(phoneSystems) do
if isStarted(phone.name) then if isStarted(phone.name) then
activePhone = phone.name debugPrint("^6Bridge^7[^3"..phone.name.."^7]: ^2Sending mail to player")
phone.send(data) phone.send(data)
break return true
end end
end end
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
if activePhone then return false
debugPrint("^6Bridge^7[^3"..activePhone.."^7]: ^2Sending mail to player")
else
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
end
end end
function sendPhoneInvoice(data)
if not data.src then return end
-- Define each supported phone system and its corresponding mail-sending function.
local phoneSystems = {
{
name = "qb-phone",
send = function(mailData)
-- Defensive check for required fields
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "src" }
for _, field in ipairs(required) do
if mailData[field] == nil then
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
return
end
end
-- Safe insert with all parameters present
MySQL.Async.insert(
'INSERT INTO phone_invoices (citizenid, amount, society, sender, sendercitizenid) VALUES (?, ?, ?, ?, ?)',
{
mailData.billedCitizenid,
mailData.amount,
mailData.job,
mailData.name,
mailData.billerCitizenid
},
function(id)
if id then
TriggerClientEvent('qb-phone:client:AcceptorDenyInvoice', mailData.src, id, mailData.name, mailData.job, mailData.billerCitizenid, mailData.amount, GetInvokingResource())
end
end
)
TriggerClientEvent('qb-phone:RefreshPhone', mailData.src)
end
},
{
name = "codem-phone",
send = function(mailData)
-- Defensive check for required fields
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "src" }
for _, field in ipairs(required) do
if mailData[field] == nil then
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
return
end
end
-- Safe insert with all parameters present
MySQL.Async.insert(
'INSERT INTO phone_invoices (citizenid, amount, society, sender, sendercitizenid) VALUES (?, ?, ?, ?, ?)',
{
mailData.billedCitizenid,
mailData.amount,
mailData.job,
mailData.name,
mailData.billerCitizenid
},
function(id)
-- if id then
-- TriggerClientEvent('qb-phone:client:AcceptorDenyInvoice', mailData.src, id, mailData.name, mailData.job, mailData.billerCitizenid, mailData.amount, GetInvokingResource())
-- end
end
)
end
},
{
name = "gks-phone",
send = function(mailData)
-- Defensive check for required fields
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "label" }
for _, field in ipairs(required) do
if mailData[field] == nil then
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
return
end
end
MySQL.Async.execute(
'INSERT INTO gksphone_invoices (citizenid, amount, society, sender, sendercitizenid, label) VALUES (@citizenid, @amount, @society, @sender, @sendercitizenid, @label)',
{
['@citizenid'] = mailData.billedCitizenid,
['@amount'] = mailData.amount,
['@society'] = mailData.job,
['@sender'] = mailData.name,
['@sendercitizenid'] = mailData.billerCitizenid,
['@label'] = mailData.label
}
)
end
}
}
-- Check each phone system in order and use the first active one.
for _, phone in ipairs(phoneSystems) do
if isStarted(phone.name) then
debugPrint("^6Bridge^7[^3"..phone.name.."^7]: ^2Sending mail to player^7", data.src)
phone.send(data)
return true
end
end
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
return false
end
------------------------------------------------------------- -------------------------------------------------------------
-- Phone System Event Handlers -- Phone System Event Handlers
------------------------------------------------------------- -------------------------------------------------------------

View File

@@ -109,19 +109,18 @@ end)
--- setThirst(playerId, 80) --- setThirst(playerId, 80)
--- ``` --- ```
function setThirst(src, thirst) function setThirst(src, thirst)
debugPrint("^4Debug^7: ^2Adding thirst^7: ^3"..thirst.." ^2to player^7: ^3"..src.."^7")
if isStarted(ESXExport) then if isStarted(ESXExport) then
TriggerClientEvent('esx_status:add', src, 'thirst', thirst) TriggerClientEvent('esx_status:add', src, 'thirst', thirst)
elseif isStarted(QBExport) or isStarted(QBXExport) then elseif isStarted(QBExport) or isStarted(QBXExport) or isStarted(RSGExport) then
local Player = Core.Functions.GetPlayer(src) local Player = Core.Functions.GetPlayer(src)
Player.Functions.SetMetaData('thirst', thirst) Player.Functions.SetMetaData('thirst', thirst)
TriggerClientEvent("hud:client:UpdateNeeds", src, thirst, Player.PlayerData.metadata.thirst) TriggerClientEvent("hud:client:UpdateNeeds", src, Player.PlayerData.metadata.hunger, thirst)
elseif isStarted(RSGExport) then
local Player = Core.Functions.GetPlayer(src)
Player.Functions.SetMetaData('thirst', thirst)
TriggerClientEvent("hud:client:UpdateNeeds", src, thirst, Player.PlayerData.metadata.thirst)
end end
end end
--- Sets the player's hunger level. --- Sets the player's hunger level.
@@ -134,18 +133,16 @@ end
--- setHunger(playerId, 60) --- setHunger(playerId, 60)
--- ``` --- ```
function setHunger(src, hunger) function setHunger(src, hunger)
debugPrint("^4Debug^7: ^2Adding hunger^7: ^3"..hunger.." ^2to player^7: ^3"..src.."^7")
if isStarted(ESXExport) then if isStarted(ESXExport) then
TriggerClientEvent('esx_status:add', src, 'hunger', hunger) TriggerClientEvent('esx_status:add', src, 'hunger', hunger)
elseif isStarted(QBExport) or isStarted(QBXExport) then elseif isStarted(QBExport) or isStarted(QBXExport) or isStarted(RSGExport) then
local Player = Core.Functions.GetPlayer(src) local Player = Core.Functions.GetPlayer(src)
Player.Functions.SetMetaData('hunger', hunger) Player.Functions.SetMetaData('hunger', hunger)
TriggerClientEvent("hud:client:UpdateNeeds", src, hunger, Player.PlayerData.metadata.hunger) TriggerClientEvent("hud:client:UpdateNeeds", src, hunger, Player.PlayerData.metadata.thirst)
elseif isStarted(RSGExport) then
local Player = Core.Functions.GetPlayer(src)
Player.Functions.SetMetaData('hunger', hunger)
TriggerClientEvent("hud:client:UpdateNeeds", src, hunger, Player.PlayerData.metadata.hunger)
end end
end end
@@ -153,6 +150,21 @@ end
-- Economy Event Handlers -- Economy Event Handlers
------------------------------------------------------------- -------------------------------------------------------------
--- BillPlayer
function billPlayer(data)
if not Config.System.Billing or Config.System.Billing == "jim" then
TriggerEvent("jim-payments:client:Charge", {
job = data.job,
gang = data.gang,
coords = data.coords.xyz,
img = data.img
})
elseif Config.System.Billing == "okok" then
-- TriggerServerEvent("okokBilling:CreateCustomInvoice", target, price, reason, invoiceSource, society, societyName)
TriggerEvent("okokBilling:ToggleCreateInvoice")
end
end
--- Charges a player by removing money from their account. --- Charges a player by removing money from their account.
--- ---
--- @param cost number The amount to charge. --- @param cost number The amount to charge.
@@ -164,6 +176,7 @@ end
--- chargePlayer(100, "cash", playerId) --- chargePlayer(100, "cash", playerId)
--- ``` --- ```
function chargePlayer(cost, moneyType, newsrc) function chargePlayer(cost, moneyType, newsrc)
local is_success = false
local src = newsrc or source local src = newsrc or source
local fundResource = "" local fundResource = ""
if cost < 0 then if cost < 0 then
@@ -172,33 +185,35 @@ function chargePlayer(cost, moneyType, newsrc)
end end
if moneyType == "cash" then if moneyType == "cash" then
if isStarted(OXInv) then fundResource = OXInv if isStarted(OXInv) then fundResource = OXInv
exports[OXInv]:RemoveItem(src, "money", cost) is_success = exports[OXInv]:RemoveItem(src, "money", cost)
elseif isStarted(QBExport) or isStarted(QBXExport) then elseif isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost) is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
fundResource = RSGExport fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost) is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
fundResource = ESXExport fundResource = ESXExport
ESX.GetPlayerFromId(src).removeMoney(cost, "") ESX.GetPlayerFromId(src).removeMoney(cost, "")
is_success = true
end end
elseif moneyType == "bank" then elseif moneyType == "bank" then
if isStarted(QBExport) or isStarted(QBXExport) then if isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost) is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
fundResource = RSGExport fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost) is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
fundResource = ESXExport fundResource = ESXExport
ESX.GetPlayerFromId(src).removeMoney(cost, "") ESX.GetPlayerFromId(src).removeMoney(cost, "")
is_success = true
end end
end end
@@ -207,6 +222,7 @@ function chargePlayer(cost, moneyType, newsrc)
else else
debugPrint("^6Bridge^7: ^2Charging ^2Player^7: '^6"..cost.."^7'", moneyType, fundResource) debugPrint("^6Bridge^7: ^2Charging ^2Player^7: '^6"..cost.."^7'", moneyType, fundResource)
end end
return is_success
end end
RegisterNetEvent(getScript()..":server:ChargePlayer", function(cost, moneyType, newsrc) RegisterNetEvent(getScript()..":server:ChargePlayer", function(cost, moneyType, newsrc)
debugPrint(GetInvokingResource()) debugPrint(GetInvokingResource())
@@ -228,39 +244,42 @@ end)
--- fundPlayer(150, "cash", playerId) --- fundPlayer(150, "cash", playerId)
--- ``` --- ```
function fundPlayer(fund, moneyType, newsrc) function fundPlayer(fund, moneyType, newsrc)
local is_success = false
local src = newsrc or source local src = newsrc or source
local fundResource = "" local fundResource = ""
if moneyType == "cash" then if moneyType == "cash" then
if isStarted(OXInv) then if isStarted(OXInv) then
fundResource = OXInv fundResource = OXInv
exports[OXInv]:AddItem(src, "money", fund) is_success = exports[OXInv]:AddItem(src, "money", fund)
elseif isStarted(QBExport) or isStarted(QBXExport) then elseif isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund) is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
fundResource = RSGExport fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund) is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
fundResource = ESXExport fundResource = ESXExport
ESX.GetPlayerFromId(src).addMoney(fund, "") ESX.GetPlayerFromId(src).addMoney(fund, "")
is_success = true
end end
elseif moneyType == "bank" then elseif moneyType == "bank" then
if isStarted(QBExport) or isStarted(QBXExport) then if isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund) is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
fundResource = RSGExport fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund) is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
fundResource = ESXExport fundResource = ESXExport
ESX.GetPlayerFromId(src).addMoney(fund, "") ESX.GetPlayerFromId(src).addMoney(fund, "")
is_success = true
end end
end end
@@ -269,6 +288,7 @@ function fundPlayer(fund, moneyType, newsrc)
else else
debugPrint("^6Bridge^7: ^2Funding Player: '^2"..fund.."^7'", moneyType, fundResource) debugPrint("^6Bridge^7: ^2Funding Player: '^2"..fund.."^7'", moneyType, fundResource)
end end
return is_success
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -392,7 +412,7 @@ function hasJob(job, source, grade)
elseif isStarted(QBExport) and not isStarted(QBXExport) then elseif isStarted(QBExport) and not isStarted(QBXExport) then
if Core.Functions.GetPlayer then if Core.Functions.GetPlayer then
local player = Core.Functions.GetPlayer(src) local player = Core.Functions.GetPlayer(src)
if not player then print("Player not found for src: "..src) end if not player then goto skip end
local jobinfo = player.PlayerData.job local jobinfo = player.PlayerData.job
if jobinfo.name == job then if jobinfo.name == job then
hasJobFlag = true hasJobFlag = true
@@ -417,11 +437,10 @@ function hasJob(job, source, grade)
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end end
end end
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
if Core.Functions.GetPlayer then if Core.Functions.GetPlayer then
local player = Core.Functions.GetPlayer(src) local player = Core.Functions.GetPlayer(src)
if not player then print("Player not found for src: "..src) end if not player then goto skip end
local jobinfo = player.PlayerData.job local jobinfo = player.PlayerData.job
if jobinfo.name == job then if jobinfo.name == job then
hasJobFlag = true hasJobFlag = true
@@ -511,6 +530,7 @@ function hasJob(job, source, grade)
print("^4ERROR^7: ^2No Core detected for hasJob() ^7- ^2Check ^3starter^1.^2lua^7") print("^4ERROR^7: ^2No Core detected for hasJob() ^7- ^2Check ^3starter^1.^2lua^7")
end end
end end
::skip::
return hasJobFlag, duty return hasJobFlag, duty
end end

View File

@@ -164,25 +164,23 @@ function openShop(data)
elseif isStarted(OXInv) then elseif isStarted(OXInv) then
exports[OXInv]:openInventory('shop', { type = data.shop }) exports[OXInv]:openInventory('shop', { type = data.shop })
elseif isStarted(QSInv) or isStarted(CodeMInv) then elseif isStarted(QSInv) then
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items) TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
elseif isStarted(TgiannInv) then elseif isStarted(TgiannInv) then
TriggerServerEvent(getScript()..':server:openServerShop', data.shop) TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
elseif isStarted(CodeMInv) then
--print(data.shop)
TriggerEvent("codem-inventory:openshop", data.shop)
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
if QBInvNew then TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
TriggerServerEvent(getScript()..':server:openServerShop', data.shop) TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
else
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
elseif isStarted(PSInv) then elseif isStarted(PSInv) then
if QBInvNew then TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
TriggerServerEvent(getScript()..':server:openServerShop', data.shop) TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
else
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
elseif isStarted(RSGInv) then elseif isStarted(RSGInv) then
TriggerServerEvent(getScript()..':server:openServerShop', data.shop) TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
@@ -191,10 +189,10 @@ function openShop(data)
end end
RegisterNetEvent(getScript()..':server:openServerShop', function(data) RegisterNetEvent(getScript()..':server:openServerShop', function(data)
if isStarted(QBInv) then if isStarted(QBInv) and checkExportExists(QBInv, "OpenShop") then
exports[QBInv]:OpenShop(source, data) exports[QBInv]:OpenShop(source, data)
end end
if isStarted(PSInv) then if isStarted(PSInv) and checkExportExists(PSInv, "OpenShop") then
exports[PSInv]:OpenShop(source, data) exports[PSInv]:OpenShop(source, data)
end end
if isStarted(TgiannInv) then if isStarted(TgiannInv) then
@@ -217,8 +215,9 @@ end)
--- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons") --- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons")
--- ``` --- ```
function registerShop(name, label, items, society) function registerShop(name, label, items, society)
local shopResource = ""
if isStarted(OXInv) then if isStarted(OXInv) then
debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Store^7:", name, label) shopResource = OXInv
exports[OXInv]:RegisterShop(name, { exports[OXInv]:RegisterShop(name, {
name = label, name = label,
inventory = items, inventory = items,
@@ -226,11 +225,11 @@ function registerShop(name, label, items, society)
}) })
elseif isStarted(TgiannInv) then elseif isStarted(TgiannInv) then
debugPrint("^6Bridge^7: ^2Registering ^3Tgiann ^2Store^7:", name, label) shopResource = TgiannInv
exports[TgiannInv]:RegisterShop(name, items) exports[TgiannInv]:RegisterShop(name, items)
elseif isStarted(QBInv) and QBInvNew then elseif isStarted(QBInv) and checkExportExists(QBInv, "CreateShop") then
debugPrint("^6Bridge^7: ^2Registering ^3QB ^2Store^7:", name, label) shopResource = QBInv
exports[QBInv]:CreateShop({ exports[QBInv]:CreateShop({
name = name, name = name,
label = label, label = label,
@@ -239,8 +238,8 @@ function registerShop(name, label, items, society)
society = society, society = society,
}) })
elseif isStarted(PSInv) and QBInvNew then elseif isStarted(PSInv) and checkExportExists(PSInv, "CreateShop") then
debugPrint("^6Bridge^7: ^2Registering ^3PS ^2Store^7:", name, label) shopResource = PSInv
exports[PSInv]:CreateShop({ exports[PSInv]:CreateShop({
name = name, name = name,
label = label, label = label,
@@ -250,7 +249,7 @@ function registerShop(name, label, items, society)
}) })
elseif isStarted(RSGInv) then elseif isStarted(RSGInv) then
debugPrint("^6Bridge^7: ^2Registering ^3RSG ^2Store^7:", name, label) shopResource = RSGInv
exports[RSGInv]:CreateShop({ exports[RSGInv]:CreateShop({
name = name, name = name,
label = label, label = label,
@@ -259,4 +258,9 @@ function registerShop(name, label, items, society)
society = society, society = society,
}) })
end end
if shopResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^5"..shopResource.." ^3Store^7:", name, "^4Label^7: "..label)
else
debugPrint("^1ERROR^7: ^1Couldn't find supported inventory to register shop^7:", name)
end
end end

View File

@@ -32,6 +32,25 @@ function skillCheck(data)
elseif Config.System.skillCheck == "gta" then elseif Config.System.skillCheck == "gta" then
exports.jim_bridge:skillCheck() 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 else
result = true result = true
end end

View File

@@ -15,6 +15,9 @@ local stash
-- If running on the server, create a callback to retrieve stash items. -- If running on the server, create a callback to retrieve stash items.
if isServer() then if isServer() then
createCallback(getScript()..':server:GetStashItems', function(source, stashName) createCallback(getScript()..':server:GetStashItems', function(source, stashName)
if stashName == nil or stashName == "" then
return {}
end
stash = getStash(stashName) stash = getStash(stashName)
return stash return stash
end) end)
@@ -35,9 +38,9 @@ local stashCache = {}
--- local cached = GetStashTimeout("playerStash") --- local cached = GetStashTimeout("playerStash")
--- ``` --- ```
function GetStashTimeout(stashName, stop) function GetStashTimeout(stashName, stop)
if stop then if stop or (stashName == nil or stashName == "") then
stashCache = {} stashCache = {}
return return false
end end
-- Retrieve cache for this stash, or initialize if not present. -- Retrieve cache for this stash, or initialize if not present.
@@ -85,7 +88,7 @@ end
--- local found, stashName = checkStashItem({"playerStash", "storageStash"}, { iron = 2, wood = 5 }) --- local found, stashName = checkStashItem({"playerStash", "storageStash"}, { iron = 2, wood = 5 })
--- ``` --- ```
function checkStashItem(stashes, itemTable) function checkStashItem(stashes, itemTable)
if not stashes then if not stashes or stashes == "" then
return hasItem(itemTable), nil return hasItem(itemTable), nil
end end
@@ -151,6 +154,22 @@ function openStash(data)
slots = data.slots or 40 slots = data.slots or 40
}) })
elseif isStarted(JPRInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
if QBInvNew then if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', { TriggerServerEvent(getScript()..':server:openServerStash', {
@@ -176,6 +195,7 @@ function openStash(data)
slots = data.slots or 40 slots = data.slots or 40
}) })
else else
TriggerEvent("ps-inventory:client:SetCurrentStash", data.stash)
TriggerEvent("inventory:client:SetCurrentStash", data.stash) TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, { TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50, slots = data.slots or 50,
@@ -211,6 +231,9 @@ RegisterNetEvent(getScript()..":server:openServerStash", function(data)
if isStarted(TgiannInv) then if isStarted(TgiannInv) then
exports[TgiannInv]:OpenInventory(source, 'stash', data.stashName, data) exports[TgiannInv]:OpenInventory(source, 'stash', data.stashName, data)
end end
if isStarted(JPRInv) then
exports[JPRInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(QBInv) then if isStarted(QBInv) then
exports[QBInv]:OpenInventory(source, data.stashName, data) exports[QBInv]:OpenInventory(source, data.stashName, data)
end end
@@ -223,8 +246,15 @@ RegisterNetEvent(getScript()..":server:openServerStash", function(data)
end) end)
function clearStash(stashId) function clearStash(stashId)
if isStarted(QBInv) then if isStarted(JPRInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..QBInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..JPRInv.."^2 Stash^7", stashId)
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashId,
['items'] = json.encode({})
})
elseif isStarted(QBInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..QBInv.."^2 Stash^7", stashId)
if QBInvNew then if QBInvNew then
exports[QBInv]:ClearStash(stashId) exports[QBInv]:ClearStash(stashId)
else else
@@ -235,42 +265,38 @@ function clearStash(stashId)
end end
elseif isStarted(OXInv) then elseif isStarted(OXInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..OXInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..OXInv.."^2 Stash^7", stashId)
exports[OXInv]:ClearInventory(stashId) exports[OXInv]:ClearInventory(stashId)
elseif isStarted(PSInv) then elseif isStarted(PSInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..PSInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..PSInv.."^2 Stash^7", stashId)
if QBInvNew then MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
exports[QBInv]:ClearStash(stashId) ['stash'] = stashId,
else ['items'] = json.encode({})
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { })
['stash'] = stashId,
['items'] = json.encode({})
})
end
elseif isStarted(QSInv) then elseif isStarted(QSInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..QSInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..QSInv.."^2 Stash^7", stashId)
exports[QSInv]:ClearOtherInventory('stash', stashId) exports[QSInv]:ClearOtherInventory('stash', stashId)
elseif isStarted(CoreInv) then elseif isStarted(CoreInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..CoreInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..CoreInv.."^2 Stash^7", stashId)
exports[CoreInv]:clearInventory("stash-"..stashId) exports[CoreInv]:clearInventory("stash-"..stashId)
elseif isStarted(CodeMInv) then elseif isStarted(CodeMInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..CodeMInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..CodeMInv.."^2 Stash^7", stashId)
exports[CodeMInv]:ClearInventory(stashId) exports[CodeMInv]:ClearInventory(stashId)
elseif isStarted(OrigenInv) then elseif isStarted(OrigenInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..OrigenInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..OrigenInv.."^2 Stash^7", stashId)
exports[OrigenInv]:ClearInventory(stashId) exports[OrigenInv]:ClearInventory(stashId)
elseif isStarted(TgiannInv) then elseif isStarted(TgiannInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..TgiannInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..TgiannInv.."^2 Stash^7", stashId)
exports["tgiann-inventory"]:DeleteInventory("stash", stashId) exports["tgiann-inventory"]:DeleteInventory("stash", stashId)
elseif isStarted(RSGInv) then elseif isStarted(RSGInv) then
debugPrint("^5Bridge^7: ^2Cleared ^3"..RSGInv.."^2 Stash^7", stashId) debugPrint("^5Bridge^7: ^2Clearing ^3"..RSGInv.."^2 Stash^7", stashId)
exports[RSGInv]:ClearStash(stashId) exports[RSGInv]:ClearStash(stashId)
end end
@@ -294,7 +320,7 @@ end
--- ``` --- ```
function getStash(stashName) function getStash(stashName)
local stashResource = "" local stashResource = ""
if type(stashName) ~= "string" then if stashName == "" or type(stashName) ~= "string" then
print("^6Bridge^7: ^2Stash name was not a string ^3"..stashName.."^7(^3"..type(stashName).."^7)") print("^6Bridge^7: ^2Stash name was not a string ^3"..stashName.."^7(^3"..type(stashName).."^7)")
return {} return {}
end end
@@ -331,6 +357,11 @@ function getStash(stashName)
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName }) local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
if result then stashItems = json.decode(result) end if result then stashItems = json.decode(result) end
elseif isStarted(JPRInv) then
stashResource = JPRInv
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
if result then stashItems = json.decode(result) end
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
stashResource = QBInv stashResource = QBInv
if QBInvNew then if QBInvNew then
@@ -392,6 +423,10 @@ end
--- stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 }) --- stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 })
--- ``` --- ```
function stashRemoveItem(stashItems, stashName, items) function stashRemoveItem(stashItems, stashName, items)
if stashName == "" or stashName == nil then
print("^1ERROR^7: ^1stashRemoveItem triggered but stashName was empty^7")
return
end
if type(stashName) ~= "table" then if type(stashName) ~= "table" then
stashName = { stashName } stashName = { stashName }
end end
@@ -449,6 +484,29 @@ function stashRemoveItem(stashItems, stashName, items)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..TgiannInv, k, v) debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..TgiannInv, k, v)
end end
elseif isStarted(JPRInv) then
if not stashItems or not next(stashItems) then
stashItems = getStash(stashName[1])
end
for k, v in pairs(items) do
for l in pairs(stashItems) do
if stashItems[l].name == k then
if (stashItems[l].amount - v) <= 0 then
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
stashItems[l] = nil
else
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with "..JPRInv, k, v)
stashItems[l].amount -= v
end
end
end
end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3JPR^2 stash '^6"..stashName[1].."^7'")
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName[1],
['items'] = json.encode(stashItems)
})
elseif isStarted(QBInv) then elseif isStarted(QBInv) then
if QBInvNew then if QBInvNew then
for k, v in pairs(items) do for k, v in pairs(items) do
@@ -531,7 +589,7 @@ RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
--- local hasAll, details = stashhasItem(currentStashItems, { iron = 2, wood = 5 }) --- local hasAll, details = stashhasItem(currentStashItems, { iron = 2, wood = 5 })
--- ``` --- ```
function stashhasItem(stashItems, items, amount) function stashhasItem(stashItems, items, amount)
local invs = { OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, QBInv, PSInv, RSGInv } local invs = { OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv, QBInv, PSInv, RSGInv }
local foundInv = "" local foundInv = ""
for _, inv in ipairs(invs) do for _, inv in ipairs(invs) do
if isStarted(inv) then if isStarted(inv) then
@@ -585,27 +643,25 @@ end
--- ) --- )
--- ``` --- ```
function registerStash(name, label, slots, weight, owner, coords) function registerStash(name, label, slots, weight, owner, coords)
local stashResource = ""
if isStarted(OXInv) then if isStarted(OXInv) then
debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Stash^7:", name, label, owner or nil) stashResource = OXInv
exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil) exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil)
--elseif isStarted(QSInv) then
-- debugPrint("^6Bridge^7: ^2Registering ^3QS ^2Stash^7:", name, label)
-- exports[QSInv]:RegisterStash(name, label, slots or 50, weight or 4000000)
--elseif isStarted(CoreInv) then
-- debugPrint("^6Bridge^7: ^2Registering ^3CoreInv ^2Stash^7:", name, label)
-- exports[CoreInv]:openHolder(nil, name, 'stash', nil, nil, false, nil)
elseif isStarted(OrigenInv) then elseif isStarted(OrigenInv) then
debugPrint("^6Bridge^7: ^2Registering ^3OrigenInv ^2Stash^7:", name, label) stashResource = OrigenInv
exports["origen_inventory"]:registerStash(name, label, slots or 50, weight or 4000000) exports["origen_inventory"]:registerStash(name, label, slots or 50, weight or 4000000)
elseif isStarted(TgiannInv) then elseif isStarted(TgiannInv) then
debugPrint("^6Bridge^7: ^2Registering ^3TgiannInv ^2Stash^7:", name, label) stashResource = TgiannInv
exports[TgiannInv]:RegisterStash(name, label, slots or 50, weight or 4000000) exports[TgiannInv]:RegisterStash(name, label, slots or 50, weight or 4000000)
end end
if stashResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^4"..stashResource.." ^3Stash^7:", name, "^4Label^7: "..label)
else
debugPrint("^1ERROR^7: ^1Couldn't find supported inventory to register stash^7:", name)
end
end end
if isServer() then if isServer() then

View File

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

View File

@@ -21,24 +21,29 @@
--- }) --- })
--- ``` --- ```
function registerCommand(command, options) function registerCommand(command, options)
local commandResource = ""
if isStarted(OXLibExport) then if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7"..OXLibExport, command) commandResource = OXLibExport
lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4]) lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4])
elseif isStarted(QBExport) and not isStarted(QBXExport) then elseif isStarted(QBExport) and not isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7"..QBExport, command) commandResource = QBExport
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil) Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7"..RSGExport, command) commandResource = RSGExport
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil) Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7ESX Legacy", command) commandResource = ESXExport
ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError) ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError)
options[4](xPlayer.source, args, showError) options[4](xPlayer.source, args, showError)
end, false, { help = options[1] }) end, false, { help = options[1] })
end end
if commandResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^4"..commandResource.."^7:", "^7/"..command)
else
print("^1ERROR^7: ^1Couldn't find supported framework to register command^7:", "/"..command)
end
end end

View File

@@ -14,6 +14,7 @@ Exports = {
CodeMInv = "codem-inventory", CodeMInv = "codem-inventory",
OrigenInv = "origen_inventory", OrigenInv = "origen_inventory",
TgiannInv = "tgiann-inventory", TgiannInv = "tgiann-inventory",
JPRInv = "jpr-inventory",
OXLibExport = "ox_lib", OXLibExport = "ox_lib",
@@ -176,7 +177,7 @@ for _, v in pairs({ -- This is a specific load order
'effects.lua', 'effects.lua',
--'warmenu.lua', --'warmenu.lua',
'_authToken.lua',
-- Do version check last -- Do version check last
'_scriptversioncheck.lua' '_scriptversioncheck.lua'
}) do }) do
@@ -188,4 +189,48 @@ for _, v in pairs({ -- This is a specific load order
if debugMode then if debugMode then
print("^5CoreLoader^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7") print("^5CoreLoader^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7")
end end
end
-- Auto Detection of Old/New QBInv/PSInv versions --
if isStarted(QBInv) then
if isServer() then
if not checkExportExists(QBInv, "CreateShop") then
print("^6Bridge^7: ^2Can^7'^2t ^2find new ^4"..QBInv.."^2 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
else
if checkExportExists(QBInv, "GetTrunkSize") then
print("^6Bridge^7: ^2Found old ^4"..QBInv.."^3 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
end
end
if isStarted(PSInv) then
if isServer() then
if not checkExportExists(PSInv, "CreateShop") then
print("^6Bridge^7: ^2Can^7'^2t ^2find new ^4"..PSInv.."^2 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
else
local function versionCompare(v1, v2)
local split = function(v) local t = {} for s in v:gmatch("[^.]+") do t[#t+1] = tonumber(s) end return t end
local a, b = split(v1), split(v2)
for i = 1, math.max(#a, #b) do
local ai, bi = a[i] or 0, b[i] or 0
if ai > bi then return true
elseif ai < bi then return false end
end
return true
end
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
print("^6Bridge^7: ^4"..PSInv.."^2 Version ^1below ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
end
end end

View File

@@ -2,13 +2,15 @@ local notifications = {}
local spacing = 60 -- vertical spacing between notifications local spacing = 60 -- vertical spacing between notifications
local activeDrawing = false -- flag for drawing loop state local activeDrawing = false -- flag for drawing loop state
if not HasStreamedTextureDictLoaded("timerbars") then local function loadTextureDict(dict)
while not HasStreamedTextureDictLoaded("timerbars") do if not HasStreamedTextureDictLoaded(dict) then
RequestStreamedTextureDict("timerbars") while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
Wait(5) end
end
end end
--- GTA Section ---
local notifTypes = { local notifTypes = {
success = "✔️", success = "✔️",
error = "", error = "",
@@ -35,12 +37,13 @@ function gtaNotify(title, message, emoji, src)
-- This allows it to be silent until the first notifcation is called, then the loop starts -- This allows it to be silent until the first notifcation is called, then the loop starts
if not activeDrawing then if not activeDrawing then
activeDrawing = true activeDrawing = true
StartDrawingLoop() StartGTADrawingLoop()
end end
end end
-- Drawing loop as a separate controlled thread -- Drawing loop as a separate controlled thread
function StartDrawingLoop() function StartGTADrawingLoop()
--loadTextureDict("timerbars")
CreateThread(function() CreateThread(function()
while #notifications > 0 do while #notifications > 0 do
local currentTime = GetGameTimer() local currentTime = GetGameTimer()
@@ -87,14 +90,14 @@ function StartDrawingLoop()
if not notif.title or notif.title == "" then if not notif.title or notif.title == "" then
moveMessage = true moveMessage = true
else else
drawNotiText(8, 0.4, vec2(0.75, 0.975), notif.title, vec2(posX, posY)) drawGTANotiText(8, 0.4, vec2(0.75, 0.975), notif.title, vec2(posX, posY))
end end
-- Message text -- Message text
drawNotiText(4, 0.3, vec2(0.75, 0.975), notif.message, vec2(posX, posY + (moveMessage and 0.015 or 0.03))) drawGTANotiText(4, 0.3, vec2(0.75, 0.975), notif.message, vec2(posX, posY + (moveMessage and 0.015 or 0.03)))
-- Emoji -- Emoji
drawNotiText(0, 0.3, vec2(0.75, 0.995), notif.emoji, vec2(posX, posY + 0.015)) drawGTANotiText(0, 0.3, vec2(0.75, 0.995), notif.emoji, vec2(posX, posY + 0.015))
::continue:: ::continue::
end end
@@ -104,7 +107,7 @@ function StartDrawingLoop()
end) end)
end end
function drawNotiText(font, scale, wrap, string, pos) function drawGTANotiText(font, scale, wrap, string, pos)
SetTextFont(font) SetTextFont(font)
SetTextScale(scale, scale) SetTextScale(scale, scale)
SetTextWrap(wrap.x, wrap.y) SetTextWrap(wrap.x, wrap.y)
@@ -116,5 +119,148 @@ function drawNotiText(font, scale, wrap, string, pos)
DrawText(pos.x, pos.y) DrawText(pos.x, pos.y)
end end
RegisterNetEvent("jim-bridge:Notify", gtaNotify)
exports("Notify", gtaNotify) ------------------
-- REDM SECTION --
------------------
---
---
local REDMnotifTypes = {
success = vec3(0, 150, 0),
error = vec3(150, 0, 0),
warning = vec3(0, 150, 150),
police = vec3(0, 0, 150),
ambulance = vec3(0, 0, 150),
}
function redNotify(title, message, style)
local notif = {
title = title and CreateVarString(10, "LITERAL_STRING", title) or nil,
message = message and CreateVarString(10, "LITERAL_STRING", message) or nil,
state = "enter",
startTime = GetGameTimer(),
style = REDMnotifTypes[style] or vec3(0, 0, 0),
progress = 0,
holdTime = 8000,
slideDuration = 100,
currentOffset = 0,
}
table.insert(notifications, notif)
-- Activate drawing loop if not already running
if not activeDrawing then
activeDrawing = true
StartREDMDrawingLoop()
end
end
-- Drawing loop as a separate controlled thread
function StartREDMDrawingLoop()
CreateThread(function()
loadTextureDict("generic_textures")
while #notifications > 0 do
local currentTime = GetGameTimer()
-- Update notifications vertical offset
for i, notif in ipairs(notifications) do
local target = (#notifications - i) * spacing
notif.currentOffset += (target - notif.currentOffset) * 0.1
end
for i = #notifications, 1, -1 do
local notif = notifications[i]
if notif.state == "enter" then
local elapsed = currentTime - notif.startTime
notif.progress = math.min(elapsed / notif.slideDuration, 1.0)
if notif.progress >= 1.0 then
notif.state = "hold"
notif.holdStart = currentTime
end
elseif notif.state == "hold" then
if currentTime - notif.holdStart >= notif.holdTime then
notif.state = "exit"
notif.exitStart = currentTime
end
elseif notif.state == "exit" then
local elapsed = currentTime - notif.exitStart
notif.progress = 1.0 - math.min(elapsed / notif.slideDuration, 1.0)
if notif.progress <= 0 then
table.remove(notifications, i)
goto continue
end
end
local startX, targetX = 1.0, 0.8
local posX = startX - (startX - targetX) * notif.progress
local posY = 0.05 + (notif.currentOffset / 1080)
-- Background sprite
DrawSprite("generic_textures", "inkroller_1a", posX + 0.09, posY + 0.025, 0.2, 0.053, 180.0, notif.style.x, notif.style.y, notif.style.z, 150)
-- Title text
local moveMessage = false
if not notif.title or notif.title == "" then
moveMessage = true
else
drawREDMNotiText(1, 0.4, vec2(0.75, 0.975), notif.title, vec2(posX, posY))
end
-- Message text
drawREDMNotiText(6, 0.3, vec2(0.75, 0.975), notif.message, vec2(posX, posY + (moveMessage and 0.0145 or 0.025)))
::continue::
end
Wait(0)
end
activeDrawing = false -- No notifications left, pause drawing
end)
end
function drawREDMNotiText(font, scale, wrap, string, pos)
SetTextFontForCurrentCommand(font)
SetTextScale(scale, scale)
SetTextColor(255, 255, 255, 255)
SetTextWrap(wrap.x, wrap.y)
SetTextJustification(2)
SetTextDropshadow(1, 0, 0, 0, 200)
DisplayText(string, pos.x, pos.y)
end
-- Exports --
-- Example:
-- TriggerEvent("jim_bridge:Notify", nil, "Short test message", "success")
--
-- exports["jim_bridge"]:Notify(nil, "Short test message", "success")
--
RegisterNetEvent("jim_bridge:RedNotify", redNotify)
RegisterNetEvent("jim_bridge:GTANotify", gtaNotify)
RegisterNetEvent("jim_bridge:Notify", function(...)
if GetCurrentGameName() == "rdr3" then
redNotify(...)
else
gtaNotify(...)
end
end)
function Notify(...)
if GetCurrentGameName() == "rdr3" then
redNotify(...)
else
gtaNotify(...)
end
end
exports("Notify", Notify)
-- Example testing notifications (can remove after testing)
--CreateThread(function()
-- TriggerEvent("jim_bridge:Notify", nil, "Short test message", "success")
-- Wait(3000)
-- TriggerEvent("jim_bridge:Notify", "Error!", "This is a longer example message to show stacking.", "error")
-- Wait(3000)
-- TriggerEvent("jim_bridge:Notify", "Test!", "This notification has to 'type' set.", nil )
-- Wait(4000)
--end)

363
ui_modules/progressbar.lua Normal file
View File

@@ -0,0 +1,363 @@
local inProgress = false
local function loadTextureDict(dict)
if not HasStreamedTextureDictLoaded(dict) then
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
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)
RemoveAnimDict(animDict)
end
function redProgressBar(data)
local ped = PlayerPedId()
local result = nil
loadTextureDict("generic_textures")
if inProgress then return false end
inProgress = true
local wait = debugMode and 1000 or data.time
local endTime = GetGameTimer() + wait
LocalPlayer.state:set("inv_busy", true, true)
TriggerEvent('inventory:client:busy:status', true)
TriggerEvent('canUseInventoryAndHotbar:toggle', not true)
-- Setup Animation/Task if specified
if data.dict then
playAnim(data.dict, data.anim, -1, data.flag or 32)
elseif data.task then
TaskStartScenarioInPlace(ped, data.task, -1, true)
end
-- Progress bar rendering loop
CreateThread(function()
while GetGameTimer() < endTime and inProgress do
Wait(0)
local elapsed = GetGameTimer()
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
-- Convert to segmented progress (assuming 5 segments here)
local segments = 1 -- Number of segments in the bar
local segmentProgress = {}
local progressPerSegment = 100 / segments
for i = 1, segments do
local segmentStart = (i - 1) * progressPerSegment
local segmentEnd = i * progressPerSegment
if percentage >= segmentEnd then
segmentProgress[i] = 100
elseif percentage <= segmentStart then
segmentProgress[i] = 0
else
segmentProgress[i] = ((percentage - segmentStart) / progressPerSegment) * 100
end
end
percentage = percentage >= 100 and 100 or percentage
-- Draw your segmented progress bar
ShowRedProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress
if data.disableMouse then
DisableControlAction(0, `INPUT_LOOK_LR`, true)
DisableControlAction(0, `INPUT_LOOK_UD`, true)
DisableControlAction(0, `INPUT_VEH_MOUSE_CONTROL_OVERRIDE`, true)
end
if data.disableMovement then
DisableControlAction(0, `INPUT_MOVE_LR`, true)
DisableControlAction(0, `INPUT_MOVE_UD`, true)
DisableControlAction(0, `INPUT_DUCK`, true)
DisableControlAction(0, `INPUT_SPRINT`, true)
end
if data.disableCarMovement then
DisableControlAction(0, `INPUT_VEH_MOVE_LEFT_ONLY`, true)
DisableControlAction(0, `INPUT_VEH_MOVE_RIGHT_ONLY`, true)
DisableControlAction(0, `INPUT_VEH_ACCELERATE`, true)
DisableControlAction(0, `INPUT_VEH_BRAKE`, true)
DisableControlAction(0, `INPUT_VEH_EXIT`, true)
end
if data.disableCombat then
DisablePlayerFiring(PlayerId(), true)
DisableControlAction(0, `INPUT_ATTACK`, true)
DisableControlAction(0, `INPUT_AIM`, true)
DisableControlAction(0, `INPUT_OPEN_WHEEL_MENU`, true)
DisableControlAction(0, `INPUT_DETONATE`, true)
DisableControlAction(0, `INPUT_THROW_GRENADE`, true)
DisableControlAction(0, `INPUT_MELEE_ATTACK`, true)
DisableControlAction(0, `INPUT_MELEE_GRAPPLE_ATTACK`, true)
end
if data.cancel and (
IsControlJustReleased(0, `INPUT_FRONTEND_RS`) or
IsControlJustReleased(0, `INPUT_SWITCH_SHOULDER`) or
IsControlJustReleased(0, `INPUT_GAME_MENU_TAB_RIGHT_SECONDARY`) or
IsControlJustReleased(0, `INPUT_FRONTEND_CANCEL`) or
IsControlJustReleased(0, `INPUT_GAME_MENU_CANCEL`) or
IsControlJustReleased(0, `INPUT_FRONTEND_RRIGHT`) or
IsControlJustReleased(0, `INPUT_QUIT`) or
IsControlJustReleased(0, `INPUT_MINIGAME_QUIT`)
) then
inProgress = false
end
end
end)
-- Wait for completion or cancel
while GetGameTimer() < endTime and inProgress do
Wait(100)
end
-- Cleanup animations/tasks
if data.dict then
stopAnim(data.dict, data.anim, ped)
end
if data.task then
ClearPedTasks(ped)
end
result = inProgress
inProgress = false
while result == nil do Wait(10) end
-- Cleanup
FreezeEntityPosition(ped, false)
LocalPlayer.state:set("inv_busy", false, true)
TriggerEvent('inventory:client:busy:status', false)
TriggerEvent('canUseInventoryAndHotbar:toggle', not false)
return result
end
function ShowRedProgressBar(currentProg, title, level)
local loc = vec2(0.40, 0.90)
local size = vec2(0.3, 0.03)
-- Draw background box
DrawSprite("generic_textures", "inkroller_1a", loc.x+0.1, loc.y-0.01, 0.25, 0.07, 180.0, 0, 0, 0, 200)
SetTextFontForCurrentCommand(6)
SetTextScale(0.35, 0.35)
SetTextColor(255, 255, 255, 255)
SetTextDropshadow(1, 0, 0, 0, 200)
BgDisplayText(title, loc.x - size.x / 4 + 0.074, loc.y - 0.034)
SetTextFontForCurrentCommand(1)
SetTextScale(0.35, 0.35)
SetTextColor(255, 255, 255, 255)
SetTextDropshadow(1, 0, 0, 0, 200)
BgDisplayText(level, loc.x - size.x / 4 + 0.246, loc.y - 0.030)
local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each)
local gap = segmentWidth / #currentProg -- Smaller gap between segments
for i = 1, #currentProg do
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
local fillPercentage = currentProg[i]
local progressBarWidth = segmentWidth * (fillPercentage / 100)
-- Semi-transparent background for each segment
DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255)
-- Filling progress for each segment
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 255, 0, 0, 200) -- Blue progress
end
end
end
function gtaProgressBar(data)
local ped = PlayerPedId()
local result = nil
loadTextureDict("timerbars")
if inProgress then return false end
inProgress = true
local wait = debugMode and 1000 or data.time
local endTime = GetGameTimer() + wait
-- Setup Animation/Task if specified
if data.dict then
playAnim(data.dict, data.anim, -1, data.flag or 32)
elseif data.task then
TaskStartScenarioInPlace(ped, data.task, -1, true)
end
-- Progress bar rendering loop
CreateThread(function()
while GetGameTimer() < endTime and inProgress do
Wait(0)
local elapsed = GetGameTimer()
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
-- Convert to segmented progress (assuming 5 segments here)
local segments = 1 -- Number of segments in the bar
local segmentProgress = {}
local progressPerSegment = 100 / segments
for i = 1, segments do
local segmentStart = (i - 1) * progressPerSegment
local segmentEnd = i * progressPerSegment
if percentage >= segmentEnd then
segmentProgress[i] = 100
elseif percentage <= segmentStart then
segmentProgress[i] = 0
else
segmentProgress[i] = ((percentage - segmentStart) / progressPerSegment) * 100
end
end
percentage = percentage >= 100 and 100 or percentage
-- Draw your segmented progress bar
ShowGTAProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress
DisablePlayerFiring(ped, true)
DisableControlAction(0, 25, true) -- Disable aim
DisableControlAction(0, 21, true) -- Disable sprint
DisableControlAction(0, 30, true) -- Disable move left/right
DisableControlAction(0, 31, true) -- Disable move forward/back
DisableControlAction(0, 36, true) -- Disable stealth
if data.cancel and (IsControlJustReleased(0, 202) or IsControlJustReleased(0, 177) or IsControlJustReleased(0, 73)) then
inProgress = false
end
end
end)
-- Wait for completion or cancel
while GetGameTimer() < endTime and inProgress do
Wait(100)
end
-- Cleanup animations/tasks
if data.dict then
stopAnim(data.dict, data.anim, ped)
end
if data.task then
ClearPedTasks(ped)
end
result = inProgress
inProgress = false
while result == nil do Wait(10) end
-- Cleanup
FreezeEntityPosition(ped, false)
LocalPlayer.state:set("inv_busy", false, true)
TriggerEvent('inventory:client:busy:status', false)
TriggerEvent('canUseInventoryAndHotbar:toggle', not false)
return result
end
function ShowGTAProgressBar(currentProg, title, level)
local loc = vec2(0.37, 0.90)
local size = vec2(0.3, 0.03)
-- Draw background box
DrawSprite("timerbars", "all_black_bg", loc.x +0.028, loc.y-0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255)
DrawSprite("timerbars", "all_black_bg", loc.x +0.170, loc.y-0.01, 0.15, 0.07, 180.0, 255, 255, 255, 255)
SetTextFont(0)
SetTextProportional(1)
SetTextScale(0.35, 0.35)
SetTextColour(255, 255, 255, 255)
SetTextDropshadow(0, 0, 0, 0, 255)
SetTextDropShadow()
SetTextOutline()
SetTextEntry("STRING")
AddTextComponentString(title)
DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034) -- Adjust text position
SetTextFont(0)
SetTextProportional(1)
SetTextScale(0.35, 0.25)
SetTextColour(255, 255, 255, 255)
SetTextEntry("STRING")
AddTextComponentString(level)
DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030) -- Right-aligned additional text
local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each)
local gap = segmentWidth / #currentProg -- Smaller gap between segments
for i = 1, #currentProg do
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
local fillPercentage = currentProg[i]
local progressBarWidth = segmentWidth * (fillPercentage / 100)
-- Semi-transparent background for each segment
DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255)
-- Filling progress for each segment
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 93, 182, 229, 255) -- Blue progress
end
end
end
function stopProgressBar() inProgress = false end
function isProgressBar() return inProgress end
function progressBar(...)
if GetCurrentGameName() == "rdr3" then
return redProgressBar(...)
else
return gtaProgressBar(...)
end
end
exports("progressBar", function(...)
return progressBar(...)
end)
exports("gtaProgressBar", function(...)
return gtaProgressBar(...)
end)
exports("redProgressBar", function(...)
return redProgressBar(...)
end)
exports("stopProgressBar", stopProgressBar)
exports("isProgressBar", isProgressBar)
--RegisterCommand("testprog", function()
-- print("test")
-- if progressBar({
-- label = "Processing...",
-- time = 10000,
-- disableMovement = true,
-- --dict = "amb@world_human_hang_out_street@female_hold_arm@base",
-- --anim = "base",
-- --flag = 49,
-- cancel = true,
-- }) then
-- Notify("Success!", "Short test message", "success")
-- else
-- Notify("Error!", "This is a longer example message to show stacking.", "error")
-- end
--end)

View File

@@ -79,7 +79,7 @@ function gtaSkillCheck()
-- Draw stationary cursor at result position -- Draw stationary cursor at result position
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255) DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
-- Display result text -- Display result text
drawSuccessText(x, y, result and "Success" or "Failed", result and 114 or 228, result and 204 or 52, result and 144 or 52) drawGTASuccessText(x, y, result and "Success" or "Failed", result and 114 or 228, result and 204 or 52, result and 144 or 52)
end end
if result then if result then
successes += 1 successes += 1
@@ -94,7 +94,7 @@ function gtaSkillCheck()
return successes == barsRequired return successes == barsRequired
end end
function drawSuccessText(x, y, text, r, g, b) function drawGTASuccessText(x, y, text, r, g, b)
SetTextFont(8) SetTextFont(8)
SetTextScale(0.45, 0.45) SetTextScale(0.45, 0.45)
SetTextColour(r, g, b, 255) SetTextColour(r, g, b, 255)
@@ -118,4 +118,10 @@ function createScaleBars(x, y, width, height)
DrawRect(x, y, width, height, 100, 100, 100, 255) DrawRect(x, y, width, height, 100, 100, 100, 255)
end end
exports("skillCheck", gtaSkillCheck) exports("skillCheck", function(...)
if GetCurrentGameName() == "rdr3" then
--redSkillCheck(...)
else
gtaSkillCheck(...)
end
end)

View File

@@ -1,12 +1,14 @@
2.0.15 2.0.21
- Fix old PSInv/QBInv stashes being wiped when it should be updating them - Fix toggleDuty() for ESX users
- Better breakout from opening inventory while crafting - Rename metadata handler functions
- Unify isInventoryOpen() function to simply check for IsNuiFocused() - Add useToolDegrade() to force limited uses for items
- Add clearStash() and doesItemExist() functions for future use - Make breakTool() smarter
- Add warmenu.lua file for gta scaleform menus (loading is disabled by default) - Fix getPlayer() not checking if player data exists yet on qbcore
- Move custom GTA native notify, skillcheck, target to separate files, can be used outside the script - Fix inbuilt gta progressbar error when stopping animations
- Complete refactor of framework shared info loading, now only does it once and then shares to scripts - lation_ui support fixes
- Added extra checks for ESX loading, should hopefully stop the coreloader line 317 error - Add fade in animations for created entities
- Fix waitForSharedLoad() not announcing when an error occurs
- Incrase authToken timeout from 5 seconds to 1 minute
https://github.com/jimathy/jim_bridge https://github.com/jimathy/jim_bridge