Compare commits

...

102 Commits

Author SHA1 Message Date
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
Jim Shield
4c514b459e Merge branch 'main' of https://github.com/jimathy/jim_bridge 2025-06-06 01:02:34 +01:00
Jim Shield
32b99d14e1 Version Bump
`2.0.14` - `2.0.15`
2025-06-06 01:01:31 +01:00
Jim Shield
de8f7c050c Complete refactor of handling of framework info
I've moved the framework caching to a new file `frameworkCache.lua`

This now caches the framework data once when `jim_bridge` starts

Then in `coreloader.lua` now, the information that is cached is now called from that script

This also includes better handling of ESX cached data and should stop errors

This greatly optimizes the loading of all my scripts and lowers server load to speed up server start times

I have been testing this for just under a week and 6 different test servers and it appears to work fine
2025-06-06 00:50:13 +01:00
Jim Shield
9c4c92e696 Move GTA notify, target, skillcheck to /ui_modules
I've separated the built-in custom native GTA Notifications, skillcheck and draw text targets to separate files to be loaded along side `jim_bridge`

This now makes it so they only load once instead of with every single script, and each one can call on it

This change also makes these systems able to be called outside of `jim_bridge`

For example in my test server I'm currently using:

```lua
function QBCore.Functions.Notify(text, texttype, length, icon)
    CreateThread(function()
        exports.jim_bridge:Notify(nil, text, icon)
    end)
end
```
2025-06-06 00:45:08 +01:00
Jim Shield
0cedd85afb Add optional warmenu.lua for gta menus 2025-06-06 00:39:24 +01:00
Jim Shield
1bb125ed31 Add clearStash() function 2025-06-06 00:38:51 +01:00
Jim Shield
72778235b8 Merge pull request #45 from oosayeroo/main 2025-06-06 00:37:33 +01:00
Jim Shield
76c34b8c03 Unify isInventoryOpen() function
This should fix issues for crafting complaining inventories are open when they aren't

I was trying to use per inventory checks for them being open until I realised I could just use `IsNuiFocused()` for all inventories to check if there was an nui on the screen that used the mouse, this should be a good workaround for it
2025-06-04 13:53:49 +01:00
Jim Shield
3ca2da2990 Try and enhance built in draw text with job+item checks 2025-06-03 22:22:49 +01:00
Jim Shield
d3035efd5c breakout from inventory exploit during crafting 2025-06-03 16:56:56 +01:00
oosayeroo
64a74a8928 Merge branch 'jimathy:main' into main 2025-06-03 08:54:36 +01:00
oosayeroo
e0fcf23dba added simple "doesItemExist(item)" function
- very simple function to check existance of an item
2025-06-03 08:52:06 +01:00
Jim Shield
ad1ce5f9ea Fix Old QBinv and new/old Psinv stashes being wiped
This appeared to only happen with `jim-mechanic` repair from stash, due to the stashitem's table not being sent to the function
Making it save an empty table to the database

But this was possible in other places too

I've added fallbacks for if it didn't receive a table, grab a fresh one from cache/database
2025-06-02 11:20:47 +01:00
Jim Shield
c3ce0ccc54 Version Bump
`2.0.13` - `2.0.14`
2025-05-31 12:18:44 +01:00
Jim Shield
0b7c96f66a Possibly fix QS Stashes wiping at script start 2025-05-31 12:12:15 +01:00
Jim Shield
3daa6a9772 Better workarounds for no info yet to getPlayer() 2025-05-26 19:10:54 +01:00
Jim Shield
c86cdf9227 Remove "Offline" check from canCarry() 2025-05-26 16:21:25 +01:00
Jim Shield
ca78c1b100 Load QBOX Weaps into Item list 2025-05-26 16:10:41 +01:00
Jim Shield
398701c4d7 attempt to fix support of ox_core job gathering 2025-05-26 16:08:51 +01:00
Jim Shield
00e420fd45 Add fix for ox_core injecting ox_lib itself 2025-05-26 15:58:49 +01:00
Jim Shield
a07cbc467d Merge branch 'main' of https://github.com/jimathy/jim_bridge 2025-05-24 11:01:24 +01:00
Jim Shield
ccd5144e7b Merge pull request #44 from dragon703/main 2025-05-24 11:00:13 +01:00
Jim Shield
195182f850 Merge pull request #43 from oosayeroo/main 2025-05-24 10:56:20 +01:00
Aditya Ray
c2b48c444c Support of Society Bank Features using Tgiann-Bank
Modified functions for fetching society account balance, charging society account and funding society accounts using Tgiann-Bank script.
2025-05-23 17:03:06 +05:30
Jim Shield
156b44a49b change blipinfo stuff to jim-blipcontroller 2025-05-21 21:04:29 +01:00
Jim Shield
a48b83155c add fallback to sellMenu if no locale system 2025-05-21 18:23:38 +01:00
Jim Shield
e455d1b43d getPlayer() fallback if player hasn't loaded yet 2025-05-21 15:38:56 +01:00
Jim Shield
14c2a79673 Version Bump
`2.0.12` - `2.0.13`
2025-05-19 23:39:46 +01:00
Jim Shield
97a1d97678 add random number helper
This function was created to help create random numbers
When you create a table of items with a random hunger value for example
`hunger = math.random(10, 20)` this will be set at script start and never change
using `hunger = {10, 20}` and then calling this function will make it generate a random number every time
It also fallsback if it simply recieved a number instead of a table
-- For example:
-- local hunger = {10, 20}
-- local hungerAmount = GetRandomTiming(hunger)
-- print(hungerAmount) -- number between 10, 20
2025-05-19 19:53:52 +01:00
Jim Shield
5001642504 Update version.txt 2025-05-19 17:14:41 +01:00
Jim Shield
5f413a2823 Update version checkers to support mini changelogs 2025-05-19 17:10:14 +01:00
Jim Shield
4d04682b33 Version Bump
`2.0.11` - `2.0.12`
2025-05-18 22:34:08 +01:00
Jim Shield
cb38d275d1 People were asking how to remove a print
I kept getting people asking how to remove prints because it "was filling up their f8 menu and looked like errors"

So I've made the "success" coreloader prints to be debugmode only.
2025-05-18 16:32:29 +01:00
Jim Shield
84cfe1c97d Fix stash stash opening for some inventories
Mainly older inventories may have had issues with custom stash sizes when opening them

This should fix compatibility with changing slots and max weight with them on trigger
2025-05-18 16:31:30 +01:00
Jim Shield
47b01df621 Remove coords from openGrabBox 2025-05-17 20:38:21 +01:00
Jim Shield
7e073b16ec Update starter.lua 2025-05-17 20:23:32 +01:00
Jim Shield
ab4298fa2d Change coreloader prints to a debugPrint 2025-05-17 19:19:15 +01:00
Jim Shield
7997ea45b7 change "Shared Load Detected" to a debugPrint 2025-05-17 19:16:24 +01:00
Jim Shield
7aa9d90d0d Improve ESX Job cache builder
When collecting Jobs from the server it tries it's best to determine which role is the "Boss"

It checks first of the Job Grade label containing the word "Boss"

If it can't find it, it then uses the highest number grade and adds `.isBoss = true` to it
2025-05-17 16:48:23 +01:00
oosayeroo
451e4cbbdb better documentation layout
- seperated the 2000+ lines in ReadMe.md into seperate files.
 - useful if things expand
 - better readability for users
 - more organised layout
2025-05-16 14:28:41 +01:00
Jim Shield
520758b355 Version Bump
`2.0.10` - `2.0.11`
2025-05-15 20:20:47 +01:00
Jim Shield
e4e9ef0fb2 add openGrabBox event for items like murderbag 2025-05-15 20:02:39 +01:00
Jim Shield
ada724069b Revert qs-inv server inv grab
Found the real issue that was causing the scripts to not think you had items, basically reverting this change as i didn't like having it in the first place
2025-05-15 19:59:51 +01:00
Jim Shield
9198068a19 Adjust hasItem function for QS-Inv
Apparently qs-inv reports two variables on the item data for how you have
`count` and `amount`
while other inventories appear to only have one of those

I'm assuming for compatibility sake

Apparently the `count` is updated differently, and `hasItem()` was checking for this first

I've swapped round the checks and appears to work better
2025-05-15 19:55:14 +01:00
Jim Shield
263d5160aa Add delay for checking for inventory items on qb
Custom inventories apparently load items separately/later into qbshared

so the script starts thinking there is definitely 0 items but its checking too early, this forces it to wait until its available to continue
2025-05-15 15:31:41 +01:00
Jim Shield
65137207ae add fallback for hasItem if no inventory is found 2025-05-14 23:38:34 +01:00
Jim Shield
3bd9935346 Skip item for sellmenu if item isn't in shared
Adds an item check before creating the sellmenu as to avoid errors
2025-05-14 23:36:46 +01:00
Jim Shield
23e2488cd1 fix typo for qs-inv callback for inventory 2025-05-14 16:09:02 +01:00
Jim Shield
e9bbdd4ce0 Version Bump
`2.0.09` - `2.0.10`
2025-05-13 22:02:37 +01:00
Jim Shield
f5a7ec9291 fix inventory exploit trigger blocking crafting
The inventory exploit check when crafting never released crafting so you couldn't craft again until reconnecting or script restart
2025-05-13 21:57:19 +01:00
Jim Shield
d80ea38a39 Fix sell menu throwing errors on openning
It was checking the wrong table name for amounts and getting `nil`
2025-05-13 21:56:18 +01:00
Jim Shield
e64912622b Testing a fix with QS-INV item checks
So..from what I can gather, QS-Inv doesn't seem to be updating the client cache of the players inventory correctly

This is all speculation, I can't check this myself

When `hasItem()` is used, its essentially reading from the client's cache.
This seemingly is only updated when there is a new item in a slot, **not** when the amount of an item in that slot changes

The only way to circumvent this is a workaround to the players inventory from the server side using a callback. This is painful to do because it can increase server load when checking multiples of items
2025-05-13 17:57:27 +01:00
Jim Shield
04b25e2c06 create waitForSharedLoad() function
This my attempt to aid ESX loading, essentially my scripts server side now need to be locked behind `onResourceStart()` because of ESX

Loading ESX into my scripts was delaying half of it and breaking the other half

This function forcibly waits until everythings loaded and then continues

This shouldn't affect other frameworks loading ability

My tests so far have been a success with it
2025-05-12 22:59:31 +01:00
64 changed files with 5119 additions and 2755 deletions

1720
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -20,35 +20,48 @@ end
function CheckBridgeVersion()
if IsDuplicityVersion() then
CreateThread(function()
Wait(4000)
local currentVersionRaw = GetResourceMetadata("jim_bridge", 'version')
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, newestVersionRaw, headers)
if not newestVersionRaw then
print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)")
return
end
CreateThread(function()
Wait(4000)
local currentVersionRaw = GetResourceMetadata("jim_bridge", 'version')
--PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/test.txt', function(err, body, headers)
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, body, headers)
if not body then
print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)")
return
end
newestVersionRaw = newestVersionRaw:match("[^\r\n]+")
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
local lines = {}
for line in body:gmatch("[^\r\n]+") do
table.insert(lines, line)
end
if compareResult == 0 then
print("^7'^3jim_bridge^7' - ^2You are running the latest version^7. ^7(^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
-- Made the check a bit more obvious
print("^1----------------------------------------------------------------------^7")
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function()
-- Do a naughty repeat message every 20 minutes until the the script is updated
CheckBridgeVersion()
end)
else
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ^3"..newestVersionRaw.."^7)")
end
end)
end)
end
local newestVersionRaw = lines[1] or "0.0.0"
local changelog = {}
for i = 2, #lines do
table.insert(changelog, lines[i])
end
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
if compareResult == 0 then
print("^7'^3jim_bridge^7' - ^2You are running the latest version^7. ^7(^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 ^3"..newestVersionRaw.."^7)")
for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line)
end
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function()
CheckBridgeVersion()
end)
else
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
end
end)
end)
end
end
CheckBridgeVersion()

View File

@@ -0,0 +1,58 @@
## Animal Ped Support
### isAnimal.lua
This module provides logic to detect if a Ped or model is an animal and classify it into specific categories (cat, dog, coyote, etc.). Useful for wildlife, animal roles, or pet systems.
❔At player load in, it attempts to get what kind of ped/animal you are but there are functions to double check in scripts
❔Alot of this is used to determine what models/animations are available
Global flags:
- `isCat`, `isDog`, `isBigDog`, `isSmallDog`, `isCoyote`, `isAnimal` — used to store classification of the player's current ped.
- **isPedAnimal(ped?)**
- Checks if a ped is an animal based on predefined animal models.
- Sets global `isAnimal` to true if matched.
- **Example:**
```lua
local isPlayerAnimal = isPedAnimal()
local isOtherPedAnimal = isPedAnimal(GetPedInVehicleSeat(vehicle, -1))
```
- **isCat(ped)**
- Returns true if the ped model matches a cat.
- **Example:**
```lua
if isCat() then print("You're a cat!") end
```
- **isDog(ped)**
- Returns two values:
- `true`, `true` — if ped is a big dog
- `true`, `false` — if ped is a small dog
- `false`, `nil` — if not a dog
- **Example:**
```lua
local isDog, isBig = isDog()
if isDog then print(isBig and "Big Dog" or "Small Dog") end
```
- **getAnimalModels()**
- Returns a flat list of all registered animal model hashes.
- Can be used to check ped models against
- **Example:**
```lua
for _, model in pairs(getAnimalModels()) do print(model) end
```
- **getAnimalAnims(ped)**
- Returns the animation set defined for the given animal model.
- **Example:**
```lua
local anims = getAnimalAnims(PlayerPedId())
if anims then playAnim(anims.sitDict, anims.sitAnim) end
```

View File

@@ -0,0 +1,177 @@
### helpers.lua
This utility module provides functions for resource checks, debugging, formatting, coordinate math, vector calculations, progress bars, and drawing tools.
- **isStarted(script)**
- Returns `true` if a resource is started.
- **Example:**
```lua
if isStarted("myResource") then print("Resource is running") end
```
- **getScript()**
- Caches and returns the name of the current resource.
- Easier than typing `GetCurrentResourceName()` over and over
- **Example:**
```lua
print("Current script:", getScript())
```
- **isServer()**
- Returns true if running on the server.
- This was mainly made becuase `IsDuplicityVersion()` kept confusing me
- **Example:**
```lua
if isServer() then print("Server-side!") end
```
- **debugPrint(...)** / **eventPrint(...)**
- Prints messages with debug context if debugMode is enabled.
- **Example:**
```lua
debugPrint("Loaded object:", objName)
```
- **jsonPrint(table)**
- Pretty-prints a table with colorized JSON if debugMode is enabled.
- **Example:**
```lua
jsonPrint(myData)
```
- **keyGen()**
- Generates a 3-character unique ID.
- Good grabbing randomly generated strings
- **Example:**
```lua
print("Generated Key:", keyGen())
```
- **cv(amount)**
- Comma-separates a number (e.g., `1000000` to `1,000,000`).
- **Example:**
```lua
print(cv(1000000)) -- "1,000,000"
```
- **formatCoord(vec)**
- Outputs a formatted string from vector types.
- Compacts and adds console colours to the vector to be printed
- **Example:**
```lua
print(formatCoord(vector3(123.45, 678.9, 10.0)))
```
- **getCenterOfZones(coords)**
- Returns average center position of a vector3 list.
- A few use cases, but I used it to see how well spaced blips were together
- **Example:**
```lua
local center = getCenterOfZones({vector3(0,0,0), vector3(10,10,0)})
```
- **countTable(tbl)**
- Returns the number of entries in a table.
- Simple function to print how many "entires" are in a table
- **Example:**
```lua
print(countTable({a=1,b=2,c=3})) -- 3
```
- **pairsByKeys(tbl)**
- Iterator for sorted keys.
- **Example:**
```lua
for k, v in pairsByKeys(myTable) do print(k, v) end
```
- **concatenateText(tbl)**
- Joins string table entries with newlines.
- **Example:**
```lua
print(concatenateText({"Line 1", "Line 2"}))
```
- **RotationToDirection(rot)**
- Converts a heading vector to a directional vector.
- **Example:**
```lua
local dir = RotationToDirection(rotation)
```
- **basicBar(percent)**
- Returns a bar like `████░░░░░` at 50%.
- Basically a progress bar but as a string, I use this in drawTexts when progressbars aren't able to be used
- **Example:**
```lua
print(basicBar(50))
```
- **normalizeVector(vec)**
- Returns a normalized version of a vector3.
- **Example:**
```lua
local norm = normalizeVector(vector3(3,4,0))
```
- **drawLine(start, end, color)** / **drawSphere(pos, color)**
- Debug drawing helpers.
- Stays visible for more than one frame
- **Example:**
```lua
drawLine(vector3(0,0,0), vector3(10,10,10), vector4(255,0,0,255))
drawSphere(vector3(5,5,5), vector4(0,255,0,255))
```
- **PerformRaycast(start, end, entity?, flags?)**
- Raycast with material detection. Returns ray hit data.
- **Example:**
```lua
local hit, hitPos, material = PerformRaycast(startVec, endVec, playerPed, 1)
if hit == 1 then
print("Hit at position:", hitPos)
print("Material:", material)
end
```
- **adjustForGround(coords)**
- Adjusts a z-coordinate to ground height.
- **Example:**
```lua
coords = adjustForGround(vector3(100, 200, 300))
```
- **ensureNetToVeh(id)** / **ensureNetToEnt(id)**
- Resolves net ID to entity safely.
- **Example:**
```lua
local veh = ensureNetToVeh(netId)
```
- **sendLog(text)** / **sendServerLog(data)**
- Logging helpers, includes player name, coords, script source.
- **Example:**
```lua
sendLog("Suspicious activity detected")
```
- **GetGroundMaterialAtPosition(coords)**
- Returns the material hash + readable name from the surface below coords.
- **Example:**
```lua
local hash, name = GetGroundMaterialAtPosition(vector3(0,0,0))
```
- **GetPropDimensions(model)**
- Loads a model and returns width, depth, height.
- I use this to parse a model to create a box target instead of entity target when creating distProps/distPeds
- **Example:**
```lua
local w,d,h = GetPropDimensions("prop_barrel_01a")
```
- **GetEntityForwardVector(entity)**
- Returns the forward direction vector based on entity heading.
- **Example:**
```lua
local fwd = GetEntityForwardVector(PlayerPedId())
```

View File

@@ -0,0 +1,89 @@
### loaders.lua
This module provides loading utilities for common asset types such as models, animations, texture dictionaries, and audio banks. It also provides animation and sound helpers.
- **loadModel(model)**
- Loads a model into memory if valid and not already loaded.
- **Example:**
```lua
loadModel('prop_chair_01a')
```
- **unloadModel(model)**
- Unloads a model from memory.
- **Example:**
```lua
unloadModel('prop_chair_01a')
```
- **loadAnimDict(animDict)**
- Loads an animation dictionary into memory.
- **Example:**
```lua
loadAnimDict('amb@world_human_hang_out_street@male_c@base')
```
- **unloadAnimDict(animDict)**
- Removes an animation dictionary from memory.
- **Example:**
```lua
unloadAnimDict('amb@world_human_hang_out_street@male_c@base')
```
- **loadPtfxDict(ptFxName)**
- Loads a particle effect (ptfx) dictionary.
- **Example:**
```lua
loadPtfxDict('core')
```
- **unloadPtfxDict(dict)**
- Unloads a particle effect dictionary from memory.
- **Example:**
```lua
unloadPtfxDict('core')
```
- **loadTextureDict(dict)**
- Loads a streamed texture dictionary.
- **Example:**
```lua
loadTextureDict('commonmenu')
```
- **loadScriptBank(bank)**
- Loads a script audio bank.
- Returns true on success.
- **Example:**
```lua
local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS')
```
- **loadAmbientBank(bank)**
- Loads an ambient audio bank.
- Returns true on success.
- **Example:**
```lua
local success = loadAmbientBank('AMB_REVERB_GENERIC')
```
- **playAnim(animDict, animName, duration?, flag?, ped?, speed?)**
- Plays an animation on a ped.
- Loads the dictionary if not already loaded.
- **Example:**
```lua
playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0)
```
- **stopAnim(animDict, animName, ped?)**
- Stops an animation and unloads the dictionary.
- **Example:**
```lua
stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
```
- **playGameSound(audioBank, soundSet, soundRef, coords, synced, range?)**
- Plays a game sound from a coordinate or entity.
- **Example:**
```lua
playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
```

View File

@@ -0,0 +1,38 @@
### callback.lua
These functions wrap the native callback handling of the selected framework (e.g., OX, QBCore, ESX) instead of implementing a standalone callback system, ensuring full compatibility.
- **createCallback(callbackName, funct)**
- Registers a callback function with the appropriate framework.
- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
- It adapts the callback function to match the expected signature for the framework.
- **Example:**
```lua
local table = { ["info"] = "HI" }
createCallback('myCallback', function(source, ...)
return table
end)
createCallback("callback:checkVehicleOwned", function(source, plate)
local result = isVehicleOwned(plate)
if result then
return true
else
return false
end
end)
```
- **triggerCallback(callbackName, ...)**
- Triggers a server callback and returns the result.
- This function uses the appropriate framework's method to call the server-side callback and awaits the result.
- **Example:**
```lua
local result = triggerCallback('myCallback')
jsonPrint(result)
local result = triggerCallback("callback:checkVehicleOwned", plate)
print(result)
```

View File

@@ -0,0 +1,28 @@
### cameras.lua
This module provides utilities for managing temporary in-game cameras, useful for cutscenes, cinematic views, or scripted perspectives.
- **createTempCam(ent, coords)**
- Creates a temporary camera at the specified coordinates or relative to an entity.
- If `ent` is an entity, the camera position is calculated as an offset from the entity's position using `GetOffsetFromEntityInWorldCoords`.
- If `ent` is a `vector3`, it is used directly as the camera's position.
- **Example:**
```lua
local cam = createTempCam(PlayerPedId(), GetEntityCoords(PlayerPedId()) + vector3(0, 2.0, 1.0))
```
- **startTempCam(cam)**
- Activates and renders the temporary camera.
- **Example:**
```lua
startTempCam(cam)
```
- **stopTempCam()**
- Deactivates and deletes all currently running custom camera, restoring normal view.
- **Example:**
```lua
stopTempCam()
```

View File

@@ -0,0 +1,45 @@
### contextmenus.lua
These functions provide a unified way to interact with different context menu systems, such as OX and WarMenu, depending on what's available on the server.
- **openMenu(Menu, data)**
- Opens a context menu using the preferred menu system.
- Automatically selects between supported systems like OX or WarMenu based on availability.
- The `Menu` parameter should be a list of menu entries, and the `data` parameter can be used to set headers, subtexts, and actions like `onBack`, `onExit`, and `canClose`.
- **Example:**
```lua
openMenu({
{ header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
{ header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
}, {
header = "Main Menu",
headertxt = "Select an option",
onBack = function() print("Return selected") end,
onExit = function() print("Menu closed") end,
canClose = true,
})
```
- **isOx()**
- Checks whether the OX context menu system is available on the server.
- Allows to do specific things if ox_lib menu is in use
- **Example:**
```lua
if isOx() then
print("OX Context Menu is available")
end
```
- **isWarMenuOpen()**
- Returns whether WarMenu is currently open.
- Useful to prevent opening a new menu if one is already active.
- **Example:**
```lua
if not isWarMenuOpen() then
openMenu("main_menu", menuData)
end
```
- Returns whether the WarMenu is currently open.

View File

@@ -0,0 +1,39 @@
### crafting.lua
These functions support crafting logic, such as opening crafting menus, handling multi-craft operations, and creating item data from recipes.
- **craftingMenu(data)**
- Opens a menu for selecting the quantity to craft.
- Presents the player with multiple crafting quantities based on `Config.Crafting.MultiCraftAmounts`.
- **Parameters:**
- `item` (`string`): The item to craft.
- `craft` (`table`): The crafting recipe.
- `craftable` (`table`): Crafting options.
- `coords` (`vector3`): Where crafting occurs.
- `stashName` (`string`): The stash name(s) for item availability.
- `onBack` (`function`): Callback when returning.
- `metadata` (`table`, optional): Metadata for the crafted item.
- **Example:**
```lua
craftingMenu({
craftable = {
Header = "Weapon Crafting",
Recipes = {
[1] = {
["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
amount = 1,
},
-- More recipes...
},
Anims = {
animDict = "amb@prop_human_parking_meter@male@idle_a",
anim = "idle_a",
},
},
coords = vector3(100.0, 200.0, 300.0),
stashTable = "crafting_stash",
job = "mechanic",
onBack = function() print("Returning to previous menu") end,
})
```

View File

@@ -0,0 +1,25 @@
### drawText.lua
These functions handle drawing and hiding styled on-screen prompts or UI overlays, compatible with different styling frameworks such as OX Lib.
- **drawText(image, input, style, oxStyleTable)**
- Displays styled text or prompts on screen.
- Designed to adapt styling depending on which UI system (e.g., OX) is in use.
- **Parameters:**
- `image` (`string`): Optional icon or image path.
- `input` (`string`): The main text to display.
- `style` (`string|table`): Preset or custom styling.
- `oxStyleTable` (`table`, optional): Extended style options if using OX UI.
- **Example:**
```lua
drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
```
- **hideText()**
- Hides any active text or UI element previously drawn with `drawText()`.
- **Example:**
```lua
hideText()
```

View File

@@ -0,0 +1,31 @@
### duifunctions.lua
These functions support dynamic UI image rendering in 3D environments using runtime texture dictionaries. Ideal for integrating `nui://` or external `http://` image URLs into MLOs or world props.
- **createDui(name, http, size, txd))**
- Sets up a runtime texture dictionary and links an image from a `nui://` or `http://` URL.
- This function should be used once to create the texture dictionary needed for rendering Dui images.
- **Example:**
```lua
createDui("logo", "https://example.com/logo.png", { x = 512, y = 256 }, scriptTxd)
```
- **DuiSelect(data)**
- Updates the URL on an existing texture dictionary to change the rendered image.
- Can be used at runtime to swap out visuals in MLOs or props.
- **Parameters:**
- `textureDict` (`string`): The texture dictionary to target.
- `texture` (`string`): The specific texture name to override.
- `url` (`string`): The new image URL.
- `width`, `height` (`number`): The texture resolution.
- **Example:**
```lua
DuiSelect({
name = "logo",
texn = "logoTex",
texd = "someTxd",
size = { x = 512, y = 256 }
})
```

View File

@@ -0,0 +1,25 @@
### input.lua
This function displays a customizable input dialog for user text or number entry. It supports both simple and complex input structures.
- **createInput(title, opts)**
- Opens a styled input dialog with configurable fields, labels, input types, and validation.
- Supports multiple field types including `text`, `number`, `password`, `checkbox`, `color`, `slider`, and more.
- **Parameters:**
- `title` (`string`): Title displayed at the top of the input box.
- `opts` (`table`): A table of fields with attributes like `label`, `name`, `type`, `value`, and more.
- **Example:**
```lua
local userInput = createInput("Enter Details", {
{ type = "text", text = "Name", name = "playerName", isRequired = true },
{ type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
{ type = "radio", label = "Gender", name = "playerGender", options = {
{ text = "Male", value = "male" },
{ text = "Female", value = "female" },
{ text = "Other", value = "other" },
}},
})
if userInput then
print(json.encode(userInput))
end
```

View File

@@ -0,0 +1,144 @@
### inventories.lua
These functions manage inventory locking, item checking, and player inventory retrieval.
- **lockInv(toggle)**
- Locks or unlocks the player's inventory.
- Freezes/unfreezes movement and toggles hotbar state.
- **Example:**
```lua
lockInv(true) -- Lock inventory
lockInv(false) -- Unlock inventory
```
- **hasItem(items, amount, src)**
- Checks if a player has the specified item(s) in sufficient quantity.
- **Returns:**
- `boolean`: Whether the player has the item(s).
- `table`: Details of available item counts.
- **Example:**
```lua
local hasAll, details = hasItem({"health_potion", "mana_potion"}, 2, playerId)
if hasAll then
-- Proceed with action
else
-- Inform the player about missing items
end
```
- **getPlayerInv(src)**
- Retrieves the players current inventory.
- May be used for inventory UI, logging, or crafting systems.
- **Example:**
```lua
local inventory = getPlayerInv(playerId)
for k, item in pairs(inventory) do
print(item.name, item.amount)
end
```
### itemcontrol.lua
This module includes a variety of utility functions for managing items in player inventories, including giving, removing, durability, and use logic.
- **createUseableItem(item, funct)**
⚠️ This doesn't work for `ox_inv`, you will need to add the event info to it's `items.lua`
- Registers a useable item and binds it to a callback function.
- **Example:**
```lua
createUseableItem("bandage", function(source)
-- Heal logic here
end)
```
- **invImg(item)**
- Returns the inventory image path for an item.
- Automatically grabs the inventory image link based on inventory script
- **Example:**
```lua
local imagePath = invImg("water_bottle")
print(imagePath)
```
- **addItem(item, amount, info, src)**
⚠️ Requires Auth callback if called from client side
- Adds a specific amount of an item to a player's inventory.
- Automatically attempts to add items using detected inventory script
- **Example:**
```lua
-- Client Side
addItem("lockpick", 3, {})
-- Server Side
addItem("lockpick", 3, {}, source)
```
- **removeItem(item, amount, src, slot)**
- Removes a specific amount of an item from a players inventory.
- Automatically attempts to remove item using detected inventory script
- **Example:**
```lua
-- Client Side
removeItem(
"ammo_pistol",
30,
nil,
slot --[[optional]]--
)
-- Server Side
removeItem(
"ammo_pistol",
30,
source,
slot --[[optional]]--
)
```
- **dupeWarn(src, item, amount)**
- Logs or handles a potential duplication exploit involving an item.
- This is called automatically if a player has been triggered exploit detection
- Disabled if debugMode is enabled
- **breakTool(data)**
- Breaks a tool or item, often due to exceeding durability.
- **Example:**
```lua
breakTool({ item = "drill", damage = 10 })
```
- **getDurability(item)**
- Returns the current durability value of a given item.
- Searched for the lowest slot number (eg. slot 1) and retreives that items durability
- **Example:**
```lua
local durability, slot = getDurability("drill")
if durability then
print("Durability:", durability)
print("Slot:", slot)
end
```
- **canCarry(itemTable, src)**
⚠️ Currently server side only
- Checks if a player can carry the item(s) specified in `itemTable`.
- **Example:**
```lua
local carryCheck = canCarry({ ["health_potion"] = 2, ["mana_potion"] = 3 }, playerId)
if carryCheck["health_potion"] and carryCheck["mana_potion"] then
-- Player can carry items.
else
-- Notify player.
end
```

View File

@@ -0,0 +1,64 @@
### jobfunctions.lua
This module provides functions for managing job-based logic, such as checking player roles, toggling duty status, and simulating job-related interactions.
- **makeBossRoles(role)**
- Sets up boss-level permissions or access for the specified job role.
- Often used to determine if a player can access job menus or perform administrative actions.
- Used mainly for creating boss locked target tables
- **Example:**
```lua
makeBossRoles("police")
```
- **jobCheck(job)**
- Simple check if the player has the specified job.
- **Example:**
```lua
if jobCheck("mechanic") then
-- Allow mechanic features.
else
-- Deny access.
end
```
- **toggleDuty()**
- Toggles the players on/off duty status, typically used for jobs like police, EMS, etc.
- **Example:**
```lua
toggleDuty()
```
- **washHands(data)**
- Simulates the action of washing hands, used in job scripts or medical job logic.
- Currently only animation and a notfication
- **Example:**
```lua
washHands({ coords = vector3(200.0, 300.0, 40.0) })
```
- **useToilet(data)**
- Triggers toilet-use interaction, likely includes animation or sound.
- Currently only animation and a notfication
- **Example:**
```lua
useToilet({ urinal = true })
-- Player uses a urinal with corresponding animations and notifications
useToilet({ urinal = false, sitcoords = vector4(215.76, -810.12, 29.73, 90.0) })
-- Player sits down to use a toilet with corresponding animations and notifications
```
- **useDoor(data)**
- Handles interactions with doors that aren't openable and teleports the player
- eg. Recycle Center, it's used to teleport the player from outside a building to the IPL
- **Example:**
```lua
useDoor({ telecoords = vector4(215.76, -810.12, 29.73, 90.0) })
```

View File

@@ -0,0 +1,156 @@
### makeBlip.lua
This module provides simple utilities for adding static or entity-based blips to the minimap.
❔ This has basic support for RedM too
- **makeBlip(data)**
- This function adds a map blip at the provided coordinates and sets various display properties such as sprite, color, scale, and more.
- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
- **Example:**
```lua
local blipData = {
coords = vector3(123.4, 567.8, 90.1),
sprite = 1,
col = 2,
scale = 0.8,
disp = 4,
category = 7,
name = "My Blip",
preview = "http://example.com/preview.png"
}
local blip = makeBlip(blipData)
```
- **makeEntityBlip(data)**
- This function adds a map blip attached to the provided entity and sets various display properties such as sprite, color, scale, and more.
- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
- **Example:**
```lua
local blipData = {
entity = myEntity,
sprite = 1,
col = 2,
scale = 0.8,
disp = 4,
category = 7,
name = "Entity Blip",
preview = "http://example.com/preview.png"
}
local blip = makeEntityBlip(blipData)
```
### makePed.lua
This module provides tools to create persistent or distance-based NPCs with optional animations, scenarios, and config randomization.
- **makeDistPed(data, coords, freeze, collision, scenario, anim, synced)**
- Creates a ped that only spawns when nearby (performance optimization).
- **Example:**
```lua
makeDistPed({model = "a_m_y_business_03" }, -- model data
vector4(450.0, -980.0, 30.0, 100.0), -- coords
true, -- freeze entity
false, -- collision
'WORLD_HUMAN_STAND_IMPATIENT', -- Scenario Animation
{ dict = "amb@world_human_clipboard@male@idle_a", anim = "idle_c" }, -- Anim Table
true -- Network Synced
)
```
- **makePed(data, coords, freeze, collision, scenario, anim, synced)**
- Spawns a ped with more persistent behavior at the given location.
- Supports animation playback and freezing.
- **Example:**
```lua
makePed({model = "a_m_y_business_03" }, -- model data
vector4(450.0, -980.0, 30.0, 100.0), -- coords
true, -- freeze entity
false, -- collision
'WORLD_HUMAN_STAND_IMPATIENT', -- Scenario Animation
{ dict = "amb@world_human_clipboard@male@idle_a", anim = "idle_c" }, -- Anim Table
true -- Network Synced
)
```
- **GenerateRandomPedData(data)**
- Returns randomized ped model/configuration based on input table.
- Useful for dynamic NPC generation.
- **Example:**
```lua
local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} })
```
### makeProp.lua
This module allows you to spawn static or distance-loaded props in the world.
- **makeProp(data, freeze, synced)**
- This function loads the model, creates the object, sets its heading, and freezes it if specified.
- **Example:**
```lua
local propData = {
prop = 'prop_chair_01a',
coords = vector4(123.4, 567.8, 90.1, 180.0)
}
local prop = makeProp(propData, true, false)
```
- **makeDistProp(data, freeze, synced, range)**
- Same as `makeProp`, but only spawns if the player is within the specified range.
- **Example:**
```lua
local propData = {
prop = 'prop_chair_01a',
coords = vector4(123.4, 567.8, 90.1, 180.0)
}
makeDistProp(propData, true, false)
```
- **destroyProp(entity)**
- Removes a previously created or targeted prop from the world.
- **Example:**
```lua
destroyProp(barrel)
```
### makeVeh.lua
This module provides functionality for spawning vehicles, including distance-based optimization and entity management.
- **makeVeh(model, coords)**
- This function loads the vehicle model, creates the vehicle in the world at the given coordinates, sets initial properties, and returns the vehicle handle.
- **Example:**
```lua
local vehicle = makeVeh("adder", vector3(250.0, -1000.0, 30.0))
```
- **makeDistVehicle(data, radius, onEnter, onExit)**
- Creates a vehicle that spawns when the player enters a designated polyzone area.
- This is used for `jim-parking` to create a static vehicle that can't move
- **Example:**
```lua
makeDistVehicle({
model = "blista",
coords = vector3(400.0, -800.0, 30.0),
heading = 180.0
}, 50.0)
```
- **removeDistVehicleZone(zoneId)**
- Removes a previously created distance-based vehicle zone.
- **Example:**
```lua
removeDistVehicleZone("garage_zone_1")
```
- **deleteVehicle(vehicle)**
- Deletes a specified vehicle entity.
- **Example:**
```lua
deleteVehicle(vehicle)
```

View File

@@ -0,0 +1,32 @@
### metaHandlers.lua
This module provides access and control over player metadata, which is useful for storing temporary or persistent player-specific values like stats, states, or tags.
- **GetPlayer(source)**
⚠️ Server side only
- Retrieves the player object (as defined by the framework in use) from the given `source` ID.
- **Example:**
```lua
local player = GetPlayer(playerId)
```
- **GetMetadata(player, key)**
- Retrieves the value of a metadata field from the given player.
- If called client-side (player is nil), it triggers a server callback to retrieve metadata.
- **Example:**
```lua
local stress = GetMetadata(player, "stress")
print("Player stress level:", stress)
```
- **SetMetadata(player, key, value)**
⚠️ Server side only
- 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.
- **Example:**
```lua
SetMetadata(player, "stress", 0)
```

View File

@@ -0,0 +1,21 @@
### notify.lua
This module provides a unified interface to trigger styled notifications across supported frameworks.
- **triggerNotify(title, message, type, src)**
- Sends a notification to a player (or to the entire client if `src` is `nil`).
- The notification style is determined by `type` (e.g., `success`, `error`, `info`).
- **Parameters:**
- `title` (`string`): The title or header of the notification.
- `message` (`string`): The body or detail text.
- `type` (`string`): Type of message (e.g., "success", "error", "info").
- `src` (`number`, optional): Server ID of the player to notify (omit to notify locally).
- **Example:**
```lua
-- Client-side usage without specifying a player (shows to the current player)
triggerNotify("Success", "You have completed the task!", "success")
-- Server-side usage specifying a player by their server ID
triggerNotify("Alert", "You have been warned for misconduct.", "error", source)
```

View File

@@ -0,0 +1,21 @@
### phones.lua
This module allows you to send in-game mail/messages to player phones, depending on the phone system integrated (e.g., QB, NPWD).
- **sendPhoneMail(data)**
- Sends a mail message to a player's in-game phone app.
- Typically used to notify players about deliveries, missions, or reminders.
- **Parameters:**
- `data` (`table`): Must contain phone/mail system-compatible fields like `sender`, `subject`, `message`, and `receiver`.
- **Example:**
```lua
sendPhoneMail({
subject = "Welcome!",
sender = "Admin",
message = "Thank you for joining our server.",
actions = {
{ label = "Reply", action = replyFunction }
}
})
```

View File

@@ -0,0 +1,115 @@
### playerfunctions.lua
This module contains helper functions for manipulating player states, interactions, and utility checks.
- **instantLookEnt(ent, ent2)**
- Instantly turns an entity to face a target (entity or coordinates) without animation.
- **Example:**
```lua
instantLookEnt(nil, vector3(200.0, 300.0, 40.0))
instantLookEnt(ped1, ped2)
```
- **lookEnt(entity)**
- Makes the current player look toward the given entity.
- Usually called after when opening a menu or something similar to make the player visually face the location
- **Example:**
```lua
lookEnt(vector3(200.0, 300.0, 40.0))
lookEnt(pedEntity)
```
- **setThirst(src, thirst)**
⚠️ Server Side Only
- Sets the thirst level of a player.
- **Example:**
```lua
setThirst(source, 75)
```
- **setHunger(src, hunger)**
⚠️ Server Side Only
- Sets the hunger level of a player.
- **Example:**
```lua
setHunger(source, 50)
```
- **chargePlayer(cost, moneyType, newsrc)**
⚠️ Server Side Only
- Deducts money from a player of the specified type (`cash`, `bank`, etc).
- **Example:**
```lua
chargePlayer(100, "cash", source)
```
- **fundPlayer(fund, moneyType, newsrc)**
⚠️ Server Side Only
- Adds money to a player's balance of a given type.
- **Example:**
```lua
fundPlayer(250, "bank", source)
```
- **ConsumeSuccess(itemName, type, data)**
- Handles logic when an item is successfully consumed (e.g., food, drink, etc).
- Supports hunger and thirst info directly from table eg. `{ hunger = 10, thirst = 20 }`
- **Example:**
```lua
ConsumeSuccess("health_pack", "food", { hunger = 10 })
ConsumeSuccess("beer", "alcohol", { thirst = 20 })
```
- **hasJob(job, source, grade)**
- Checks if a player has a certain job and optionally checks for a specific grade.
- Similar to `jobCheck()` but also retrieves as much player job info as possible
- **Example:**
```lua
-- Check if the player has the 'police' job and is on duty
local hasPoliceJob, isOnDuty = hasJob("police")
if hasPoliceJob and isOnDuty then
-- Grant access to police-specific features
end
-- Check if a specific player has the 'gang_leader' job with at least grade 2
local hasGangLeaderJob, _ = hasJob("gang_leader", playerId, 2)
if hasGangLeaderJob then
-- Allow gang leader actions
end
```
- **getPlayer(source)**
- Retrieves basic player information (name, cash, bank, job, etc.) based on the active core/inventory system.
- Can be called server-side (passing a player source) or client-side (for current player).
- Called often in my scripts as its makes use of frameworks "GetPlayerData" etc.
- **Example:**
```lua
-- Get information for a specific player
local playerInfo = getPlayer(playerId)
print(playerInfo.name, playerInfo.cash, playerInfo.bank)
-- Get information for the current player (client-side)
local myInfo = getPlayer()
print(myInfo.name, myInfo.cash, myInfo.bank)
```
- **GetPlayersFromCoords(coords, radius)**
- Returns a list of players within a specified radius of a set of coordinates.
- **Example:**
```lua
local nearby = GetPlayersFromCoords(GetEntityCoords(PlayerPedId()), 10.0)
for _, playerId in pairs(nearby) do
print("Nearby player ID:", playerId)
end
```

View File

@@ -0,0 +1,44 @@
### polyZone.lua
This module provides helpers for creating and removing polygon or circular zones using PolyZone-compatible data structures.
- **createPoly(data)**
- Creates a polygonal zone using the detected polyzone library (ox_lib or PolyZone).
- Automatically checks which polyzone script is active. When using ox_lib, it converts the provided 2D points to 3D (setting a constant z value) and sets a thickness value.
- For PolyZone, it creates the zone and attaches onEnter and onExit callbacks for ease of use.
- **Example:**
```lua
createPoly({
name = 'testZone',
debug = true,
points = { vec2(100.0, 100.0), vec2(200.0, 100.0), vec2(200.0, 200.0), vec2(100.0, 200.0) },
onEnter = function() print("Entered Test Zone") end,
onExit = function() print("Exited Test Zone") end,
})
```
- **createCirclePoly(data)**
- When using ox_lib, it creates a sphere zone. For PolyZone, it creates a CircleZone and attaches onEnter and onExit callbacks.
- **Example:**
```lua
createCirclePoly({
name = 'circleZone',
coords = vector3(150.0, 150.0, 20.0),
radius = 50.0,
onEnter = function() print("Entered Circle Zone") end,
onExit = function() print("Exited Circle Zone") end,
})
```
- **removePolyZone(Location)**
- Removes a previously created polygon or circle zone by name.
- Detects the active polyzone library and calls the appropriate removal method.
- **Example:**
```lua
local zone = createPoly({...})
---
removePolyZone(zone)
```

View File

@@ -0,0 +1,23 @@
### progressBars.lua
This module provides a unified progress bar system compatible with various UI frameworks.
❔It also contains an experimental "Shared Progressbar" system which was created for things like "giving an item to another player"
- **progressBar(data)**
- Displays a progress bar using the current configured system (e.g., ox_lib, qb, etc).
- **Example:**
```lua
local success = progressBar({
label = "Processing...",
time = 5000,
dict = "amb@world_human_hang_out_street@female_hold_arm@base",
anim = "base",
flag = 49,
cancel = true,
})
if success then
print("Success!")
else
print("Cancelled")
end
```

View File

@@ -0,0 +1,24 @@
### scaleEntity.lua
This utility provides a simple interface to scale an entity (ped, object, vehicle) in the world.
⚠️ **Important:** Unless the model's collision is removed or modified, the scale will reset when interacted with by other entities (e.g., walking into it, driving over it).
- **scaleEntity(entity, scale)**
- Scales the specified entity by the given factor.
- **Parameters:**
- `entity` (`number`): Entity handle (ped, object, vehicle).
- `scale` (`number`): Scale factor (e.g., `1.0` is normal size, `0.5` is half size).
- **Example:**
```lua
scaleEntity(PlayerPedId(), 0.8) -- Shrinks player slightly
```
- **resetScale(entity)**
- Resets the entity's scale back to its original default.
- **Example:**
```lua
resetScale(PlayerPedId()) -- Return to default size
```

View File

@@ -0,0 +1,63 @@
### shops.lua
This module provides functions to open, sell to, and register in-game shops and markets.
- **sellMenu(data)**
- Opens a UI menu for selling items to a vendor or market system.
- A simple system for the ability to sell all of an item in a players inventory
- Good for money making eg. pawn shops
- **Example:**
```lua
sellMenu({
sellTable = {
Header = "Sell Items",
Items = {
["gold_ring"] = 100,
["diamond"] = 500,
},
},
ped = pedEntity,
onBack = function() print("Returning to previous menu") end,
})
```
- **sellAnim(data)**
- Plays an animation during the selling process for immersion.
- Used in sellMenu, but can be called externally if needed
- **Example:**
```lua
sellAnim({
item = "gold_ring",
price = 100,
ped = pedEntity,
onBack = function() print("Sold Items") end,
)
```
- **openShop(data)**
- Opens a shop interface for purchasing items.
- Checks job/gang restrictions, then uses the active inventory system to open the shop.
- **Example:**
```lua
openShop({
shop = "weapon_shop",
items = weaponShopItems,
coords = vector3(100.0, 200.0, 300.0),
job = "police",
})
```
- **registerShop(name, label, items, society)**
- Registers a named shop with associated items and an optional society for fund handling.
- Supports either OXInv or QBInv (with QBInvNew flag).
- **Example:**
```lua
registerShop("fishing_shop", "Bait & Tackle", {
{ item = "fishing_rod", price = 50 },
{ item = "bait", price = 5 }
}, "fishing_society")
```

View File

@@ -0,0 +1,16 @@
### skillcheck.lua
This module provides a UI-based skill check system, useful for crafting, hacking, or other interactive gameplay scenarios.
- **skillCheck()**
- Starts a skill check minigame sequence using the provided configuration.
- Configuration may define speed, difficulty, bar size, or success zones.
- **Example:**
```lua
if skillCheck() then
print("Success!")
else
print("Failed :(")
end
```

View File

@@ -0,0 +1,28 @@
### societybank.lua
This module handles financial operations for society accounts, typically used for jobs or organizations.
- **getSocietyAccount(society)**
- Retrieves the current balance for the specified society.
- **Example:**
```lua
local balance = getSocietyAccount("police")
print("Police account balance: $"..balance)
```
- **chargeSociety(society, amount)**
- Deducts a specified amount from the society's account.
- **Example:**
```lua
chargeSociety("police", 500)
```
- **fundSociety(society, amount)**
- Adds funds to a societys account.
- **Example:**
```lua
fundSociety("ambulance", 1200)
```

View File

@@ -0,0 +1,44 @@
### stashcontrol.lua
This module handles logic for interacting with stashes—shared inventories for crafting, jobs, or storage systems.
- **checkStashItem(stashes, itemTable)**
- Retrieves (or updates) a local stash cache entry with a timeout.
- **Example:**
```lua
local found, foundinStash = checkStashItem({"crafting_stash"}, { item = "steel", amount = 2 })
if found then print("Found item in "..foundInStash) end
```
- **openStash(data)**
- Opens a stash using the active inventory system.
- Checks for job or gang restrictions before opening the stash.
- **Example:**
```lua
openStash({
stash = "playerStash",
label = "Player Stash",
coords = vector3(100, 200, 30)
})
```
- **getStash(stashName)**
- Retrieves the stash data by name (usually used for querying contents).
- **Example:**
```lua
local items = getStash("playerStash")
for k, v in pairs(items) do
print(k)
end
```
- **stashRemoveItem(stashItems, stashName, items)**
- Removes specified items from a stash. Used during crafting or transfers.
- **Example:**
```lua
stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 })
```

View File

@@ -0,0 +1,108 @@
### targets.lua
⚠️ This module provides utility functions for adding and removing interaction targets with entities, models, zones, and coordinates. Supports common targeting frameworks like `ox_target`, `qb-target`, and more.
- **createEntityTarget(entity, opts, dist)**
- Adds interaction targets to a specific in-world entity.
- **Example:**
```lua
createEntityTarget(entityId, {
{
action = function()
openStorage()
end,
icon = "fas fa-box",
job = "police",
label = "Open Storage",
},
}, 2.0)
```
- **createBoxTarget(data, opts, dist)**
- Creates an interactable box zone with configurable options.
- **Example:**
```lua
createBoxTarget(
{
'storageBox',
vector3(100.0, 200.0, 30.0),
2.0,
2.0,
{
name = 'storageBox',
heading = 100.0,
debugPoly = true,
minZ = 27.0,
maxZ = 32.0,
},
},
{
{
action = function()
openStorage()
end,
icon = "fas fa-box",
job = "police",
label = "Open Storage",
},
}, 2.0)
```
- **createCircleTarget(data, opts, dist)**
- Creates an interactable circular zone.
- **Example:**
```lua
createCircleTarget({
name = 'centralPark',
coords = vector3(200.0, 300.0, 40.0),
radius = 50.0,
options = { debugPoly = false }
}, {
{ icon = "fas fa-tree", label = "Relax", action = relaxAction }
}, 2.0)
```
- **createModelTarget(models, opts, dist)**
- Adds interactions to all matching models globally.
- **Example:**
```lua
createModelTarget({ model1, model2 },
{
{
action = function()
openStorage()
end,
icon = "fas fa-box",
job = "police",
label = "Open Storage",
},
}, 2.0)
```
- **removeEntityTarget(entity)**
- Removes all targets linked to the specified entity.
- **Example:**
```lua
removeEntityTarget(vehicle)
```
- **removeZoneTarget(target)**
- Removes a named zone-based target.
- **Example:**
```lua
removeZoneTarget("shop_box")
```
- **removeModelTarget(model)**
- Removes interactions tied to a model globally.
- **Example:**
```lua
removeModelTarget("prop_vend_soda")
```

View File

@@ -0,0 +1,63 @@
### vehicles.lua
This module provides utilities for reading, modifying, and interacting with vehicle properties and positioning.
- **searchCar(vehicle)**
- Searches the 'Vehicles' table for a specific vehicle's details.
- If the vehicle differs from the last searched, it retrieves its model and updates the carInfo table.
- The table includes the vehicle's name, price, and class information.
- **Example:**
```lua
local info = searchCar(vehicleEntity)
print(info.name, info.price, info.class.name, info.class.index)
```
- **getVehicleProperties(vehicle)**
- Retrieves the properties of a given vehicle using the active framework.
- **Example:**
```lua
local props = getVehicleProperties(vehicle)
if props then
print(json.encode(props))
end
```
- **setVehicleProperties(vehicle, props)**
- Sets the properties of a given vehicle if changes are detected.
- It compares the current properties with the new ones and applies the update using the active framework.
- **Example:**
```lua
setVehicleProperties(vehicle, props)
```
- **checkDifferences(vehicle, newProps)**
- Checks for differences between the current and new vehicle properties.
- Compares properties using JSON encoding for deep comparison and logs differences.
- **Example:**
```lua
if checkDifferences(vehicleEntity, newProperties) then
setVehicleProperties(vehicleEntity, newProperties)
end
```
- **pushVehicle(entity)**
- This function ensures that the vehicle is controlled by the current player and is set as a mission entity.
- It requests network control and sets the vehicle accordingly to synchronize changes across clients.
- **Example:**
```lua
pushVehicle(vehicle)
```
- **getClosestVehicle(coords, src)**
- Finds the closest vehicle to the specified coordinates.
- The function uses different APIs based on whether a source is provided.
- **Example:**
```lua
local closestVeh, distance = getClosestVehicle({ x = 100, y = 200, z = 30 }, source)
```

View File

@@ -0,0 +1,37 @@
### wrapperfunctions.lua
Provides wrapper compatibility functions for command and inventory stash systems across different frameworks (OX, QB, ESX, QS, etc).
- **registerCommand(command, options)**
⚠️ Server Side Only
- Registers a command using the appropriate framework's API.
- **Parameters:**
- `command`: Command name (string)
- `options`: Table including help, params, callback, autocomplete, restrictedGroup
- **Example:**
```lua
registerCommand("greet", {
"Greets the player",
{ name = "name", help = "Name of the player to greet" },
function(source, args) print("Hello, "..args[1].."!") end,
nil,
"admin"
})
```
- **registerStash(name, label, slots?, weight?, owner?, coords?)**
⚠️ Server Side Only
- Registers a stash using OX, QS, or Origen inventory systems.
- **Example:**
```lua
registerStash(
"playerStash",
"Player Stash",
100,
8000000,
"player123",
{ x = 100.0, y = 200.0, z = 30.0 }
)
```

129
documentation/scaleforms.md Normal file
View File

@@ -0,0 +1,129 @@
## Scaleforms
### debugScaleform.lua
This module renders debug text in-game when `debugMode` is enabled. Useful for live diagnostics or UI placement feedback.
- **debugScaleForm(textTable, loc)**
- Displays an overlay of lines of text at the specified screen location.
- **Note:** This only works if `debugMode` is set to `true`.
- **Parameters:**
- `textTable` (`table`): A list of strings to display.
- `loc` (`vector2`, optional): Top-left anchor point on screen (default: `vec2(0.05, 0.65)`).
- **Example:**
```lua
CreateThread(function()
while true do
debugScaleForm({
"Line 1: Debug info",
"Line 2: More info"
})
Wait(0)
end
end)
```
### instructionalButtons.lua
This module renders instructional button prompts using native scaleforms in GTA V and RedM.
⚠️ **Note:** Because it uses native scaleforms, it must be run inside a `while` loop to remain visible.
- **makeInstructionalButtons(info)**
- Draws instructional buttons using the GTA scaleform `instructional_buttons`.
- **Parameters:**
- `info` (`table`): An array of tables, where each entry contains:
- `keys` (`table`): Control key codes (e.g., `{38, 29}`).
- `text` (`string`): The label for the button.
- **Example:**
```lua
CreateThread(function()
while true do
makeInstructionalButtons({
{ keys = {38, 29}, text = "Open Menu" },
{ keys = {45}, text = "Close Menu" }
})
Wait(0)
end
end)
```
- **makeRedInstructionalButtons(info, title)**
- Experimental: Displays prompts using RedM-style `PromptSetGroup` API.
- **Note:** Still must be run in a loop for visibility.
- **Example:**
```lua
CreateThread(function()
while true do
makeRedInstructionalButtons({
{ keys = {0x760A9C6F}, text = "Mount Horse" },
{ keys = {0x4CC0E2FE}, text = "Dismount" }
}, "Horse Controls")
Wait(0)
end
end)
```
### scaleform_basic.lua
This module includes helper functions for 3D text rendering and UI overlays using basic native scaleform techniques.
- **DrawText3D(coord, text, highlight)**
- Draws 3D text in the world at the given coordinates, with optional highlight.
- Includes a semi-transparent black background box for visibility.
- **Parameters:**
- `coord` (`vector3`): World position to draw text.
- `text` (`string`): Text content.
- `highlight` (`boolean`, optional): Highlights `~w~` sections with yellow.
- **Example:**
```lua
CreateThread(function()
while true do
DrawText3D(vector3(100, 200, 300), "Hello World", true)
Wait(0)
end
end)
```
- **DisplayHelpMsg(text)**
- Shows a help message in the top-left corner of the screen.
- **Example:**
```lua
DisplayHelpMsg("Press E to interact")
```
- **displaySpinner(text)**
- Shows a busy spinner with a message (e.g., "Saving...").
- **Example:**
```lua
displaySpinner("Saving data...")
```
- **stopSpinner()**
- Hides any active busy spinner (client-only).
- **Example:**
```lua
stopSpinner()
```
### timerBars.lua
This module provides a native scaleform-based timer bar HUD. Ideal for displaying tasks, loading indicators, or time-sensitive objectives.
- **createTimerHud(title, data, alpha)**
⚠️ This was an attempt to recreate the gta native shooting ranges )fairly specific use case)
- Draws a timer bar with custom label and right-aligned values using the native GTA HUD system.
- **Parameters:**
- `title` (`string`): Title/header displayed on the bar.
- `data` (`table`): List of `label = value` entries to display (max 4 rows).
- `alpha` (`number`, optional): Opacity of the bar (0-255).
- **Example:**
```lua
CreateThread(function()
while true do
createTimerHud("Timer Bar", {
{ stat = "Health", value = "85%" },
{ stat = "Armor", value = "50%", multi = 2 },
{ stat = "Stamina", value = "100%" },
}, 180)
Wait(0)
end
end)
```

331
frameworkCache.lua Normal file
View File

@@ -0,0 +1,331 @@
--[[
Cached Resource Initialization Module
--------------------------------------
Initializes shared resource data (Items, Vehicles, Jobs, Gangs) once and caches it
to improve performance and reduce redundant data fetching across scripts.
]]
-- Define resource names used in different frameworks
local Exports = {
QBExport = "qb-core",
QBXExport = "qbx_core",
ESXExport = "es_extended",
OXCoreExport = "ox_core",
OXInv = "ox_inventory",
QBInv = "qb-inventory",
PSInv = "ps-inventory",
QSInv = "qs-inventory",
CoreInv = "core_inventory",
CodeMInv = "codem-inventory",
OrigenInv = "origen_inventory",
TgiannInv = "tgiann-inventory",
JPRInv = "jpr-inventory",
OXLibExport = "ox_lib",
QBMenuExport = "qb-menu",
QBTargetExport = "qb-target",
OXTargetExport = "ox_target",
-- REDM
RSGExport = "rsg-core",
RSGInv = "rsg-inventory"
}
-- Prevent reloading if cache is already initialized
if _G.__jimBridgeDataCache then return end
_G.__jimBridgeDataCache = {}
local cache = _G.__jimBridgeDataCache
-- 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")
end
-- Ensure oxmysql resource is loaded
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua')))
fileLoader()
if checkExists(Exports.OXCoreExport) then
-- Detected OX_Core in server, wait for it to be started if needed
while GetResourceState(Exports.OXCoreExport) ~= "started" do Wait(100) end
local fileLoader = assert(load(LoadResourceFile(Exports.OXCoreExport, ('lib/init.lua')), ('@@'..Exports.OXCoreExport..'/lib/init.lua')))
fileLoader()
end
if checkExists(Exports.ESXExport) then
-- Detected ESX in server, wait for it to be started if needed
while GetResourceState(Exports.ESXExport) ~= "started" do Wait(100) end
local fileLoader = assert(load(LoadResourceFile(Exports.ESXExport, ('/imports.lua')), ('@@'..Exports.ESXExport..'/imports.lua')))
fileLoader()
end
-- Initialize variables for caching
local Items, Vehicles, Jobs, Gangs, Core = {}, {}, {}, {}, nil
local itemResource, jobResource, vehResource = "N/A", "N/A", "N/A"
-- Print just to announce it knows the exports/scripts exist in the server
for _, v in pairs(Exports) do
if checkExists(v) then
print("^6Bridge^7: '^3"..v.."^7' detected")
end
end
---------------------
---- Load Items -----
---------------------
-- Items initialization based on detected inventory system
if checkExists(Exports.OXInv) then
-- Wait for OX Inventory to start if it's not already started
while GetResourceState(Exports.OXInv) ~= "started" do Wait(100) end
itemResource = Exports.OXInv
local success, result = pcall(function()
return exports[Exports.OXInv]:Items()
end)
if success and result then
Items = result
end
if Items == nil or not next(Items) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.OXInv.." ^7Items ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Items = {} -- Fallback to an empty table
end
-- Get Weapon info and duplicate them if they are uppercase
-- (duplicate incase anything checks for the uppercase version)
for k, v in pairs(Items) do
if type(k) == "string" then
if k:find("WEAPON") then
Items[k:lower()] = Items[k]
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
elseif checkExists(Exports.QBExport) then
while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end
itemResource = Exports.QBExport
Core = exports[Exports.QBExport]:GetCoreObject()
Items = Core.Shared.Items
if Items == nil or not next(Items) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.QBExport.." ^7Items ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Items = {} -- Fallback to an empty table
end
elseif checkExists(Exports.ESXExport) then
itemResource = Exports.ESXExport
if GetResourceState(Exports.QSInv):find("start") then
Items = exports[Exports.QSInv]:GetItemList()
else
Items = ESX.GetItems()
while not next(Items) do
Items = ESX.GetItems()
Wait(1000)
end
end
elseif checkExists(Exports.RSGExport) then
while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end
itemResource = Exports.RSGExport
Core = exports[Exports.RSGExport]:GetCoreObject()
Items = Core.Shared.Items
if Items == nil or not next(Items) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.RSGExport.." ^7Items ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Items = {} -- Fallback to an empty table
end
end
---------------------
--- Load Vehicles ---
---------------------
-- Vehicle loading depending on framework
if checkExists(Exports.QBXExport) or checkExists(Exports.QBExport) then
vehResource = Exports.QBExport
Core = Core or exports[Exports.QBExport]:GetCoreObject()
Vehicles = Core.Shared.Vehicles
if Vehicles == nil or not next(Vehicles) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find "..Exports.QBExport.." ^7Vehicles ^1list^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Vehicles = {} -- Fallback to an empty table
end
elseif checkExists(Exports.OXCoreExport) then
vehResource = Exports.OXCoreExport
Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
Vehicles[k] = {
model = k, hash = GetHashKey(k),
price = v.price,
name = v.name,
brand = v.make
}
end
elseif checkExists(Exports.ESXExport) then
vehResource = Exports.ESXExport
while not MySQL do Wait(1000) end
Vehicles = {}
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
Vehicles[v.model] = {
model = v.model,
hash = GetHashKey(v.model),
price = v.price,
name = v.name,
}
end
elseif checkExists(Exports.RSGExport) then
vehResource = Exports.RSGExport
Core = Core or exports[Exports.RSGExport]:GetCoreObject()
Vehicles = Core.Shared.Vehicles
if Vehicles == nil or not next(Vehicles) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find shared ^7Vehicles ^1table^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Vehicles = {} -- Fallback to an empty table
end
end
---------------------
----- Load Jobs -----
---------------------
-- Jobs loading based on framework
if checkExists(Exports.QBXExport) then
jobResource = Exports.QBXExport
Core = Core or exports[Exports.QBXExport]:GetCoreObject()
Jobs, Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
if Jobs == nil or not next(Jobs) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find shared ^7Jobs ^1table^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Jobs = {} -- Fallback to an empty table
end
elseif checkExists(Exports.OXCoreExport) then
jobResource = Exports.OXCoreExport
Jobs = {}
while not MySQL do Wait(1000) end
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
local gradeMap = {}
for _, grade in pairs(tempGrades) do
gradeMap[grade.group] = gradeMap[grade.group] or {}
gradeMap[grade.group][grade.grade] = { name = grade.label }
end
for _, job in pairs(tempJobs) do
Jobs[job.name] = {
label = job.label,
grades = gradeMap[job.name] or {}
}
end
Gangs = Jobs
elseif checkExists(Exports.QBExport) then
jobResource = Exports.QBExport
Core = Core or exports[Exports.QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
elseif checkExists(Exports.ESXExport) then
jobResource = Exports.ESXExport
ESX = exports[Exports.ESXExport]:getSharedObject()
Jobs = ESX.GetJobs()
while not next(Jobs) do
Wait(100)
Jobs = ESX.GetJobs()
end
for Role, Grades in pairs(Jobs) do
-- Check for if user has added grades
if Grades.grades == nil or not next(Grades.grades) then
goto continue
end
for grade, info in pairs(Grades.grades) do
if info.label and info.label:find("[Bb]oss") then
Jobs[Role].grades[grade].isBoss = true
goto continue
end
end
local highestGrade = nil
for k in pairs(Grades.grades) do
local num = tonumber(k)
if num and (not highestGrade or num > highestGrade) then
highestGrade = num
end
end
if highestGrade then
Jobs[Role].grades[tostring(highestGrade)].isBoss = true
end
::continue::
end
Gangs = Jobs
elseif checkExists(Exports.RSGExport) then
jobResource = Exports.RSGExport
Core = Core or exports[Exports.RSGExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
if Jobs == nil or not next(Jobs) then
print("^1--------------------------------------------^7")
print("^1ERROR^7: ^1Can NOT find shared ^7Jobs ^1table^7, ^1possible error in that file^7?")
print("^1--------------------------------------------^7")
Jobs = {} -- Fallback to an empty table
end
end
-- Save to global cache
cache.Items = Items
cache.Vehicles = Vehicles
cache.Jobs = Jobs
cache.Gangs = Gangs
CreateThread(function()
local counts = {
Items = 0,
Vehicles = 0,
Jobs = 0,
Gangs = 0,
}
for k, v in pairs(cache) do
for count in pairs(v) do
counts[k] += 1
end
end
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Items).."^2 Items from ^7"..itemResource)
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Vehicles).."^2 Vehicles from ^7"..vehResource)
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Jobs).."^2 Jobs from ^7"..jobResource)
print("^6FrameworkCache^7: ^2Loaded ^5"..tostring(counts.Gangs).."^2 Gangs from ^7"..jobResource)
end)
RegisterNetEvent("jim_bridge:requestCache", function()
local src = source
TriggerClientEvent("jim_bridge:receiveCache", src, _G.__jimBridgeDataCache)
end)
exports("GetSharedData", function()
-- Wait for data to be ready before returning it
local timeout = GetGameTimer() + 5000
while (
not _G.__jimBridgeDataCache or
(not _G.__jimBridgeDataCache.Items or next(_G.__jimBridgeDataCache.Items) == nil) or
(not _G.__jimBridgeDataCache.Vehicles or next(_G.__jimBridgeDataCache.Vehicles) == nil) or
(not _G.__jimBridgeDataCache.Jobs or next(_G.__jimBridgeDataCache.Jobs) == nil)
) and GetGameTimer() < timeout do
Wait(50)
end
return _G.__jimBridgeDataCache
end)

View File

@@ -1,6 +1,6 @@
name "Jim_Bridge"
author "Jimathy"
version "2.0.09"
version "2.0.18"
description "Framework Bridge By Jimathy"
fx_version "cerulean"
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
@@ -16,4 +16,11 @@ files {
}
-- Version checker
server_scripts { '_versioncheck.lua' }
server_scripts {
'frameworkCache.lua',
'_versioncheck.lua'
}
client_scripts {
'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 ""
end
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent)
authCooldown[src] = true
SetTimeout(5000, function() -- 5 second 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,16 +47,21 @@ function onPlayerLoaded(func, onStart)
if isStarted(QBExport) or isStarted(QBXExport) then
onPlayerFramework = QBExport
AddEventHandler('QBCore:Client:OnPlayerLoaded', tempFunc)
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', tempFunc)
elseif isStarted(ESXExport) then
onPlayerFramework = ESXExport
AddEventHandler('esx:playerLoaded', tempFunc)
RegisterNetEvent('esx:playerLoaded', function()
if waitForSharedLoad() then
tempFunc()
end
end
)
elseif isStarted(OXCoreExport) then
onPlayerFramework = OXCoreExport
AddEventHandler('ox:playerLoaded', tempFunc)
RegisterNetEvent('ox:playerLoaded', tempFunc)
elseif isStarted(RSGExport) then
onPlayerFramework = RSGExport
AddEventHandler('RSGCore:Client:OnPlayerLoaded', tempFunc)
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
end
if onPlayerFramework ~= "" then
@@ -77,12 +82,15 @@ end
--- end)
--- ```
function onPlayerUnload(func)
AddEventHandler('QBCore:Client:OnPlayerUnload', function() func() end)
AddEventHandler('ox:playerLogout', function() func() end)
AddEventHandler('RSGCore:Client:OnPlayerUnload', function() func() end)
debugPrint("^6Bridge^7: ^2Registering ^3onPlayerUnload^7()")
RegisterNetEvent('QBCore: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
-------------------------------------------------------------
@@ -99,11 +107,19 @@ end
--- -- Initialization code on resource start.
--- end, true)
--- ```
local hasPrinted = false
function onResourceStart(func, thisScript)
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^7()")
AddEventHandler('onResourceStart', function(resourceName)
if getScript() == resourceName and (thisScript or true) then
func()
if waitForSharedLoad() then
if not hasPrinted then
debugPrint("^6Bridge^7: ^2Shared Load Detected^7.")
hasPrinted = true
end
if isStarted(ESXExport) then Wait(10000) end
func()
end
end
end)
end
@@ -175,3 +191,41 @@ function waitForLogin()
return true
end
end
local messageShown = false
function waitForSharedLoad()
local timeout = 100000 -- 10 seconds in milliseconds
local startTime = GetGameTimer()
local loaded = true
--Wait(1000)
local count = {}
local loop = 0
while ((not Jobs or not next(Jobs)) and (not Items or not next(Items)) and (not Vehicles or not next(Vehicles))) and (GetGameTimer() - startTime) < timeout do
if not messageShown then
if (not Jobs or not next(Jobs)) then
debugPrint("^4Debug^7: ^2Waiting for Jobs to be loaded")
end
if (not Items or not next(Items)) then
debugPrint("^4Debug^7: ^2Waiting for Items to be loaded")
end
if (not Vehicles or not next(Vehicles)) then
debugPrint("^4Debug^7: ^2Waiting for Vehicles to be loaded")
end
end
messageShown = true
--print((GetGameTimer() - startTime) < timeout)
Wait(1000)
if Jobs and Items and Vehicles then
--print("^6Bridge^7: ^2Jobs, Items, and Vehicles Loaded^7.")
loaded = true
break
end
loop += 1
end
if not loaded then
print("^4Error^7: ^1Timeout reached while waiting for shared load^7.")
return false
else
return true
end
end

View File

@@ -15,7 +15,7 @@ function compareVersions(current, newest)
if c < n then return -1
elseif c > n then return 1 end
end
return 0 -- equal
return 0
end
function capitalize(str)
@@ -33,51 +33,63 @@ function CheckVersion()
if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then
CreateThread(function()
Wait(4000)
local script = getScript()
local currentVersionRaw = GetResourceMetadata(script, 'version')
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..script..'.txt', function(err, newestVersionRaw, headers)
if not newestVersionRaw then
-- fallback
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..script..'/master/version.txt', function(err, fallbackVersionRaw, headers)
if not fallbackVersionRaw then
print("^1Currently unable to run a version check for ^7'^3"..script.."^7' (^3"..currentVersionRaw.."^7)")
return
end
fallbackVersionRaw = fallbackVersionRaw:match("[^\r\n]+"):gsub("v", "")
local lines = {}
for line in fallbackVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
local fallbackVersion = (lines[1] or "0.0.0"):gsub("v", "")
local changelog = {}
for i = 2, #lines do table.insert(changelog, lines[i]) end
local compareResult = compareVersions(currentVersionRaw, fallbackVersionRaw)
local compareResult = compareVersions(currentVersionRaw, fallbackVersion)
if compareResult == 0 then
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersionRaw.."^7)")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersion.."^7)")
if #changelog > 0 then
for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line)
end
end
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function()
-- Do a naughty repeat message every 20 minutes until the the script is updated
CheckVersion()
end)
SetTimeout(1200000, function() CheckVersion() end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersionRaw.."^7)")
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersion.."^7)")
end
end)
else
newestVersionRaw = newestVersionRaw:match("[^\r\n]+"):gsub("v", "")
local lines = {}
for line in newestVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
local newestVersion = (lines[1] or "0.0.0"):gsub("v", "")
local changelog = {}
for i = 2, #lines do table.insert(changelog, lines[i]) end
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
if compareResult == 0 then
local compareResult = compareVersions(currentVersionRaw, newestVersion)
if compareResult == 0 then
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersion.."^7)")
if #changelog > 0 then
for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line)
end
end
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function()
-- Do a naughty repeat message every 20 minutes until the the script is updated
CheckVersion()
end)
SetTimeout(1200000, function() CheckVersion() end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
end
end
end)

View File

@@ -24,8 +24,8 @@
--- ```
function createCallback(callbackName, funct)
if isServer() then
debugPrint("^6Bridge^7: ^3Registering callback^7:", callbackName)
if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..OXLibExport.." ^3Callback^7:", callbackName)
lib.callback.register(callbackName, funct)
else
local adaptedFunction = function(source, cb, ...)
@@ -34,12 +34,14 @@ function createCallback(callbackName, funct)
end
if isStarted(QBExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
Core = Core or exports[QBExport]:GetCoreObject()
Core.Functions.CreateCallback(callbackName, adaptedFunction)
elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
ESX.RegisterServerCallback(callbackName, adaptedFunction)
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
@@ -64,7 +66,7 @@ end
--- ```
function triggerCallback(callbackName, ...)
local result = nil
debugPrint("^6Bridge^7: ^3Triggering callback^7:", callbackName)
debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName)
if isStarted(OXLibExport) then
result = lib.callback.await(callbackName, false, ...)
elseif isStarted(QBExport) then

View File

@@ -194,7 +194,7 @@ function openMenu(Menu, data)
exports[QBMenuExport]:openMenu(Menu)
elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(Menu), data.header, data.headertxt or " ", {
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
titleColor = { 222, 255, 255 },
maxOptionCountOnScreen = 15,
width = 0.25,
@@ -232,6 +232,7 @@ function openMenu(Menu, data)
if pressed and not Menu[k].isMenuHeader then
WarMenu.CloseMenu()
close = false
Wait(10)
Menu[k].onSelect()
end
end

View File

@@ -1,25 +1,8 @@
--[[
Resource Initialization Module
--------------------------------
This module initializes and loads shared data (Items, Vehicles, Jobs, Gangs) from the
various frameworks/inventory systems (OX, QB, ESX, etc.). It also corrects export names,
caches framework exports into simple variables, and prints debug information if enabled.
]]
-------------------------------------------------------------
-- Global Variable Initialization
-------------------------------------------------------------
Items, Vehicles, Jobs, Gangs, Core = {}, nil, nil, nil, nil
-------------------------------------------------------------
-- Correct QB Inventory Export
-------------------------------------------------------------
-- Correct ps-invetory to ls-inventory if somehow you still have that
-- Shared Exports Initialization
Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv
-------------------------------------------------------------
-- Framework Exports and Inventory Identifiers
-------------------------------------------------------------
OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
Exports.OXLibExport or "",
Exports.QBXExport or "",
@@ -27,7 +10,7 @@ OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
Exports.ESXExport 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.QBInv or "",
Exports.PSInv or "",
@@ -35,250 +18,95 @@ OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv =
Exports.CoreInv or "",
Exports.CodeMInv or "",
Exports.OrigenInv or "",
Exports.TgiannInv or ""
RSGExport, RSGInv =
Exports.RSGExport or "",
Exports.RSGInv or ""
Exports.TgiannInv or "",
Exports.JPRInv or ""
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
QBMenuExport = Exports.QBMenuExport or ""
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
-------------------------------------------------------------
-- Debug: Print Found Exports
-------------------------------------------------------------
-- Print a list of all exports that are currently started (if debugMode is enabled).
for _, v in pairs(Exports) do
if isStarted(v) then
debugPrint("^6Bridge^7: '^3"..v.."^7' export found")
end
end
OxPlayer = nil
if isStarted(OXCoreExport) then
if not isServer() then
OxPlayer = Ox.GetPlayer()
end
end
-------------------------------------------------------------
-- Resource Variables for Items, Jobs, and Vehicles
-------------------------------------------------------------
local itemResource, jobResource, vehResource = "", "", ""
-------------------------------------------------------------
-- Loading Items
-------------------------------------------------------------
-- Load and compile shared items from the detected inventory system.
if isStarted(OXInv) then
itemResource = OXInv
Items = exports[OXInv]:Items()
for k, v in pairs(Items) do
if v.client and v.client.image then
Items[k].image = (v.client.image):gsub("nui://"..OXInv.."/web/images/", "")
else
Items[k].image = k..".png"
end
Items[k].hunger = v.client and v.client.hunger or nil
Items[k].thirst = v.client and v.client.thirst or nil
end
elseif isStarted(QBExport) then
itemResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = Core or exports[QBExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
end)
end
elseif isStarted(ESXExport) then
itemResource = ESXExport
CreateThread(function()
if isServer() then
Items = ESX.GetItems()
while not createCallback do Wait(100) end
createCallback(getScript()..":getItems", function(source)
return Items
end)
end
if not isServer() then
Items = triggerCallback(getScript()..":getItems")
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7"..itemResource)
end
end)
elseif isStarted(RSGExport) then
itemResource = RSGExport
Core = Core or exports[RSGExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
RegisterNetEvent('RSGCore:Client:UpdateObject', function()
Core = Core or exports[RSGExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
end)
end
if itemResource == nil then
print("^4ERROR^7: ^2No Item info detected ^7- ^2Check ^3starter^1.^2lua^7")
else
while not Items do Wait(100) end
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7"..itemResource)
end
-------------------------------------------------------------
-- Loading Vehicles
-------------------------------------------------------------
-- Compile vehicles from the detected frameworks into a unified table.
if isStarted(QBXExport) or isStarted(QBExport) then
vehResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = Core or exports[QBExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
end)
end
elseif isStarted(RSGExport) then
Core = Core or exports[RSGExport]:GetCoreObject()
end
elseif isStarted(OXCoreExport) then
vehResource = OXCoreExport
Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
Vehicles[k] = { model = k, hash = GetHashKey(k), price = v.price, name = v.name, brand = v.make }
end
elseif isStarted(ESXExport) then
vehResource = ESXExport
if IsDuplicityVersion() then
CreateThread(function()
if isServer() then
createCallback(getScript()..":getVehiclesPrices", function(source)
return Vehicles
end)
while not MySQL do Wait(2000) print("^1Waiting for MySQL to exist") end
Vehicles = MySQL.query.await('SELECT model, price, name FROM vehicles')
local cache = nil
local timeout = GetGameTimer() + 5000 -- 5 seconds max wait
-- Wait until jim_bridge is started and export is available
while not cache and GetGameTimer() < timeout do
if GetResourceState("jim_bridge"):find("start") then
local success, result = pcall(function()
return exports["jim_bridge"]:GetSharedData()
end)
if success and result then
cache = result
--print(json.encode(cache, {indent = true}))
end
end
Wait(100)
end
if not isServer() then
--while not triggerCallback do print("waiting") Wait(100) end
local TempVehicles = triggerCallback(getScript()..":getVehiclesPrices")
for _, v in pairs(TempVehicles) do
Vehicles = Vehicles or {}
if not cache then
print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
return
end
Items = cache.Items
Vehicles = cache.Vehicles
Jobs = cache.Jobs
Gangs = cache.Gangs
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
end)
else
local hasCache = false
-- 🔹 Client Side: Request from server
_G.__jimBridgeDataCache = {}
RegisterNetEvent("jim_bridge:receiveCache", function(data)
if not hasCache then
_G.__jimBridgeDataCache = data
hasCache = true
else
return
end
end)
TriggerServerEvent("jim_bridge:requestCache")
CreateThread(function()
while not _G.__jimBridgeDataCache or not next(_G.__jimBridgeDataCache) do Wait(50) end
local cache = _G.__jimBridgeDataCache
Items = cache.Items or {}
Vehicles = cache.Vehicles or {}
Jobs = cache.Jobs or {}
Gangs = cache.Gangs or {}
if isStarted(ESXExport) then
for _, v in pairs(Vehicles) do
Vehicles[v.model] = {
model = v.model,
hash = GetHashKey(v.model),
hash = v.hash,
price = v.price,
name = v.name,
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
}
end
end
end)
elseif isStarted(RSGExport) then
vehResource = RSGExport
Core = Core or exports[RSGExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
RegisterNetEvent('RSGCore:Client:UpdateObject', function()
Core = Core or exports[RSGExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
end)
end
if vehResource == nil then
print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3starter^1.^2lua^7")
else
CreateThread(function()
while not Vehicles do Wait(1000) end
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..vehResource)
end)
end
-------------------------------------------------------------
-- Loading Jobs and Gangs
-------------------------------------------------------------
-- Compile jobs and gangs from the detected framework.
if isStarted(QBXExport) then
jobResource = QBXExport
Core = Core or exports[QBExport]:GetCoreObject()
Jobs, Gangs = exports[QBXExport]:GetJobs(), exports[QBXExport]:GetGangs()
elseif isStarted(OXCoreExport) then
jobResource = OXExport
CreateThread(function()
if isServer() then
createCallback(getScript()..":getOxGroups", function(source)
Jobs = MySQL.query.await('SELECT * FROM `ox_groups`')
return Jobs
end)
else
local TempJobs = triggerCallback(getScript()..":getOxGroups")
Jobs = {}
for k, v in pairs(TempJobs) do
local grades = {}
--for i = 1, #v.grades do
-- grades[i] = { name = v.grades[i], isboss = (i == #v.grades) }
--end
Jobs[v.name] = { label = v.label, grades = grades }
if isStarted(OXInv) then
for k, v in pairsByKeys(Items) do
local tempInfo = exports[OXInv]:Items(k)
if tempInfo and tempInfo.client then
Items[k].image = (tempInfo.client and tempInfo.client.image) and tempInfo.client.image:gsub("nui://"..OXInv.."/web/images/", "") or k..".png"
Items[k].hunger = tempInfo.client and tempInfo.client.hunger
Items[k].thirst = tempInfo.client and tempInfo.client.thirst
end
end
Gangs = Jobs
end
end)
elseif isStarted(QBExport) then
jobResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = exports[QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end)
end
elseif isStarted(ESXExport) then
jobResource = ESXExport
if isServer() then
-- If server, create callback to get jobs
createCallback(getScript()..":getESXJobs", function(source)
return Jobs
end)
-- Populate jobs table with ESX.GetJobs()
Jobs = ESX.GetJobs()
--If retreived jobs is empty, wait for ESX to load
while countTable(Jobs) == 0 do
Jobs = ESX.GetJobs()
Wait(100)
end
-- Organise into a table the script can use
for k, v in pairs(Jobs) do
local count = countTable(Jobs[k].grades) - 1
Jobs[k].grades[tostring(count)].isBoss = true
end
-- ESX Default doesn't have gangs, so copy jobs to gangs
Gangs = Jobs
end
-- If client side, trigger callback to get jobs
if not isServer() then
Jobs = triggerCallback(getScript()..":getESXJobs")
Gangs = Jobs
end
elseif isStarted(RSGExport) then
jobResource = RSGExport
Core = Core or exports[RSGExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
RegisterNetEvent('RSGCore:Client:UpdateObject', function()
Core = exports[RSGExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end)
end
if jobResource == nil then
print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3starter^1.^2lua^7")
else
while not Jobs do Wait(1000) end
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Jobs).." ^3Jobs^2 from ^7"..jobResource)
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Gangs).." ^3Gangs^2 from ^7"..jobResource)
end

View File

@@ -88,6 +88,7 @@ function craftingMenu(data)
-- Check if the player can carry the required items (server callback).
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
-- Process each recipe to create menu entries.
for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
@@ -106,10 +107,13 @@ function craftingMenu(data)
local metaTable = {}
-- Build ingredient details.
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
itemTable[l] = b
--Wait(0)
end
while not canCarryTable do Wait(0) end
@@ -117,17 +121,17 @@ function craftingMenu(data)
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 "")
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 ""
setheader = isNew .. setheader .. statusEmoji
Menu[#Menu + 1] = {
arrow = not disable and canCarryTable[k],
arrow = isOx() and (not disable and canCarryTable[k]),
isMenuHeader = disable or not canCarryTable[k],
icon = invImg((metadata and metadata.image) or tostring(k)),
image = invImg((metadata and metadata.image) or tostring(k)),
header = setheader,
txt = (isStarted(QBMenuExport) or disable) and settext or nil,
txt = settext or nil,
metadata = metaTable,
onSelect = (not disable and canCarryTable[k]) and function()
local transdata = {
@@ -334,6 +338,10 @@ function makeItem(data)
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({
@@ -355,8 +363,13 @@ function makeItem(data)
if isInventoryOpen() then
print("^1Error^7: ^2Inventory is open, you tried to break things")
crafted, crafting = false, false
stopTempCam()
ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(data) end
CraftLock = false
return
end
if crafted then
local craftProp = nil
if prop then
@@ -368,8 +381,9 @@ function makeItem(data)
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end
if isInventoryOpen() then
print("^1Error^7: ^2Inventory is open, you tried to break things")
--print("^1Error^7: ^2Inventory is open, you tried to break things")
crafted, crafting = false, false
crafted, crafting, CraftLock = false, false, false
return
end
if crafting and progressBar({
@@ -386,7 +400,7 @@ function makeItem(data)
currentToken = nil -- clear client cached token
CreateThread(function()
if data.craft["hasCrafted"] ~= nil then
debugPrint("hasCrafted Found, marking '"..data.item.."' as crafted for player")
debugPrint("^5Bridge^7: ^3hasCrafted ^2Found^7, ^2marking ^7'^4"..data.item.."^7' ^2as crafted for player^7")
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems)
end
@@ -394,7 +408,7 @@ function makeItem(data)
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give
jsonPrint(data.craft["exp"])
debugPrint("exp found, giving exp for '"..data.item.."'")
debugPrint("^6Bridge^7: ^3EXP ^2found^7, ^2giving exp for ^7'^4"..data.item.."^7'")
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel)
end
end)
@@ -443,9 +457,9 @@ end
--- @usage
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata, token)
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
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
end

View File

@@ -56,6 +56,25 @@ function isServer()
return IsDuplicityVersion()
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
-------------------------------------------------------------
@@ -262,7 +281,7 @@ end
--- ```
function countTable(tbl)
local i = 0
for _ in pairs(tbl) do i = i + 1 end
for _ in pairs(tbl) do i += 1 end
return i
end
@@ -474,7 +493,7 @@ end
--- end
--- ```
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
while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do
timeout -= 1
@@ -502,7 +521,7 @@ end
--- end
--- ```
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
while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do
timeout -= 1
@@ -550,6 +569,24 @@ end
RegisterNetEvent(getScript()..":server:sendlog", sendServerLog)
-- This function was created to help create random numbers
-- When you create a table of items with a random hunger value for example
-- `hunger = math.random(10, 20)` this will be set at script start and never change
-- using `hunger = {10, 20}` and then calling this function will make it generate a random number every time
-- It also fallsback if it simply recieved a number instead of a table
-- For example:
-- local hunger = {10, 20}
-- local hungerAmount = GetRandomTiming(hunger)
-- print(hungerAmount) -- number between 10, 20
function GetRandomTiming(tbl)
if type(tbl) == "table" then
return math.random(tbl[1], tbl[2])
else
return tbl
end
end
-------------------------------------------------------------
-- Material and Prop Functions
-------------------------------------------------------------

View File

@@ -50,8 +50,9 @@ function hasItem(items, amount, src)
local count = 0
for _, itemData in pairs(grabInv) do
--jsonPrint(itemData)
if itemData and itemData.name == item then
count += (itemData.count or itemData.amount or 1)
count += (itemData.amount or itemData.count or 1)
end
end
foundInv = foundInv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
@@ -67,6 +68,9 @@ function hasItem(items, amount, src)
end
return true, hasTable
end
-- if can't find inventory, return false
print("^1Error^7: ^1Can't find players inventory for some reason")
return false, {}
end
--- Retrieves a player's inventory based on the active inventory system.
@@ -134,6 +138,14 @@ function getPlayerInv(src)
grabInv = exports[TgiannInv]:GetPlayerItems()
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
foundInv = QBInv
if src then
@@ -144,8 +156,11 @@ function getPlayerInv(src)
elseif isStarted(PSInv) then
foundInv = PSInv
if src then grabInv = Core.Functions.GetPlayer(src).PlayerData.items
else grabInv = Core.Functions.GetPlayerData().items end
if src then
grabInv = Core.Functions.GetPlayer(src).PlayerData.items
else
grabInv = Core.Functions.GetPlayerData().items
end
elseif ESX and isStarted(ESXExport) then
foundInv = ESX
@@ -171,41 +186,10 @@ function getPlayerInv(src)
return grabInv, foundInv
end
function isInventoryOpen()
if isStarted(OXInv) then
return LocalPlayer.state.invBusy
elseif isStarted(QSInv) then
return exports[QSInv]:inInventory()
return IsNuiFocused()
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 exports[TgiannInv]:IsInventoryActive()
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
-------------------------------------------------------------
@@ -240,12 +224,14 @@ function invImg(item)
elseif isStarted(OrigenInv) then
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
imgLink = "nui://"..QBInv.."/html/images/"..(Items[item].image or "")
elseif isStarted(PSInv) then
imgLink = "nui://"..PSInv.."/html/images/"..(Items[item].image or "")
elseif isStarted(TgiannInv) then
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.
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.
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.
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
onPlayerLoaded(function()
Wait(2000)
Wait(1000)
-- 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.
isPedAnimal()
if isAnimal then
@@ -41,18 +41,18 @@ if not isServer() then
-- Determine if the Ped is a cat:
-- 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:
isDog, isBigDog = isDog(ped)
isPedDog, isBigDog = isDog(ped)
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):
isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`)
-- 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, true)
@@ -79,7 +79,7 @@ if not isServer() then
for animalModelHash, _ in pairs(animalCategory) do
if PedModel == animalModelHash then
isAnimal = true
debugPrint("^6Bridge^7: ^2Ped is Animal")
debugPrint("^4isAnimal^7: ^2Ped is ^4Animal")
return true
end
end
@@ -109,6 +109,7 @@ if not isServer() then
local PedModel = GetEntityModel(ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.CatPeds) do
if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Cat")
return true
end
end
@@ -126,8 +127,8 @@ if not isServer() then
---
---@usage
--- ```lua
--- local isDog, isBigDog = isDog()
--- if isDog then
--- local isPedDog, isBigDog = isDog()
--- if isPedDog then
--- if isBigDog then
--- print("Player is a big dog!")
--- else
@@ -151,11 +152,13 @@ if not isServer() then
local PedModel = GetEntityModel(ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.BigDogs) do
if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Dog")
return true, true
end
end
for modelHash, _ in pairs(AnimalPeds.SmallDogs) do
if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Small Dog")
return true, false
end
end

View File

@@ -30,25 +30,34 @@ validTokens = {}
--- end)
--- ```
function createUseableItem(item, funct)
if isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7es_extended^7", item)
while not ESX do Wait(0) end
ESX.RegisterUsableItem(item, funct)
if doesItemExist(item) then
local itemResource = ""
if isStarted(ESXExport) then
itemResource = ESXExport
while not ESX do Wait(0) end
ESX.RegisterUsableItem(item, funct)
elseif isStarted(QBExport) and not isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qb-core^7", item)
Core.Functions.CreateUseableItem(item, funct)
elseif isStarted(QBExport) and not isStarted(QBXExport) then
itemResource = QBExport
Core.Functions.CreateUseableItem(item, funct)
elseif isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^7qbx_core^7", item)
exports[QBXExport]:CreateUseableItem(item, funct)
elseif isStarted(QBXExport) then
itemResource = QBXExport
exports[QBXExport]:CreateUseableItem(item, funct)
elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^2Registering item as ^3Usable^2 with ^1rsg-core^7", item)
Core.Functions.CreateUseableItem(item, funct)
elseif isStarted(RSGExport) then
itemResource = RSGExport
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
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
@@ -186,6 +195,20 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
invName = TgiannInv
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
invName = QBInv
while remamount > 0 do
@@ -196,7 +219,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
break
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)
end
@@ -210,7 +233,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
break
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)
end
@@ -223,7 +246,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
break
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)
end
@@ -252,6 +275,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
else
local amountToAdd = amount or 1
if isStarted(OXInv) then invName = OXInv
jsonPrint(info)
exports[OXInv]:AddItem(src, item, amountToAdd, info, slot)
elseif isStarted(QSInv) then invName = QSInv
@@ -269,6 +293,13 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
elseif isStarted(TgiannInv) then invName = TgiannInv
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
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)
@@ -276,7 +307,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
elseif isStarted(PSInv) then invName = PSInv
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)
end
end
@@ -378,7 +409,7 @@ end
function getDurability(item)
local lowestSlot = 100
local durability = nil
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) then
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) or isStarted(JPRInv) then
local itemcheck = Core.Functions.GetPlayerData().items
for k, v in pairs(itemcheck) do
if v.name == item then
@@ -487,7 +518,7 @@ end
--- ```
RegisterNetEvent(getScript()..":server:setMetaData", function(data)
local src = 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)
local Player = Core.Functions.GetPlayer(src)
Player.PlayerData.items[data.slot].info = data.metadata
@@ -625,6 +656,22 @@ function canCarry(itemTable, src)
resultTable[k] = exports[TgiannInv]:CanCarryItem(source, 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
elseif isStarted(QBInv) then
if QBInvNew then
for k, v in pairs(itemTable) do
@@ -639,7 +686,7 @@ function canCarry(itemTable, src)
end
for k, v in pairs(itemTable) do
local itemInfo = Items[k]
if not itemInfo and not Player.Offline then
if not itemInfo then
resultTable[k] = true
else
resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight
@@ -656,7 +703,7 @@ function canCarry(itemTable, src)
end
for k, v in pairs(itemTable) do
local itemInfo = Items[k]
if not itemInfo and not Player.Offline then
if not itemInfo then
resultTable[k] = true
else
resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight
@@ -670,8 +717,6 @@ end
-------------------------------------------------------------
-- Server Callback Registration
-------------------------------------------------------------
AuthEvent = nil
currentToken = nil
if isServer() then
createCallback(getScript()..":server:canCarry", function(source, itemTable)
local result = canCarry(itemTable, source)
@@ -697,102 +742,149 @@ if isServer() then
end
return maxCanCarry
end)
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 = {}
createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source
--debugPrint(GetInvokingResource())
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
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent)
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
function getMaxInvWeight()
local weight = 0
if isStarted(QBInv) then
if checkExportExists(QBInv, "GetMaxWeight") then
return exports[QBInv]:GetMaxWeight()
else
return InventoryWeight
end
end
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
if isStarted(OXInv) then
return exports[OXInv]:GetPlayerMaxWeight()
end
if isStarted(QSInv) then
return InventoryWeight
end
if isStarted(CoreInv) then
return InventoryWeight
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
if isStarted(OXInv) then
weight = exports[OXInv]:GetPlayerWeight()
end
if isStarted(QSInv) then
local itemcheck = exports[QSInv]:getUserInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(CoreInv) then
local itemcheck = exports[CoreInv]:getInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(CodeMInv) then
local itemcheck = exports[CodeMInv]:GetClientPlayerInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(OrigenInv) then
local itemcheck = exports[OrigenInv]:getPlayerInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(TgiannInv) then
local itemcheck = exports[TgiannInv]:GetPlayerItems()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
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 pairs(xPlayer.inventory) do
weight += ((v.weight * v.amount) or 0)
end
end
end
return weight
end
-------------------------------------------------------------
-- Check Item Existance - return boolean(true,false)
-------------------------------------------------------------
--- checks whether an item exists in your item database
---
--- @param item string The item name.
---
--- @usage
--- ```lua
--- local item = "apple"
--- if doesItemExist(item) then print("item exists") end
--- ```
function doesItemExist(item)
if not item or item == "" then return false end
if Items[item] ~= nil then
return true
end
return false
end

View File

@@ -46,7 +46,7 @@ function makeBossRoles(role)
local data = (Jobs and Jobs[role]) or (Gangs and Gangs[role])
if data then
for grade, info in pairs(data.grades) do
if info.isboss or info.bankAuth then
if info.isboss or info.bankAuth or info.isBoss then
boss[role] = boss[role] and math.min(boss[role], tonumber(grade)) or tonumber(grade)
end
end
@@ -231,3 +231,19 @@ function useDoor(data)
DoScreenFadeIn(1000)
Wait(100)
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)
loadAnimDict(animDict)
debugPrint("Attempting to make player play anim", animDict, animName)
debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName)
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
@@ -204,7 +204,7 @@ end
--- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
--- ```
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(), animName, animDict, 0.5)
unloadAnimDict(animDict)

View File

@@ -54,7 +54,7 @@ function makeBlip(data)
AddTextComponentString(tostring(data.name))
EndTextCommandSetBlipName(blip)
-- Handle preview image if certain resources are running
if isStarted("fs_smallresources") or isStarted("blip_info") or isStarted("blipinfo") then
if isStarted("jim-blipcontroller") then
if data.preview then
local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
if data.preview:find("http") or data.preview:find("nui") then
@@ -62,8 +62,11 @@ function makeBlip(data)
else
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
end
exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'scriptTxd', txname)
exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false)
exports["jim-blipcontroller"]:ShowBlipInfo(blip, {
title = data.name,
dict = getScript()..'scriptTxd',
tex = txname,
})
end
end
end

View File

@@ -54,14 +54,14 @@ function makeDistVehicle(data, radius, onEnter, onExit)
onEnter = function()
vehicle = makeVeh(data.model, data.coords)
if onEnter then
debugPrint("makeDistVehicle onEnter running")
debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onEnter^7() ^2running^7")
onEnter(vehicle)
end
end,
onExit = function()
deleteVehicle(vehicle)
if onExit then
debugPrint("makeDistVehicle onExit running")
debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onExit^7() ^2running^7")
onExit(vehicle)
end
end,

View File

@@ -1,4 +1,5 @@
local inProgress = false
local storedPID = nil
--- Displays a progress bar using the configured progress bar system.
---
@@ -43,11 +44,12 @@ function progressBar(data)
local result = nil
if data.cam then startTempCam(data.cam) end
if Config.System.ProgressBar == "ox" then
if exports[OXLibExport]:progressBar({
local options = {
duration = debugMode and 1000 or data.time,
label = data.label,
position = data.position or "bottom",
useWhileDead = data.dead or false,
canCancel = data.cancel and data.cancel or true,
canCancel = data.cancel or true,
anim = {
dict = data.dict,
clip = data.anim,
@@ -55,12 +57,41 @@ function progressBar(data)
scenario = data.task
},
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
else
result = false
}
if data.prop and data.prop.model then
options.prop = {
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
elseif Config.System.ProgressBar == "qb" then
@@ -69,8 +100,19 @@ function progressBar(data)
debugMode and 1000 or data.time,
data.dead or false,
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()
result = true
end, function()
@@ -95,14 +137,15 @@ function progressBar(data)
elseif Config.System.ProgressBar == "red" then
-- Currently only uses jim-redui if you choose this option
if exports["jim-redui"]:progressBar({
if exports["jim_bridge"]:redProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
cancel = true,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
result = true
else
@@ -110,76 +153,26 @@ function progressBar(data)
end
elseif Config.System.ProgressBar == "gta" then
loadTextureDict("timerbars")
if inProgress then return false end
inProgress = true
local wait = debugMode and 1000 or data.time
local endTime = GetGameTimer() + wait
local ped = PlayerPedId()
-- 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)
if exports["jim_bridge"]:gtaProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
result = true
else
result = false
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
while result == nil do Wait(10) end
while result == nil do
Wait(10)
end
-- Cleanup
FreezeEntityPosition(ped, false)
@@ -203,51 +196,6 @@ function progressBar(data)
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
--- Stops the current progress bar.
---
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
@@ -256,15 +204,14 @@ function stopProgressBar()
exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "gta" then
inProgress = false
elseif Config.System.ProgressBar == "gta" or Config.System.ProgressBar == "red" then
exports["jim_bridge"]:stopProgressBar()
end
end
-- System to handle sending/sharing progress bars between players --
-- For example, healing someone --
local storedPID = nil
--- 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.

View File

@@ -31,6 +31,7 @@
--- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId)
--- ```
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 not src then
TriggerEvent('okokNotify:Alert', title, message, 6000, type)
@@ -50,18 +51,10 @@ function triggerNotify(title, message, type, src)
TriggerClientEvent('ox_lib:notify', src, { title = title, description = message, type = type or "success" })
end
elseif Config.System.Notify == "gta" then
if isStarted("jim-gtaui") then
if not src then
TriggerEvent("jim-gtaui:Notify", title, message, type)
else
TriggerClientEvent("jim-gtaui:Notify", src, title, message, type)
end
if not src then
exports.jim_bridge:Notify(title, message, type)
else
if not src then
TriggerEvent(getScript()..":DisplayGTANotify", title, message)
else
TriggerClientEvent(getScript()..":DisplayGTANotify", src, title, message)
end
TriggerClientEvent("jim-bridge:Notify", src, title, message, type)
end
elseif Config.System.Notify == "esx" then
if not src then
@@ -116,25 +109,25 @@ end)
--- ```lua
--- TriggerEvent(getScript()..":DisplayGTANotify", "taxiname", "Taxi service has arrived.")
--- ```
RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text)
local iconTable = {}
if getScript() == "jim-npcservice" then
iconTable = {
[Loc[Config.Lan].notify["taxiname"]] = "CHAR_TAXI",
[Loc[Config.Lan].notify["limoname"]] = "CHAR_CASINO",
[Loc[Config.Lan].notify["ambiname"]] = "CHAR_CALL911",
[Loc[Config.Lan].notify["pilotname"]] = "CHAR_DEFAULT",
[Loc[Config.Lan].notify["planename"]] = "CHAR_BOATSITE2",
[Loc[Config.Lan].notify["heliname"]] = "CHAR_BOATSITE2",
}
end
BeginTextCommandThefeedPost("STRING")
AddTextComponentSubstringKeyboardDisplay(text)
EndTextCommandThefeedPostMessagetext(
iconTable[title] or "CHAR_DEFAULT",
iconTable[title] or "CHAR_DEFAULT",
true, 1, title, nil, text
)
EndTextCommandThefeedPostTicker(true, false)
end)
--RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text)
-- local iconTable = {}
-- if getScript() == "jim-npcservice" then
-- iconTable = {
-- [Loc[Config.Lan].notify["taxiname"]] = "CHAR_TAXI",
-- [Loc[Config.Lan].notify["limoname"]] = "CHAR_CASINO",
-- [Loc[Config.Lan].notify["ambiname"]] = "CHAR_CALL911",
-- [Loc[Config.Lan].notify["pilotname"]] = "CHAR_DEFAULT",
-- [Loc[Config.Lan].notify["planename"]] = "CHAR_BOATSITE2",
-- [Loc[Config.Lan].notify["heliname"]] = "CHAR_BOATSITE2",
-- }
-- end
--
-- BeginTextCommandThefeedPost("STRING")
-- AddTextComponentSubstringKeyboardDisplay(text)
-- EndTextCommandThefeedPostMessagetext(
-- iconTable[title] or "CHAR_DEFAULT",
-- iconTable[title] or "CHAR_DEFAULT",
-- true, 1, title, nil, text
-- )
-- EndTextCommandThefeedPostTicker(true, false)
--end)

View File

@@ -73,6 +73,11 @@ function sendPhoneMail(data)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
{ name = "npwd_qbx_mail",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
{ name = "jpr-phonesystem",
send = function(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.
for _, phone in ipairs(phoneSystems) do
if isStarted(phone.name) then
activePhone = phone.name
debugPrint("^6Bridge^7[^3"..phone.name.."^7]: ^2Sending mail to player")
phone.send(data)
break
return true
end
end
if activePhone then
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
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
return false
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
-------------------------------------------------------------

View File

@@ -109,19 +109,18 @@ end)
--- setThirst(playerId, 80)
--- ```
function setThirst(src, thirst)
debugPrint("^4Debug^7: ^2Adding thirst^7: ^3"..thirst.." ^2to player^7: ^3"..src.."^7")
if isStarted(ESXExport) then
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)
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
--- Sets the player's hunger level.
@@ -134,18 +133,16 @@ end
--- setHunger(playerId, 60)
--- ```
function setHunger(src, hunger)
debugPrint("^4Debug^7: ^2Adding hunger^7: ^3"..hunger.." ^2to player^7: ^3"..src.."^7")
if isStarted(ESXExport) then
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)
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
@@ -153,6 +150,21 @@ end
-- 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.
---
--- @param cost number The amount to charge.
@@ -164,6 +176,7 @@ end
--- chargePlayer(100, "cash", playerId)
--- ```
function chargePlayer(cost, moneyType, newsrc)
local is_success = false
local src = newsrc or source
local fundResource = ""
if cost < 0 then
@@ -172,33 +185,35 @@ function chargePlayer(cost, moneyType, newsrc)
end
if moneyType == "cash" then
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
fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
elseif isStarted(RSGExport) then
fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).removeMoney(cost, "")
is_success = true
end
elseif moneyType == "bank" then
if isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
elseif isStarted(RSGExport) then
fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).removeMoney(cost, "")
is_success = true
end
end
@@ -207,6 +222,7 @@ function chargePlayer(cost, moneyType, newsrc)
else
debugPrint("^6Bridge^7: ^2Charging ^2Player^7: '^6"..cost.."^7'", moneyType, fundResource)
end
return is_success
end
RegisterNetEvent(getScript()..":server:ChargePlayer", function(cost, moneyType, newsrc)
debugPrint(GetInvokingResource())
@@ -228,39 +244,42 @@ end)
--- fundPlayer(150, "cash", playerId)
--- ```
function fundPlayer(fund, moneyType, newsrc)
local is_success = false
local src = newsrc or source
local fundResource = ""
if moneyType == "cash" then
if isStarted(OXInv) then
fundResource = OXInv
exports[OXInv]:AddItem(src, "money", fund)
is_success = exports[OXInv]:AddItem(src, "money", fund)
elseif isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
elseif isStarted(RSGExport) then
fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).addMoney(fund, "")
is_success = true
end
elseif moneyType == "bank" then
if isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport
Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
elseif isStarted(RSGExport) then
fundResource = RSGExport
Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).addMoney(fund, "")
is_success = true
end
end
@@ -269,6 +288,7 @@ function fundPlayer(fund, moneyType, newsrc)
else
debugPrint("^6Bridge^7: ^2Funding Player: '^2"..fund.."^7'", moneyType, fundResource)
end
return is_success
end
-------------------------------------------------------------
@@ -539,6 +559,7 @@ function getPlayer(source)
local src = tonumber(source)
if isStarted(ESXExport) then
local info = ESX.GetPlayerFromId(src)
if not info then return {} end
Player = {
name = info.getName(),
cash = info.getMoney(),
@@ -563,9 +584,10 @@ function getPlayer(source)
local chunk = assert(load(import, ('@@ox_core/%s'):format(file)))
chunk()
local player = Ox.GetPlayer(src)
if not player then return {} end
Player = {
firstname = player.firstName,
lastname = player.lastName ,
lastname = player.lastName,
name = ('%s %s'):format(player.firstName, player.lastName),
cash = exports[OXInv]:Search(src, 'count', "money"),
bank = 0,
@@ -577,10 +599,11 @@ function getPlayer(source)
--onDuty = info.job.onduty,
--account = info.charinfo.account,
citizenId = player.stateId,
}
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayer(src)
if not info then return {} end
Player = {
firstname = info.PlayerData.charinfo.firstname,
lastname = info.PlayerData.charinfo.lastname,
@@ -601,9 +624,11 @@ function getPlayer(source)
isDown = info.PlayerData.metadata["inlaststand"],
charInfo = info.charinfo,
}
elseif isStarted(QBExport) and not isStarted(QBXExport) then
if Core.Functions.GetPlayer then
if Core.Functions.GetPlayer(src) then
local info = Core.Functions.GetPlayer(src).PlayerData
if not info then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
@@ -625,9 +650,11 @@ function getPlayer(source)
charInfo = info.charinfo,
}
end
elseif isStarted(RSGExport) then
if Core.Functions.GetPlayer then
if Core.Functions.GetPlayer(src) then
local info = Core.Functions.GetPlayer(src).PlayerData
if not info then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
@@ -648,6 +675,7 @@ function getPlayer(source)
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
end
else
print("^4ERROR^7: ^2No Core detected for getPlayer() - Check starter.lua")
@@ -656,6 +684,8 @@ function getPlayer(source)
-- Client-side: Get current player info.
if isStarted(ESXExport) and ESX ~= nil then
local info = ESX.GetPlayerData()
if not info.firstName then return {} end
local cash, bank = 0, 0
for k, v in pairs(info.accounts) do
if v.name == "money" then cash = v.money end
@@ -678,7 +708,9 @@ function getPlayer(source)
isDead = IsEntityDead(PlayerPedId()),
isDown = IsPedDeadOrDying(PlayerPedId(), true)
}
elseif isStarted(OXCoreExport) then
if not OxPlayer.userId then return {} end
Player = {
firstname = OxPlayer.get("firstName"),
lastname = OxPlayer.get("lastName"),
@@ -696,8 +728,10 @@ function getPlayer(source)
isDead = IsEntityDead(PlayerPedId()),
isDown = IsPedDeadOrDying(PlayerPedId(), true)
}
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayerData()
if not info.charinfo then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
@@ -718,9 +752,12 @@ function getPlayer(source)
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
elseif isStarted(QBExport) and not isStarted(QBXExport) then
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
if not info.charinfo then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
@@ -741,9 +778,12 @@ function getPlayer(source)
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
elseif isStarted(RSGExport) then
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
if not info.charinfo then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
@@ -764,6 +804,7 @@ function getPlayer(source)
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
else
print("^4ERROR^7: ^2No Core detected for hasJob ^7- ^2Check ^3starter^1.^2lua^7")
end

View File

@@ -30,15 +30,18 @@ function sellMenu(data)
for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
local _, hasTable = hasItem(itemList)
for k, v in pairsByKeys(data.sellTable.Items) do
Menu[#Menu + 1] = {
isMenuHeader = not hasTable[k].hasItem,
icon = invImg(k),
header = Items[k].label..(hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
txt = Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"],
onSelect = function()
sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
end,
}
if Items[k] then
Menu[#Menu + 1] = {
isMenuHeader = not hasTable[k].hasItem,
icon = invImg(k),
header = Items[k].label..(hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
txt = (Loc and Loc[Config.Lan]) and Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"]
or "Sell ALL at $"..v.." each",
onSelect = function()
sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
end,
}
end
end
else
for k, v in pairsByKeys(data.sellTable) do
@@ -48,7 +51,9 @@ function sellMenu(data)
header = k,
txt = "Amount of items: "..countTable(v.Items),
onSelect = function()
v.onBack = function() sellMenu(origData) end
v.onBack = function()
sellMenu(origData)
end
v.sellTable = data.sellTable[k]
sellMenu(v)
end,
@@ -57,8 +62,8 @@ function sellMenu(data)
end
end
openMenu(Menu, {
header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items),
headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items) or "",
header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items or data.sellTable),
headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items or data.sellTable),
canClose = true,
onBack = data.onBack,
})
@@ -105,12 +110,14 @@ function sellAnim(data)
TriggerServerEvent(getScript().."Sellitems", data)
lookEnt(data.ped)
local dict = "mp_common"
playAnim(dict, "givetake2_a", 0.3, 2)
playAnim(dict, "givetake2_b", 0.3, 2, data.ped)
playAnim(dict, "givetake2_a", 0.3, 48)
playAnim(dict, "givetake2_b", 0.3, 48, data.ped)
Wait(2000)
StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5)
StopAnimTask(data.ped, dict, "givetake2_b", 0.5)
if data.onBack then data.onBack() end
if data.onBack then
data.onBack()
end
end
--- Server event handler for processing item sales.
@@ -157,25 +164,23 @@ function openShop(data)
elseif isStarted(OXInv) then
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)
elseif isStarted(TgiannInv) then
TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
elseif isStarted(CodeMInv) then
--print(data.shop)
TriggerEvent("codem-inventory:openshop", data.shop)
elseif isStarted(QBInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
else
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
elseif isStarted(PSInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
else
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
elseif isStarted(RSGInv) then
TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
@@ -184,10 +189,10 @@ function openShop(data)
end
RegisterNetEvent(getScript()..':server:openServerShop', function(data)
if isStarted(QBInv) then
if isStarted(QBInv) and checkExportExists(QBInv, "OpenShop") then
exports[QBInv]:OpenShop(source, data)
end
if isStarted(PSInv) then
if isStarted(PSInv) and checkExportExists(PSInv, "OpenShop") then
exports[PSInv]:OpenShop(source, data)
end
if isStarted(TgiannInv) then
@@ -210,8 +215,9 @@ end)
--- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons")
--- ```
function registerShop(name, label, items, society)
local shopResource = ""
if isStarted(OXInv) then
debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Store^7:", name, label)
shopResource = OXInv
exports[OXInv]:RegisterShop(name, {
name = label,
inventory = items,
@@ -219,11 +225,11 @@ function registerShop(name, label, items, society)
})
elseif isStarted(TgiannInv) then
debugPrint("^6Bridge^7: ^2Registering ^3Tgiann ^2Store^7:", name, label)
shopResource = TgiannInv
exports[TgiannInv]:RegisterShop(name, items)
elseif isStarted(QBInv) and QBInvNew then
debugPrint("^6Bridge^7: ^2Registering ^3QB ^2Store^7:", name, label)
elseif isStarted(QBInv) and checkExportExists(QBInv, "CreateShop") then
shopResource = QBInv
exports[QBInv]:CreateShop({
name = name,
label = label,
@@ -232,8 +238,8 @@ function registerShop(name, label, items, society)
society = society,
})
elseif isStarted(PSInv) and QBInvNew then
debugPrint("^6Bridge^7: ^2Registering ^3PS ^2Store^7:", name, label)
elseif isStarted(PSInv) and checkExportExists(PSInv, "CreateShop") then
shopResource = PSInv
exports[PSInv]:CreateShop({
name = name,
label = label,
@@ -243,7 +249,7 @@ function registerShop(name, label, items, society)
})
elseif isStarted(RSGInv) then
debugPrint("^6Bridge^7: ^2Registering ^3RSG ^2Store^7:", name, label)
shopResource = RSGInv
exports[RSGInv]:CreateShop({
name = name,
label = label,
@@ -252,4 +258,9 @@ function registerShop(name, label, items, society)
society = society,
})
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

View File

@@ -30,127 +30,10 @@ function skillCheck(data)
result = false
end
elseif Config.System.skillCheck == "gta" then
loadTextureDict("timerbars")
local successes = 0
local barsRequired = 3
exports.jim_bridge:skillCheck()
for bar = 1, barsRequired do
debugPrint("^6Bridge^7: ^2Starting Bar ^3"..bar.."^7/^3"..barsRequired.."^7")
activeSkillCheck = true
local width, height = 0.2, 0.01
local x, y = 0.5, 0.8
-- Random highlighted zone
local highlightSize = math.random(10, 20) / 100
local highlightStart = math.random(10, 50) / 100
local highlightEnd = highlightStart + highlightSize
local highlightAlpha = 0
local cursorPos = 0.0
local cursorSpeed = 0.025
local movingRight = true
while activeSkillCheck do
Wait(0)
makeInstructionalButtons({
{ keys = { 177 }, text = "Exit" },
{ keys = { 38 }, text = "Confirm" },
})
createScaleBars(x, y, width, height)
local pulse = (math.sin(GetGameTimer() / 250) + 1) / 2 -- Creates a pulsing effect
highlightAlpha = math.floor(150 + (pulse * 105)) -- Pulsing between 150 and 255 alpha
-- Draw highlighted zone (success area) with pulsing effect
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, 93, 182, 229, highlightAlpha )
-- Draw moving cursor
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
-- Move cursor
if movingRight then
cursorPos += cursorSpeed
if cursorPos >= 1.0 then movingRight = false end
else
cursorPos -= cursorSpeed
if cursorPos <= 0.0 then movingRight = true end
end
if IsControlJustPressed(0, 177) then -- Backspace to cancel
local displayTime = GetGameTimer() + 2000
PlaySoundFrontend(-1, 'Highlight_Cancel', 'DLC_HEIST_PLANNING_BOARD_SOUNDS', 1)
while GetGameTimer() < displayTime do
Wait(0)
createScaleBars(x, y, width, height)
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, 228, 52, 52, 255)
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
drawSuccessText(x, y, "Failed", 228, 52, 52)
end
return false
end
-- Check for keypress (E)
if IsControlJustPressed(0, 38) then
activeSkillCheck = false
result = cursorPos >= highlightStart and cursorPos <= highlightEnd
if result then
PlaySoundFrontend(-1, "YES", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
else
PlaySoundFrontend(-1, 'Highlight_Cancel', 'DLC_HEIST_PLANNING_BOARD_SOUNDS', 1)
end
local displayTime = GetGameTimer() + 2000
while GetGameTimer() < displayTime do
Wait(0)
createScaleBars(x, y, width, height)
-- Draw highlighted zone
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, result and 93 or 228, result and 182 or 52, result and 229 or 52, 180)
-- Draw stationary cursor at result position
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
-- 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)
end
if result then
successes += 1
else
return false
end
end
end
end
activeSkillCheck = false
debugPrint("^6Bridge^7: ^2Skill Check Result^7: ^3" .. successes .. "^7/^3" .. barsRequired.."^7")
return successes == barsRequired
else
result = true
end
return result
end
function drawSuccessText(x, y, text, r, g, b)
SetTextFont(8)
SetTextScale(0.45, 0.45)
SetTextColour(r, g, b, 255)
SetTextDropshadow(0, 0, 0, 0, 255)
SetTextEdge(2, 0, 0, 0, 150)
SetTextDropShadow()
SetTextOutline()
SetTextCentre(true)
SetTextEntry("STRING")
SetTextCentre(true)
SetTextEntry("STRING")
AddTextComponentString(text)
DrawText(x, y + 0.03)
end
function createScaleBars(x, y, width, height)
-- Draw background box
DrawSprite("timerbars", "all_black_bg", x - (width / 4) - 0.006, y, (width / 2) + 0.08, height + 0.04, 0.0, 255, 255, 255, 255)
DrawSprite("timerbars", "all_black_bg", x + (width / 4) + 0.006, y, (width / 2) + 0.08, height + 0.04, 180.0, 255, 255, 255, 255)
-- Draw full bar (dark background)
DrawRect(x, y, width, height, 100, 100, 100, 255)
end

View File

@@ -8,6 +8,7 @@
• Renewed-Banking
• fd_banking
• okokBanking
• Tgiann-bank
]]
--- Retrieves the current balance of a society's bank account.
@@ -54,6 +55,14 @@ function getSocietyAccount(society)
elseif isStarted("okokBanking") then
bankScript = "okokBanking"
amount = exports['okokBanking']:GetAccount(society)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
amount = exports["tgiann-bank"]:GetJobAccountBalance(society)
else
amount = exports["tgiann-bank"]:GetGangAccountBalance(society)
end
end
if bankScript == "" then
@@ -104,6 +113,13 @@ function chargeSociety(society, amount)
bankScript = "okokBanking"
exports['okokBanking']:RemoveMoney(society, amount)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
exports["tgiann-bank"]:RemoveJobMoney(society, amount)
else
exports["tgiann-bank"]:RemoveGangMoney(society, amount)
end
end
if bankScript == "" then
@@ -122,7 +138,7 @@ end
--- fundSociety("police", 500)
--- ```
function fundSociety(society, amount)
local bankScript, newAmount = "", 0
local bankScript, newAmount, success = "", 0, false
if isStarted("qb-banking") then
@@ -159,6 +175,13 @@ function fundSociety(society, amount)
bankScript = "okokBanking"
exports['okokBanking']:AddMoney(society, amount)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
exports["tgiann-bank"]:AddJobMoney(society, amount)
else
exports["tgiann-bank"]:AddGangMoney(society, amount)
end
end
if bankScript == "" then

View File

@@ -151,6 +151,22 @@ function openStash(data)
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
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
@@ -161,7 +177,10 @@ function openStash(data)
})
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(PSInv) then
@@ -173,8 +192,12 @@ function openStash(data)
slots = data.slots or 40
})
else
TriggerEvent("ps-inventory:client:SetCurrentStash", data.stash)
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(RSGInv) then
@@ -188,7 +211,10 @@ function openStash(data)
else
--Fallback to these commands
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
lookEnt(data.coords)
@@ -202,6 +228,9 @@ RegisterNetEvent(getScript()..":server:openServerStash", function(data)
if isStarted(TgiannInv) then
exports[TgiannInv]:OpenInventory(source, 'stash', data.stashName, data)
end
if isStarted(JPRInv) then
exports[JPRInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(QBInv) then
exports[QBInv]:OpenInventory(source, data.stashName, data)
end
@@ -213,6 +242,63 @@ RegisterNetEvent(getScript()..":server:openServerStash", function(data)
end
end)
function clearStash(stashId)
if isStarted(JPRInv) then
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
exports[QBInv]:ClearStash(stashId)
else
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(OXInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..OXInv.."^2 Stash^7", stashId)
exports[OXInv]:ClearInventory(stashId)
elseif isStarted(PSInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..PSInv.."^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(QSInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..QSInv.."^2 Stash^7", stashId)
exports[QSInv]:ClearOtherInventory('stash', stashId)
elseif isStarted(CoreInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..CoreInv.."^2 Stash^7", stashId)
exports[CoreInv]:clearInventory("stash-"..stashId)
elseif isStarted(CodeMInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..CodeMInv.."^2 Stash^7", stashId)
exports[CodeMInv]:ClearInventory(stashId)
elseif isStarted(OrigenInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..OrigenInv.."^2 Stash^7", stashId)
exports[OrigenInv]:ClearInventory(stashId)
elseif isStarted(TgiannInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..TgiannInv.."^2 Stash^7", stashId)
exports["tgiann-inventory"]:DeleteInventory("stash", stashId)
elseif isStarted(RSGInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..RSGInv.."^2 Stash^7", stashId)
exports[RSGInv]:ClearStash(stashId)
end
end
-------------------------------------------------------------
-- Stash Retrieval Function
@@ -268,6 +354,11 @@ function getStash(stashName)
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
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
stashResource = QBInv
if QBInvNew then
@@ -386,6 +477,29 @@ function stashRemoveItem(stashItems, stashName, items)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..TgiannInv, k, v)
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
if QBInvNew then
for k, v in pairs(items) do
@@ -393,6 +507,9 @@ function stashRemoveItem(stashItems, stashName, items)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
end
else
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
@@ -414,6 +531,9 @@ function stashRemoveItem(stashItems, stashName, items)
end
elseif isStarted(PSInv) 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
@@ -462,7 +582,7 @@ RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
--- local hasAll, details = stashhasItem(currentStashItems, { iron = 2, wood = 5 })
--- ```
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 = ""
for _, inv in ipairs(invs) do
if isStarted(inv) then
@@ -516,27 +636,25 @@ end
--- )
--- ```
function registerStash(name, label, slots, weight, owner, coords)
local stashResource = ""
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)
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
debugPrint("^6Bridge^7: ^2Registering ^3OrigenInv ^2Stash^7:", name, label)
stashResource = OrigenInv
exports["origen_inventory"]:registerStash(name, label, slots or 50, weight or 4000000)
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)
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
if isServer() then
@@ -565,3 +683,18 @@ if isServer() then
registerStash(name, label, slots, weight, owner, coords)
end)
end
RegisterNetEvent(getScript()..":openGrabBox", function(data)
if isStarted(OXInv) then
return
end
local id = ""
if data.metadata then
id = data.metadata.id
elseif data.info then
id = data.info.id
end
openStash({
stash = id,
})
end)

View File

@@ -20,24 +20,7 @@
-------------------------------------------------------------
-- Utility Data & Tables
-------------------------------------------------------------
---
local KEY_TABLE = { 38, 29, 47, 23, 45, }
-- Mapping of key codes to human-readable key names.
local Keys = {
[322] = "ESC", [288] = "F1", [289] = "F2", [170] = "F3", [166] = "F5",
[167] = "F6", [168] = "F7", [169] = "F8", [56] = "F9", [57] = "F10",
[243] = "~", [157] = "1", [158] = "2", [160] = "3", [164] = "4", [165] = "5",
[159] = "6", [161] = "7", [162] = "8", [163] = "9", [84] = "-", [83] = "=",
[177] = "BACKSPACE", [37] = "TAB",
[44] = "Q", [32] = "W", [38] = "E", [45] = "R", [245] = "T", [246] = "Y",
[303] = "U", [199] = "P",
[39] = "[", [40] = "]", [18] = "ENTER", [137] = "CAPS",
[34] = "A", [8] = "S", [9] = "D", [23] = "F", [47] = "G",
[74] = "H", [311] = "K", [182] = "L", [21] = "LEFTSHIFT",
[20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N",
[244] = "M", [82] = ",", [81] = "."
}
-- Tables for storing created targets for the fallback system and zone management.
local TextTargets = {} -- For fallback DrawText3D targets.
@@ -82,38 +65,9 @@ function createEntityTarget(entity, opts, dist)
-- Fallback: Use DrawText3D if targeting systems are disabled or unavailable.
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
local entityCoords = GetEntityCoords(entity)
debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with DrawText for entity ^7"..entity)
local existingTarget = nil
for _, target in pairs(TextTargets) do
if #(target.coords - entityCoords) < 0.01 then
existingTarget = target
break
end
end
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
exports.jim_bridge:createEntityTarget(entity, opts, dist)
if existingTarget then
for i = 1, #opts do
local key = KEY_TABLE[#existingTarget.options + i]
opts[i].key = key
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
existingTarget.options[#existingTarget.options + 1] = opts[i]
end
updateCachedText(existingTarget)
else
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
TextTargets[entity] = {
coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z),
buttontext = tempText,
options = opts,
dist = dist,
text = table.concat(tempText, "\n")
}
end
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
local options = {}
@@ -129,10 +83,12 @@ function createEntityTarget(entity, opts, dist)
}
end
exports[OXTargetExport]:addLocalEntity(entity, options)
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..QBTargetExport.." ^2for entity ^7"..entity)
local options = { options = opts, distance = dist }
exports[QBTargetExport]:AddTargetEntity(entity, options)
end
end
@@ -192,37 +148,8 @@ end
function createBoxTarget(data, opts, dist)
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^2 for zone ^7"..data[1])
local existingTarget = nil
for _, target in pairs(TextTargets) do
if #(target.coords - data[2]) < 0.01 then
existingTarget = target
break
end
end
return exports.jim_bridge:createZoneTarget(data, opts, dist)
if existingTarget then
for i = 1, #opts do
local key = KEY_TABLE[#existingTarget.options + i]
opts[i].key = key
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
existingTarget.options[#existingTarget.options + 1] = opts[i]
end
updateCachedText(existingTarget)
else
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
TextTargets[data[1]] = {
coords = data[2],
buttontext = tempText,
options = opts,
dist = dist,
text = table.concat(tempText, "\n")
}
end
return data[1]
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local options = {}
@@ -250,12 +177,14 @@ function createBoxTarget(data, opts, dist)
})
boxTargets[#boxTargets + 1] = target
return target
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^2for zone ^7"..data[1])
local options = { options = opts, distance = dist }
local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
boxTargets[#boxTargets + 1] = target
return data[1]
end
end
@@ -298,37 +227,8 @@ end
function createCircleTarget(data, opts, dist)
if Config.System.DontUseTarget then
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6DrawText ^2for zone ^7"..data[1])
local existingTarget = nil
for _, target in pairs(TextTargets) do
if #(target.coords - data[2]) < 0.01 then
existingTarget = target
break
end
end
return exports.jim_bridge:createZoneTarget(data, opts, dist)
if existingTarget then
for i = 1, #opts do
local key = KEY_TABLE[#existingTarget.options + i]
opts[i].key = key
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
existingTarget.options[#existingTarget.options + 1] = opts[i]
end
updateCachedText(existingTarget)
else
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
TextTargets[data[1]] = {
coords = data[2],
buttontext = tempText,
options = opts,
dist = dist,
text = table.concat(tempText, "\n")
}
end
return data[1]
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local options = {}
@@ -387,30 +287,8 @@ end
---```
function createModelTarget(models, opts, dist)
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
if type(models) ~= "table" then
models = { models }
end
return exports.jim_bridge:createModelTarget(models, opts, dist)
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
local keyStr = ""
for i, m in ipairs(models) do
keyStr = keyStr .. tostring(m) .. (i < #models and "_" or "")
end
local targetKey = "model_" .. keyStr
TextTargets[targetKey] = {
models = models,
buttontext = tempText,
options = opts,
dist = dist,
coords = vec3(0, 0, 0),
text = table.concat(tempText, "\n")
}
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..OXTargetExport)
local options = {}
@@ -453,7 +331,7 @@ function removeEntityTarget(entity)
exports[OXTargetExport]:removeLocalEntity(entity, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
TextTargets[entity] = nil
exports.jim_bridge:removeEntityTarget(entity)
end
end
@@ -474,7 +352,7 @@ function removeZoneTarget(target)
exports[OXTargetExport]:removeZone(target, true)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
TextTargets[target] = nil
exports.jim_bridge:removeZoneTarget(target)
end
end
@@ -494,7 +372,7 @@ function removeModelTarget(model)
exports[OXTargetExport]:removeModel(model, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
TextTargets[entity] = nil
exports.jim_bridge:removeZoneTarget(target)
end
end
-------------------------------------------------------------
@@ -504,69 +382,105 @@ end
-- If no targeting system is detected and this is a client script, use DrawText3D for targets.
if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) and not isServer() then
CreateThread(function()
local wait = 1000
while true do
local pedCoords = GetEntityCoords(PlayerPedId())
local ped = PlayerPedId()
local pedCoords = GetEntityCoords(ped)
local camCoords = GetGameplayCamCoord()
local camRot = GetGameplayCamRot(2)
local camForward = RotationToDirection(camRot)
local closestTarget, closestDist = nil, math.huge
local notificationShown = false
local closestTarget = nil
local closestDist = math.huge
local targetEntity = nil
-- Update model targets and determine the closest target.
for _, target in pairs(TextTargets) do
-- Shallow copy for safety
local targetsCopy = {}
for k, v in pairs(TextTargets) do
targetsCopy[k] = v
end
-- Detect models and update coords
for _, target in pairs(targetsCopy) do
if target.models then
for _, model in ipairs(target.models) do
local entity = GetClosestObjectOfType(pedCoords.x, pedCoords.y, pedCoords.z, target.dist, model, false, false, false)
if entity and entity ~= 0 then
target.coords = GetEntityCoords(entity)
targetEntity = entity
break
if not target.entity or not DoesEntityExist(target.entity) then
for _, model in ipairs(target.models) do
local entity = GetClosestObjectOfType(pedCoords.x, pedCoords.y, pedCoords.z, target.dist, model, false, false, false)
if entity and entity ~= 0 then
target.entity = entity
target.coords = GetEntityCoords(entity)
break
end
end
else
target.coords = GetEntityCoords(target.entity)
end
end
end
local dist = #(pedCoords - target.coords)
if dist <= target.dist then
-- Identify closest visible target
for _, target in pairs(targetsCopy) do
if target.coords then
local dist = #(pedCoords - target.coords)
local vecToTarget = target.coords - camCoords
local normVec = normalizeVector(vecToTarget)
local dot = camForward.x * normVec.x + camForward.y * normVec.y + camForward.z * normVec.z
if dot > 0.5 and dist < closestDist then
closestDist = dist
closestTarget = target
local isFacing = dot > 0.5
if dist <= target.dist and isFacing then
if dist < closestDist then
closestDist = dist
closestTarget = target
targetEntity = target.entity
end
end
end
end
-- Render targets, listen for key presses and display the help notification.
for key, target in pairs(TextTargets) do
if #(pedCoords - target.coords) <= target.dist then
-- Render + handle input
for _, target in pairs(targetsCopy) do
if target.coords and #(pedCoords - target.coords) <= target.dist then
local isClosest = (target == closestTarget)
for i, opt in ipairs(target.options) do
for _, opt in ipairs(target.options) do
if IsControlJustPressed(0, opt.key) and isClosest then
if opt.onSelect then opt.onSelect(targetEntity) end
if opt.action then opt.action(targetEntity) end
local canInteract = (not target.canInteract or target.canInteract())
local hasItem = (not opt.item or hasItem(opt.item))
local hasJob = (not opt.job or hasJob(opt.job, nil))
if canInteract and hasItem and hasJob then
if opt.onSelect then opt.onSelect(targetEntity) end
if opt.action then opt.action(targetEntity) end
end
end
end
notificationShown = true
ShowFloatingHelpNotification(vec3(target.coords.x, target.coords.y, target.coords.z + 0.7), target.text)
-- Draw each eligible text line
local baseZ = target.coords.z + 1.0
local lineHeight = -0.16
local lineOffset = 0
for i, opt in ipairs(target.options) do
local canInteract = (not target.canInteract or target.canInteract())
local hasItem = (not opt.item or hasItem(opt.item))
local hasJob = (not opt.job or hasJob(opt.job, nil))
if canInteract and hasItem and hasJob then
local text = target.buttontext[i]
local zOffset = lineOffset * lineHeight
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + zOffset), text, isClosest)
lineOffset += 1
end
end
end
end
-- If no notification was drawn this frame, clear help messages.
if notificationShown then
wait = 0
else
ClearAllHelpMessages()
wait = 1000
end
Wait(wait)
Wait(0)
end
end)
end
function ShowFloatingHelpNotification(coord, text, highlight)
AddTextEntry("FloatingText", text)
SetFloatingHelpTextWorldPosition(1, coord.x, coord.y, coord.z)

694
shared/warmenu.lua Normal file
View File

@@ -0,0 +1,694 @@
WarMenu = { }
WarMenu.__index = WarMenu
if not isServer() then
-- Deprecated
WarMenu.debug = false
function WarMenu.SetDebugEnabled(enabled)
end
function WarMenu.IsDebugEnabled()
return false
end
---
local menus = { }
local keys = { down = 187, scrollDown = 242, up = 188, scrollUp = 241, left = 189, right = 190, select = 191, accept = 237, back = 194, cancel = 238 }
local skipInputNextFrame = true
local optionCount = 0
local currentKey = nil
local currentMenu = nil
local toolTipWidth = 0.153
local spriteWidth = 0.027
local spriteHeight = spriteWidth * GetAspectRatio()
local titleHeight = 0.101
local titleYOffset = 0.021
local titleFont = 1
local titleScale = 1.0
local buttonHeight = 0.038
local buttonFont = 0
local buttonScale = 0.365
local buttonTextXOffset = 0.005
local buttonTextYOffset = 0.005
local buttonSpriteXOffset = 0.002
local buttonSpriteYOffset = 0.005
local defaultStyle = {
x = 0.0175,
y = 0.025,
width = 0.23,
maxOptionCountOnScreen = 10,
titleVisible = true,
titleColor = { 0, 0, 0, 255 },
titleBackgroundColor = { 245, 127, 23, 255 },
titleBackgroundSprite = nil,
subTitleColor = { 245, 127, 23, 255 },
textColor = { 254, 254, 254, 255 },
subTextColor = { 189, 189, 189, 255 },
focusTextColor = { 0, 0, 0, 255 },
focusColor = { 245, 245, 245, 255 },
backgroundColor = { 0, 0, 0, 160 },
subTitleBackgroundColor = { 0, 0, 0, 255 },
buttonPressedSound = { name = 'SELECT', set = 'HUD_FRONTEND_DEFAULT_SOUNDSET' }, --https://pastebin.com/0neZdsZ5
}
local function IsNavigatedDown()
return IsControlJustReleased(2, keys.down) or IsControlJustReleased(2, keys.scrollDown)
end
local function IsNavigatedUp()
return IsControlJustReleased(2, keys.up) or IsControlJustReleased(2, keys.scrollUp)
end
local function IsSelectedPressed()
return IsControlJustReleased(2, keys.select) or IsControlJustReleased(2, keys.accept)
end
local function IsBackPressed()
return IsControlJustReleased(2, keys.back) or IsControlJustReleased(2, keys.cancel)
end
local function setMenuProperty(id, property, value)
if not id then
return
end
local menu = menus[id]
if menu then
menu[property] = value
end
end
local function setStyleProperty(id, property, value)
if not id then
return
end
local menu = menus[id]
if menu then
if not menu.overrideStyle then
menu.overrideStyle = { }
end
menu.overrideStyle[property] = value
end
end
local function getStyleProperty(property, menu)
menu = menu or currentMenu
if menu.overrideStyle then
local value = menu.overrideStyle[property]
if value ~= nil then
return value
end
end
return menu.style and menu.style[property] or defaultStyle[property]
end
local function getTitleHeight()
return getStyleProperty('titleVisible') and titleHeight or 0
end
local function copyTable(t)
if type(t) ~= 'table' then
return t
end
local result = { }
for k, v in pairs(t) do
result[k] = copyTable(v)
end
return result
end
local function setMenuVisible(id, visible, holdCurrentOption)
if currentMenu then
if visible then
if currentMenu.id == id then
return
end
else
if currentMenu.id ~= id then
return
end
end
end
if visible then
local menu = menus[id]
if not currentMenu then
menu.currentOption = 1
else
if not holdCurrentOption then
menus[currentMenu.id].currentOption = 1
end
end
currentMenu = menu
skipInputNextFrame = true
SetUserRadioControlEnabled(false)
HudWeaponWheelIgnoreControlInput(true)
else
HudWeaponWheelIgnoreControlInput(false)
SetUserRadioControlEnabled(true)
currentMenu = nil
end
end
local function setTextParams(font, color, scale, center, shadow, alignRight, wrapFrom, wrapTo)
SetTextFont(font)
SetTextColour(color[1], color[2], color[3], color[4] or 255)
SetTextScale(scale, scale)
if shadow then
SetTextDropShadow()
end
if center then
SetTextCentre(true)
elseif alignRight then
SetTextRightJustify(true)
end
if not wrapFrom or not wrapTo then
wrapFrom = wrapFrom or getStyleProperty('x')
wrapTo = wrapTo or getStyleProperty('x') + getStyleProperty('width') - buttonTextXOffset
end
SetTextWrap(wrapFrom, wrapTo)
end
local function getLinesCount(text, x, y)
BeginTextCommandLineCount('TWOSTRINGS')
AddTextComponentString(tostring(text))
return EndTextCommandGetLineCount(x, y)
end
local function drawText(text, x, y)
BeginTextCommandDisplayText('TWOSTRINGS')
AddTextComponentString(tostring(text))
EndTextCommandDisplayText(x, y)
end
local function drawRect(x, y, width, height, color)
DrawRect(x, y, width, height, color[1], color[2], color[3], color[4] or 255)
end
local function getCurrentIndex()
if currentMenu.currentOption <= getStyleProperty('maxOptionCountOnScreen') and optionCount <= getStyleProperty('maxOptionCountOnScreen') then
return optionCount
elseif optionCount > currentMenu.currentOption - getStyleProperty('maxOptionCountOnScreen') and optionCount <= currentMenu.currentOption then
return optionCount - (currentMenu.currentOption - getStyleProperty('maxOptionCountOnScreen'))
end
return nil
end
local function drawTitle()
if not getStyleProperty('titleVisible') then
return
end
local x = getStyleProperty('x') + getStyleProperty('width') / 2
local y = getStyleProperty('y') + titleHeight / 2
if getStyleProperty('titleBackgroundSprite') then
DrawSprite(getStyleProperty('titleBackgroundSprite').dict, getStyleProperty('titleBackgroundSprite').name, x, y, getStyleProperty('width'), titleHeight, 0., 255, 255, 255, 255)
else
drawRect(x, y, getStyleProperty('width'), titleHeight, getStyleProperty('titleBackgroundColor'))
end
if currentMenu.title then
setTextParams(titleFont, getStyleProperty('titleColor'), titleScale, true)
drawText(currentMenu.title, x, y - titleHeight / 2 + titleYOffset)
end
end
local function drawSubTitle()
local x = getStyleProperty('x') + getStyleProperty('width') / 2
local y = getStyleProperty('y') + getTitleHeight() + buttonHeight / 2
drawRect(x, y, getStyleProperty('width'), buttonHeight, getStyleProperty('subTitleBackgroundColor'))
setTextParams(buttonFont, getStyleProperty('subTitleColor'), buttonScale, false)
drawText(currentMenu.subTitle, getStyleProperty('x') + buttonTextXOffset, y - buttonHeight / 2 + buttonTextYOffset)
if optionCount > getStyleProperty('maxOptionCountOnScreen') then
setTextParams(buttonFont, getStyleProperty('subTitleColor'), buttonScale, false, false, true)
drawText(tostring(currentMenu.currentOption)..' / '..tostring(optionCount), getStyleProperty('x') + getStyleProperty('width'), y - buttonHeight / 2 + buttonTextYOffset)
end
end
local function drawButton(text, subText)
local currentIndex = getCurrentIndex()
if not currentIndex then
return
end
local backgroundColor = nil
local textColor = nil
local subTextColor = nil
local shadow = false
if currentMenu.currentOption == optionCount then
backgroundColor = getStyleProperty('focusColor')
textColor = getStyleProperty('focusTextColor')
subTextColor = getStyleProperty('focusTextColor')
else
backgroundColor = getStyleProperty('backgroundColor')
textColor = getStyleProperty('textColor')
subTextColor = getStyleProperty('subTextColor')
shadow = true
end
local x = getStyleProperty('x') + getStyleProperty('width') / 2
local y = getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * currentIndex) - buttonHeight / 2
drawRect(x, y, getStyleProperty('width'), buttonHeight, backgroundColor)
setTextParams(buttonFont, textColor, buttonScale, false, shadow)
drawText(text, getStyleProperty('x') + buttonTextXOffset, y - (buttonHeight / 2) + buttonTextYOffset)
if subText then
setTextParams(buttonFont, subTextColor, buttonScale, false, shadow, true)
drawText(subText, getStyleProperty('x') + buttonTextXOffset, y - buttonHeight / 2 + buttonTextYOffset)
end
end
function WarMenu.CreateMenu(id, title, subTitle, style)
-- Default settings
local menu = { }
-- Members
menu.id = id
menu.previousMenu = nil
menu.currentOption = 1
menu.title = title
menu.subTitle = subTitle and string.upper(subTitle) or 'INTERACTION MENU'
-- Style
if style then
menu.style = style
end
menus[id] = menu
end
function WarMenu.CreateSubMenu(id, parent, subTitle, style)
local parentMenu = menus[parent]
if not parentMenu then
return
end
WarMenu.CreateMenu(id, parentMenu.title, subTitle and string.upper(subTitle) or parentMenu.subTitle)
local menu = menus[id]
menu.previousMenu = parent
if parentMenu.overrideStyle then
menu.overrideStyle = copyTable(parentMenu.overrideStyle)
end
if style then
menu.style = style
elseif parentMenu.style then
menu.style = copyTable(parentMenu.style)
end
end
function WarMenu.CurrentMenu()
return currentMenu and currentMenu.id or nil
end
function WarMenu.OpenMenu(id)
if id and menus[id] then
PlaySoundFrontend(-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
setMenuVisible(id, true, true)
end
end
function WarMenu.IsMenuOpened(id)
return currentMenu and currentMenu.id == id
end
WarMenu.Begin = WarMenu.IsMenuOpened
function WarMenu.IsAnyMenuOpened()
return currentMenu ~= nil
end
function WarMenu.IsMenuAboutToBeClosed()
return false
end
function WarMenu.CloseMenu()
if currentMenu then
setMenuVisible(currentMenu.id, false)
optionCount = 0
currentKey = nil
PlaySoundFrontend(-1, 'QUIT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
end
end
function WarMenu.ToolTip(text, width, flipHorizontal)
if not currentMenu then
return
end
local currentIndex = getCurrentIndex()
if not currentIndex then
return
end
width = width or toolTipWidth
local x = nil
if not flipHorizontal then
x = getStyleProperty('x') + getStyleProperty('width') + width / 2 + buttonTextXOffset
else
x = getStyleProperty('x') - width / 2 - buttonTextXOffset
end
local textX = x - (width / 2) + buttonTextXOffset
setTextParams(buttonFont, getStyleProperty('textColor'), buttonScale, false, true, false, textX, textX + width - (buttonTextYOffset * 2))
local linesCount = getLinesCount(text, textX, getStyleProperty('y'))
local height = GetTextScaleHeight(buttonScale, buttonFont) * (linesCount + 1) + buttonTextYOffset
local y = getStyleProperty('y') + getTitleHeight() + (buttonHeight * currentIndex) + height / 2
drawRect(x, y, width, height, getStyleProperty('backgroundColor'))
y = y - (height / 2) + buttonTextYOffset
drawText(text, textX, y)
end
function WarMenu.Button(text, subText)
if not currentMenu then
return
end
optionCount = optionCount + 1
drawButton(text, subText)
local pressed = false
if currentMenu.currentOption == optionCount then
if currentKey == keys.select then
pressed = true
PlaySoundFrontend(-1, getStyleProperty('buttonPressedSound').name, getStyleProperty('buttonPressedSound').set, true)
elseif currentKey == keys.left or currentKey == keys.right then
PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
end
end
return pressed
end
function WarMenu.SpriteButton(text, dict, name, r, g, b, a)
if not currentMenu then
return
end
local pressed = WarMenu.Button(text)
local currentIndex = getCurrentIndex()
if not currentIndex then
return
end
if not HasStreamedTextureDictLoaded(dict) then
RequestStreamedTextureDict(dict)
end
DrawSprite(dict, name, getStyleProperty('x') + getStyleProperty('width') - spriteWidth / 2 - buttonSpriteXOffset, getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * currentIndex) - spriteHeight / 2 + buttonSpriteYOffset, spriteWidth, spriteHeight, 0., r or 255, g or 255, b or 255, a or 255)
return pressed
end
function WarMenu.InputButton(text, windowTitleEntry, defaultText, maxLength, subText)
if not currentMenu then
return
end
local pressed = WarMenu.Button(text, subText)
local inputText = nil
if pressed then
DisplayOnscreenKeyboard(1, windowTitleEntry or 'FMMC_MPM_NA', '', defaultText or '', '', '', '', maxLength or 255)
while true do
DisableAllControlActions(0)
local status = UpdateOnscreenKeyboard()
if status == 2 then
break
elseif status == 1 then
inputText = GetOnscreenKeyboardResult()
break
end
Citizen.Wait(0)
end
end
return pressed, inputText
end
function WarMenu.MenuButton(text, id, subText)
if not currentMenu then
return
end
local pressed = WarMenu.Button(text, subText)
if pressed then
currentMenu.currentOption = optionCount
setMenuVisible(currentMenu.id, false)
setMenuVisible(id, true, true)
end
return pressed
end
function WarMenu.CheckBox(text, checked, callback)
if not currentMenu then
return
end
local name = nil
if currentMenu.currentOption == optionCount + 1 then
name = checked and 'shop_box_tickb' or 'shop_box_blankb'
else
name = checked and 'shop_box_tick' or 'shop_box_blank'
end
local pressed = WarMenu.SpriteButton(text, 'commonmenu', name)
if pressed then
checked = not checked
if callback then callback(checked) end
end
return pressed
end
function WarMenu.ComboBox(text, items, currentIndex, selectedIndex, callback)
if not currentMenu then
return
end
local itemsCount = #items
local selectedItem = items[currentIndex]
local isCurrent = currentMenu.currentOption == optionCount + 1
selectedIndex = selectedIndex or currentIndex
if itemsCount > 1 and isCurrent then
selectedItem = ''..tostring(selectedItem)..''
end
local pressed = WarMenu.Button(text, selectedItem)
if pressed then
selectedIndex = currentIndex
elseif isCurrent then
if currentKey == keys.left then
if currentIndex > 1 then currentIndex = currentIndex - 1 else currentIndex = itemsCount end
elseif currentKey == keys.right then
if currentIndex < itemsCount then currentIndex = currentIndex + 1 else currentIndex = 1 end
end
end
if callback then callback(currentIndex, selectedIndex) end
return pressed, currentIndex
end
function WarMenu.Display()
if currentMenu then
if not IsPauseMenuActive() then
ClearAllHelpMessages()
HudWeaponWheelIgnoreSelection()
DisablePlayerFiring(PlayerId(), true)
DisableControlAction(0, 25, true)
drawTitle()
drawSubTitle()
currentKey = nil
if skipInputNextFrame then
skipInputNextFrame = false
else
if IsNavigatedDown() then
PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
if currentMenu.currentOption < optionCount then
currentMenu.currentOption = currentMenu.currentOption + 1
else
currentMenu.currentOption = 1
end
elseif IsNavigatedUp() then
PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
if currentMenu.currentOption > 1 then
currentMenu.currentOption = currentMenu.currentOption - 1
else
currentMenu.currentOption = optionCount
end
elseif IsControlJustReleased(2, keys.left) then
currentKey = keys.left
elseif IsControlJustReleased(2, keys.right) then
currentKey = keys.right
elseif IsSelectedPressed() then
currentKey = keys.select
elseif IsBackPressed() then
if menus[currentMenu.previousMenu] then
setMenuVisible(currentMenu.previousMenu, true)
PlaySoundFrontend(-1, 'BACK', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
else
WarMenu.CloseMenu()
end
end
end
end
optionCount = 0
end
end
WarMenu.End = WarMenu.Display
function WarMenu.CurrentOption()
if currentMenu and optionCount ~= 0 then
return currentMenu.currentOption
end
return nil
end
function WarMenu.IsItemHovered()
if not currentMenu or optionCount == 0 then
return false
end
return currentMenu.currentOption == optionCount
end
function WarMenu.IsItemSelected()
return currentKey == keys.select and WarMenu.IsItemHovered()
end
function WarMenu.SetTitle(id, title)
setMenuProperty(id, 'title', title)
end
WarMenu.SetMenuTitle = WarMenu.SetTitle
function WarMenu.SetSubTitle(id, text)
setMenuProperty(id, 'subTitle', string.upper(text))
end
WarMenu.SetMenuSubTitle = WarMenu.SetSubTitle
function WarMenu.SetMenuStyle(id, style)
setMenuProperty(id, 'style', style)
end
function WarMenu.SetMenuTitleVisible(id, visible)
setStyleProperty(id, 'titleVisible', visible)
end
function WarMenu.SetMenuX(id, x)
setStyleProperty(id, 'x', x)
end
function WarMenu.SetMenuY(id, y)
setStyleProperty(id, 'y', y)
end
function WarMenu.SetMenuWidth(id, width)
setStyleProperty(id, 'width', width)
end
function WarMenu.SetMenuMaxOptionCountOnScreen(id, count)
setStyleProperty(id, 'maxOptionCountOnScreen', count)
end
function WarMenu.SetTitleColor(id, r, g, b, a)
setStyleProperty(id, 'titleColor', { r, g, b, a })
end
WarMenu.SetMenuTitleColor = WarMenu.SetTitleColor
function WarMenu.SetMenuSubTitleColor(id, r, g, b, a)
setStyleProperty(id, 'subTitleColor', { r, g, b, a })
end
function WarMenu.SetMenuSubTitleBackgroundColor(id, r, g, b, a)
setStyleProperty(id, 'subTitleBackgroundColor', { r, g, b, a })
end
function WarMenu.SetTitleBackgroundColor(id, r, g, b, a)
setStyleProperty(id, 'titleBackgroundColor', { r, g, b, a })
end
WarMenu.SetMenuTitleBackgroundColor = WarMenu.SetTitleBackgroundColor
function WarMenu.SetTitleBackgroundSprite(id, dict, name)
RequestStreamedTextureDict(dict)
setStyleProperty(id, 'titleBackgroundSprite', { dict = dict, name = name })
end
WarMenu.SetMenuTitleBackgroundSprite = WarMenu.SetTitleBackgroundSprite
function WarMenu.SetMenuBackgroundColor(id, r, g, b, a)
setStyleProperty(id, 'backgroundColor', { r, g, b, a })
end
function WarMenu.SetMenuTextColor(id, r, g, b, a)
setStyleProperty(id, 'textColor', { r, g, b, a })
end
function WarMenu.SetMenuSubTextColor(id, r, g, b, a)
setStyleProperty(id, 'subTextColor', { r, g, b, a })
end
function WarMenu.SetMenuFocusColor(id, r, g, b, a)
setStyleProperty(id, 'focusColor', { r, g, b, a })
end
function WarMenu.SetMenuFocusTextColor(id, r, g, b, a)
setStyleProperty(id, 'focusTextColor', { r, g, b, a })
end
function WarMenu.SetMenuButtonPressedSound(id, name, set)
setStyleProperty(id, 'buttonPressedSound', { name = name, set = set })
end
end

View File

@@ -21,24 +21,29 @@
--- })
--- ```
function registerCommand(command, options)
local commandResource = ""
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])
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)
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)
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)
options[4](xPlayer.source, args, showError)
end, false, { help = options[1] })
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

View File

@@ -14,6 +14,7 @@ Exports = {
CodeMInv = "codem-inventory",
OrigenInv = "origen_inventory",
TgiannInv = "tgiann-inventory",
JPRInv = "jpr-inventory",
OXLibExport = "ox_lib",
@@ -81,16 +82,23 @@ for Type, ScriptTable in pairs(filesToLoad) do
local state = GetResourceState(scriptName)
-- if the resource is started, load the file
if state == "started" then
if scriptName == Exports.OXLibExport and GetResourceState(Exports.OXCoreExport):find("start") then
if debugMode then print("OX_Core found, skipping OX_Lib loading") end
goto skip
end
-- Force items into a table if they are not
if type(files) == "string" then
files = { files }
end
for _, file in pairs(files) do
--print("^5CoreLoader^7: '"..k.."/"..file.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
--print("^5CoreLoader^7: '"..scriptName.."/"..file.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
local fileLoader = assert(load(LoadResourceFile(scriptName, (file)), ('@@'..scriptName..'/'..file)))
fileLoader()
print("^5CoreLoader^7: ^2loaded ^1Core ^2file^7: ^3"..scriptName.."^7/^3"..(file):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").." ^2into ^7'"..GetCurrentResourceName().."'")
if debugMode then
print("^5CoreLoader^7: ^2loaded ^1Core ^2file^7: ^3"..scriptName.."^7/^3"..(file):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").." ^2into ^7'"..GetCurrentResourceName().."'")
end
end
::skip::
end
-- if script is in server, but not started warn the user
@@ -118,7 +126,7 @@ for _, v in pairs({ -- This is a specific load order
'_eventDebug.lua',
'callback.lua',
'coreloader.lua', -- needs to be second to load all core related stuff before everything else
'coreloader.lua',
'duifunctions.lua',
@@ -168,6 +176,8 @@ for _, v in pairs({ -- This is a specific load order
'vehicles.lua',
'effects.lua',
--'warmenu.lua',
'_authToken.lua',
-- Do version check last
'_scriptversioncheck.lua'
}) do
@@ -177,6 +187,50 @@ for _, v in pairs({ -- This is a specific load order
local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v)))
fileLoader()
if debugMode then
print("^5Success^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
-- 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("ps-inventory", "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

View File

@@ -0,0 +1,266 @@
local notifications = {}
local spacing = 60 -- vertical spacing between notifications
local activeDrawing = false -- flag for drawing loop state
local function loadTextureDict(dict)
if not HasStreamedTextureDictLoaded(dict) then
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
end
end
--- GTA Section ---
local notifTypes = {
success = "✔️",
error = "",
warning = "⚠️",
police = "🚓",
ambulance = "🚑",
}
function gtaNotify(title, message, emoji, src)
local notif = {
title = title,
message = message,
emoji = notifTypes[emoji] or "",
state = "enter",
startTime = GetGameTimer(),
progress = 0,
holdTime = 8000,
slideDuration = 100,
currentOffset = 0,
}
table.insert(notifications, notif)
-- Activate drawing loop if not already running
-- This allows it to be silent until the first notifcation is called, then the loop starts
if not activeDrawing then
activeDrawing = true
StartGTADrawingLoop()
end
end
-- Drawing loop as a separate controlled thread
function StartGTADrawingLoop()
--loadTextureDict("timerbars")
CreateThread(function()
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("timerbars", "all_black_bg", posX + 0.11, posY + 0.025, 0.2, 0.053, 0.0, 255, 255, 255, 255)
-- Title text
local moveMessage = false
if not notif.title or notif.title == "" then
moveMessage = true
else
drawGTANotiText(8, 0.4, vec2(0.75, 0.975), notif.title, vec2(posX, posY))
end
-- Message text
drawGTANotiText(4, 0.3, vec2(0.75, 0.975), notif.message, vec2(posX, posY + (moveMessage and 0.015 or 0.03)))
-- Emoji
drawGTANotiText(0, 0.3, vec2(0.75, 0.995), notif.emoji, vec2(posX, posY + 0.015))
::continue::
end
Wait(0)
end
activeDrawing = false -- No notifications left, pause drawing
end)
end
function drawGTANotiText(font, scale, wrap, string, pos)
SetTextFont(font)
SetTextScale(scale, scale)
SetTextWrap(wrap.x, wrap.y)
SetTextJustification(2)
SetTextColour(255, 255, 255, 255)
SetTextOutline()
SetTextEntry("STRING")
AddTextComponentString(string)
DrawText(pos.x, pos.y)
end
------------------
-- 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)

344
ui_modules/progressbar.lua Normal file
View File

@@ -0,0 +1,344 @@
local inProgress = false
local function loadTextureDict(dict)
if not HasStreamedTextureDictLoaded(dict) then
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
end
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)

127
ui_modules/skillcheck.lua Normal file
View File

@@ -0,0 +1,127 @@
local activeSkillCheck = false
function gtaSkillCheck()
if activeSkillCheck then return end
local result = false
local successes = 0
local barsRequired = 3
for bar = 1, barsRequired do
activeSkillCheck = true
local width, height = 0.2, 0.01
local x, y = 0.5, 0.8
-- Random highlighted zone
local highlightSize = math.random(10, 20) / 100
local highlightStart = math.random(10, 50) / 100
local highlightEnd = highlightStart + highlightSize
local highlightAlpha = 0
local cursorPos = 0.0
local cursorSpeed = 0.025
local movingRight = true
while activeSkillCheck do
Wait(0)
createScaleBars(x, y, width, height)
local pulse = (math.sin(GetGameTimer() / 250) + 1) / 2 -- Creates a pulsing effect
highlightAlpha = math.floor(150 + (pulse * 105)) -- Pulsing between 150 and 255 alpha
-- Draw highlighted zone (success area) with pulsing effect
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, 93, 182, 229, highlightAlpha )
-- Draw moving cursor
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
-- Move cursor
if movingRight then
cursorPos += cursorSpeed
if cursorPos >= 1.0 then movingRight = false end
else
cursorPos -= cursorSpeed
if cursorPos <= 0.0 then movingRight = true end
end
if IsControlJustPressed(0, 177) then -- Backspace to cancel
local displayTime = GetGameTimer() + 2000
PlaySoundFrontend(-1, 'Highlight_Cancel', 'DLC_HEIST_PLANNING_BOARD_SOUNDS', 1)
while GetGameTimer() < displayTime do
Wait(0)
createScaleBars(x, y, width, height)
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, 228, 52, 52, 255)
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
drawSuccessText(x, y, "Failed", 228, 52, 52)
end
return false
end
-- Check for keypress (E)
if IsControlJustPressed(0, 38) then
activeSkillCheck = false
result = cursorPos >= highlightStart and cursorPos <= highlightEnd
if result then
PlaySoundFrontend(-1, "YES", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
else
PlaySoundFrontend(-1, 'Highlight_Cancel', 'DLC_HEIST_PLANNING_BOARD_SOUNDS', 1)
end
local displayTime = GetGameTimer() + 2000
while GetGameTimer() < displayTime do
Wait(0)
createScaleBars(x, y, width, height)
-- Draw highlighted zone
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, result and 93 or 228, result and 182 or 52, result and 229 or 52, 180)
-- Draw stationary cursor at result position
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
-- Display result text
drawGTASuccessText(x, y, result and "Success" or "Failed", result and 114 or 228, result and 204 or 52, result and 144 or 52)
end
if result then
successes += 1
else
return false
end
end
end
end
activeSkillCheck = false
print("^5GTAUI^7: ^2Skill Check Result^7: ^3" .. successes .. "^7/^3" .. barsRequired.."^7")
return successes == barsRequired
end
function drawGTASuccessText(x, y, text, r, g, b)
SetTextFont(8)
SetTextScale(0.45, 0.45)
SetTextColour(r, g, b, 255)
SetTextDropshadow(0, 0, 0, 0, 255)
SetTextEdge(2, 0, 0, 0, 150)
SetTextDropShadow()
SetTextOutline()
SetTextCentre(true)
SetTextEntry("STRING")
SetTextCentre(true)
SetTextEntry("STRING")
AddTextComponentString(text)
DrawText(x, y + 0.03)
end
function createScaleBars(x, y, width, height)
-- Draw background box
DrawSprite("timerbars", "all_black_bg", x - (width / 4) - 0.006, y, (width / 2) + 0.08, height + 0.04, 0.0, 255, 255, 255, 255)
DrawSprite("timerbars", "all_black_bg", x + (width / 4) + 0.006, y, (width / 2) + 0.08, height + 0.04, 180.0, 255, 255, 255, 255)
-- Draw full bar (dark background)
DrawRect(x, y, width, height, 100, 100, 100, 255)
end
exports("skillCheck", function(...)
if GetCurrentGameName() == "rdr3" then
--redSkillCheck(...)
else
gtaSkillCheck(...)
end
end)

318
ui_modules/target.lua Normal file
View File

@@ -0,0 +1,318 @@
-- Global Key Table, defined once.
---
local KEY_TABLE = { 38, 29, 47, 23, 45, 159, 162, 163 }
-- Mapping of key codes to human-readable key names.
local Keys = {
[322] = "ESC", [288] = "F1", [289] = "F2", [170] = "F3", [166] = "F5",
[167] = "F6", [168] = "F7", [169] = "F8", [56] = "F9", [57] = "F10",
[243] = "~", [157] = "1", [158] = "2", [160] = "3", [164] = "4", [165] = "5",
[159] = "6", [161] = "7", [162] = "8", [163] = "9", [84] = "-", [83] = "=",
[177] = "BACKSPACE", [37] = "TAB",
[44] = "Q", [32] = "W", [38] = "E", [45] = "R", [245] = "T", [246] = "Y",
[303] = "U", [199] = "P",
[39] = "[", [40] = "]", [18] = "ENTER", [137] = "CAPS",
[34] = "A", [8] = "S", [9] = "D", [23] = "F", [47] = "G",
[74] = "H", [311] = "K", [182] = "L", [21] = "LEFTSHIFT",
[20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N",
[244] = "M", [82] = ",", [81] = "."
}
-- Tables for storing created targets.
local TextTargets = {} -- For fallback DrawText3D targets.
local targetEntities = {} -- For entity targets.
function createEntityTarget(entity, opts, dist)
startTargetLoop()
targetEntities[#targetEntities + 1] = entity
local entityCoords = GetEntityCoords(entity)
local existingTarget = nil
for _, target in pairs(TextTargets) do
if #(target.coords - entityCoords) < 0.01 then
existingTarget = target
break
end
end
if existingTarget then
for i = 1, #opts do
local key = KEY_TABLE[#existingTarget.options + i]
opts[i].key = key
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
existingTarget.options[#existingTarget.options + 1] = opts[i]
end
updateCachedText(existingTarget)
else
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
TextTargets[entity] = {
coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z),
buttontext = tempText,
options = opts,
dist = dist,
text = table.concat(tempText, "\n")
}
end
end
function createZoneTarget(data, opts, dist)
startTargetLoop()
local existingTarget = nil
for _, target in pairs(TextTargets) do
if #(target.coords - data[2]) < 0.01 then
existingTarget = target
break
end
end
if existingTarget then
for i = 1, #opts do
local key = KEY_TABLE[#existingTarget.options + i]
opts[i].key = key
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
existingTarget.options[#existingTarget.options + 1] = opts[i]
end
updateCachedText(existingTarget)
else
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
TextTargets[data[1]] = {
coords = data[2],
buttontext = tempText,
options = opts,
dist = dist,
text = table.concat(tempText, "\n")
}
end
return data[1]
end
function createModelTarget(models, opts, dist)
startTargetLoop()
if type(models) ~= "table" then
models = { models }
end
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
local keyStr = ""
for i, m in ipairs(models) do
keyStr = keyStr .. tostring(m) .. (i < #models and "_" or "")
end
local targetKey = "model_" .. keyStr
TextTargets[targetKey] = {
models = models,
buttontext = tempText,
options = opts,
dist = dist,
coords = vec3(0, 0, 0),
text = table.concat(tempText, "\n")
}
return targetKey
end
function removeEntityTarget(entity)
TextTargets[entity] = nil
end
function removeZoneTarget(target)
TextTargets[target] = nil
end
function removeModelTarget(model)
TextTargets[model] = nil
end
exports("createEntityTarget", createEntityTarget)
exports("createZoneTarget", createZoneTarget)
exports("createModelTarget", createModelTarget)
exports("removeEntityTarget", removeEntityTarget)
exports("removeZoneTarget", removeZoneTarget)
exports("removeModelTarget", removeModelTarget)
-------------------------------------------------------------
-- Fallback: DrawText3D Targets (Experimental)
-------------------------------------------------------------
local started = false
function startTargetLoop()
if started then return end
Config = {
System = {
}
}
started = true
local fileLoader = assert(load(LoadResourceFile("jim_bridge", ('starter.lua')), ('@@jim_bridge/starter.lua')))
fileLoader()
-- Model Entity Refresher
CreateThread(function()
while true do
local pedCoords = GetEntityCoords(PlayerPedId())
for _, target in pairs(TextTargets) do
if target.models then
for _, model in ipairs(target.models) do
local entity = GetClosestObjectOfType(pedCoords.x, pedCoords.y, pedCoords.z, target.dist, model, false, false, false)
if entity and entity ~= 0 then
target.entity = entity
target.coords = GetEntityCoords(entity)
break
end
end
end
end
Wait(3000) -- Refresh every 3s
end
end)
-- Main Target Loop
CreateThread(function()
while true do
local ped = PlayerPedId()
local pedCoords = GetEntityCoords(ped)
local camCoords = GetGameplayCamCoord()
local camRot = GetGameplayCamRot(2)
local camForward = RotationToDirection(camRot)
local closestTarget, closestDist, targetEntity = nil, math.huge, nil
for _, target in pairs(TextTargets) do
local coords = target.coords
if coords then
local dist = #(pedCoords - coords)
if dist <= target.dist then
local normVec = normalizeVector(coords - camCoords)
local dot = camForward.x * normVec.x + camForward.y * normVec.y + camForward.z * normVec.z
if dot > 0.5 and dist < closestDist then
closestTarget = target
closestDist = dist
targetEntity = target.entity
end
end
end
end
for _, target in pairs(TextTargets) do
if not target.coords then goto continue end
local dist = #(pedCoords - target.coords)
if dist > target.dist then goto continue end
local isClosest = (target == closestTarget)
for i, opt in ipairs(target.options) do
if IsControlJustPressed(0, opt.key) and isClosest then
if (not target.canInteract or target.canInteract()) and
(not opt.item or hasItem(opt.item)) and
(not opt.job or hasJob(opt.job, nil)) then
if opt.onSelect then opt.onSelect(targetEntity) end
if opt.action then opt.action(targetEntity) end
end
end
end
local baseZ, lineHeight = target.coords.z + 1.0, -0.16
local lineOffset = 0
for i, opt in ipairs(target.options) do
if (not target.canInteract or target.canInteract()) and
(not opt.item or hasItem(opt.item)) and
(not opt.job or hasJob(opt.job, nil)) then
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + lineHeight * lineOffset), target.buttontext[i], isClosest)
lineOffset = lineOffset + 1
end
end
::continue::
end
Wait(1) -- Throttled
end
end)
end
function DrawText3D(coord, text, highlight)
SetTextScale(0.30, 0.30)
SetTextFont(0)
SetTextProportional(1)
SetTextColour(255, 255, 255, 215)
SetTextEntry("STRING")
SetTextCentre(true)
local totalLength = string.len(text)
local textMaxLength = 99 -- max 99
local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text
AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text)
SetDrawOrigin(coord.x, coord.y, coord.z, 0)
DrawText(0.0, 0.0)
local count, length = GetLineCountAndMaxLength(text)
local padding = 0.005
local heightFactor = (count / 43) + padding
local weightFactor = (length / 150) + padding
local height = (heightFactor / 2) - padding / 1
local width = (weightFactor / 2) - padding / 1
DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
ClearDrawOrigin()
end
--- Calculates the number of lines and the maximum line length from the given text.
---
--- @param text string The text to analyze.
--- @return number, number The line count and maximum line length.
---
--- @usage
--- ```lua
--- local count, maxLen = GetLineCountAndMaxLength("Hello World")
--- ```
function GetLineCountAndMaxLength(text)
local lineCount, maxLength = 0, 0
for line in text:gmatch("[^\n]+") do
lineCount += 1
local lineLength = string.len(line)
if lineLength > maxLength then
maxLength = lineLength
end
end
if lineCount == 0 then lineCount = 1 end
return lineCount, maxLength
end
function RotationToDirection(rot)
local adjust = math.pi / 180
return vec3(
-math.sin(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
math.cos(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
math.sin(adjust * rot.x)
)
end
function normalizeVector(vec)
local len = math.sqrt(vec.x^2 + vec.y^2 + vec.z^2)
if len ~= 0 then
return vec3(vec.x / len, vec.y / len, vec.z / len)
else
return vec3(0, 0, 0)
end
end
-- Helper to update cached text.
function updateCachedText(target)
target.text = table.concat(target.buttontext, "\n")
end

View File

@@ -1 +1,17 @@
2.0.09
2.0.18
- Fix ox_inv client item caching (images)
- Add better checks if errors in ox_inv items and add more prominent error messages all around
- Tidy up several debugPrints
- Move AuthEvents to new file
- Fix clearStash error with PSInv and JPRInv
- Add extra trigger event for PSInv when opening stash (hopefully fix missing items for some)
- Fix onPlayerUnloaded() not triggering
- Add support for esx_society in openBossMenu()
- Added support for okokbilling to billPlayer()
- Rework progressBar and ui_modules folder
- Rework isAnimal.lua to be more automated and smarter
- Rework/Fix auto detection of QBInvNew
- Add new functions to get players current Inventory Weight
https://github.com/jimathy/jim_bridge