Compare commits

..

217 Commits
v2.0 ... 2.0.20

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

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

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

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

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

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

---
Also slipped in an `experimental` function that grabs inventory config files to check their set InventoryWeight and InventorySlots to automatically apply it for other functions
---
2025-06-26 20:03:12 +01:00
Jim Shield
9d74e7cb46 Update setMetadata server event
Before it was JUST for durability but fixed it to work for all item metadata
2025-06-26 19:57:03 +01:00
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
Jim Shield
51473bf0b8 Version Bump
`2.0.08` - `2.0.09`
2025-05-12 13:52:14 +01:00
Jim Shield
921b973f81 Try and fix ESX loading
So information and request for help.

The issue here is that my scripts require Job and Vehicle information when the script starts, to do a debug check if the job exists for the location.

ESX doesn't share this with the client for some reason, so this requires me to request it from the server side at script ensure.

This is causing issues because it delays loading of the rest of `jim_bridge` while it slowly requests `ESX.GetJobs()` table from server side

This is delayed even more when the script is placed towards the top of the server load order and it needs to load other things like MLO's

As its trying to load this the rest of the script carries on loading with missing information.

So it loads the script thinking there are 0 jobs and breaks it.

If anyone knows of a better way of doing this then please help.
The only partial solution is to start my scripts last in the server load order
2025-05-12 13:51:32 +01:00
Jim Shield
42d08ccf4c Tidy up the stashRemoveItem function 2025-05-12 13:44:37 +01:00
Jim Shield
68412d8006 Enforce numbers when changing stats
if the value ends up being `nil` this *should* force it to 0

Also added a check for if it can actually give `randomReward` before trying to
2025-05-11 14:16:02 +01:00
Jim Shield
4c89ec3190 Version Bump
`2.0.07` - `2.0.08`
2025-05-10 19:55:51 +01:00
Jim Shield
b2fa1ef247 Update starter.lua
Updated and putting into use the core file loader function in starter.lua
2025-05-10 19:54:31 +01:00
Jim Shield
9872c77591 Only allow script version checks for my scripts
Added a check to version check for if "Jimathy" is the author, this then stops it and doesn't check for the versions on my github repositories
2025-05-10 19:20:47 +01:00
Jim Shield
7ecb2f18e9 Refactor ESX Core loading
I realised I was loading ESX the stupidest way

I think I was loading ESX object, then instantly clearing it and then used workarounds to try and reload it (thinking is hard ok)

It should now load correctly and faster

also I realised my custom `onPlayerLoaded()` function was loading too fast, so I've added a delay to it but removed the while loops for esx checks that could potentially break the script
2025-05-07 23:01:29 +01:00
Jim Shield
d0c09f3e00 switch old ps-inv stash call back to old event
older ps didn't call `ps-inventory` for stash, just `inventory`
2025-05-07 21:12:38 +01:00
Jim Shield
c1e38a93bd Version Bump
`2.0.06` - `2.0.07`
2025-05-07 16:42:00 +01:00
Jim Shield
6a3041524a Re-add old qb-inv support for canCarry()
When updating to use the new export for qb-inv, I accidently removed the old functions, this readds them and fixes support for it
2025-05-07 16:38:57 +01:00
Jim Shield
f069db5c61 Update QBInv stash retrieval
Now uses GetInventory to grab the stash from its cache instead of directly from database

Also fixes old qb inv support for stash retrieval
2025-05-07 16:36:18 +01:00
Jim Shield
3b768d5c7d update isStarted() fuction
Add fallback for if somehow the script name being checked was sent as `nil`, if `nil` it returns false
2025-05-07 13:56:26 +01:00
Jim Shield
6b8698353b Expand on the core file loading system
This is still not in use for now, but it WILL be used in the future

I'm getting too many support tickets about the "Failed to load" warning in console about files they don't have/need
(if you don't run `ox_core` don't worry about `ox_core` not loading)

This system is intended to load it through the script instead of `fxmanifest.lua`

It is intended to check if the user has the required script and also warn them if it's not started (load order issue), or just load it as if it were told to in the fxmanifest
2025-05-07 13:55:12 +01:00
Jim Shield
5868312388 fix ps-inv/lj-inv check
If you didn't have either PS-Inventory or LJ-Inventory, this ended up breaking loading as it ended up checking is `nil` was started
2025-05-07 13:32:54 +01:00
Jim Shield
b0b139b179 Rework ps-inventory support
PS-Inv used to be exactly the same as QB-Inv so alot of the function calls were shared

This separates them and makes it easier to make changes to support their inventory

Also unified the calls for server side functions into single functions instead of having separate per inventory

Also moves:
- `invImg()` to inventories.lua
- `registerStash()` to stashcontrol.lua
2025-05-07 12:58:39 +01:00
Jim Shield
4bf4806b9e workaround for a prop claiming it couldn't find W
this is just a quick workaround, if a prop is created without a heading, it auto sets it to 0
2025-05-07 12:54:23 +01:00
Jim Shield
826e745fc3 fallback for the fallback
Not having the server convars added to the server.cfg was causing issues for some users

it should have worked, but this forces things to gta if you don't have any values set for them anywhere

I've added fallbacks to "gta" settings to hopefully stop fivem errors
2025-05-07 01:59:03 +01:00
Jim Shield
bc2867984c Version Bump
`2.0.05` - `2.0.06`
2025-05-06 10:53:34 +01:00
Jim Shield
31633bc61d crafting exploit fixes
This has several changes

When crafting, it now checks if the inventory is open and stops crafting dead and warns the player with a print.

Multicraft now checks if you have space in your inventory for the items and limits the max amount craftable

Also fixes the "title" of the qb-input box when crafting

Also changed the anim flag for the "placing items" to hopefully keep you in place
2025-05-06 00:17:07 +01:00
Jim Shield
1623feacf1 fix multicraft for qb-input side
I forgot to correct the result as qb-input returns strings, this fixes that

Also added a fallback for if the user deletes the value and tries to craft `nil`
2025-05-05 22:52:46 +01:00
Jim Shield
a203657e2a Add event to clear AuthEvent for player
When a player logs out and back in (switches character) it was thinking that the `AuthEvent` was being called more than once, triggering a warning message

This should clear the event when a player unloads and then allows them to request a new one
2025-05-05 20:01:15 +01:00
Jim Shield
1f4a2ee3d9 refactor checkStashItem()
This'll probably break it, (it works for me) but refactored the stash item check, it was calling `stashHasItem()` for every ingredient check, now it sends the whole table of ingredients per item to craft and checks them together instead of separately

Also removes a `Wait()`, as it shouldn't need it with this change..maybe
2025-05-05 12:28:30 +01:00
Jim Shield
e9c54cbdd4 multiCraft() refactor
An attempt to speed up `multiCraft()` function creation logic

Before it created a table of items while it was trying to check them I've separated this so it just gets that out of the way and checks items in it's own `for` loop

Also added a `maxCreation` variable that users can edit if needed which decides how many of an item can be created (I'll change this soon probably to tie into the config options in scripts for multicraft again soon

Also removed a few `Wait()`s that were there to help it not get confused, but after testing I believe this didn't make a difference, let me know if there's any issues with stuff like it saying it can craft when you dont have the items
2025-05-05 12:24:38 +01:00
Jim Shield
4e8a15590f make a checkToken() event its own function
Several events were calling very similar check token events, so I've just moved them to one to help unify it and help future debugging
2025-05-05 11:44:51 +01:00
Jim Shield
c745d51832 Fix up dupwarn and additem auth messages
Forgot to update this, now prints an optional message if available

and made dupeWarn kick message less specific
2025-05-04 20:33:51 +01:00
Jim Shield
f9b97b7505 Version bump 2.0.05 2025-05-04 16:24:55 +01:00
Jim Shield
489da64c5e add a new warning for token auth 2025-05-04 15:40:20 +01:00
Jim Shield
07c35cd917 workaround for ox_inventory returning a bool
I think, if ox_inventory checks a stash that doesn't exist yet, it returns a boolean or `nil`

This checks for that and instead returns a table to hopefully stop the scripts from breaking
2025-05-04 15:21:02 +01:00
Jim Shield
3f72a21d75 Update bigMessageInstance.lua 2025-05-03 20:28:13 +01:00
Jim Shield
4ad6170a49 Fix ox_target items variable
Completely overlooked that ox_target item variable is `items` not `item`

changes
```lua
                item = opts[i].item or nil,
```
to:
```lua
                items = opts[i].item or nil,
```
2025-05-03 00:11:28 +01:00
Jim Shield
18df0817b1 Make version checks a bit more obvious
Adds more obvious prints if the current version is outdated

(and also repeats it every 20 minutes)
2025-05-02 22:22:47 +01:00
Jim Shield
0eca7ae29e Fix CodeM item slot check for metadata function
The slot data for CodeM apparently returns as a string, this fixes it trying to compare a string to a number

```lua
            if v.name == item and v.slot <= lowestSlot then
```
to
```lua
            if v.name == item and tonumber(v.slot) <= lowestSlot then
```
2025-05-02 22:20:26 +01:00
Jim Shield
6a5913230f Add better fallbacks for if no metadata table is found
```lua
                durability = v.info.durability
```
to:
```lua
                durability = v.info and v.info.durability or nil
```
2025-05-02 22:16:51 +01:00
Jim Shield
b7a284e051 Fix new duty sync
This should have been placed in a `onPlayerLoaded()` but it was checking at script start for player info before that info was able to be grabbed
2025-05-02 12:29:02 +01:00
Jim Shield
0accdda2dd version bump
`2.0.04`
2025-05-02 11:36:54 +01:00
Jim Shield
37b855d7a7 Better syncing of onDuty variable
Added a check to the script start to get the current players duty (helpful if yours saves the duty through restarts)

Also added core specific triggers for job/duty changes to help keep these in sync
2025-05-02 11:20:09 +01:00
Jim Shield
7432dd5118 revert qbinv dectetion
Wasn't playing as well as I thought it would..
2025-05-01 20:05:00 +01:00
Jim Shield
59ff34c9cc Add exploit protection for :Crafting:GetItem
I unfortunately forgot to add this before when adding the extra exploit protection. Now demands an auth token before allowing crafting results to help reduce exploiters calling it externally
2025-05-01 18:07:39 +01:00
Jim Shield
b81ada265b Better support for RedM's RSG Core
Nothing public uses this yet, but its very similar to QBCore which makes it very for me to support

Nothing special to note, just improves the integration for future ventures into RedM
2025-05-01 17:49:37 +01:00
Jim Shield
65c9c6c586 Add automated check for QBInv version
Currently if you still have the old qb-inventory or a css edit it would require you to manually edit the starter.lua
```lua
QBInvNew = true
```

I'm testing a change to grab the version and return true of false if its above `2.0.0` (200)
```lua
local qbInvVer = GetResourceMetadata(Exports.QBInv, 'version', nil):gsub("%.", "")
QBInvNew = tonumber(qbInvVer) >= 200
```
If this causes issue's I'll revert this
2025-05-01 17:08:21 +01:00
Jim Shield
278cbb4d34 fix discord bot yml 2025-05-01 13:36:44 +01:00
Jim Shield
381c19e701 add gitbot yml 2025-05-01 00:20:35 +01:00
Jim Shield
fc2ebefc9e fix CodeM server side inventory info grab 2025-04-30 17:54:04 +01:00
Jim Shield
423b75e7d9 Tgiann Inv support added 2025-04-30 17:53:26 +01:00
Jim Shield
e984a437df attempt to fix shops for QS-Inv 2025-04-30 16:54:33 +01:00
Jim Shield
0818dae0df change qs-inv metadata call to info 2025-04-30 13:21:39 +01:00
Jim Shield
771a2a3217 fix quasar crafting stash item removal 2025-04-29 23:26:10 +01:00
Jim Shield
04b3f87b32 change CodeM metadata check to info 2025-04-29 19:55:30 +01:00
Jim Shield
6c3d0ffc5e change CodeM client inv check to getUserInventory() 2025-04-29 19:55:09 +01:00
Jim Shield
3f093ce934 fix QS-Inv registerStash function 2025-04-29 17:43:37 +01:00
Jim Shield
9a47ec59b0 bump fxmanifest version 2025-04-29 17:24:20 +01:00
Jim Shield
69ce8d9225 remove nui from jim_bridge, i think its killing things 2025-04-29 17:23:28 +01:00
Jim Shield
93b884de01 Bump to 2.0.01 2025-04-29 12:31:05 +01:00
Jim Shield
a308f82be9 version bump 2025-04-29 12:27:00 +01:00
Jim Shield
b3100a336c possible fix for lib error 2025-04-29 12:26:49 +01:00
Jim Shield
8f7f9bb009 force stash to a table when removing items 2025-04-29 02:37:47 +01:00
Jim Shield
25dadae446 Merge pull request #41 from jimathy/1.2 2025-04-28 18:19:38 +01:00
Jim Shield
1d588c4f41 Add a complete readme with documentation 2025-04-22 19:17:34 +01:00
Jim Shield
ac58fe4bb7 Fix and enhance update checker 2025-04-22 19:17:03 +01:00
Jim Shield
45c5af8e81 Fix duplicate functions and typos 2025-04-22 19:16:31 +01:00
Jim Shield
673d9e3a12 fix alt forcing nui focus 2025-04-20 23:44:09 +01:00
Jim Shield
738e11c8e3 Add support for built in nui menu 2025-04-20 19:17:40 +01:00
Jim Shield
842379e8f9 Add override convar checks 2025-04-20 19:17:16 +01:00
Jim Shield
d1bb796d5f implement inbuilt nui menu 2025-04-20 01:22:32 +01:00
Jim Shield
b7db98ffdc esx fixes 2025-04-18 19:45:56 +01:00
Jim Shield
6ce6770df4 add checks for if MySQL has loaded for ESX 2025-04-18 16:14:42 +01:00
Jim Shield
56ef3b75b2 Fix change dui functions 2025-04-14 21:58:38 +01:00
Jim Shield
bd1be5e953 add function to get animal anim table for that ped 2025-04-13 19:07:02 +01:00
Jim Shield
8f72968e76 Create FUNDING.yml 2025-04-13 13:26:09 +01:00
Jim Shield
b27f91ac87 fix playerdata check for isDead in server 2025-04-12 01:09:21 +01:00
Jim Shield
35357c07a3 add isDead and isDown to getPlayer() 2025-04-12 00:19:22 +01:00
Jim Shield
943436467e add ox checkbox option for input 2025-04-12 00:18:48 +01:00
Jim Shield
747fd272d4 fix bigmessage scaleform being local 2025-04-12 00:17:47 +01:00
Jim Shield
3ec00956df Enhance drawtext feature 2025-04-08 17:51:30 +01:00
Jim Shield
1248102635 Add more support for RedM RSGInv 2025-04-08 17:48:38 +01:00
Jim Shield
2be706a860 hopefully fix esx loading 2025-04-08 17:47:12 +01:00
Jim Shield
2da2c56705 add basic support for RedM (RSGCore) 2025-04-07 20:52:59 +01:00
Jim Shield
4d8305c844 changes for beta branch 2025-04-01 14:15:50 +01:00
Jim Shield
8611bc6d3c I am still alive 2025-03-26 21:43:48 +00:00
Jim Shield
2572f86030 input compat fixes 2025-03-11 22:41:13 +00:00
Jim Shield
83ba74bc12 fixes 2025-03-08 20:55:05 +00:00
Jim Shield
6bfb549fd0 refactor + attempt better support for other inventories 2025-03-08 13:44:05 +00:00
Jim Shield
31a7ac2951 general fixes and updates 2025-03-07 13:32:10 +00:00
Jim Shield
1cb2bdb525 fixes for jim-crafting changes 2025-03-07 13:30:17 +00:00
Jim Shield
daa9dca142 fix createCallback complaining on client side 2025-03-07 13:29:43 +00:00
Jim Shield
c947993e9e Add multiscript banking functions 2025-03-07 13:27:47 +00:00
Jim Shield
8dd2b7ae9a Beta: Fixes for existing scripts + feature for jim-crafting 2025-03-06 23:49:07 +00:00
Jim Shield
8eb98bff29 (Beta) Fixes for multiframework support 2025-03-01 12:58:43 +00:00
Jim Shield
f9812a689e Add files via upload 2025-02-22 13:21:54 +00:00
87 changed files with 15146 additions and 7796 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: jixelpatterns
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://jimathy666.tebex.io/']

17
.github/workflows/notify-discord.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Discord Commit Notifier
on:
push:
branches:
- '*' # triggers on all branches
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Discord Commits
uses: Sniddl/discord-commits@v1.6
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
template: "avatar-with-link"
include-extras: true

88
README.md Normal file
View File

@@ -0,0 +1,88 @@
# Jim_Bridge
This script is intended to be used with my all my scripts (soon)
It was started due to wanting to bring the same features from some scripts into others with minimal work and without multiple updates fer script
It BRIDGES frameworks and cores together through this script and does it best to detect what is being used to automate the process
- Having certain functions in one place makes it easier to update, enchance and fix things already in place
- This brings the possibility of branching to mutliple frameworks as I've added some already:
- `"qb-core"`
- `"qbx-core"`
- `"ox_core"`
- `"es_extended"` - requires ox_lib and ox_inventory
- `"rsg-core"` - basic support for RedM's RSG Core
All of my scripts will use this script and be added as a dependancy
This was originally designed to be used for my scripts but has grown into a whole framework of unified functions that anyone can use for their own, I encourage it
------
## I want this script to grow with help of others who know more about other cores, I'm not a book of framework knowledge
This script was designed by me through over a year of research and testing.
Some of it of it hasn't been personally tested but the information has been gathered through documentation on other scripts
I hope it works as well I intend, but feel free to do pull requests if you know how to fix an issue
(Please also keep it to a similar format to prevent breakages in other scripts)
------
## Installation
The installation of this script is simple
- REMOVE `-main` from the folder name, like any other github hosted script
- It just needs to start before any script that requires it
- It can start before core scripts if you want
- For example with `qb-core` I personally place this script in `resources > [standalone]`
### Optional
I've added the ability to add override server convars to your server.cfg
- This can be used to ensure you don't have silly mistakes like forgetting to change what inventory system you use
- Also it can force debug mode off, to ensure your live server doesn't accidently get polyzones and debug information showing
- This isn't required but helpful if you are like me
```
# Jim Config Settings
# These optional but forces settings for all jim scripts
#-------
# Add this to your live server.cfg and set to true to force debug mode off
# Set to false if to dev server to allow debug mode
setr jim_DisableDebug true
setr jim_DisableEventDebug true
# Force the default setting for what framework scripts should be used
setr jim_menuScript qb # qb, ox, gta, lation
setr jim_notifyScript gta # qb, ox, gta, esx, okok, red, lation
setr jim_drawTextScript qb # qb, ox, gta, esx, lation
setr jim_progressBarScript qb # qb, ox, gta, esx, lation
setr jim_skillCheckScript qb # qb, ox, gta, lation
setr jim_dontUseTarget false # Set to true to disable target systems and use draw text 3d
```
-----
## Support for different frameworks and scripts
In `starter.lua` is the list of script folder names, this is already setup but this is for people who have customised/renamed their cores or scripts
# WIP
## Documentation
For organised documentation please see the new `documentation` folder within this resource
## Usage
In your own resource, simply call the desired function exported from `jim_bridge`. Each function is built to work across multiple frameworks, offering compatibility and consistency.
```lua
-- Example usage:
createCallback("myCallback", function(data)
print("Callback received:", data)
end)
triggerCallback("myCallback", "Hello World")
```
---

67
_versioncheck.lua Normal file
View File

@@ -0,0 +1,67 @@
local function parseVersion(version)
local parts = {}
for num in version:gmatch("%d+") do
table.insert(parts, tonumber(num))
end
return parts
end
local function compareVersions(current, newest)
local currentParts = parseVersion(current)
local newestParts = parseVersion(newest)
for i = 1, math.max(#currentParts, #newestParts) do
local c = currentParts[i] or 0
local n = newestParts[i] or 0
if c < n then return -1
elseif c > n then return 1 end
end
return 0 -- equal
end
function CheckBridgeVersion()
if IsDuplicityVersion() then
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
local lines = {}
for line in body:gmatch("[^\r\n]+") do
table.insert(lines, line)
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()

32
clientFrameworkCache.lua Normal file
View File

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

View File

@@ -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)
```

454
frameworkCache.lua Normal file
View File

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

View File

@@ -1,14 +1,27 @@
name "Jim_Bridge" name "Jim_Bridge"
author "Jimathy" author "Jimathy"
version "2.0" version "2.0.20"
description "Framework Bridge By Jimathy" description "Framework Bridge By Jimathy"
fx_version "cerulean" fx_version "cerulean"
game "gta5" rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
lua54 'yes' games { 'gta5', 'rdr3' }
lua54 'yes'
files {
'starter.lua',
'shared/*.lua', files {
'shared/make/*.lua', 'starter.lua',
'shared/scaleforms/*.lua', 'shared/*.lua',
} 'shared/make/*.lua',
'shared/scaleforms/*.lua',
}
-- Version checker
server_scripts {
'frameworkCache.lua',
'_versioncheck.lua',
}
client_scripts {
'clientFrameworkCache.lua',
'ui_modules/*.lua',
}

118
shared/_authToken.lua Normal file
View File

@@ -0,0 +1,118 @@
-------------------------------------------------------------
-- Exploit Auth System
-------------------------------------------------------------
AuthEvent = nil
currentToken = nil
if isServer() then
local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen()
validTokens = validTokens or {}
createCallback(AuthEvent, function(source)
local src = source
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
--debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return ""
end
debugPrint("^1Auth^7:^2 Player Source^7: "..src.." ^2requested new token^7:", token)
validTokens[src] = token
timeOutAuth(validTokens[src], src) -- Give script 10 seconds, then clear token
return token
end)
function timeOutAuth(token, src)
local token = token
SetTimeout(10000, function()
if token == validTokens[src] then
print("^1--------------------------------------------^7")
print("^7Clearing token for player ^1"..src.."^7", token)
print("^7This shouldn't happen unless a token has been called by a player or script and it hasn't been used")
print("^1--------------------------------------------^7")
end
end)
end
RegisterNetEvent(getScript()..":clearAuthToken", function()
local src = source
debugPrint("^1Auth^7: ^2Manually removing token for Player Source^7:", src, validTokens[src])
validTokens[src] = nil
end)
receivedEvent = {}
authCooldown = {}
createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7")
return ""
end
if authCooldown[src] then
debugPrint("^1Auth^7: ^3Cooldown active^7 for Player ^1"..src.."^7, ignoring additional auth request")
return ""
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

@@ -1,149 +1,149 @@
-- IN NO WAY PERFECT -- ** Experimental debugging -- IN NO WAY PERFECT -- ** Experimental debugging
function toggleDebug() function toggleDebug()
Config.System.Debug = not Config.System.Debug Config.System.Debug = not Config.System.Debug
print("Debug Prints = "..tostring(Config.System.Debug)) print("Debug Prints = "..tostring(Config.System.Debug))
end end
exports("toggleDebug", toggleDebug) exports("toggleDebug", toggleDebug)
function getDebug() return Config.System.Debug end function getDebug() return Config.System.Debug end
exports("getDebug", getDebug) exports("getDebug", getDebug)
local origRegisterNetEvent = RegisterNetEvent local origRegisterNetEvent = RegisterNetEvent
local origTriggerEvent = TriggerEvent local origTriggerEvent = TriggerEvent
local origTriggerServerEvent = TriggerServerEvent local origTriggerServerEvent = TriggerServerEvent
local origTriggerClientEvent = TriggerClientEvent local origTriggerClientEvent = TriggerClientEvent
local origExecuteCommand = ExecuteCommand local origExecuteCommand = ExecuteCommand
local origRegisterCommand = RegisterCommand local origRegisterCommand = RegisterCommand
local origPairs = pairs local origPairs = pairs
local origiPairs = ipairs local origiPairs = ipairs
function getDebugInfo(info) function getDebugInfo(info)
local info = info local info = info
local level = 2 local level = 2
if info and info.short_src:match("scheduler.lua") then if info and info.short_src:match("scheduler.lua") then
repeat repeat
info = debug.getinfo(level, "nSl") info = debug.getinfo(level, "nSl")
level += 1 level += 1
local found = false local found = false
for _, v in pairs({ for _, v in pairs({
"deffered.lua", "deffered.lua",
"scheduler.lua", "scheduler.lua",
"_eventDebug.lua", "_eventDebug.lua",
"targets.lua", "targets.lua",
"init.lua", "init.lua",
"MySQL.lua", "MySQL.lua",
"helpers.lua", "helpers.lua",
}) do }) do
if info and info.short_src:match(v) then if info and info.short_src:match(v) then
found = true found = true
end end
end end
until not info or (info.short_src and found == false) until not info or (info.short_src and found == false)
end end
return " ^7[^3"..(info and info.short_src:match("^.+/(.+)$") or "unknown").."^7:^3"..(info and info.currentline or "unknown").."^7]" return " ^7[^3"..(info and info.short_src:match("^.+/(.+)$") or "unknown").."^7:^3"..(info and info.currentline or "unknown").."^7]"
end end
--This is just a for debugging, not important, just announces which events are being registered triggered when these functions are used --This is just a for debugging, not important, just announces which events are being registered triggered when these functions are used
function RegisterNetEvent(name, funct) function RegisterNetEvent(name, funct)
if Config.System.EventDebug then if Config.System.EventDebug then
if name:find("__ox_cb_") then if name:find("__ox_cb_") then
print("^6Bridge^7: ^2Registered ^3"..(isServer() and "Server" or "Client").." ^2Callback^7: ^6"..name:gsub("__ox_cb_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: ^2Registered ^3"..(isServer() and "Server" or "Client").." ^2Callback^7: ^6"..name:gsub("__ox_cb_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
else else
print("^6Bridge^7: ^2Registering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: ^2Registering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
end end
end end
origRegisterNetEvent(name, funct) origRegisterNetEvent(name, funct)
end end
function TriggerEvent(name, ...) function TriggerEvent(name, ...)
local data = {...} local data = {...}
if Config.System.EventDebug then if Config.System.EventDebug then
if name:find("__cfx_export") then if name:find("__cfx_export") then
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Export^7: ^6"..name:gsub("__cfx_export_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Export^7: ^6"..name:gsub("__cfx_export_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
else else
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
end end
for i, value in ipairs(data) do for i, value in ipairs(data) do
if value then if value then
local valueStr = (type(value) == "table" and json.encode(value) or tostring(value)) local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr)) print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
end end
end end end end
origTriggerEvent(name, ...) origTriggerEvent(name, ...)
end end
function TriggerServerEvent(name, ...) -- Client side, trigger a server event function TriggerServerEvent(name, ...) -- Client side, trigger a server event
local data = {...} local data = {...}
if Config.System.EventDebug then if Config.System.EventDebug then
if name:find("__ox_cb") then if name:find("__ox_cb") then
print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Server ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Server ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
else else
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Server ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Server ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
end end
for i, value in ipairs(data) do for i, value in ipairs(data) do
if value then if value then
local valueStr = (type(value) == "table" and json.encode(value) or tostring(value)) local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr)) print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
end end
end end end end
origTriggerServerEvent(name, ...) origTriggerServerEvent(name, ...)
end end
function TriggerClientEvent(name, ...) -- Server side, trigger a client event function TriggerClientEvent(name, ...) -- Server side, trigger a client event
local data = {...} local data = {...}
if Config.System.EventDebug then if Config.System.EventDebug then
if name:find("__ox_cb") then if name:find("__ox_cb") then
print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Client ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Client ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
else else
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Client ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Client ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
end end
for i, value in ipairs(data) do for i, value in ipairs(data) do
if value then if value then
local valueStr = (type(value) == "table" and json.encode(value) or tostring(value)) local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr)) print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
end end
end end end end
origTriggerClientEvent(name, ...) origTriggerClientEvent(name, ...)
end end
function RegisterCommand(command, funct, restrict) function RegisterCommand(command, funct, restrict)
if Config.System.EventDebug then if Config.System.EventDebug then
print("^6Bridge^7: ^2Registering ^2Command^7: /"..command.." ^7| ^4Funct^7: "..tostring(funct):gsub("function: ", "").." ^7| ^4Admin^7: "..(restict and "true" or "false")..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: ^2Registering ^2Command^7: /"..command.." ^7| ^4Funct^7: "..tostring(funct):gsub("function: ", "").." ^7| ^4Admin^7: "..(restict and "true" or "false")..getDebugInfo(debug.getinfo(2, "nSl")))
end end
origRegisterCommand(command, funct, restrict) origRegisterCommand(command, funct, restrict)
end end
function ExecuteCommand(comm) -- Client side, execute /command function ExecuteCommand(comm) -- Client side, execute /command
if Config.System.EventDebug then if Config.System.EventDebug then
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3ExecuteCommand^7: /"..comm..getDebugInfo(debug.getinfo(2, "nSl"))) print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3ExecuteCommand^7: /"..comm..getDebugInfo(debug.getinfo(2, "nSl")))
end end
origExecuteCommand(comm) origExecuteCommand(comm)
end end
function pairs(tbl) function pairs(tbl)
if not tbl then if not tbl then
print("^1Error^7: ^1nil ^2for ^3pairs^7(), ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^1Error^7: ^1nil ^2for ^3pairs^7(), ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
return origPairs({}) return origPairs({})
end end
return origPairs(tbl) return origPairs(tbl)
end end
function ipairs(tbl) function ipairs(tbl)
local tbl = tbl local tbl = tbl
if not tbl then if not tbl then
if Config.System.EventDebug then if Config.System.EventDebug then
print("^1Error^7: ^3iPairs^7() ^1nil ^2recieved^7, ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl"))) print("^1Error^7: ^3iPairs^7() ^1nil ^2recieved^7, ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
end end
tbl = {} tbl = {}
end end
return pairsByKeys(tbl) -- change to pairsByKeys for less errors return pairsByKeys(tbl) -- change to pairsByKeys for less errors
end end
--[[ --[[
local origPrint = print local origPrint = print
function print(...) function print(...)
origPrint(getDebugInfo(debug.getinfo(2, "nSl"))..":") origPrint(getDebugInfo(debug.getinfo(2, "nSl"))..":")
origPrint(...) origPrint(...)
end end
]] ]]

View File

@@ -1,110 +1,232 @@
--- Executes a function when the player character is loaded into the game. --[[
--- Player & Resource Event Utility Functions
--- This function sets up event listeners for player loading based on the game framework detected (e.g., QB, ESX, OX). -------------------------------------------
--- This module provides functions to:
--- If `onStart` is `true`, it will also attempt to execute the function on resource start after ensuring the player is logged in. (Helpful for debugging) • Execute code when the player character is loaded or unloaded.
--- • Execute code on resource start and stop.
--- @param func function The function to execute when the player is loaded. • Wait for the player to be logged in before proceeding.
--- @param onStart boolean (optional) If `true`, the function will also execute on resource start. Default is `false`. ]]
---
--- @usage -------------------------------------------------------------
--- ```lua -- Player Loaded and Unloaded Events
--- onPlayerLoaded(function() -------------------------------------------------------------
--- -- Your code here
--- end, true) --- Executes a function when the player character is loaded.
--- ``` --- If onStart is true, the function will also run on resource start (after ensuring the player is logged in).
function onPlayerLoaded(func, onStart) ---
local onPlayerName = "" --- @param func function The function to execute when the player is loaded.
local loaded = false --- @param onStart boolean (optional) If true, also execute on resource start. Default is false.
if onStart then --- @usage
onResourceStart(function() --- ```lua
if not LocalPlayer.state.isLoggedIn then --- onPlayerLoaded(function()
Wait(3000) --- print("Player logged in")
if not LocalPlayer.state.isLoggedIn then -- If the player is not logged in after waiting, skip execution --- -- Your initialization code here.
return --- end, true)
end --- ```
end function onPlayerLoaded(func, onStart)
loaded = true -- Mark as already loaded local onPlayerFramework = ""
debugPrint("^6Bridge^7: ^2Loading ^3onResourceStart^7() ^2through ^3onPlayerLoaded^7()") local loaded = false
Wait(2000)
func() if onStart then
end, true) onResourceStart(function()
end if not waitForLogin() then return end
if not loaded then
local tempFunc = function() loaded = true
debugPrint("^6Bridge^7: ^2Executing ^3onPlayerLoaded^7()") debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 executed through ^3onPlayerLoaded^7()")
func() Wait(2000)
end func()
if isStarted(QBExport) or isStarted(QBXExport) then onPlayerName = QBExport end, true)
AddEventHandler('QBCore:Client:OnPlayerLoaded', tempFunc) end
elseif isStarted(ESXExport) then onPlayerName = ESXExport
AddEventHandler('esx:playerLoaded', tempFunc) if not loaded then
elseif isStarted(OXCoreExport) then onPlayerName = OXCoreExport local tempFunc = function()
AddEventHandler('ox:playerLoaded', tempFunc) Wait(2000)
end debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded")
if onPlayerName ~= "" then func()
debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^2 with ^7"..onPlayerName) end
else
print("^4ERROR^7: ^2No Core detected for onPlayerLoaded ^7- ^2Check ^3exports^1.^2lua^7") if isStarted(QBExport) or isStarted(QBXExport) then
end onPlayerFramework = QBExport
end RegisterNetEvent('QBCore:Client:OnPlayerLoaded', tempFunc)
end elseif isStarted(ESXExport) then
onPlayerFramework = ESXExport
--- Executes a function when the resource starts. RegisterNetEvent('esx:playerLoaded', function()
--- if waitForSharedLoad() then
--- This function wraps the `onResourceStart` event, allowing you to execute code when the resource starts. tempFunc()
--- end
--- @param func function The function to execute on resource start. end
--- @param thisScript boolean (optional) If `true`, only runs the function when this resource starts. Default is `true`. )
--- elseif isStarted(OXCoreExport) then
--- @usage onPlayerFramework = OXCoreExport
--- ```lua RegisterNetEvent('ox:playerLoaded', tempFunc)
--- onResourceStart(function() elseif isStarted(RSGExport) then
--- -- Your code here onPlayerFramework = RSGExport
--- end, true) RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
--- ``` end
function onResourceStart(func, thisScript)
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^2") if onPlayerFramework ~= "" then
AddEventHandler('onResourceStart', function(resourceName) debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^7()^2 with ^3" .. onPlayerFramework.."^7")
if getScript() == resourceName and (thisScript or true) then else
func() print("^4ERROR^7: No supported core detected for onPlayerLoaded - Check starter.lua")
end end
end) end
end end
--- Executes a function when the resource stops. --- Executes a function when the player character is unloaded.
--- --- @param func function The function to execute when the player unloads.
--- This function wraps the `onResourceStop` event, allowing you to execute code when the resource stops. --- @usage
--- --- ```lua
--- @param func function The function to execute on resource stop. --- onPlayerUnload(function()
--- @param thisScript boolean (optional) If `true`, only runs the function when this resource stops. Default is `true`. --- print("Player has logged out of their character")
--- --- -- Your cleanup code here.
--- @usage --- end)
--- ```lua --- ```
--- onResourceStop(function() function onPlayerUnload(func)
--- -- Cleanup code here debugPrint("^6Bridge^7: ^2Registering ^3onPlayerUnload^7()")
--- end, true) RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() func() end)
--- ```
function onResourceStop(func, thisScript) RegisterNetEvent('ox:playerLogout', function() func() end)
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^2")
AddEventHandler('onResourceStop', function(resourceName) RegisterNetEvent('RSGCore:Client:OnPlayerUnload', function() func() end)
if getScript() == resourceName and (thisScript or true) then
func() RegisterNetEvent('esx:onPlayerLogout', function() func() end)
end
end) end
end
-------------------------------------------------------------
--- Waits until the player is logged in before continuing execution. -- Resource Start and Stop Events
--- -------------------------------------------------------------
--- This function blocks execution until `LocalPlayer.state.isLoggedIn` is `true`.
--- --- Executes a function when the resource starts.
---@usage --- @param func function The function to execute.
--- ```lua --- @param thisScript boolean (optional) If true, only runs when this resource starts (default true).
--- waitForLogin() --- @usage
--- ``` --- ```lua
function waitForLogin() --- onResourceStart(function()
while not LocalPlayer.state.isLoggedIn do --- print("Script ensured")
debugPrint("Waiting") --- -- Initialization code on resource start.
Wait(100) --- end, true)
end --- ```
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
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
--- Executes a function when the resource stops.
--- @param func function The function to execute.
--- @param thisScript boolean (optional) If true, only runs when this resource stops (default true).
--- @usage
--- ```lua
--- onResourceStop(function()
--- -- Cleanup code here.
--- end, true)
--- ```
function onResourceStop(func, thisScript)
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^7()")
AddEventHandler('onResourceStop', function(resourceName)
if getScript() == resourceName and (thisScript or true) then
func()
end
end)
end
-------------------------------------------------------------
-- Wait for Login
-------------------------------------------------------------
--- Blocks execution until the player is logged in.
--- @usage
--- waitForLogin()
function waitForLogin()
local timeout = 10000 -- 10 seconds in milliseconds
local startTime = GetGameTimer()
local loggedIn = false
if isStarted(ESXExport) then
while (GetGameTimer() - startTime) < timeout do
local playerData = ESX.GetPlayerData()
if playerData and playerData.job then
loggedIn = true
break
end
Wait(100)
end
elseif isStarted(OXCoreExport) then
if OxPlayer["stateId"] then
loggedIn = true
end
while not OxPlayer["stateId"] do
Wait(1000)
debugPrint("Waiting for stateId to class as logged in")
if OxPlayer.get["stateId"] then
loggedIn = true
break
end
end
else
-- For other frameworks, use LocalPlayer.state.isLoggedIn.
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
loggedIn = LocalPlayer.state.isLoggedIn
end
if not loggedIn then
print("^4Error^7: ^2Timeout reached while waiting for player login^7.")
return false
else
debugPrint("^6Bridge^7: ^2Player Login Detected^7.")
return true
end
end
local messageShown = false
function waitForSharedLoad()
local timeout = 100000 -- 10 seconds in milliseconds
local startTime = GetGameTimer()
local loaded = true
local loop = 0
while ((not Jobs or not next(Jobs)) or
(not Items or not next(Items)) or
(not Vehicles or not next(Vehicles))
) and ((GetGameTimer() - startTime) < timeout) do
if loop >= 3 and not messageShown then
if (not Jobs or not next(Jobs)) then
print("^4Debug^7: ^2Waiting for ^7Jobs^2 to be loaded")
end
if (not Items or not next(Items)) then
print("^4Debug^7: ^2Waiting for ^7Items^2 to be loaded")
end
if (not Vehicles or not next(Vehicles)) then
print("^4Debug^7: ^2Waiting for ^7Vehicles^2 to be loaded")
end
messageShown = true
end
--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 end

View File

@@ -0,0 +1,100 @@
function parseVersion(version)
local parts = {}
for num in version:gmatch("%d+") do
table.insert(parts, tonumber(num))
end
return parts
end
function compareVersions(current, newest)
local currentParts = parseVersion(current)
local newestParts = parseVersion(newest)
for i = 1, math.max(#currentParts, #newestParts) do
local c = currentParts[i] or 0
local n = newestParts[i] or 0
if c < n then return -1
elseif c > n then return 1 end
end
return 0
end
function capitalize(str)
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
end
local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
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
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, 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"..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() CheckVersion() end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersion.."^7)")
end
end)
else
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, 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"..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() CheckVersion() end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
end
end
end)
end)
end
end
CheckVersion()

View File

@@ -1,68 +1,89 @@
--- Registers a callback function with the appropriate framework. --- 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. --- 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. --- It adapts the callback function to match the expected signature for the framework.
--- ---
---@param callbackName string The name of the callback to register. ---@param callbackName string The name of the callback to register.
---@param funct function The function to be called when the callback is triggered. ---@param funct function The function to be called when the callback is triggered.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- createCallback('myCallback', function(source, ...) --- local table = { ["info"] = "HI" }
--- -- Your callback code here --- createCallback('myCallback', function(source, ...)
--- end) --- return table
--- ``` --- end)
function createCallback(callbackName, funct) ---
if isStarted(OXLibExport) then --- createCallback("callback:checkVehicleOwned", function(source, plate)
lib.callback.register(callbackName, funct) --- local result = isVehicleOwned(plate)
else --- if result then
local adaptedFunction = function(source, cb, ...) --- return true
local result = funct(source, ...) --- else
cb(result) --- return false
end --- end
--- end)
if isStarted(QBExport) then --- ```
Core = Core or exports[QBExport]:GetCoreObject() function createCallback(callbackName, funct)
Core.Functions.CreateCallback(callbackName, adaptedFunction) if isServer() then
elseif isStarted(ESXExport) then if isStarted(OXLibExport) then
ESX.RegisterServerCallback(callbackName, adaptedFunction) debugPrint("^6Bridge^7: ^2Registering ^4"..OXLibExport.." ^3Callback^7:", callbackName)
else lib.callback.register(callbackName, funct)
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to register callback with", callbackName) else
end local adaptedFunction = function(source, cb, ...)
end local result = funct(source, ...)
end cb(result)
end
--- Triggers a server callback and returns the result.
--- if isStarted(QBExport) then
--- This function triggers a server callback using the appropriate framework's method and awaits the result. debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
--- Core = Core or exports[QBExport]:GetCoreObject()
---@param callbackName string The name of the callback to trigger. Core.Functions.CreateCallback(callbackName, adaptedFunction)
---@param ... any Additional arguments to pass to the callback. elseif isStarted(ESXExport) then
--- debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
---@return any any The result returned by the callback function. ESX.RegisterServerCallback(callbackName, adaptedFunction)
--- else
---@usage print("^1ERROR^7: ^1Can't find any supported framework to register callback with^7: "..callbackName)
--- ```lua end
--- local result = triggerCallback('myCallback', arg1, arg2) end
--- ``` end
function triggerCallback(callbackName, ...) end
local result = nil
if isStarted(OXLibExport) then --- Triggers a server callback and returns the result.
result = lib.callback.await(callbackName, false, ...) ---
elseif isStarted(QBExport) then --- This function triggers a server callback using the appropriate framework's method and awaits the result.
local p = promise.new() ---
Core.Functions.TriggerCallback(callbackName, function(cbResult) ---@param callbackName string The name of the callback to trigger.
p:resolve(cbResult) ---@param ... any Additional arguments to pass to the callback.
end, ...) ---
result = Citizen.Await(p) ---@return any any The result returned by the callback function.
elseif isStarted(ESXExport) then ---
local p = promise.new() ---@usage
ESX.TriggerServerCallback(callbackName, function(cbResult) --- ```lua
p:resolve(cbResult) --- local result = triggerCallback('myCallback')
end, ...) --- jsonPrint(result)
result = Citizen.Await(p) ---
else --- local result = triggerCallback("callback:checkVehicleOwned", plate)
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with", callbackName) --- print(result)
end --- ```
return result function triggerCallback(callbackName, ...)
local result = nil
debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName)
if isStarted(OXLibExport) then
result = lib.callback.await(callbackName, false, ...)
elseif isStarted(QBExport) then
local p = promise.new()
Core.Functions.TriggerCallback(callbackName, function(cbResult)
p:resolve(cbResult)
end, ...)
result = Citizen.Await(p)
Wait(10)
elseif isStarted(ESXExport) then
local p = promise.new()
ESX.TriggerServerCallback(callbackName, function(cbResult)
p:resolve(cbResult)
end, ...)
result = Citizen.Await(p)
else
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with", callbackName)
end
return result
end end

View File

@@ -1,290 +1,321 @@
--- Opens a menu using the configured menu system. --[[
--- Menu Opening Module
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`. ---------------------
--- This module provides a unified function to open menus using the configured menu system.
---@param Menu table A table containing the menu options to display. Supported systems include:
--- Each menu item can include: • jim_bridge (built-in nui menu that works on any framework)
--- - **header** (`string`): The text to display for the menu item. • ox (or ox_context)
--- - **txt** (`string`, optional): Additional text or description. • qb (using QBMenuExport)
--- - **icon** (`string`, optional): Icon to display with the menu item. • gta (using WarMenu)
--- - **onSelect** (`function`, optional): Function to execute when the menu item is selected. • esx (using ESX.UI.Menu)
--- - **arrow** (`boolean`, optional): Whether to display an arrow next to the item (for certain menus). ]]
--- - **params** (`table`, optional): Additional parameters, such as events and arguments.
--- - **isMenuHeader** (`boolean`, optional): Marks the item as a header. --- Opens a menu using the configured menu system.
--- - **disabled** (`boolean`, optional): Disables the menu item if `true`. ---
--- --- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
---@param data table A table containing configuration data for the menu. ---
--- - **header** (`string`): The header/title of the menu. ---@param Menu table A table containing the menu options to display.
--- - **headertxt** (`string`, optional): Additional header text. --- Each menu item can include:
--- - **onBack** (`function`, optional): Function to call when the "Return" option is selected. --- - header (`string`): The text to display for the menu item.
--- - **onExit** (`function`, optional): Function to call when the menu is exited. --- - txt (`string`, optional): Additional text or description.
--- - **onSelected** (`function`, optional): Function to call when a menu item is selected (for certain menu systems). --- - icon (`string`, optional): Icon to display with the menu item.
--- - **canClose** (`boolean`, optional): Whether the menu can be closed by the user. --- - onSelect (`function`, optional): Function to execute when the menu item is selected.
--- --- - arrow (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
---@usage --- - params (`table`, optional): Additional parameters, such as events and arguments.
--- ```lua --- - isMenuHeader (`boolean`, optional): Marks the item as a header.
--- openMenu({ --- - disabled (`boolean`, optional): Disables the menu item if `true`.
--- { 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 }, ---@param data table A table containing configuration data for the menu.
--- }, { --- - header (`string`): The header/title of the menu.
--- header = "Main Menu", --- - headertxt (`string`, optional): Additional header text.
--- headertxt = "Select an option", --- - onBack (`function`, optional): Function to call when the "Return" option is selected.
--- onBack = function() print("Return selected") end, --- - onExit (`function`, optional): Function to call when the menu is exited.
--- onExit = function() print("Menu closed") end, --- - onSelected (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
--- canClose = true, --- - canClose (`boolean`, optional): Whether the menu can be closed by the user.
--- }) ---
--- ``` ---@usage
function openMenu(Menu, data) --- ```lua
if Config.System.Menu == "jim" then --- openMenu({
if data.onBack then --- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
table.insert(Menu, 1, { --- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
icon = "fas fa-circle-arrow-left", --- }, {
title = "Return", --- header = "Main Menu",
onSelect = data.onBack, --- headertxt = "Select an option",
}) --- onBack = function() print("Return selected") end,
end --- onExit = function() print("Menu closed") end,
exports["jim-nui"]:openMenu({ --- canClose = true,
title = data.header..(data.headertxt and " -- "..data.headertxt or ""), --- })
canClose = data.canClose and data.canClose or nil, --- ```
onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil, function openMenu(Menu, data)
onExit = data.onExit and data.onExit or nil, if Config.System.Menu == "ox" then
options = Menu, local index = nil
}) if data.onBack and not data.onSelected then
table.insert(Menu, 1, {
elseif Config.System.Menu == "ox" then icon = "fas fa-circle-arrow-left",
local index = nil title = "Return",
if data.onBack and not data.onSelected then onSelect = data.onBack,
table.insert(Menu, 1, { label = "Return",
icon = "fas fa-circle-arrow-left", })
title = "Return", end
onSelect = data.onBack, for k in pairs(Menu) do
label = "Return", if data.onSelected and Menu[k].arrow then
}) Menu[k].icon = "fas fa-angle-right"
end end
for k in pairs(Menu) do -- If no title, use header or txt as title/label.
if data.onSelected and Menu[k].arrow then if not Menu[k].title then
Menu[k].icon = "fas fa-angle-right" if Menu[k].header ~= nil and Menu[k].header ~= "" then
end Menu[k].title = Menu[k].header
if not Menu[k].title then Menu[k].label = Menu[k].header
if Menu[k].header ~= nil and Menu[k].header ~= "" then if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
Menu[k].title = Menu[k].header else
Menu[k].label = Menu[k].header Menu[k].title = Menu[k].txt
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end Menu[k].label = Menu[k].txt
else end
Menu[k].title = Menu[k].txt end
Menu[k].label = Menu[k].txt -- Copy parameters from 'params' if available.
end if Menu[k].params then
end Menu[k].event = Menu[k].params.event
if Menu[k].params then Menu[k].args = Menu[k].params.args or {}
Menu[k].event = Menu[k].params.event end
Menu[k].args = Menu[k].params.args or {} if Menu[k].isMenuHeader then
end Menu[k].readOnly = true
if Menu[k].isMenuHeader then end
Menu[k].disabled = true end
end local menuID = 'Menu'
end (data.onSelected and lib.registerMenu or lib.registerContext)({
local menuID = 'Menu' id = menuID,
(data.onSelected and lib.registerMenu or lib.registerContext)({ title = data.header..br..br..(data.headertxt and data.headertxt or ""),
id = menuID, position = 'top-right',
title = data.header..br..br..(data.headertxt and data.headertxt or ""), options = Menu,
position = 'top-right', canClose = data.canClose and data.canClose or nil,
options = Menu, onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
canClose = data.canClose and data.canClose or nil, onExit = data.onExit and data.onExit or nil,
onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil, onSelected = data.onSelected and (function(selected) index = selected end) or nil,
onExit = data.onExit and data.onExit or nil, }, data.onSelected and (function(x, y, args)
onSelected = data.onSelected and (function(selected) index = selected end) or nil, if Menu[x].refresh then
}, data.onSelected and (function(x, y, args) if Menu[x].onSelect then
if Menu[x].refresh then Menu[x].onSelect()
if Menu[x].onSelect then end
Menu[x].onSelect() lib.showMenu(menuID, index)
end else
lib.showMenu(menuID, index) if Menu[x].onSelect then
else Menu[x].onSelect()
if Menu[x].onSelect then else
Menu[x].onSelect() lib.showMenu(menuID, index)
else end
lib.showMenu(menuID, index) end
end end) or nil)
end if data.onSelected then
end) or nil) lib.showMenu(menuID, 1)
if data.onSelected then else
lib.showMenu(menuID, 1) lib.showContext(menuID)
else end
lib.showContext(menuID)
end elseif Config.System.Menu == "qb" then
if data.onBack then
elseif Config.System.Menu == "qb" then table.insert(Menu, 1, {
if data.onBack then icon = "fas fa-circle-arrow-left",
table.insert(Menu, 1, { header = " ",
icon = "fas fa-circle-arrow-left", txt = "Return",
header = " ", params = {
txt = "Return", isAction = true,
params = { event = data.onBack,
isAction = true, },
event = data.onBack, })
}, elseif data.canClose then
}) table.insert(Menu, 1, {
elseif data.canClose then icon = "fas fa-circle-xmark",
table.insert(Menu, 1, { header = " ",
icon = "fas fa-circle-xmark", txt = "Close",
header = " ", params = {
txt = "Close", isAction = true,
params = { event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
isAction = true, },
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end), })
}, end
}) if data.header ~= nil then
end local tempMenu = {}
if data.header ~= nil then for k, v in pairs(Menu) do tempMenu[k + 1] = v end
local tempMenu = {} tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
for k, v in pairs(Menu) do tempMenu[k + 1] = v end Menu = tempMenu
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true } end
Menu = tempMenu for k in pairs(Menu) do
end if not Menu[k].params or not Menu[k].params.event then
for k in pairs(Menu) do Menu[k].params = {
if not Menu[k].params or not Menu[k].params.event then isAction = true,
if Menu[k].onSelect then event = Menu[k].onSelect or function() end,
Menu[k].params = { }
isAction = true, end
event = Menu[k].onSelect, if not Menu[k].header then Menu[k].header = " " end
} if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
else Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
Menu[k].params = { end
isAction = true, exports[QBMenuExport]:openMenu(Menu)
event = function() end,
} elseif Config.System.Menu == "gta" then
end WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
end titleColor = { 222, 255, 255 },
if not Menu[k].header then Menu[k].header = " " end maxOptionCountOnScreen = 15,
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end width = 0.25,
end x = 0.7,
exports[QBMenuExport]:openMenu(Menu) })
if WarMenu.IsAnyMenuOpened() then return end
elseif Config.System.Menu == "gta" then WarMenu.OpenMenu(tostring(Menu))
WarMenu.CreateMenu(tostring(Menu), CreateThread(function()
data.header, local close = true
data.headertxt or " ", while true do
{ if WarMenu.Begin(tostring(Menu)) then
titleColor = { 222, 255, 255 }, if data.onBack then
maxOptionCountOnScreen = 15, if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
width = 0.25, WarMenu.CloseMenu()
x = 0.7, Wait(10)
}) data.onBack()
if WarMenu.IsAnyMenuOpened() then return end end
WarMenu.OpenMenu(tostring(Menu)) end
CreateThread(function() for k in pairs(Menu) do
local close = true local pressed = WarMenu.Button(Menu[k].header)
while true do if not Menu[k].header then
if WarMenu.Begin(tostring(Menu)) then Menu[k].header = Menu[k].txt
if data.onBack then Menu[k].txt = nil
if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then end
WarMenu.CloseMenu() if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
Wait(10) if Menu[k].disabled or Menu[k].isMenuHeader then
data.onBack() WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
end else
end WarMenu.ToolTip(
for k in pairs(Menu) do (Menu[k].blip and "~BLIP_".."8".."~ " or "")..
local pressed = WarMenu.Button(Menu[k].header) Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
if not Menu[k].header then true)
Menu[k].header = Menu[k].txt end
Menu[k].txt = nil end
end if pressed and not Menu[k].isMenuHeader then
if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then WarMenu.CloseMenu()
if Menu[k].disabled or Menu[k].isMenuHeader then close = false
WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true) Wait(10)
else Menu[k].onSelect()
WarMenu.ToolTip( end
(Menu[k].blip and "~BLIP_".."8".."~ " or "").. end
Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18, WarMenu.End()
true) else
end return
end end
if pressed and not Menu[k].isMenuHeader then if not WarMenu.IsAnyMenuOpened() and close then
WarMenu.CloseMenu() stopTempCam(cam)
close = false if data.onExit then data.onExit() end
Menu[k].onSelect() end
end Wait(0)
end end
WarMenu.End() end)
else
return elseif Config.System.Menu == "esx" then
end for k in pairs(Menu) do
if not WarMenu.IsAnyMenuOpened() and close then Menu[k].label = Menu[k].header
stopTempCam(cam) Menu[k].name = "button"..k
if data.onExit then data.onExit() end end
end if data.canClose then
Wait(0) table.insert(Menu, 1, {
end icon = "fas fa-circle-xmark",
end) label = "Close",
name = "close",
elseif Config.System.Menu == "esx" then onSelect = data.onExit,
for k in pairs(Menu) do })
Menu[k].label = Menu[k].header end
Menu[k].name = "button"..k if data.onBack then
end table.insert(Menu, 1, {
if data.canClose then icon = "fas fa-circle-arrow-left",
table.insert(Menu, 1, { label = "Return",
icon = "fas fa-circle-xmark", name = "return",
label = "Close", onSelect = data.onBack,
name = "close", })
onSelect = data.onExit, end
}) ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
end title = data.header,
if data.onBack then align = 'top-right',
table.insert(Menu, 1, { elements = Menu,
icon = "fas fa-circle-arrow-left", },
label = "Return", function(menuData, menu)
name = "return", for k in pairs(Menu) do
onSelect = data.onBack, if menuData.current.name == Menu[k].name then
}) menu.close()
end Wait(10)
Menu[k].onSelect()
ESX.UI.Menu.Open("default", getScript(), "Example_Menu", { end
title = data.header, end
align = 'top-right', end,
elements = Menu, function(data, menu)
}, menu.close()
function(menuData, menu) end)
for k in pairs(Menu) do
if menuData.current.name == Menu[k].name then elseif Config.System.Menu == "lation" then
menu.close() if data.onBack then
Wait(10) table.insert(Menu, 1, {
Menu[k].onSelect() icon = "fas fa-circle-arrow-left",
end onSelect = data.onBack,
end header = "Return",
end, })
function(data, menu) end
menu.close()
end) for k in pairs(Menu) do
end if data.onSelected and Menu[k].arrow then
end Menu[k].icon = "fas fa-angle-right"
end
--- A line break constant used for formatting menu headers. -- If no title, use header or txt as title/label.
br = (Config.System.Menu == "ox" or Config.System.Menu == "gta") and "\n" or "<br>" if not Menu[k].title then
if Menu[k].header ~= nil and Menu[k].header ~= "" then
--- Checks if the menu system is classified as 'ox' or 'gta'. Menu[k].title = Menu[k].header
--- Menu[k].label = Menu[k].header
--- This function is used to decide how to make line breaks in menu headers. if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
--- else
--- @return boolean Returns `true` if the menu system is 'ox' or 'gta'; otherwise, `false`. Menu[k].title = Menu[k].txt
--- Menu[k].label = Menu[k].txt
--- @usage end
--- ```lua end
--- if isOx() then -- Copy parameters from 'params' if available.
--- -- Use specific formatting if Menu[k].params then
--- end Menu[k].event = Menu[k].params.event
--- ``` Menu[k].args = Menu[k].params.args or {}
function isOx() return (Config.System.Menu == "ox" or Config.System.Menu == "gta") end end
if Menu[k].isMenuHeader then
Menu[k].readOnly = true
--- Checks if any WarMenu menu is currently open. end
--- end
--- @return boolean Returns `true` if a WarMenu menu is open; otherwise, `false`.
--- exports.lation_ui:registerMenu({
--- @usage id = 'menu',
--- ```lua title = data.header,
--- if isWarMenuOpen() then onExit = data.onExit and data.onExit or nil,
--- -- Do something subtitle = (data.headertxt and data.headertxt or ""),
--- end options = Menu,
--- ``` })
-- Show menu
exports.lation_ui:showMenu('menu')
end
end
--- A line break constant used for menu header formatting.
br = (Config.System.Menu == "ox" or Config.System.Menu == "gta" or Config.System.Menu == "lation") and "\n" or "<br>"
--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes.
--- @return boolean boolean True if using ox or gta menus, otherwise false.
--- @usage
--- ```lua
--- if isOx() then
--- -- Use specific formatting
--- end
--- ```
function isOx() return (Config.System.Menu == "ox" or Config.System.Menu == "gta") end
--- Checks if any WarMenu menu is currently open.
---
--- @return boolean boolean Returns `true` if a WarMenu menu is open; otherwise, `false`.
---
--- @usage
--- ```lua
--- if isWarMenuOpen() then
--- -- Do something
--- end
--- ```
function isWarMenuOpen() if Config.System.Menu == "gta" then return WarMenu.IsAnyMenuOpened() else return false end end function isWarMenuOpen() if Config.System.Menu == "gta" then return WarMenu.IsAnyMenuOpened() else return false end end

View File

@@ -1,170 +1,104 @@
-- Create empty Variables -- Items, Vehicles, Jobs, Gangs = nil, nil, nil, nil
Items, Vehicles, Jobs, Gangs, Core, ESX = {}, nil, nil, nil, nil, nil
-- Shared Exports Initialization
-- Correct QB inventory export (if needed) from 'qb-inventory' to 'ps-inventory' or 'lj-inventory' -- Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv
Exports.QBInv = (isStarted("ps-inventory") and "ps-inventory") or (isStarted("lj-inventory") and "lj-inventory") or Exports.QBInv
OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
-- Create simple variables based on the corresponding framework exports -- Exports.OXLibExport or "",
OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport = Exports.OXLibExport or "", Exports.QBXExport or "", Exports.QBExport or "", Exports.ESXExport or "", Exports.OXCoreExport or "" Exports.QBXExport or "",
Exports.QBExport or "",
-- Create simple variables based on the corresponding inventory names -- Exports.ESXExport or "",
OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv = Exports.OXInv or "", Exports.QBInv or "", Exports.PSInv or "", Exports.QSInv or "", Exports.CoreInv or "", Exports.CodeMInv or "", Exports.OrigenInv or "" Exports.OXCoreExport or ""
-- QB-Menu export name grabbed from exports.lua -- OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
QBMenuExport = Exports.QBMenuExport or "" Exports.OXInv or "",
Exports.QBInv or "",
-- Target exports based on what is loaded -- Exports.PSInv or "",
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or "" Exports.QSInv or "",
Exports.CoreInv or "",
-- If Debug mode is on in the loading script, print the list of found exports -- Exports.CodeMInv or "",
-- Some may "lie", 'ox_target' attempts to use 'qb-target' exports and this print will say its loaded (which is technically true) -- Exports.OrigenInv or "",
for _, v in pairs(Exports) do Exports.TgiannInv or "",
if isStarted(v) then debugPrint("^6Bridge^7: '^3"..v.."^7' ^2export found ^7") end Exports.JPRInv or ""
end
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
local itemResource, jobResource, vehResource = "", "", "" QBMenuExport = Exports.QBMenuExport or ""
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
-- Load item lists --
-- Complies the items from ox_inventory, qb-core or esx into 'Items' and loads them in a layout similar to qb-core's Shared items.lua -- if isStarted(QBXExport) or isStarted(QBExport) then
-- For example this makes it so instead of QBCore.Shared.Items[item], you can load 'Item[item]' in the script -- Core = Core or exports[QBExport]:GetCoreObject()
if isStarted(OXInv) then itemResource = OXInv elseif isStarted(RSGExport) then
Items = exports[OXInv]:Items() Core = Core or exports[RSGExport]:GetCoreObject()
for k, v in pairs(Items) do end
if v.client and v.client.image then
Items[k].image = (v.client.image):gsub("nui://"..OXInv.."/web/images/", "")
else if IsDuplicityVersion() then
Items[k].image = k..".png" local cache = nil
end local timeout = GetGameTimer() + 120000 -- 2 minutes max wait (had to up this from 5 seconds because of slow servers)
Items[k].hunger = v.client and v.client.hunger or nil
Items[k].thirst = v.client and v.client.thirst or nil -- Wait until jim_bridge is started and export is available
end while not cache and GetGameTimer() < timeout do
if GetResourceState("jim_bridge"):find("start") then
elseif isStarted(QBExport) then itemResource = QBExport local success, result = pcall(function()
Core = Core or exports[QBExport]:GetCoreObject() return exports["jim_bridge"]:GetSharedData()
Items = Core and Core.Shared.Items or nil end)
if isStarted(QBExport) and not isStarted(QBXExport) then if success and result then
RegisterNetEvent('QBCore:Client:UpdateObject', function() cache = result
Core = Core or exports[QBExport]:GetCoreObject() end
Items = Core and Core.Shared.Items or nil end
end) Wait(100)
end end
elseif isStarted(ESXExport) then itemResource = ESXExport if not cache then
ESX = exports[ESXExport]:getSharedObject() print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
Items = ESX and ESX.Items or nil return
end end
-- If it fails to load items, then it will print the error below --
-- If it loads them and debug is on, print how many items and where from -- Items = cache.Items
if not Items then Vehicles = cache.Vehicles
print("^4ERROR^7: ^2No Core Items detected ^7- ^2Check ^3exports^1.^2lua^7") Jobs = cache.Jobs
else Gangs = cache.Gangs
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource) InventoryWeight = cache.InventoryWeight or InventoryWeight
end InventorySlots = cache.InventorySlots or 40
-- Load Vehicles -- debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
-- Complies the vehicles from the detected frameworks into a table in the style of qb-cores shared vehicles.lua -- --print(countTable(Items), countTable(Vehicles), countTable(Jobs))
-- For example, instead of using QBCore.Shared.Vehicles[vehicle] you can load 'Vehicles[vehicle]' in the script -- else
if isStarted(QBXExport) or isStarted(QBExport) then -- Shared bridge cache - prevent duplicate listeners
Core = Core or exports[QBExport]:GetCoreObject() exports["jim_bridge"]:GetBridgeCache(function(cache)
Vehicles = Core and Core.Shared.Vehicles Items = cache.Items or {}
if isStarted(QBExport) and not isStarted(QBXExport) then Vehicles = cache.Vehicles or {}
RegisterNetEvent('QBCore:Client:UpdateObject', function() Jobs = cache.Jobs or {}
Core = Core or exports[QBExport]:GetCoreObject() Gangs = cache.Gangs or {}
Vehicles = Core and Core.Shared.Vehicles InventoryWeight = cache.InventoryWeight or InventoryWeight
end) InventorySlots = cache.InventorySlots or 50
end
vehResource = QBExport CreateThread(function()
elseif isStarted(OXCoreExport) then if isStarted(ESXExport) then
Vehicles = {} for _, v in pairs(Vehicles) do
for k, v in pairs(Ox.GetVehicleData()) do Vehicles[v.model] = {
Vehicles[k] = { model = k, price = v.price, name = v.name, brand = v.make } model = v.model,
end hash = v.hash,
vehResource = OXCoreExport price = v.price,
elseif isStarted(ESXExport) then name = v.name,
-- print("^6Bridge^7: ^2Loading ^3Vehicles^2 from ^7"..ESXExport) brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
-- print("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..ESXExport) }
CreateThread(function() end
if isServer() then end
createCallback(getScript()..":getVehiclesPrices", function(source)
Vehicles = MySQL.query.await('SELECT model, price, name FROM vehicles') if isStarted(OXInv) then
vehResource = ESXExport for k, v in pairs(Items) do
return Vehicles local tempInfo = exports[OXInv]:Items(k)
end) Items[k].image = k..".png" -- Set default image
end if tempInfo and tempInfo.client then -- if client info, check for hard set image
if not isServer() then Items[k].image = (tempInfo.client.image) and tempInfo.client.image:gsub("nui://"..OXInv.."/web/images/", "") or Items[k].image
local TempVehicles = triggerCallback(getScript()..":getVehiclesPrices") Items[k].hunger = tempInfo.client.hunger
for _, v in pairs(TempVehicles) do Items[k].thirst = tempInfo.client.thirst
Vehicles = Vehicles or {} end
Vehicles[v.model] = { model = v.model, price = v.price, name = v.name, brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper) } end
end end
end end)
end)
end debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
if vehResource == nil then end)
print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3exports^1.^2lua^7") end
else
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..vehResource)
end
-- Load Jobs --
-- Attempts to load the details of jobs and gangs and compile into tables --
-- For example, instead of using QBCore.Shared.Jobs[job] you can load 'Jobs[job]' in the script --
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 = TempJobs or {}
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 }
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
--print("^6Bridge^7: ^2Loading ^3Jobs^7/^3Gangs^2 from ^7"..ESXExport)
ESX = exports[ESXExport]:getSharedObject()
if isServer() then
Jobs = ESX.GetJobs()
for k, v in pairs(Jobs) do
local count = countTable(Jobs[k].grades)-1
Jobs[k].grades[tostring(count)].isBoss = true
end
Gangs = Jobs
end
CreateThread(function()
while not ESX do Wait(0) end
if isServer() then
createCallback(getScript()..":getJobs", function(source)
return Jobs
end)
end
if not isServer() then
Jobs = triggerCallback(getScript()..":getJobs")
Gangs = Jobs
end
end)
end
if not isStarted(ESXExport) and Jobs then
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Jobs).." ^3Jobs^2 from ^7"..jobResource, "^6Bridge^7: ^2Loading ^6"..countTable(Gangs).." ^3Gangs^2 from ^7"..jobResource)
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,115 @@
local radarTable = {} --[[
Text Drawing Module
--- Displays text on the screen using the configured draw text system. ---------------------
--- This module provides functions to display and hide text on screen using
--- This function handles displaying text with optional images or icons using different frameworks like 'qb', 'ox', 'gta', and 'esx'. various frameworks: QB, OX, GTA, and ESX.
--- ]]
---@param image string|nil An optional image or icon to display with the text. Can be a URL, path, or a reference to an icon.
---@param input table A table of strings, each representing a line of text to display. --- Displays text on the screen using the configured draw text system.
---@param style string|nil An optional style code for default GTA popups (e.g., '~g~' for green text). ---
---@param oxStyleTable table|nil An optional table specifying style parameters for the 'ox' draw text system. --- Depending on Config.System.drawText, this function will use different methods to
--- --- display text along with optional images/icons.
---@usage ---
--- ```lua --- @param image string|nil Optional image/icon identifier to display with the text.
--- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~") --- @param input table An array of strings; each string is a line of text to display.
--- ``` --- @param style string|nil Optional style code for default GTA popups (e.g., "~g~" for green).
function drawText(image, input, style, oxStyleTable) local text = "" --- @param oxStyleTable table|nil Optional table specifying style parameters for the OX text UI.
if Config.System.drawText == "qb" then ---
for i = 1, #input do ---@usage
text = text..input[i].."</span>"..(input[i+1] ~= nil and "<br>" or "") end --- ```lua
local text = text:gsub("%:", ":<span style='color:yellow'>") --- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
if image then --- ```
text = '<img src="'..(radarTable[image] or nil)..'" style="width:12px;height:12px">'..text function drawText(image, input, style, oxStyleTable)
end local text = ""
exports[QBExport]:DrawText(text, 'left') if not radarTable then radarTable = {} end
if Config.System.drawText == "qb" then
elseif Config.System.drawText == "ox" then -- Concatenate lines for QB system with HTML line breaks.
for k, v in pairs(input) do for i = 1, #input do
input[k] = v.." \n" text = text..input[i].."</span>"..(input[i + 1] and "<br>" or "")
end end
lib.showTextUI(table.concat(input), { icon = (image and radarTable[image] or image) or nil, position = 'left-center', style = oxStyleTable}) text = text:gsub("%:", ":<span style='color:yellow'>")
if image then
elseif Config.System.drawText == "gta" then text = '<img src="'..(radarTable[image] or "")..'" style="width:12px;height:12px">'..text
for i = 1, #input do if input[i] ~= "" then text = text..input[i].."\n~s~" end end end
if image then text = "~BLIP_"..image.."~ "..text end exports[QBExport]:DrawText(text, 'left')
DisplayHelpMsg(text:gsub("%:", ":~"..(style or "g").."~")) elseif Config.System.drawText == "ox" then
elseif Config.System.drawText == "esx" then -- Append newline spacing to each input line.
for i = 1, #input do local inputnum = countTable(input)
text = text..input[i].."</span>"..(input[i+1] ~= nil and "<br>" or "") end for k, v in pairs(input) do
local text = text:gsub("%:", ":<span style='color:yellow'>") input[k] = v..(inputnum ~= k and " \n" or "")
if image then end
text = '<img src="'..radarTable[image]..'" style="width:12px;height:12px">'..text lib.showTextUI(table.concat(input), { icon = (image and radarTable[image] or image) or nil, position = 'left-center', style = oxStyleTable })
end
ESX.TextUI(text, nil) elseif Config.System.drawText == "lation" then
end local inputnum = countTable(input)
end for k, v in pairs(input) do
input[k] = v..(inputnum ~= k and " \n" or "")
--- Hides any text currently being displayed on the screen. end
--- exports.lation_ui:showText({
--- This function clears the text displayed by the `drawText` function, using the appropriate method based on the configured draw text system. --title = " ",
function hideText() description = table.concat(input),
if Config.System.drawText == "qb" then keybind = nil,
exports[QBExport]:HideText() icon = (image and radarTable[image] or image) or nil,
elseif Config.System.drawText == "ox" then iconColor = '#3B82F6',
lib.hideTextUI() position = 'center-left'
elseif Config.System.drawText == "gta" then })
ClearAllHelpMessages()
elseif Config.System.drawText == "esx" then elseif Config.System.drawText == "gta" then
ESX.HideUI() -- Concatenate input lines and apply GTA style formatting.
end for i = 1, #input do
if input[i] ~= "" then
text = text..input[i].."\n~s~"
end
end
if image then
text = "~BLIP_"..image.."~ "..text
end
DisplayHelpMsg(text:gsub("%:", ":~"..(style or "g").."~"))
elseif Config.System.drawText == "esx" then
-- ESX-based text UI uses similar HTML formatting as QB.
for i = 1, #input do
text = text..input[i].."</span>"..(input[i + 1] and "<br>" or "")
end
text = text:gsub("%:", ":<span style='color:yellow'>")
if image then
text = '<img src="'..(radarTable[image] or "")..'" style="width:12px;height:12px">'..text
end
ESX.TextUI(text, nil)
elseif Config.System.drawText == "red" then
-- Concatenate input lines and apply GTA style formatting.
for i = 1, #input do
if input[i] ~= "" then
text = text..input[i].."\n~q~"
end
end
TriggerEvent("jim-redui:DrawText", text)
end
end
--- Hides any text currently displayed on the screen.
---
--- Clears the text using the appropriate method for the configured draw text system.
---
--- @usage
--- ```lua
--- hideText()
--- ```
function hideText()
if Config.System.drawText == "qb" then
exports[QBExport]:HideText()
elseif Config.System.drawText == "ox" then
lib.hideTextUI()
elseif Config.System.drawText == "lation" then
exports.lation_ui:hideText()
elseif Config.System.drawText == "gta" then
ClearAllHelpMessages()
elseif Config.System.drawText == "esx" then
ESX.HideUI()
elseif Config.System.drawText == "red" then
TriggerEvent("jim-redui:HideText")
end
end end

View File

@@ -1,122 +1,161 @@
-- DUI STUFF -- * Experimental * -- if gameName ~= "rdr3" then
--[[
scriptTxd = not isServer() and CreateRuntimeTxd(getScript()..'scriptTxd') or nil DUI Module (Experimental)
customDUIList = {} --------------------------
This module handles the creation, modification, and removal of custom DUI (Display UI)
-- DUI CLIENT elements using runtime textures. It supports both client and server functionality to update DUI
function createDui(name, http, size, txd) images dynamically.
--print(name, http, size, txd) ]]
if not customDUIList[name] then
local newTxt = CreateDui(http, math.floor(size.x), math.floor(size.y)) -- Create a runtime texture dictionary on the client if not running on the server.
while not GetDuiHandle(newTxt) do Wait(0) end scriptTxd = not isServer() and CreateRuntimeTxd(getScript()..'scriptTxd') or nil
CreateRuntimeTextureFromDuiHandle(txd, name, GetDuiHandle(newTxt)) customDUIList = {}
customDUIList[name] = newTxt
SetDuiUrl(customDUIList[name], http) -------------------------------------------------------------
else -- DUI Client Functions
SetDuiUrl(customDUIList[name], http) -------------------------------------------------------------
end
end --- Creates or updates a DUI element.
---
function DuiSelect(data) --- @param name string The unique name for the DUI element.
local image = "" --- @param http string The URL to load into the DUI.
for k, v in pairs(duiList[data.name]) do --- @param size table A table with .x and .y fields specifying the DUI dimensions.
if v.tex.texn == data.texn then --- @param txd table The runtime texture dictionary where the DUI texture will be created.
if duiList[data.name][k] then --- @usage
image = "<center>- Current Image -<br>".. --- ```lua
"<img src="..duiList[data.name][k].url.." width=150px><br>".. --- createDui("logo", "https://example.com/logo.png", { x = 512, y = 256 }, scriptTxd)
"Size: ["..math.floor(data.size.x)..", "..math.floor(data.size.y).."]<br><br>" --- ```
end function createDui(name, http, size, txd)
end if not customDUIList[name] then
end local newDui = CreateDui(http, math.floor(size.x), math.floor(size.y))
local dialog = exports['qb-input']:ShowInput({ while not GetDuiHandle(newDui) do Wait(0) end
header = image..Loc[Config.Lan].menu["dui_new"], CreateRuntimeTextureFromDuiHandle(txd, name, GetDuiHandle(newDui))
submitText = Loc[Config.Lan].menu["dui_change"], customDUIList[name] = newDui
inputs = { { type = 'text', isRequired = true, name = 'url', text = Loc[Config.Lan].menu["dui_url"] } } }) SetDuiUrl(customDUIList[name], http)
if dialog then else
if not dialog.url then return end SetDuiUrl(customDUIList[name], http)
data.url = dialog.url end
--Scan the link to see if it has an image extention otherwise, stop here. end
local searchList = { "png", "jpg", "jpeg", "gif", "webp", "bmp" }
--Scan the link for certain terms that will flag it and refuse to show it --- Opens a DUI selection input allowing the user to change the DUI image URL.
local banList = { "porn" } -- I dunno, let me know what links people manage to find ---
local searchFound = false --- @param data table A table containing DUI data:
for k, v in pairs(searchList) do --- - name: The key name in the DUI list.
if string.find(tostring(data.url), tostring(v))then --- - texn: The texture name.
searchFound = true --- - texd: The texture dictionary.
end --- - size: A table with .x and .y dimensions.
end ---
for k, v in pairs(banList) do --- @usage
if string.find(tostring(data.url), tostring(v)) then --- ```lua
searchFound = false print("BANNED WORD: "..v) --- DuiSelect({ name = "logo", texn = "logoTex", texd = "someTxd", size = { x = 512, y = 256 } })
end --- ```
end function DuiSelect(data)
if searchFound then local imagePreview = "![test]("..data.url..")"
TriggerServerEvent(getScript()..":Server:ChangeDUI", data) --local imagePreview = "<center>- Current Image -<br>" ..
end -- "<img src="..data.url.." width=150px><br>" ..
end -- "Size: ["..math.floor(data.size.x)..", "..math.floor(data.size.y).."]<br><br>"
end
local dialog = createInput(imagePreview, {
RegisterNetEvent(getScript()..":Client:ChangeDUI", function(data) { type = "text", text = "dui_url", name = "url", isRequired = true },
debugPrint("^6Bridge^7: ^2Recieving new DUI ^7- ^6"..data.url.."^7") })
if tostring(data.url) ~= "-" then
createDui(data.texn, tostring(data.url), data.size, scriptTxd) if dialog then
AddReplaceTexture(tostring(data.texd), tostring(data.texn), getScript()..'scriptTxd', tostring(data.texn)) data.url = dialog.url or dialog[1]
end -- Scan URL for valid image extension and banned words.
end) local searchList = { "png", "jpg", "jpeg", "gif", "webp", "bmp" }
local banList = { "porn" }
RegisterNetEvent(getScript()..":Client:ClearDUI", function(data) local searchFound = false
if customDUIList[tostring(data.texn)] then for _, ext in pairs(searchList) do
RemoveReplaceTexture(tostring(data.texd), tostring(data.texn)) if string.find(tostring(data.url), ext) then
if IsDuiAvailable(customDUIList[tostring(data.texn)]) then searchFound = true
SetDuiUrl(customDUIList[data.name], nil) break
end end
end end
end) for _, banned in pairs(banList) do
if string.find(tostring(data.url), banned) then
-- DUI SERVER searchFound = false
RegisterNetEvent(getScript()..":Server:ChangeDUI", function(data) print("BANNED WORD: "..banned)
-- if no url given, "reset" it back to preset break
if not data.url then end
for k, v in pairs(duiList[data.name]) do end
if v.tex.texn == data.texn then if searchFound then
debugPrint("^6Bridge^7: ^2Preset^7: ^6"..tostring(duiList[data.name][k].preset).."^7") TriggerServerEvent(getScript()..":Server:ChangeDUI", data)
data.url = duiList[data.name][k].preset end
end end
end end
end
-- if it has a url, update server DUI list and send to players --- Client event handler to update DUI elements.
for k, v in pairs(duiList[data.name]) do RegisterNetEvent(getScript()..":Client:ChangeDUI", function(data)
if v.tex.texn == data.texn then debugPrint("^6Bridge^7: ^2Receiving new DUI ^7- ^6"..data.url.."^7")
duiList[data.name][k].url = data.url if tostring(data.url) ~= "-" then
end createDui(data.texn, tostring(data.url), data.size, scriptTxd)
end AddReplaceTexture(tostring(data.texd), tostring(data.texn), getScript().."scriptTxd", tostring(data.texn))
debugPrint("^6Bridge^7: ^3DUI^2 Sending new DUI to all players^7 - ^6"..data.url.."^7") end
TriggerClientEvent(getScript()..":Client:ChangeDUI", -1, data) end)
end)
--- Client event handler to clear DUI elements.
RegisterNetEvent(getScript()..":Server:ClearDUI", function(data) RegisterNetEvent(getScript()..":Client:ClearDUI", function(data)
if data.url == "-" then if customDUIList[tostring(data.texn)] then
for k, v in pairs(duiList[data.name]) do RemoveReplaceTexture(tostring(data.texd), tostring(data.texn))
if v.tex.texn == data.texn then if IsDuiAvailable(customDUIList[tostring(data.texn)]) then
duiList[data.name][k].url = "-" SetDuiUrl(customDUIList[data.name], nil)
end end
end end
end end)
-- Clear the DUI from loading
TriggerClientEvent(getScript()..":Client:ClearDUI", -1, data) -------------------------------------------------------------
--duiList[tostring(data.tex)].url = "" -- DUI Server Functions
end) -------------------------------------------------------------
AddEventHandler('onResourceStop', function(r) if r ~= getScript() then return end --- Server event handler to change DUI settings.
for k, v in pairs(duiList or {}) do --- If no URL is provided, resets to the preset value.
for i = 1, #v do RegisterNetEvent(getScript()..":Server:ChangeDUI", function(data)
RemoveReplaceTexture(tostring(v[i].tex.texd), tostring(v[i].tex.texn)) if not data.url then
end debugPrint("^6Bridge^7: ^2Preset: ^6"..tostring(data.preset).."^7")
end data.url = data.preset
end) else
for k, v in pairs(Locations[data.name].duiList) do
if isServer() then if v.tex.texn == data.texn then
createCallback(getScript()..":Server:duiList", function(source) Locations[data.name].duiList[k].url = data.url
return duiList end
end) end
end end
debugPrint("^6Bridge^7: ^3DUI^2 Sending new DUI to all players^7 - ^6"..data.url.."^7")
TriggerClientEvent(getScript()..":Client:ChangeDUI", -1, data)
end)
--- Server event handler to clear DUI settings.
RegisterNetEvent(getScript()..":Server:ClearDUI", function(data)
if data.url == "-" then
for k, v in pairs(Locations[data.name].duiList) do
if v.tex.texn == data.texn then
Locations[data.name].duiList[k].url = "-"
end
end
end
TriggerClientEvent(getScript()..":Client:ClearDUI", -1, data)
end)
-------------------------------------------------------------
-- Resource Cleanup
-------------------------------------------------------------
onResourceStop(function()
for k, v in pairs(duiList or {}) do
for i = 1, #v do
RemoveReplaceTexture(tostring(v[i].tex.texd), tostring(v[i].tex.texn))
end
end
end, true)
-------------------------------------------------------------
-- DUI List Callback (Server)
-------------------------------------------------------------
if isServer() then
createCallback(getScript()..":Server:duiList", function(source)
return duiList
end)
end
end

View File

@@ -1,190 +1,190 @@
--Screen Effects --Screen Effects
local alienEffect = false local alienEffect = false
function AlienEffect() function AlienEffect()
if alienEffect then return else alienEffect = true end if alienEffect then return else alienEffect = true end
debugPrint("^5Debug^7: ^3AlienEffect^7() ^2activated") debugPrint("^5Debug^7: ^3AlienEffect^7() ^2activated")
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0) AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
Wait(math.random(5000, 8000)) Wait(math.random(5000, 8000))
local Ped = PlayerPedId() local Ped = PlayerPedId()
local animDict = "MOVE_M@DRUNK@VERYDRUNK" local animDict = "MOVE_M@DRUNK@VERYDRUNK"
loadAnimDict(animDict) loadAnimDict(animDict)
SetPedCanRagdoll(Ped, true) SetPedCanRagdoll(Ped, true)
ShakeGameplayCam('DRUNK_SHAKE', 2.80) ShakeGameplayCam('DRUNK_SHAKE', 2.80)
SetTimecycleModifier("Drunk") SetTimecycleModifier("Drunk")
SetPedMovementClipset(Ped, animDict, 1) SetPedMovementClipset(Ped, animDict, 1)
SetPedMotionBlur(Ped, true) SetPedMotionBlur(Ped, true)
SetPedIsDrunk(Ped, true) SetPedIsDrunk(Ped, true)
Wait(1500) Wait(1500)
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0) SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
Wait(13500) Wait(13500)
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0) SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
Wait(120500) Wait(120500)
ClearTimecycleModifier() ClearTimecycleModifier()
ResetScenarioTypesEnabled() ResetScenarioTypesEnabled()
ResetPedMovementClipset(Ped, 0) ResetPedMovementClipset(Ped, 0)
SetPedIsDrunk(Ped, false) SetPedIsDrunk(Ped, false)
SetPedMotionBlur(Ped, false) SetPedMotionBlur(Ped, false)
AnimpostfxStopAll() AnimpostfxStopAll()
ShakeGameplayCam('DRUNK_SHAKE', 0.0) ShakeGameplayCam('DRUNK_SHAKE', 0.0)
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0) AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
Wait(math.random(45000, 60000)) Wait(math.random(45000, 60000))
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0) AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
AnimpostfxStop("DrugsMichaelAliensFightIn") AnimpostfxStop("DrugsMichaelAliensFightIn")
AnimpostfxStop("DrugsMichaelAliensFight") AnimpostfxStop("DrugsMichaelAliensFight")
AnimpostfxStop("DrugsMichaelAliensFightOut") AnimpostfxStop("DrugsMichaelAliensFightOut")
alienEffect = false alienEffect = false
debugPrint("^5Debug^7: ^3AlienEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3AlienEffect^7() ^2stopped")
end end
local weedEffect = false local weedEffect = false
function WeedEffect() function WeedEffect()
if weedEffect then return else weedEffect = true end if weedEffect then return else weedEffect = true end
debugPrint("^5Debug^7: ^3WeedEffect^7() ^2activated") debugPrint("^5Debug^7: ^3WeedEffect^7() ^2activated")
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0) AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
Wait(math.random(3000, 20000)) Wait(math.random(3000, 20000))
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0) AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
Wait(math.random(15000, 20000)) Wait(math.random(15000, 20000))
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0) AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
AnimpostfxStop("DrugsMichaelAliensFightIn") AnimpostfxStop("DrugsMichaelAliensFightIn")
AnimpostfxStop("DrugsMichaelAliensFight") AnimpostfxStop("DrugsMichaelAliensFight")
AnimpostfxStop("DrugsMichaelAliensFightOut") AnimpostfxStop("DrugsMichaelAliensFightOut")
weedEffect = false weedEffect = false
debugPrint("^5Debug^7: ^3WeedEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3WeedEffect^7() ^2stopped")
end end
local trevorEffect = false local trevorEffect = false
function TrevorEffect() function TrevorEffect()
if trevorEffect then return else trevorEffect = true end if trevorEffect then return else trevorEffect = true end
debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2activated") debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2activated")
AnimpostfxPlay("DrugsTrevorClownsFightIn", 3.0, 0) AnimpostfxPlay("DrugsTrevorClownsFightIn", 3.0, 0)
Wait(3000) Wait(3000)
AnimpostfxPlay("DrugsTrevorClownsFight", 3.0, 0) AnimpostfxPlay("DrugsTrevorClownsFight", 3.0, 0)
Wait(30000) Wait(30000)
AnimpostfxPlay("DrugsTrevorClownsFightOut", 3.0, 0) AnimpostfxPlay("DrugsTrevorClownsFightOut", 3.0, 0)
AnimpostfxStop("DrugsTrevorClownsFight") AnimpostfxStop("DrugsTrevorClownsFight")
AnimpostfxStop("DrugsTrevorClownsFightIn") AnimpostfxStop("DrugsTrevorClownsFightIn")
AnimpostfxStop("DrugsTrevorClownsFightOut") AnimpostfxStop("DrugsTrevorClownsFightOut")
trevorEffect = false trevorEffect = false
debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2stopped")
end end
local turboEffect = false local turboEffect = false
function TurboEffect() function TurboEffect()
if turboEffect then return else turboEffect = true end if turboEffect then return else turboEffect = true end
debugPrint("^5Debug^7: ^3TurboEffect^7() ^2activated") debugPrint("^5Debug^7: ^3TurboEffect^7() ^2activated")
AnimpostfxPlay('RaceTurbo', 0, true) AnimpostfxPlay('RaceTurbo', 0, true)
SetTimecycleModifier('rply_motionblur') SetTimecycleModifier('rply_motionblur')
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25) ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
Wait(30000) Wait(30000)
StopGameplayCamShaking(true) StopGameplayCamShaking(true)
SetTransitionTimecycleModifier('default', 0.35) SetTransitionTimecycleModifier('default', 0.35)
Wait(1000) Wait(1000)
ClearTimecycleModifier() ClearTimecycleModifier()
AnimpostfxStop('RaceTurbo') AnimpostfxStop('RaceTurbo')
turboEffect = false turboEffect = false
debugPrint("^5Debug^7: ^3TurboEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3TurboEffect^7() ^2stopped")
end end
local rampageEffect = false local rampageEffect = false
function RampageEffect() function RampageEffect()
if rampageEffect then return else rampageEffect = true end if rampageEffect then return else rampageEffect = true end
debugPrint("^5Debug^7: ^3RampageEffect^7() ^2activated") debugPrint("^5Debug^7: ^3RampageEffect^7() ^2activated")
AnimpostfxPlay('Rampage', 0, true) AnimpostfxPlay('Rampage', 0, true)
SetTimecycleModifier('rply_motionblur') SetTimecycleModifier('rply_motionblur')
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25) ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
Wait(30000) Wait(30000)
StopGameplayCamShaking(true) StopGameplayCamShaking(true)
SetTransitionTimecycleModifier('default', 0.35) SetTransitionTimecycleModifier('default', 0.35)
Wait(1000) Wait(1000)
ClearTimecycleModifier() ClearTimecycleModifier()
AnimpostfxStop('Rampage') AnimpostfxStop('Rampage')
rampageEffect = false rampageEffect = false
debugPrint("^5Debug^7: ^3RampageEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3RampageEffect^7() ^2stopped")
end end
local focusEffect = false local focusEffect = false
function FocusEffect() function FocusEffect()
if focusEffect then return else focusEffect = true end if focusEffect then return else focusEffect = true end
debugPrint("^5Debug^7: ^3FocusEffect^7() ^2activated") debugPrint("^5Debug^7: ^3FocusEffect^7() ^2activated")
Wait(1000) Wait(1000)
AnimpostfxPlay('FocusIn', 0, true) AnimpostfxPlay('FocusIn', 0, true)
Wait(30000) Wait(30000)
AnimpostfxStop('FocusIn') AnimpostfxStop('FocusIn')
focusEffect = false focusEffect = false
debugPrint("^5Debug^7: ^3FocusEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3FocusEffect^7() ^2stopped")
end end
local nightVisionEffect = false local nightVisionEffect = false
function NightVisionEffect() function NightVisionEffect()
if nightVisionEffect then return else nightVisionEffect = true end if nightVisionEffect then return else nightVisionEffect = true end
debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2activated") debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2activated")
SetNightvision(true) SetNightvision(true)
Wait(math.random(3000, 4000)) -- FEEL FREE TO CHANGE THIS Wait(math.random(3000, 4000)) -- FEEL FREE TO CHANGE THIS
SetNightvision(false) SetNightvision(false)
SetSeethrough(false) SetSeethrough(false)
nightVisionEffect = false nightVisionEffect = false
debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2stopped")
end end
local thermalEffect = false local thermalEffect = false
function ThermalEffect() function ThermalEffect()
if thermalEffect then return else thermalEffect = true end if thermalEffect then return else thermalEffect = true end
debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2activated") debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2activated")
SetNightvision(true) SetNightvision(true)
SetSeethrough(true) SetSeethrough(true)
Wait(math.random(2000, 3000)) -- FEEL FREE TO CHANGE THIS Wait(math.random(2000, 3000)) -- FEEL FREE TO CHANGE THIS
SetNightvision(false) SetNightvision(false)
SetSeethrough(false) SetSeethrough(false)
thermalEffect = false thermalEffect = false
debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2stopped")
end end
--Built-in Buff effects --Built-in Buff effects
local healEffect = false local healEffect = false
function HealEffect(data) function HealEffect(data)
if healEffect then return end if healEffect then return end
debugPrint("^5Debug^7: ^3HealEffect^7() ^2activated") debugPrint("^5Debug^7: ^3HealEffect^7() ^2activated")
healEffect = true healEffect = true
local count = (data[1] / 1000) local count = (data[1] / 1000)
while count > 0 do while count > 0 do
Wait(1000) Wait(1000)
count -= 1 count -= 1
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + data[2]) SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + data[2])
end end
healEffect = false healEffect = false
debugPrint("^5Debug^7: ^3HealEffect^7() ^2stopped") debugPrint("^5Debug^7: ^3HealEffect^7() ^2stopped")
end end
local staminaEffect = false local staminaEffect = false
function StaminaEffect(data) function StaminaEffect(data)
if staminaEffect then return end if staminaEffect then return end
debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2activated") debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2activated")
staminaEffect = true staminaEffect = true
local startStamina = (data[1] / 1000) local startStamina = (data[1] / 1000)
SetRunSprintMultiplierForPlayer(PlayerId(), 1.49) SetRunSprintMultiplierForPlayer(PlayerId(), 1.49)
while startStamina > 0 do while startStamina > 0 do
Wait(1000) Wait(1000)
if math.random(5, 100) < 10 then RestorePlayerStamina(PlayerId(), data[2]) end if math.random(5, 100) < 10 then RestorePlayerStamina(PlayerId(), data[2]) end
startStamina -= 1 startStamina -= 1
if math.random(5, 100) < 51 then end if math.random(5, 100) < 51 then end
end end
startStamina = 0 startStamina = 0
SetRunSprintMultiplierForPlayer(PlayerId(), 1.0) SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
staminaEffect = false staminaEffect = false
debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2stopped") debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2stopped")
end end
function StopEffects() -- Used to clear up any effects stuck on screen function StopEffects() -- Used to clear up any effects stuck on screen
debugPrint("^5Bridge^7: ^2All screen effects stopped") debugPrint("^5Bridge^7: ^2All screen effects stopped")
ShakeGameplayCam('DRUNK_SHAKE', 0.0) ShakeGameplayCam('DRUNK_SHAKE', 0.0)
SetPedToRagdoll(PlayerPedId(), 5000, 1000, 1, 0, 0, 0) SetPedToRagdoll(PlayerPedId(), 5000, 1000, 1, 0, 0, 0)
ClearTimecycleModifier() ClearTimecycleModifier()
ResetScenarioTypesEnabled() ResetScenarioTypesEnabled()
ResetPedMovementClipset(PlayerPedId(), 0) ResetPedMovementClipset(PlayerPedId(), 0)
SetPedIsDrunk(PlayerPedId(), false) SetPedIsDrunk(PlayerPedId(), false)
SetPedMotionBlur(PlayerPedId(), false) SetPedMotionBlur(PlayerPedId(), false)
SetNightvision(false) SetNightvision(false)
SetSeethrough(false) SetSeethrough(false)
AnimpostfxStop("DrugsMichaelAliensFightIn") AnimpostfxStop("DrugsMichaelAliensFightIn")
AnimpostfxStop("DrugsMichaelAliensFight") AnimpostfxStop("DrugsMichaelAliensFight")
AnimpostfxStop("DrugsMichaelAliensFightOut") AnimpostfxStop("DrugsMichaelAliensFightOut")
AnimpostfxStop("DrugsTrevorClownsFight") AnimpostfxStop("DrugsTrevorClownsFight")
AnimpostfxStop("DrugsTrevorClownsFightIn") AnimpostfxStop("DrugsTrevorClownsFightIn")
AnimpostfxStop("DrugsTrevorClownsFightOut") AnimpostfxStop("DrugsTrevorClownsFightOut")
AnimpostfxStop('RaceTurbo') AnimpostfxStop('RaceTurbo')
AnimpostfxStop('FocusIn') AnimpostfxStop('FocusIn')
AnimpostfxStop('Rampage') AnimpostfxStop('Rampage')
end end

File diff suppressed because it is too large Load Diff

View File

@@ -1,156 +1,333 @@
-- INPUT -- --[[
-- Multiscript input script function to create simple input text boxes -- Input Dialog Module
---------------------
--- Creates a simple input dialog compatible with multiple menu systems. This module provides a function to create a simple input dialog compatible with
--- multiple menu systems (OX, QB, GTA/WarMenu, and ESX). It supports various input
--- This function generates input dialogs for different frameworks (OX, QB, GTA) based on the configuration. types such as radio buttons, numbers, text, and select dropdowns.
--- It supports various input types such as radio buttons, numbers, text, and select dropdowns.
--- ]]
---@param title string The title/header of the input dialog.
---@param opts table A table containing input options. Each option should have a `type` and other relevant fields based on the type. --- Creates a simple input dialog using the configured menu system.
--- - **type** (`string`): The type of input. Supported types: "radio", "number", "text", "select". ---
--- - **label** (`string`, optional): The label for the input (used for "radio" and "select" types in OX). --- @param title string The title or header of the input dialog.
--- - **text** (`string`, optional): The text prompt for the input. --- @param opts table A table of input option definitions. Each option should include:
--- - **name** (`string`): The identifier name for the input. --- - type (string): The input type ("radio", "number", "text", "select").
--- - **isRequired** (`boolean`, optional): Whether the input is required. --- - label (string, optional): A label for the input (used in radio/select for OX).
--- - **default** (`any`, optional): The default value for the input. --- - text (string, optional): The text prompt for the input.
--- - **options** (`table`, optional): A table of options for "radio" and "select" types. --- - name (string): The identifier for the input.
--- - **min** (`number`, optional): The minimum value (used for "select" type). --- - isRequired (boolean, optional): Whether input is mandatory.
--- - **max** (`number`, optional): The maximum value (used for "number" and "select" types). --- - default (any, optional): The default value.
--- - **txt** (`string`, optional): Additional text or description for the input. --- - options (table, optional): A table of choices for "radio" and "select" types.
--- --- - min (number, optional): Minimum value (for "number" and "select").
---@return table|nil table Returns the user's input as a table if the dialog is submitted, otherwise returns `nil`. --- - max (number, optional): Maximum value.
--- --- - txt (string, optional): Additional description.
---@usage ---
--- ```lua --- @return table|nil table Returns the user's input as a table if submitted, otherwise nil.
--- local userInput = createInput("Enter Details", { ---
--- { type = "text", text = "Name", name = "playerName", isRequired = true }, ---@usage
--- { type = "number", text = "Age", name = "playerAge", min = 18, max = 99 }, --- ```lua
--- { type = "radio", label = "Gender", name = "playerGender", options = { --- local userInput = createInput("Enter Details", {
--- { text = "Male", value = "male" }, --- { type = "text", text = "Name", name = "playerName", isRequired = true },
--- { text = "Female", value = "female" }, --- { type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
--- { text = "Other", value = "other" }, --- { type = "radio", label = "Gender", name = "playerGender", options = {
--- }}, --- { text = "Male", value = "male" },
--- }) --- { text = "Female", value = "female" },
--- ``` --- { text = "Other", value = "other" },
function createInput(title, opts) --- }},
local dialog = nil --- })
local options = {} --- ```
function createInput(title, opts)
if Config.System.Menu == "ox" then local dialog = nil
for i = 1, #opts do local options = {}
if opts[i].type == "radio" then local currentNum = 0
-- Convert radio options to select type for OX if Config.System.Menu == "ox" then
for k in pairs(opts[i].options) do for i = 1, #opts do
opts[i].options[k].label = opts[i].options[k].text currentNum += 1
end if opts[i] == nil then currentNum -= 1 goto skip end
options[i] = { if opts[i].type == "radio" then
type = "select", -- Convert radio options to select type for OX
isRequired = opts[i].isRequired, for k in pairs(opts[i].options) do
label = opts[i].label or opts[i].text, opts[i].options[k].label = opts[i].options[k].text
name = opts[i].name, end
default = opts[i].default or opts[i].options[1].value, options[currentNum] = {
options = opts[i].options, type = "select",
} isRequired = opts[i].isRequired,
end label = opts[i].label or opts[i].text,
if opts[i].type == "number" then name = opts[i].name,
options[i] = { default = opts[i].default or opts[i].options[1].value,
type = "number", options = opts[i].options,
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""), }
isRequired = opts[i].isRequired, end
name = opts[i].name, if opts[i].type == "number" then
options = opts[i].options, options[currentNum] = {
} type = opts[i].type,
end label = (opts[i].label or opts[i].text)..(opts[i].txt and " - "..opts[i].txt or ""),
if opts[i].type == "text" then isRequired = opts[i].isRequired,
options[i] = { name = opts[i].name,
type = "input", options = opts[i].options,
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""), }
default = opts[i].default, end
isRequired = opts[i].isRequired, if opts[i].type == "text" then
} options[currentNum] = {
end type = "input",
if opts[i].type == "select" then label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
options[i] = { default = opts[i].default,
type = "select", isRequired = opts[i].isRequired,
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""), }
isRequired = opts[i].isRequired, end
name = opts[i].name, if opts[i].type == "select" then
options = opts[i].options, options[currentNum] = {
min = opts[i].min, type = opts[i].type,
max = opts[i].max, label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
default = opts[i].default, isRequired = opts[i].isRequired,
} name = opts[i].name,
end options = opts[i].options,
end min = opts[i].min,
dialog = exports[OXLibExport]:inputDialog(title, options) max = opts[i].max,
return dialog default = opts[i].default,
end }
end
if Config.System.Menu == "qb" then
dialog = exports['qb-input']:ShowInput({ header = title, submitText = "Accept", inputs = opts }) if opts[i].type == "checkbox" then
return dialog jsonPrint(opts[i])
end for k in pairs(opts[i].options) do
if options[currentNum] then currentNum += 1 end
if Config.System.Menu == "gta" then options[currentNum] = {
WarMenu.CreateMenu(tostring(opts), type = opts[i].type,
title, label = opts[i].options[k].text..(opts[i].txt and " - "..opts[i].txt or ""),
" ", name = opts[i].options[k].value,
{ }
titleColor = { 222, 255, 255 },
maxOptionCountOnScreen = 15, end
width = 0.25, end
x = 0.7,
}) if opts[i].type == "color" then
if WarMenu.IsAnyMenuOpened() then return end options[currentNum] = {
WarMenu.OpenMenu(tostring(opts)) type = opts[i].type,
label = opts[i].label,
local close = true isRequired = opts[i].isRequired,
local _comboBoxItems = {} format = opts[i].format,
local _comboBoxIndex = { 1, 1 } default = opts[i].default,
}
while true do end
if WarMenu.Begin(tostring(opts)) then if opts[i].type == "slider" then
for i = 1, #opts do options[currentNum] = {
if opts[i].type == "radio" then type = opts[i].type,
for k in pairs(opts[i].options) do label = opts[i].label,
if not _comboBoxItems[i] then _comboBoxItems[i] = {} end isRequired = opts[i].required,
_comboBoxItems[i][k] = opts[i].options[k].text min = opts[i].min,
end max = opts[i].max,
local _, comboBoxIndex = WarMenu.ComboBox(opts[i].label, _comboBoxItems[i], _comboBoxIndex[i]) default = opts[i].default,
if _comboBoxIndex[i] ~= comboBoxIndex then }
_comboBoxIndex[i] = comboBoxIndex end
end ::skip::
end end
if opts[i].type == "number" then dialog = exports[OXLibExport]:inputDialog(title, options)
for b = 1, opts[i].max do return dialog
if not _comboBoxItems[i] then _comboBoxItems[i] = {} end elseif Config.System.Menu == "qb" then
_comboBoxItems[i][b] = b for k, v in pairs(opts) do
end currentNum += 1
local _, comboBoxIndex = WarMenu.ComboBox(opts[i].text, _comboBoxItems[i], _comboBoxIndex[i]) if opts[k] == nil then
if _comboBoxIndex[i] ~= comboBoxIndex then currentNum -= 1
_comboBoxIndex[i] = comboBoxIndex else
end options[currentNum] = opts[k]
end end
end end
local pressed = WarMenu.Button("Pay") dialog = exports['qb-input']:ShowInput(
if pressed then { header = title, submitText = "Accept", inputs = options }
WarMenu.CloseMenu() )
close = false return dialog
local result = {}
for i = 1, #_comboBoxIndex do elseif Config.System.Menu == "lation" then
result[i] = _comboBoxItems[i][_comboBoxIndex[i]]
end for i in pairs(opts) do
return result currentNum += 1
end if opts[i] == nil then currentNum -= 1 goto skip end
WarMenu.End() if opts[i].type == "radio" then
else for k in pairs(opts[i].options) do
return opts[i].options[k].label = opts[i].options[k].text
end end
if not WarMenu.IsAnyMenuOpened() and close then options[currentNum] = {
if data.onExit then data.onExit() end type = "select",
end required = opts[i].isRequired,
Wait(0) label = opts[i].label or opts[i].text,
end name = opts[i].name,
end default = opts[i].default or opts[i].options[1].value,
options = opts[i].options,
}
end
if opts[i].type == "number" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label or opts[i].text,
description = opts[i].txt and " - "..opts[i].txt or "",
required = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
}
end
if opts[i].type == "text" then
options[currentNum] = {
type = "input",
label = opts[i].label or opts[i].text,
description = (opts[i].txt and " - "..opts[i].txt or ""),
placeholder = opts[i].default,
required = opts[i].isRequired,
}
end
if opts[i].type == "select" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label or opts[i].text,
description = opts[i].txt and " - "..opts[i].txt or "",
required = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
if opts[i].type == "checkbox" then
for k in pairs(opts[i].options) do
if options[currentNum] then currentNum += 1 end
options[currentNum] = {
type = opts[i].type,
label = opts[i].options[k].text..(opts[i].txt and " - "..opts[i].txt or ""),
name = opts[i].options[k].value,
}
end
end
if opts[i].type == "color" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
required = opts[i].isRequired,
format = opts[i].format,
default = opts[i].default,
}
end
if opts[i].type == "slider" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
required = opts[i].required,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
::skip::
end
local dialog = exports.lation_ui:input({
title = title,
submitText = "Accept",
options = options
})
return dialog
elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(opts),
title,
" ",
{
titleColor = { 222, 255, 255 },
maxOptionCountOnScreen = 15,
width = 0.25,
x = 0.7,
})
if WarMenu.IsAnyMenuOpened() then return end
WarMenu.OpenMenu(tostring(opts))
local close = true
local _comboBoxItems = {}
local _comboBoxIndex = { 1, 1 }
while true do
if WarMenu.Begin(tostring(opts)) then
for i = 1, #opts do
if opts[i].type == "radio" then
for k in pairs(opts[i].options) do
if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
_comboBoxItems[i][k] = opts[i].options[k].text
end
local _, comboBoxIndex = WarMenu.ComboBox(opts[i].label, _comboBoxItems[i], _comboBoxIndex[i])
if _comboBoxIndex[i] ~= comboBoxIndex then
_comboBoxIndex[i] = comboBoxIndex
end
end
if opts[i].type == "number" then
for b = 1, opts[i].max do
if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
_comboBoxItems[i][b] = b
end
local _, comboBoxIndex = WarMenu.ComboBox(opts[i].text, _comboBoxItems[i], _comboBoxIndex[i])
if _comboBoxIndex[i] ~= comboBoxIndex then
_comboBoxIndex[i] = comboBoxIndex
end
end
end
local pressed = WarMenu.Button("Pay")
if pressed then
WarMenu.CloseMenu()
close = false
local result = {}
for i = 1, #_comboBoxIndex do
result[i] = _comboBoxItems[i][_comboBoxIndex[i]]
end
return result
end
WarMenu.End()
else
return
end
if not WarMenu.IsAnyMenuOpened() and close then
if data.onExit then data.onExit() end
end
Wait(0)
end
elseif Config.System.Menu == "esx" then -- horrible input dialog, not even worth using, get OX
local results = {}
for i, opt in ipairs(opts) do
local prompt = opt.text or opt.label or "Enter value"
-- For radio/select types, append available options in the prompt.
if (opt.type == "radio" or opt.type == "select") and opt.options then
local choices = ""
for j, choice in ipairs(opt.options) do
choices = choices .. choice.text .. " (" .. tostring(choice.value) .. ")"
if j < #opt.options then choices = choices .. ", " end
end
prompt = prompt .. " [" .. choices .. "]"
elseif opt.type == "number" then
prompt = prompt .. " (number between " .. (opt.min or 0) .. " and " .. (opt.max or 100) .. ")"
end
local value = nil
ESX.UI.Menu.Open('dialog', getScript(), 'input_' .. i, {
title = prompt
}, function(data, menu)
value = data.value
menu.close()
end, function(data, menu)
menu.close()
end)
-- Wait until the player submits a value.
while value == nil do
Wait(0)
end
-- Convert to a number if needed.
if opt.type == "number" then
value = tonumber(value)
end
results[opt.name or i] = value
end
return results
end
end end

246
shared/inventories.lua Normal file
View File

@@ -0,0 +1,246 @@
-------------------------------------------------------------
-- Item Availability & Inventory Retrieval
-------------------------------------------------------------
---
--- Locks or unlocks the player's inventory.
--- Freezes/unfreezes the player's position, sets inventory busy state, and toggles hotbar usage.
---
--- @param toggle boolean True to lock inventory; false to unlock.
---
--- @usage
--- ```lua
--- lockInv(true) -- Lock inventory.
--- lockInv(false) -- Unlock inventory.
--- ```
function lockInv(toggle)
FreezeEntityPosition(PlayerPedId(), toggle)
LocalPlayer.state:set("inv_busy", toggle, true)
LocalPlayer.state:set("invBusy", toggle, true)
TriggerEvent('inventory:client:busy:status', toggle)
TriggerEvent('canUseInventoryAndHotbar:toggle', not toggle)
end
--- Checks if a player has the specified items in their inventory.
---
--- Verifies whether the required quantities are present. Returns a boolean and a table of details.
---
--- @param items string|table A single item name or table with required amounts.
--- @param amount number The required quantity (default 1).
--- @param src number|nil Player source ID (defaults to caller).
--- @return boolean boolean True if all items are available; otherwise, false.
--- @return table|nil table Table detailing counts for each item.
---
---@usage
--- ```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
--- ```
function hasItem(items, amount, src)
local amount = amount and amount or 1
local grabInv, foundInv = getPlayerInv(src)
if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
if grabInv then
local hasTable = {}
for item, amt in pairs(items) do
if not Items[item] then print("^4ERROR^7: ^2Script can't find ingredient item in Shared Items - ^1"..item.."^7") end
local count = 0
for _, itemData in pairs(grabInv) do
--jsonPrint(itemData)
if itemData and itemData.name == item then
count += (itemData.amount or itemData.count or 1)
end
end
foundInv = foundInv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
local foundMessage = "^6Bridge^7: ^3hasItem^7[^6"..foundInv.."^7]: "..tostring(item).." ^3"..count.."^7/^3"..amt
if count >= amt then foundMessage = foundMessage.." ^5FOUND^7" else foundMessage = foundMessage .." ^1NOT FOUND^7" end
debugPrint(foundMessage)
hasTable[item] = { hasItem = count >= amt, count = count }
end
for k, v in pairs(hasTable) do
if not v.hasItem then
return false, hasTable
end
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.
---
--- @param src number|nil The player source ID (if nil, retrieves current player's inventory).
--- @return table|nil table The inventory items.
--- @return string|nil string The name of the inventory system.
---
---@usage
--- ```lua
--- local inventory, system = getPlayerInv(playerId)
--- if inventory then
--- -- Process inventory
--- end
--- ```
function getPlayerInv(src)
local grabInv = nil
local foundInv = ""
if isStarted(OXInv) then
foundInv = OXInv
if src then
grabInv = exports[OXInv]:GetInventoryItems(src)
else
grabInv = exports[OXInv]:GetPlayerItems()
end
elseif isStarted(QSInv) then
foundInv = QSInv
if src then
grabInv = exports[QSInv]:GetInventory(src)
else
grabInv = exports[QSInv]:getUserInventory()
end
elseif isStarted(OrigenInv) then
foundInv = OrigenInv
if src then
grabInv = exports[OrigenInv]:getInventory(src)
else
grabInv = exports[OrigenInv]:getInventory()
end
elseif isStarted(CoreInv) then
foundInv = CoreInv
if src then
grabInv = exports[CoreInv]:getInventory(src)
else
grabInv = exports[CoreInv]:getInventory()
end
elseif isStarted(CodeMInv) then
foundInv = CodeMInv
if src then
grabInv = exports[CodeMInv]:GetInventory(getPlayer(src).citizenId, src)
else
grabInv = exports[CodeMInv]:getUserInventory()
end
elseif isStarted(TgiannInv) then
foundInv = TgiannInv
if src then
grabInv = exports[TgiannInv]:GetPlayerItems(src)
else
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
grabInv = Core.Functions.GetPlayer(src).PlayerData.items
else
grabInv = Core.Functions.GetPlayerData().items
end
elseif isStarted(PSInv) then
foundInv = PSInv
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
if src then
local xPlayer = ESX.GetPlayerFromId(src)
grabInv = xPlayer.inventory
else
local xPlayer = ESX.GetPlayerData() -- Client side, if available
grabInv = xPlayer.inventory
end
elseif isStarted(RSGInv) then
foundInv = RSGInv
if src then
grabInv = Core.Functions.GetPlayer(src).PlayerData.items
else
grabInv = Core.Functions.GetPlayerData().items
end
else
print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3starter^1.^2lua^7")
end
return grabInv, foundInv
end
function isInventoryOpen()
return IsNuiFocused()
end
-------------------------------------------------------------
-- Item Image Retrieval
-------------------------------------------------------------
--- Retrieves the NUI link for an item's image from the active inventory system.
---
--- @param item string The item name.
--- @return string string A `nui://` link to the item's image, or an empty string if not found.
---
--- @usage
--- ```lua
--- local imageLink = invImg("health_potion")
--- if imageLink ~= "" then print(imageLink) end
--- ```
function invImg(item)
local imgLink = ""
if item ~= "" and Items[item] then
if isStarted(OXInv) then
imgLink = "nui://"..OXInv.."/web/images/"..(Items[item].image or "")
elseif isStarted(QSInv) then
imgLink = "nui://"..QSInv.."/html/images/"..(Items[item].image or "")
elseif isStarted(CoreInv) then
imgLink = "nui://"..CoreInv.."/html/img/"..(Items[item].image or "")
elseif isStarted(CodeMInv) then
imgLink = "nui://"..CodeMInv.."/html/itemimages/"..(Items[item].image or "")
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 "")
elseif isStarted(RSGInv) then
imgLink = "nui://"..RSGInv.."/html/images/"..(Items[item].image or "")
else
print("^4ERROR^7: ^2No Inventory detected for invImg - Check starter.lua")
end
end
return imgLink
end

View File

@@ -1,442 +1,287 @@
isCat, isDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false --[[
Animal Detection Module
if not isServer() then -------------------------
onPlayerLoaded(function() This module determines whether a Ped is an animal and categorizes it as a cat, dog,
Wait(2000) or other type (e.g., coyote). It uses predefined model hashes stored in the AnimalPeds table.
isCat, isDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false
isPedAnimal() Global Flags:
if isAnimal then - isCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal: Booleans to track the player's
local ped = PlayerPedId() current animal classification.
local pedModel = GetEntityModel(ped)
When running client-side (not on the server), the module checks the player's Ped after they load.
isCat = (isCat(ped) or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`)
Usage Examples:
isDog, isBigDog = isDog(ped) -- Check if the player's Ped is an animal:
isSmallDog = not isBigDog local animalStatus = isPedAnimal()
if isDog and pedModel == `a_c_coyote` then isDog = false end
-- Check if a given Ped is a cat:
isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`) if isCat(somePed) then print("This is a cat!") end
if pedModel == `ft-capmonkey2` then isDog = true end -- Determine if a Ped is a dog and whether it's big or small:
end local isDogFlag, isBig = isDog(somePed)
end, true)
-- Retrieve a flat list of all animal model hashes:
local allAnimalModels = getAnimalModels()
--- Determines if a given Ped is classified as an animal.
--- ]]
--- This function checks whether the specified Ped (or the player's Ped if none is provided)
--- is listed within the predefined `AnimalPeds` tables. It iterates through all animal types -- Global animal classification flags.
--- to verify if the Ped's model hash matches any known animal models. isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false
---
---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`). if not isServer() then
--- onPlayerLoaded(function()
---@return boolean `true` if the Ped is an animal, otherwise `false`. Wait(1000)
--- -- Reset classification flags
--- @usage isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false
--- ```lua -- Check if the player's Ped is an animal.
--- local isPlayerAnimal = isAnimal() isPedAnimal()
--- local isSpecificPedAnimal = isAnimal(somePedEntity) if isAnimal then
--- ``` local ped = PlayerPedId()
function isPedAnimal(ped) local pedModel = GetEntityModel(ped)
local PedModel = GetEntityModel(ped or PlayerPedId())
-- Determine if the Ped is a cat:
for _, animalTypeTable in pairs(AnimalPeds) do -- Also treat 'ft-raccoon' as a cat unless it is 'ft-sphynx'
for animalModelHash, _ in pairs(animalTypeTable) do isPedCat = (isCat(ped) == true or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`)
if PedModel == animalModelHash then
isAnimal = true -- Determine if the Ped is a dog and whether it's big:
break isPedDog, isBigDog = isDog(ped)
end isSmallDog = not isBigDog
end if isPedDog and pedModel == `a_c_coyote` then isPedDog = false end
if isAnimal then
debugPrint("^6Debug^7: ^2Ped is Animal^1") -- Determine if the Ped is a coyote (special case):
break isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`)
end
end -- Special override: if model is 'ft-capmonkey2', treat as a dog.
if pedModel == `ft-capmonkey2` then isPedDog = true end
return isAnimal end
end end, true)
--- Checks if a given Ped is classified specifically as a cat.
--- -------------------------------------------------------------
--- This function verifies whether the specified Ped (or the player's Ped if none is provided) -- Animal Classification Functions
--- matches any of the model hashes listed under `AnimalPeds.CatPeds`. It returns `true` if a match is found. -------------------------------------------------------------
---
---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`). --- Determines whether a given Ped is classified as an animal.
--- ---
---@return boolean `true` if the Ped is a cat, otherwise `false`. --- Checks if the Ped's model hash appears in any of the animal categories defined in AnimalPeds.
--- ---
---@usage --- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped.
--- ```lua --- @return boolean boolean True if the Ped is an animal, otherwise false.
--- if isCat() then ---
--- print("Player is a cat!") --- @usage
--- end --- ```lua
--- --- local isPlayerAnimal = isAnimal()
--- local anotherPed = GetPedInVehicleSeat(vehicle, -1) --- local isSpecificPedAnimal = isAnimal(somePedEntity)
--- if isCat(anotherPed) then --- ```
--- print("Driver is a cat!") function isPedAnimal(ped)
--- end local PedModel = GetEntityModel(ped or PlayerPedId())
--- ``` for _, animalCategory in pairs(AnimalPeds) do
function isCat(ped) for animalModelHash, _ in pairs(animalCategory) do
local PedModel = GetEntityModel(ped or PlayerPedId()) if PedModel == animalModelHash then
for k, v in pairs(AnimalPeds.CatPeds) do isAnimal = true
if PedModel == k then debugPrint("^4isAnimal^7: ^2Ped is ^4Animal")
return true return true
end end
end end
return false end
end return false
end
--- Determines if a given Ped is classified as a dog and identifies its size category.
--- --- Checks if a given Ped is classified as a cat.
--- This function checks whether the specified Ped (or the player's Ped if none is provided) ---
--- matches any model hashes listed under `AnimalPeds.BigDogs` or `AnimalPeds.SmallDogs`. It returns --- Iterates through the CatPeds table and returns true if the Ped's model matches.
--- two values: the first indicates if the Ped is a dog, and the second specifies whether it's a ---
--- large dog (`true`) or a small dog (`false`). If the Ped is not a dog, the second return value is `nil`. --- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped.
--- --- @return boolean True if the Ped is a cat, otherwise false.
---@param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped (`PlayerPedId()`). ---
--- ---@usage
---@return boolean, boolean|nil Returns `true` and `true` if the Ped is a big dog, --- ```lua
--- `true` and `false` if it's a small dog, --- if isCat() then
--- or `false` and `nil` if it's not a dog. --- print("Player is a cat!")
--- --- end
---@usage ---
--- ```lua --- local anotherPed = GetPedInVehicleSeat(vehicle, -1)
--- local isDog, isBigDog = isDog() --- if isCat(anotherPed) then
--- if isDog then --- print("Driver is a cat!")
--- if isBigDog then --- end
--- print("Player is a big dog!") --- ```
--- else function isCat(ped)
--- print("Player is a small dog!") local PedModel = GetEntityModel(ped or PlayerPedId())
--- end for modelHash, _ in pairs(AnimalPeds.CatPeds) do
--- else if PedModel == modelHash then
--- print("Player is not a dog.") debugPrint("^4isAnimal^7: ^2Ped is ^4Cat")
--- end return true
--- end
--- local somePed = GetPedInVehicleSeat(vehicle, 0) end
--- local isPetDog, isLargeDog = isDog(somePed) return false
--- if isPetDog then end
--- if isLargeDog then
--- print("Passenger is a big dog!") --- Determines if a given Ped is a dog and identifies its size category.
--- else ---
--- print("Passenger is a small dog!") --- Checks the BigDogs and SmallDogs tables to see if the Ped's model matches any dog model.
--- end ---
--- end --- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped.
--- ``` ---@return boolean, boolean|nil boolean Returns `true` and `true` if the Ped is a big dog,
function isDog(ped) --- `true` and `false` if it's a small dog,
local PedModel = GetEntityModel(ped or PlayerPedId()) --- or `false` and `nil` if it's not a dog.
for k, v in pairs(AnimalPeds.BigDogs) do ---
if PedModel == k then ---@usage
return true, true --- ```lua
end --- local isPedDog, isBigDog = isDog()
end --- if isPedDog then
--- if isBigDog then
for k, v in pairs(AnimalPeds.SmallDogs) do --- print("Player is a big dog!")
if PedModel == k then --- else
return true, false --- print("Player is a small dog!")
end --- end
end --- else
return false, nil --- print("Player is not a dog.")
end --- end
---
--- Retrieves a list of all animal model hashes. --- local somePed = GetPedInVehicleSeat(vehicle, 0)
--- --- local isPetDog, isLargeDog = isDog(somePed)
--- This function compiles and returns a flat table containing all model hashes --- if isPetDog then
--- from the various animal categories defined within the `AnimalPeds` table. --- if isLargeDog then
--- It's useful for iterating over or performing bulk operations on all animal models. --- print("Passenger is a big dog!")
--- --- else
---@return table table A table containing all animal model hashes. --- print("Passenger is a small dog!")
--- --- end
---@usage --- end
--- ```lua --- ```
--- local allAnimalModels = getAnimalModels() function isDog(ped)
--- for _, modelHash in ipairs(allAnimalModels) do local PedModel = GetEntityModel(ped or PlayerPedId())
--- print("Animal Model Hash:", modelHash) for modelHash, _ in pairs(AnimalPeds.BigDogs) do
--- end if PedModel == modelHash then
--- ``` debugPrint("^4isAnimal^7: ^2Ped is ^4Dog")
function getAnimalModels() return true, true
local animalTable = {} end
for k in pairs(AnimalPeds) do end
for v in pairs(AnimalPeds[k]) do for modelHash, _ in pairs(AnimalPeds.SmallDogs) do
animalTable[#animalTable+1] = v if PedModel == modelHash then
end debugPrint("^4isAnimal^7: ^2Ped is ^4Small Dog")
end return true, false
return animalTable end
end end
end return false, nil
end
AnimalPeds = {
BigDogs = { --- Compiles and returns a flat table of all animal model hashes.
-- Big Dogs ---
[`a_c_chop`] = { --- Iterates through every category in AnimalPeds and collects all model hashes.
deathAnim = "dead_right", deathDict = "creatures@chop@move", ---
exitAnim = "getup_r", exitDict = "creatures@chop@getup", --- @return table table A table containing all animal model hashes.
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" ---
}, ---@usage
[`a_c_k9`] = { --- ```lua
deathAnim = "dead_right", deathDict = "creatures@chop@move", --- local allAnimalModels = getAnimalModels()
exitAnim = "getup_r", exitDict = "creatures@chop@getup", --- for _, modelHash in ipairs(allAnimalModels) do
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" --- print("Animal Model Hash:", modelHash)
}, --- end
[`a_c_husky`] = { --- ```
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", function getAnimalModels()
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", local animalModels = {}
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" for _, animalCategory in pairs(AnimalPeds) do
}, for modelHash, _ in pairs(animalCategory) do
[`a_c_retriever`] = { table.insert(animalModels, modelHash)
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", end
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", end
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" return animalModels
}, end
[`a_c_shepherd`] = {
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", --- Compiles and returns a table of animal animations for the ped model.
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", ---
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" --- Iterates through every category in AnimalPeds and collects all anims.
}, ---
[`a_c_rottweiler`] = { --- @return table table A table containing all current model anims.
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", ---
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", ---@usage
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" --- ```lua
}, --- local getAnim = getAnimalAnims(ped)
[`ft-aushep`] = { --- playAnim(getAnim.sitDict, getAnim.sitAnim, -1, 1)
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", --- ```
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", function getAnimalAnims(ped)
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" local model = GetEntityModel(ped)
}, local animalTable = {}
[`golden_r`] = { for _, animalCategory in pairs(AnimalPeds) do
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", for k, v in pairs(animalCategory) do
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", if k == model then
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" animalTable = v
}, break
[`ft-dobermanv2`] = { end
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", end
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", end
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" return animalTable
}, end
[`doberman`] = { end
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move",
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", -------------------------------------------------------------
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" -- Animal Models Data
}, -------------------------------------------------------------
[`ft-gs`] = { -- Define the animal models and their associated animations.
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", AnimalPeds = {
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", BigDogs = {
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_chop`] = { deathAnim = "dead_right", deathDict = "creatures@chop@move", exitAnim = "getup_r", exitDict = "creatures@chop@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
}, [`a_c_k9`] = { deathAnim = "dead_right", deathDict = "creatures@chop@move", exitAnim = "getup_r", exitDict = "creatures@chop@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`k9_husky`] = { [`a_c_husky`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`a_c_retriever`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`a_c_shepherd`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_rottweiler`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
}, [`ft-aushep`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`ft-bloodhound`] = { [`golden_r`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`ft-dobermanv2`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`doberman`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`ft-gs`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
}, [`k9_husky`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`bernard`] = { [`ft-bloodhound`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`bernard`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`ft-pterrier`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`ft-labrador`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
}, [`dane`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`ft-pterrier`] = { [`ft_malinois`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`abdog`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`dalmatian`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_dalmatian`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
}, [`ft-boxer`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`ft-labrador`] = { [`ft-bs`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`chowchow`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`a_c_coyote`] = { deathAnim = "dead_right", deathDict = "creatures@coyote@move", exitAnim = "getup_r", exitDict = "creatures@coyote@getup", sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_coyote_02`] = { deathAnim = "dead_right", deathDict = "creatures@coyote@move", exitAnim = "getup_r", exitDict = "creatures@coyote@getup", sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
}, },
[`dane`] = { SmallDogs = {
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`a_c_poodle`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`ft-chihuahua`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_pug`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
}, [`a_c_pug_02`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
[`ft_malinois`] = { [`a_c_westy`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`ft-pretriever`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`ft-shepk9`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
}, CatPeds = {
[`abdog`] = { [`bshorthair`] = { deathAnim = "dead_right", deathDict = "creatures@cat@move", exitAnim = "getup_r", exitDict = "creatures@cat@getup", sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`a_c_cat_01`] = { deathAnim = "dead_right", deathDict = "creatures@cat@move", exitAnim = "getup_r", exitDict = "creatures@cat@getup", sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`ft-sphynx`] = { deathAnim = "dead_right", deathDict = "creatures@coyote@move", exitAnim = "getup_r", exitDict = "creatures@coyote@getup", sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
}, OtherPeds = {
[`dalmatian`] = { [`ft-raccoon`] = { deathAnim = "dead_right", deathDict = "creatures@cat@move", exitAnim = "getup_r", exitDict = "creatures@cat@getup", sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`a_c_hen`] = { deathAnim = "dead_right", deathDict = "creatures@hen@move", exitAnim = "getup_r", exitDict = "creatures@hen@getup" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`a_c_rabbit_01`] = { deathAnim = "dead_right", deathDict = "creatures@rabbit@move", exitAnim = "getup_r", exitDict = "creatures@rabbit@getup", sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_rabbit_02`] = { deathAnim = "dead_right", deathDict = "creatures@rabbit@move", exitAnim = "getup_r", exitDict = "creatures@rabbit@getup", sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
}, [`a_c_rat`] = { deathAnim = "dead_right", deathDict = "creatures@rat@move", exitAnim = "getup_r", exitDict = "creatures@rat@getup" },
[`a_c_dalmatian`] = { [`a_c_deer`] = { deathAnim = "dead_right", deathDict = "creatures@deer@move", exitAnim = "getup_r", exitDict = "creatures@deer@getup" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`a_c_boar`] = { deathAnim = "dead_right", deathDict = "creatures@boar@move", exitAnim = "getup_r", exitDict = "creatures@boar@getup" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`a_c_boar_02`] = { deathAnim = "dead_right", deathDict = "creatures@boar@move", exitAnim = "getup_r", exitDict = "creatures@boar@getup" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_chicken`] = { deathAnim = "dead_right", deathDict = "creatures@chicken@move", exitAnim = "getup_r", exitDict = "creatures@chicken@getup" },
}, [`a_c_pig`] = { deathAnim = "dead_right", deathDict = "creatures@pig@move", exitAnim = "getup_r", exitDict = "creatures@pig@getup" },
[`ft-boxer`] = { [`a_c_sharkhammer`] = { deathAnim = "dead_right", deathDict = "creatures@sharkhammer@move", exitAnim = "getup_r", exitDict = "creatures@sharkhammer@getup" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`a_c_sharktiger`] = { deathAnim = "dead_right", deathDict = "creatures@sharktiger@move", exitAnim = "getup_r", exitDict = "creatures@sharktiger@getup" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`a_c_crow`] = { deathAnim = "dead_down", deathDict = "creatures@crow@move", exitAnim = "nill", exitDict = "creatures@pug@move" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_pigeon`] = { deathAnim = "dead_down", deathDict = "creatures@pigeon@move", exitAnim = "nill", exitDict = "creatures@pug@move" },
}, },
[`ft-bs`] = { Monekys = {
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", [`ft-chimpanzee`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", [`a_c_chimp`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" [`a_c_chimp_02`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
}, [`a_c_rhesus`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
[`chowchow`] = { [`ft-capmonkey2`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", }
exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup",
sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car"
},
[`a_c_coyote`] = {
deathAnim = "dead_right", deathDict = "creatures@coyote@move",
exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
},
[`a_c_coyote_02`] = {
deathAnim = "dead_right", deathDict = "creatures@coyote@move",
exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
},
},
SmallDogs = {
-- Small Dogs
[`a_c_poodle`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
[`ft-chihuahua`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
[`a_c_pug`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
[`a_c_pug_02`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
[`a_c_westy`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
[`ft-pretriever`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
[`ft-shepk9`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
},
CatPeds = {
-- Cat
[`bshorthair`] = {
deathAnim = "dead_right", deathDict = "creatures@cat@move",
exitAnim = "getup_r", exitDict = "creatures@cat@getup",
sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
},
[`a_c_cat_01`] = {
deathAnim = "dead_right", deathDict = "creatures@cat@move",
exitAnim = "getup_r", exitDict = "creatures@cat@getup",
sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
},
[`ft-sphynx`] = {
deathAnim = "dead_right", deathDict = "creatures@coyote@move",
exitAnim = "getup_r", exitDict = "creatures@coyote@getup",
sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
},
},
OtherPeds = {
-- Other Animals
[`ft-raccoon`] = {
deathAnim = "dead_right", deathDict = "creatures@cat@move",
exitAnim = "getup_r", exitDict = "creatures@cat@getup",
sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base"
},
[`a_c_hen`] = {
deathAnim = "dead_right", deathDict = "creatures@hen@move",
exitAnim = "getup_r", exitDict = "creatures@hen@getup"
},
[`a_c_rabbit_01`] = {
deathAnim = "dead_right", deathDict = "creatures@rabbit@move",
exitAnim = "getup_r", exitDict = "creatures@rabbit@getup",
sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
},
[`a_c_rabbit_02`] = {
deathAnim = "dead_right", deathDict = "creatures@rabbit@move",
exitAnim = "getup_r", exitDict = "creatures@rabbit@getup",
sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base"
},
[`a_c_rat`] = {
deathAnim = "dead_right", deathDict = "creatures@rat@move",
exitAnim = "getup_r", exitDict = "creatures@rat@getup"
},
[`a_c_deer`] = {
deathAnim = "dead_right", deathDict = "creatures@deer@move",
exitAnim = "getup_r", exitDict = "creatures@deer@getup"
},
[`a_c_boar`] = {
deathAnim = "dead_right", deathDict = "creatures@boar@move",
exitAnim = "getup_r", exitDict = "creatures@boar@getup"
},
[`a_c_boar_02`] = {
deathAnim = "dead_right", deathDict = "creatures@boar@move",
exitAnim = "getup_r", exitDict = "creatures@boar@getup"
},
[`a_c_chicken`] = {
deathAnim = "dead_right", deathDict = "creatures@chicken@move",
exitAnim = "getup_r", exitDict = "creatures@chicken@getup"
},
[`a_c_pig`] = {
deathAnim = "dead_right", deathDict = "creatures@pig@move",
exitAnim = "getup_r", exitDict = "creatures@pig@getup"
},
[`a_c_sharkhammer`] = {
deathAnim = "dead_right", deathDict = "creatures@sharkhammer@move",
exitAnim = "getup_r", exitDict = "creatures@sharkhammer@getup"
},
[`a_c_sharktiger`] = {
deathAnim = "dead_right", deathDict = "creatures@sharktiger@move",
exitAnim = "getup_r", exitDict = "creatures@sharktiger@getup"
},
[`a_c_crow`] = {
deathAnim = "dead_down", deathDict = "creatures@crow@move",
exitAnim = "nill", exitDict = "creatures@pug@move", -- no get up anim
},
[`a_c_pigeon`] = {
deathAnim = "dead_down", deathDict = "creatures@pigeon@move",
exitAnim = "nill", exitDict = "creatures@pug@move", -- no get up anim
},
},
Monekys = {
[`ft-chimpanzee`] = {
deathAnim = "dead", deathDict = "dead_a",
exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
},
[`a_c_chimp`] = {
deathAnim = "dead", deathDict = "dead_a",
exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
},
[`a_c_chimp_02`] = {
deathAnim = "dead", deathDict = "dead_a",
exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
},
[`a_c_rhesus`] = {
deathAnim = "dead", deathDict = "dead_a",
exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0"
},
[`ft-capmonkey2`] = {
deathAnim = "dead_right", deathDict = "creatures@pug@move",
exitAnim = "getup_r", exitDict = "creatures@pug@getup",
sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base"
},
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,196 +1,249 @@
-- Global variable to track duty status --[[
onDuty = false Duty & Interaction Utilities Module
--------------------------------------
--- Scans the 'Jobs' and 'Gangs' tables to identify roles classified as Bosses. This module provides functions related to:
--- • Determining boss roles from Jobs and Gangs tables.
--- This function iterates through the specified role's grades within the `Jobs` or `Gangs` tables. • Checking a player's job and duty status.
--- It identifies which grades are marked as bosses (`isboss`) or have bank authorization (`bankAuth`). • Toggling duty state.
--- The function returns a table where each role maps to the lowest grade number that qualifies as a boss. • Simulating player interactions such as hand washing, using toilets/urinals,
--- and teleporting via doors.
---@param role string The name of the job or gang role to check for boss grades. ]]
---
---@return table table A table containing roles mapped to their respective boss grade numbers. -------------------------------------------------------------
--- -- Global Duty Status
---@usage -------------------------------------------------------------
--- ```lua -- This attempts to sync up with the players framework onDuty variable
--- local bosses = makeBossRoles("police") -- This stops the script getting confused when one is false and one is true
--- if bosses["police"] then onDuty = false
--- print("Police role has a boss grade.")
--- end if not isServer() then
--- ``` onPlayerLoaded(function()
function makeBossRoles(role) onDuty = getPlayer().onDuty
local boss = {} end, true)
local data = (Jobs and Jobs[role]) or (Gangs and Gangs[role]) end
if data then
for grade, info in pairs(data.grades) do -------------------------------------------------------------
if info.isboss or info.bankAuth then -- Boss Role Detection
boss[role] = boss[role] and math.min(boss[role], tonumber(grade)) or tonumber(grade) -------------------------------------------------------------
end
end --- Scans the 'Jobs' and 'Gangs' tables to identify roles classified as bosses.
end ---
return boss --- Iterates through the specified role's grades in the Jobs or Gangs table and returns
end --- a table mapping the role to the lowest grade number that qualifies as a boss (isboss or bankAuth).
---
--- Checks if the player has a specific job and is on duty. --- @param role string The job or gang role to check.
--- --- @return table table A table with the role mapped to its boss grade number.
--- This function verifies whether the player possesses the specified job and, if applicable, ---
--- whether they are currently on duty. It provides a notification if the player fails these checks. --- @usage
--- --- ```lua
---@param job string The name of the job or gang to check. --- local bosses = makeBossRoles("police")
--- --- if bosses["police"] then
---@return boolean Returns `true` if the player has the job (and is on duty if required), otherwise `false`. --- print("Police role has a boss grade.")
--- --- end
---@usage --- ```
--- ```lua function makeBossRoles(role)
--- if jobCheck("mechanic") then local boss = {}
--- -- Allow access to mechanic-related features local data = (Jobs and Jobs[role]) or (Gangs and Gangs[role])
--- else if data then
--- -- Deny access or notify the player for grade, info in pairs(data.grades) do
--- end if info.isboss or info.bankAuth or info.isBoss then
--- ``` boss[role] = boss[role] and math.min(boss[role], tonumber(grade)) or tonumber(grade)
function jobCheck(job) end
canDo = true end
if Jobs[job] then end
if not hasJob(job) or not onDuty then return boss
triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"]) end
canDo = false
end -------------------------------------------------------------
end -- Job & Duty Checks
if Gangs[job] then -------------------------------------------------------------
if not hasJob(job) then
canDo = false --- Checks if the player has a specific job (or gang) and is on duty.
end ---
end --- Verifies whether the player possesses the specified role. If the role is defined in the Jobs table,
return canDo --- it also checks that the player is clocked in (onDuty). If the check fails, a notification is sent.
end ---
--- @param job string The job or gang to check.
--- Toggles the player's duty status. --- @return boolean Returns true if the player meets the criteria; false otherwise.
--- ---
--- This function switches the player's duty state between on-duty and off-duty. --- @usage
--- It integrates with QBcore's duty system if available; otherwise, it manually toggles the `onDuty` variable --- ```lua
--- and sends a notification to the player about their new duty status. --- if jobCheck("mechanic") then
--- --- -- Allow mechanic features.
---@usage --- else
--- ```lua --- -- Deny access.
--- toggleDuty() --- end
--- -- Player will receive a notification indicating their new duty status --- ```
--- ``` function jobCheck(job)
function toggleDuty() local canDo = true
if isStarted(QBExport) or isStarted(QBXExport) then if Jobs[job] then
TriggerServerEvent("QBCore:ToggleDuty") if not hasJob(job) or not getPlayer().onDuty then
else triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"])
onDuty = not onDuty canDo = false
if onDuty then end
triggerNotify(nil, "Now on duty", "success") end
else if Gangs[job] then
triggerNotify(nil, "Now off duty", "success") if not hasJob(job) then
end canDo = false
end end
end end
return canDo
--- Initiates the hand-washing action for the player. end
---
--- This function triggers an animation and a progress bar to simulate the player washing their hands. --- Toggles the player's duty status.
--- Upon completion, it sends a success notification. If the action is canceled, it notifies the player of the cancellation. ---
--- --- Switches the player's duty state between on-duty and off-duty. If using QBcore,
---@param data table A table containing the coordinates where the hand-washing action takes place. --- it triggers the appropriate server event. Otherwise, it manually toggles the onDuty variable and notifies the player.
--- - **coords** (`vector3`): The position where the hand-washing animation and camera are focused. ---
--- --- @usage
---@return void --- ```lua
--- --- toggleDuty() -- Player receives a notification of their new duty status.
---@usage --- ```
--- ```lua function toggleDuty()
--- washHands({ coords = vector3(200.0, 300.0, 40.0) }) if isStarted(QBExport) or isStarted(QBXExport) then
--- -- Player will perform the hand-washing animation at the specified location TriggerServerEvent("QBCore:ToggleDuty")
--- ``` Wait(100)
function washHands(data) local ped = PlayerPedId() onDuty = getPlayer().onDuty
lookEnt(data.coords) elseif isStarted(RSGExport) then
local cam = createTempCam(ped, data.coords) TriggerServerEvent("RSGCore:ToggleDuty")
if progressBar({ Wait(100)
label = Loc[Config.Lan].progressbar["progress_washing"], onDuty = getPlayer().onDuty
time = 5000, else
cancel = true, if onDuty then
dict = "mp_arresting", triggerNotify(nil, "Now on duty", "success")
anim = "a_uncuff", else
flag = 32, triggerNotify(nil, "Now off duty", "success")
icon = "fas fa-hand-holding-droplet", end
cam = cam end
}) then end
triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
else -- Framework specific functions to keep duty status synced
triggerNotify(nil, Loc[Config.Lan].error["cancel"], 'error') RegisterNetEvent("QBCore:Client:OnJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
end RegisterNetEvent("QBCore:Client:SetDuty", function(duty) onDuty = duty end)
ClearPedTasks(ped)
end RegisterNetEvent("qbx_core:client:onJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
--- Handles the player's interaction with a toilet or urinal. RegisterNetEvent("RSGCore:Client:OnJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
--- RegisterNetEvent("RSGCore:Client:SetDuty", function(duty) onDuty = duty end)
--- This function manages the animations and progress bars associated with using a toilet or urinal.
--- Depending on whether the interaction is with a urinal (`data.urinal`), it plays the appropriate animation -------------------------------------------------------------
--- and triggers server events upon successful completion. If the action is canceled, it notifies the player. -- Interaction Functions
--- -------------------------------------------------------------
---@param data table A table containing data about the toilet interaction.
--- - **urinal** (`boolean`): Indicates whether the interaction is with a urinal (`true`) or a toilet (`false`). --- Initiates the hand-washing action for the player.
--- - **sitcoords** (`vector4`): The coordinates and heading for the seating animation when using a toilet. ---
--- --- Triggers an animation and a progress bar to simulate hand washing at the specified coordinates.
---@usage --- On success, it notifies the player; if cancelled, it sends an error notification.
--- ```lua ---
--- useToilet({ urinal = true }) --- @param data table A table containing:
--- -- Player uses a urinal with corresponding animations and notifications --- - coords (vector3): The location where the hand-washing action occurs.
--- ---
--- useToilet({ urinal = false, sitcoords = vector4(215.76, -810.12, 29.73, 90.0) }) --- @usage
--- -- Player sits down to use a toilet with corresponding animations and notifications --- ```lua
--- ``` --- washHands({ coords = vector3(200.0, 300.0, 40.0) })
function useToilet(data) --- ```
if data.urinal then function washHands(data)
if progressBar({ local ped = PlayerPedId()
label = "Using Urinal", lookEnt(data.coords)
time = 5000, local cam = createTempCam(ped, data.coords)
cancel = true, if progressBar({
dict = "misscarsteal2peeing", label = Loc[Config.Lan].progressbar["progress_washing"],
anim = "peeing_loop", time = 5000,
flag = 32 cancel = true,
}) then dict = "mp_arresting",
TriggerServerEvent(getScript().."server:Urinal") anim = "a_uncuff",
else flag = 32,
lockInv(false) icon = "fas fa-hand-holding-droplet",
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error') cam = cam
end }) then
else triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
TaskStartScenarioAtPosition(PlayerPedId(), "PROP_HUMAN_SEAT_CHAIR_MP_PLAYER", data.sitcoords.x, data.sitcoords.y, data.sitcoords.z, data.sitcoords[4], 0, 1, true) else
if progressBar({ triggerNotify(nil, Loc[Config.Lan].error["cancel"], "error")
label = "Using Toilet", end
time = 10000, ClearPedTasks(ped)
cancel = true end
}) then
TriggerServerEvent(getScript().."server:Urinal") --- Handles the player's interaction with a toilet or urinal.
ClearPedTasks(PlayerPedId()) ---
else --- Manages animations and progress bars for using a urinal or a toilet. If the action is successful,
lockInv(false) --- it triggers the appropriate server event (urinal usage) or notifies the player if cancelled.
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error') ---
end --- @param data table A table containing:
end --- - urinal (boolean): `true if using a urinal; false for a toilet.`
end --- - sitcoords (vector4): `Coordinates and heading for seating when using a toilet.`
---
--- Teleports the player to specified coordinates with a fade effect. --- @usage
--- --- ```lua
--- This function fades the screen out, moves the player to the target coordinates (`data.telecoords`), --- useToilet({ urinal = true })
--- sets the player's heading, and then fades the screen back in. It's commonly used for door interactions --- -- Player uses a urinal with corresponding animations and notifications
--- or teleportation points within the game. ---
--- --- useToilet({ urinal = false, sitcoords = vector4(215.76, -810.12, 29.73, 90.0) })
---@param data table A table containing teleportation data. --- -- Player sits down to use a toilet with corresponding animations and notifications
--- - **telecoords** (`vector4`): The target coordinates and heading for the teleportation. --- ```
--- function useToilet(data)
---@usage if data.urinal then
--- ```lua if progressBar({
--- useDoor({ telecoords = vector4(215.76, -810.12, 29.73, 90.0) }) label = "Using Urinal",
--- -- Player is teleported to the specified coordinates with a fade effect time = 5000,
--- ``` cancel = true,
function useDoor(data) dict = "misscarsteal2peeing",
DoScreenFadeOut(500) anim = "peeing_loop",
while not IsScreenFadedOut() do Wait(10) end flag = 32
SetEntityCoords(PlayerPedId(), data.telecoords.xyz, 0, 0, 0, false) }) then
SetEntityHeading(PlayerPedId(), data.telecoords.w) TriggerServerEvent(getScript().."server:Urinal")
DoScreenFadeIn(1000) else
Wait(100) lockInv(false)
end triggerNotify(nil, Loc[Config.Lan].error["cancelled"], "error")
end
else
TaskStartScenarioAtPosition(PlayerPedId(), "PROP_HUMAN_SEAT_CHAIR_MP_PLAYER", data.sitcoords.x, data.sitcoords.y, data.sitcoords.z, data.sitcoords[4], 0, 1, true)
if progressBar({
label = "Using Toilet",
time = 10000,
cancel = true
}) then
TriggerServerEvent(getScript().."server:Urinal")
ClearPedTasks(PlayerPedId())
else
lockInv(false)
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], "error")
end
end
end
--- Teleports the player to specified coordinates with a fade effect.
---
--- Fades the screen out, moves the player to the target coordinates, sets the player's heading,
--- then fades the screen back in. Commonly used for door interactions or teleportation points.
---
--- @param data table A table containing:
--- - telecoords (vector4): The target coordinates and heading.
---
--- @usage
--- ```lua
--- useDoor({ telecoords = vector4(215.76, -810.12, 29.73, 90.0) })
--- ```
function useDoor(data)
DoScreenFadeOut(500)
while not IsScreenFadedOut() do Wait(10) end
SetEntityCoords(PlayerPedId(), data.telecoords.xyz, 0, 0, 0, false)
SetEntityHeading(PlayerPedId(), data.telecoords.w)
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

@@ -1,75 +1,81 @@
--- Creates a temporary camera at a specified position, pointing towards given coordinates. --- Creates a temporary camera at a specified position, pointing towards given coordinates.
-- --
-- This function creates a camera at a position relative to an entity or at a specified position and orients it to look at the target coordinates. -- This function creates a camera at a position relative to an entity or at a specified position and orients it to look at the target coordinates.
-- The camera is only created if `Config.Crafting.craftCam` is enabled in the configuration. -- The camera is only created if `Config.Crafting.craftCam` is enabled in the configuration.
-- --
---@param ent entityId|coords The base position for the camera. Can be an entity handle or a `vector3` position. ---@param ent entityId|coords The base position for the camera. Can be an entity handle or a `vector3` position.
-- If `ent` is an entity, the camera position is calculated as an offset from the entity's position using `GetOffsetFromEntityInWorldCoords`. -- 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. -- If `ent` is a `vector3`, it is used directly as the camera's position.
-- --
---@param coords vector3 The target `vector3` coordinates that the camera will point at. ---@param coords vector3 The target `vector3` coordinates that the camera will point at.
-- --
---@return cam camID The handle of the created camera, or `nil` if the camera was not created (e.g., if `Config.Crafting.craftCam` is `false`). ---@return cam camID The handle of the created camera, or `nil` if the camera was not created (e.g., if `Config.Crafting.craftCam` is `false`).
-- --
---@usage ---@usage
-- ```lua -- ```lua
-- local cam = createTempCam(entity, targetCoords) -- local cam = createTempCam(entity, targetCoords)
-- ``` -- ```
function createTempCam(ent, coords) function createTempCam(ent, coords)
local cam = nil local cam = nil
if Config.Crafting.craftCam then if Config.Crafting.craftCam then
if debugMode then if debugMode then
triggerNotify(nil, "ModCam Created", "success") triggerNotify(nil, "ModCam Created", "success")
end end
local camCoords = nil local camCoords = nil
if type(ent) ~= "vector3" then local pointCoords = nil
camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8) if type(ent) ~= "vector3" then
else camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
camCoords = ent else
end camCoords = ent
-- Create the camera with specified parameters end
cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
-- Point the camera at the target coordinates cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
PointCamAtCoord(cam, coords)
end if type(coords) == "number" then
return cam SetCamCoord(cam, GetCamCoord(cam) + vec3(0, 0, 1.0))
end PointCamAtEntity(cam, coords)
else
--- Activates and starts rendering the temporary camera. PointCamAtCoord(cam, coords)
-- end
-- This function sets the specified camera as active and begins rendering it with a smooth transition. end
-- The camera is only activated if `Config.Crafting.craftCam` is enabled in the configuration. return cam
-- end
---@param cam camID The handle of the camera to activate and render.
-- --- Activates and starts rendering the temporary camera.
---@usage --
-- ```lua -- This function sets the specified camera as active and begins rendering it with a smooth transition.
-- startTempCam(cam) -- The camera is only activated if `Config.Crafting.craftCam` is enabled in the configuration.
-- ``` --
function startTempCam(cam) ---@param cam camID The handle of the camera to activate and render.
if Config.Crafting.craftCam then --
SetCamActive(cam, true) ---@usage
RenderScriptCams(true, true, 1000, true, true) -- ```lua
end -- startTempCam(cam)
end -- ```
function startTempCam(cam)
--- Deactivates the temporary camera and stops rendering. if Config.Crafting.craftCam then
-- SetCamActive(cam, true)
-- This function waits for one second, then stops rendering script cameras and destroys all cameras. RenderScriptCams(true, true, 1000, true, true)
-- The delay allows for any transitions or animations to complete. end
-- end
-- The camera is only deactivated if `Config.Crafting.craftCam` is enabled in the configuration.
-- --- Deactivates the temporary camera and stops rendering.
---@usage --
-- ```lua -- This function waits for one second, then stops rendering script cameras and destroys all cameras.
-- stopTempCam() -- The delay allows for any transitions or animations to complete.
-- ``` --
function stopTempCam() -- The camera is only deactivated if `Config.Crafting.craftCam` is enabled in the configuration.
if Config.Crafting.craftCam then --
CreateThread(function() ---@usage
Wait(1000) -- ```lua
RenderScriptCams(false, true, 500, true, true) -- stopTempCam()
DestroyAllCams() -- ```
end) function stopTempCam()
end if Config.Crafting.craftCam then
CreateThread(function()
Wait(1000)
RenderScriptCams(false, true, 500, true, true)
DestroyAllCams()
end)
end
end end

View File

@@ -1,239 +1,241 @@
local time = 500 local time = 500
--- Loads a specified model into memory. --- Loads a specified model into memory.
--- ---
--- This function checks if the model is valid and not already loaded. --- This function checks if the model is valid and not already loaded.
--- If not loaded, it requests the model and waits until it is loaded or times out. --- If not loaded, it requests the model and waits until it is loaded or times out.
--- ---
---@param model string|number The name or hash of the model to load. ---@param model string|number The name or hash of the model to load.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- loadModel('prop_chair_01a') --- loadModel('prop_chair_01a')
--- ``` --- ```
function loadModel(model) function loadModel(model)
if not IsModelValid(model) then print("^6Bridge^7: ^1ERROR^7: ^2Model^7 - '^6"..model.."^7' ^2does not exist in server") return if not IsModelValid(model) then print("^6Bridge^7: ^1ERROR^7: ^2Model^7 - '^6"..model.."^7' ^2does not exist in server") return
else else
if not HasModelLoaded(model) then if not HasModelLoaded(model) then
debugPrint("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'") debugPrint("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'")
while not HasModelLoaded(model) and time > 0 do time -= 1 RequestModel(model) Wait(0) end while not HasModelLoaded(model) and time > 0 do time -= 1 RequestModel(model) Wait(0) end
if not HasModelLoaded(model) then print("^6Bridge^7: ^3LoadModel^7: ^2Timed out loading model ^7'^6"..model.."^7'") end if not HasModelLoaded(model) then print("^6Bridge^7: ^3LoadModel^7: ^2Timed out loading model ^7'^6"..model.."^7'") end
end end
time = 500 time = 500
end end
end end
--- Unloads a model from memory. --- Unloads a model from memory.
--- ---
--- This function marks a model as no longer needed, allowing the game to free up memory. --- This function marks a model as no longer needed, allowing the game to free up memory.
--- ---
---@param model string|number The name or hash of the model to unload. ---@param model string|number The name or hash of the model to unload.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- unloadModel('prop_chair_01a') --- unloadModel('prop_chair_01a')
--- ``` --- ```
function unloadModel(model) function unloadModel(model)
debugPrint("^6Bridge^7: ^2Removing Model from memory cache^7: '^6"..model.."^7'") debugPrint("^6Bridge^7: ^2Removing Model from memory cache^7: '^6"..model.."^7'")
SetModelAsNoLongerNeeded(model) SetModelAsNoLongerNeeded(model)
end end
--- Loads an animation dictionary into memory. --- Loads an animation dictionary into memory.
--- ---
--- This function checks if the animation dictionary exists and requests it. --- This function checks if the animation dictionary exists and requests it.
--- It waits until the animation dictionary is loaded before proceeding. --- It waits until the animation dictionary is loaded before proceeding.
--- ---
---@param animDict string The name of the animation dictionary to load. ---@param animDict string The name of the animation dictionary to load.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- loadAnimDict('amb@world_human_hang_out_street@male_c@base') --- loadAnimDict('amb@world_human_hang_out_street@male_c@base')
--- ``` --- ```
function loadAnimDict(animDict) function loadAnimDict(animDict)
if not DoesAnimDictExist(animDict) then if not DoesAnimDictExist(animDict) then
print("^6Bridge^7: ^1ERROR^7: ^2Anim Dictionary^7 - '^6"..animDict.."^7' ^2does not exist in server") return print("^6Bridge^7: ^1ERROR^7: ^2Anim Dictionary^7 - '^6"..animDict.."^7' ^2does not exist in server") return
else else
debugPrint("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..animDict.."^7'") debugPrint("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..animDict.."^7'")
while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end
end end
end end
--- Unloads an animation dictionary from memory. --- Unloads an animation dictionary from memory.
--- ---
--- This function removes the animation dictionary from the game's memory cache. --- This function removes the animation dictionary from the game's memory cache.
--- ---
---@param animDict string The name of the animation dictionary to unload. ---@param animDict string The name of the animation dictionary to unload.
--- ---
---@usage ---@usage
---@ ---@
--- ```lua --- ```lua
--- unloadAnimDict('amb@world_human_hang_out_street@male_c@base') --- unloadAnimDict('amb@world_human_hang_out_street@male_c@base')
--- ``` --- ```
function unloadAnimDict(animDict) function unloadAnimDict(animDict)
debugPrint("^6Bridge^7: ^2Removing Anim Dictionary from memory cache^7: '^6"..animDict.."^7'") debugPrint("^6Bridge^7: ^2Removing Anim Dictionary from memory cache^7: '^6"..animDict.."^7'")
RemoveAnimDict(animDict) RemoveAnimDict(animDict)
end end
--- Loads a particle effects (ptfx) dictionary into memory. --- Loads a particle effects (ptfx) dictionary into memory.
--- ---
--- This function requests the named particle effects asset and waits until it's loaded. --- This function requests the named particle effects asset and waits until it's loaded.
--- ---
---@param ptFxName string The name of the particle effects dictionary to load. ---@param ptFxName string The name of the particle effects dictionary to load.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- loadPtfxDict('core') --- loadPtfxDict('core')
--- ``` --- ```
function loadPtfxDict(ptFxName) function loadPtfxDict(ptFxName)
if not HasNamedPtfxAssetLoaded(ptFxName) then if not HasNamedPtfxAssetLoaded(ptFxName) then
debugPrint("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..ptFxName.."^7'") debugPrint("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..ptFxName.."^7'")
while not HasNamedPtfxAssetLoaded(ptFxName) do RequestNamedPtfxAsset(ptFxName) Wait(5) end while not HasNamedPtfxAssetLoaded(ptFxName) do RequestNamedPtfxAsset(ptFxName) Wait(5) end
end end
end end
--- Unloads a particle effects (ptfx) dictionary from memory. --- Unloads a particle effects (ptfx) dictionary from memory.
--- ---
--- This function removes the named particle effects asset from the game's memory cache. --- This function removes the named particle effects asset from the game's memory cache.
--- ---
---@param dict string The name of the particle effects dictionary to unload. ---@param dict string The name of the particle effects dictionary to unload.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- unloadPtfxDict('core') --- unloadPtfxDict('core')
--- ``` --- ```
function unloadPtfxDict(dict) function unloadPtfxDict(dict)
debugPrint("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'") debugPrint("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'")
RemoveNamedPtfxAsset(dict) RemoveNamedPtfxAsset(dict)
end end
--- Loads a texture dictionary into memory. --- Loads a texture dictionary into memory.
--- ---
--- This function requests the streamed texture dictionary and waits until it's loaded. --- This function requests the streamed texture dictionary and waits until it's loaded.
--- ---
---@param dict string The name of the texture dictionary to load. ---@param dict string The name of the texture dictionary to load.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- loadTextureDict('commonmenu') --- loadTextureDict('commonmenu')
--- ``` --- ```
function loadTextureDict(dict) function loadTextureDict(dict)
if not HasStreamedTextureDictLoaded(dict) then if not HasStreamedTextureDictLoaded(dict) then
debugPrint("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'") debugPrint("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'")
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
end end
end end
--- Loads a script audio bank into memory. --- Loads a script audio bank into memory.
--- ---
--- This function requests a script audio bank and waits until it's loaded or times out. --- This function requests a script audio bank and waits until it's loaded or times out.
--- ---
---@param bank string The name of the script audio bank to load. ---@param bank string The name of the script audio bank to load.
--- ---
---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`. ---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS') --- local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS')
--- ``` --- ```
function loadScriptBank(bank) function loadScriptBank(bank)
local timeout = 2000 local timeout = 2000
debugPrint("^6Debug^7: ^2Loading ^3Script ^2AudioBank^7...") debugPrint("^6Bridge^7: ^2Loading ^3Script ^2AudioBank^7...")
while not RequestScriptAudioBank(bank, 0) do Wait(10) timeout -= 10 if timeout <= 0 then break end end while not RequestScriptAudioBank(bank, false) do Wait(10) timeout -= 10 if timeout <= 0 then break end end
local success = RequestScriptAudioBank(bank, 0) local success = RequestScriptAudioBank(bank, false)
debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'") debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
return success return success
end end
--- Loads an ambient audio bank into memory. --- Loads an ambient audio bank into memory.
--- ---
--- This function requests an ambient audio bank and waits until it's loaded or times out. --- This function requests an ambient audio bank and waits until it's loaded or times out.
--- ---
---@param bank string The name of the ambient audio bank to load. ---@param bank string The name of the ambient audio bank to load.
--- ---
---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`. ---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- local success = loadAmbientBank('AMB_REVERB_GENERIC') --- local success = loadAmbientBank('AMB_REVERB_GENERIC')
--- ``` --- ```
function loadAmbientBank(bank) function loadAmbientBank(bank)
local timeout = 2000 local timeout = 2000
debugPrint("^6Debug^7: ^2Loading ^3Ambient ^2AudioBank^7...") debugPrint("^6Bridge^7: ^2Loading ^3Ambient ^2AudioBank^7...")
while not RequestAmbientAudioBank(bank, 0) do while not RequestAmbientAudioBank(bank, 0) do
Wait(10) Wait(10)
timeout -= 10 timeout -= 10
if timeout <= 0 then break end if timeout <= 0 then break end
end end
local success = RequestAmbientAudioBank(bank, 0) local success = RequestAmbientAudioBank(bank, 0)
debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'") debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
return success return success
end end
--- Plays an animation on a specified ped. --- Plays an animation on a specified ped.
--- ---
--- This function loads the animation dictionary and instructs the ped to play the animation. --- This function loads the animation dictionary and instructs the ped to play the animation.
--- ---
---@param animDict string The name of the animation dictionary. ---@param animDict string The name of the animation dictionary.
---@param animName string The name of the animation within the dictionary. ---@param animName string The name of the animation within the dictionary.
---@param duration number (optional) The duration to play the animation in milliseconds. Default is `30000`. ---@param duration number (optional) The duration to play the animation in milliseconds. Default is `30000`.
---@param flag number (optional) The animation flag controlling how the animation is played. Default is `50`. ---@param flag number (optional) The animation flag controlling how the animation is played. Default is `50`.
---@param ped number (optional) The ped on which to play the animation. Defaults to the player's ped if not specified. ---@param ped number (optional) The ped on which to play the animation. Defaults to the player's ped if not specified.
---@param speed number (optional) The speed multiplier for the animation. Default is `8.0`. ---@param speed number (optional) The speed multiplier for the animation. Default is `8.0`.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0) --- playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0)
--- ``` --- ```
function playAnim(animDict, animName, duration, flag, ped, speed) function playAnim(animDict, animName, duration, flag, ped, speed)
loadAnimDict(animDict) loadAnimDict(animDict)
debugPrint("Attempting to make player play anim", animDict, animName) debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName)
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false) TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
end end
--- Stops a specified animation on a ped. --- Stops a specified animation on a ped.
--- ---
--- This function stops the animation and unloads the animation dictionary from memory. --- This function stops the animation and unloads the animation dictionary from memory.
--- ---
---@param animDict string The name of the animation dictionary. ---@param animDict string The name of the animation dictionary.
---@param animName string The name of the animation within the dictionary. ---@param animName string The name of the animation within the dictionary.
---@param ped number (optional) The ped on which to stop the animation. Defaults to the player's ped if not specified. ---@param ped number (optional) The ped on which to stop the animation. Defaults to the player's ped if not specified.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId()) --- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
--- ``` --- ```
function stopAnim(animDict, animName, ped) function stopAnim(animDict, animName, ped)
debugPrint("Stopping anim for "..(ped or PlayerPedId())) debugPrint("^6Bridge^7: ^3stopAnim^7() ^2Triggered^7: ", animDict, animName)
StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5) StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5) StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
unloadAnimDict(animDict) unloadAnimDict(animDict)
end end
--- Plays a game sound from a specified coordinate or entity. --- Plays a game sound from a specified coordinate or entity.
--- ---
--- This function attempts to play a sound from either a coordinate or an entity, using the specified audio bank and sound name. --- This function attempts to play a sound from either a coordinate or an entity, using the specified audio bank and sound name.
--- ---
---@param bank string The name of the audio bank containing the sound. ---@param bank string The name of the audio bank containing the sound.
---@param sound string The name of the sound to play. ---@param sound string The name of the sound to play.
---@param coords vector3|number A `vector3` coordinate or an entity handle from which to play the sound. ---@param coords vector3|number A `vector3` coordinate or an entity handle from which to play the sound.
---@param synced boolean A boolean indicating whether the sound is synced across clients. ---@param synced boolean A boolean indicating whether the sound is synced across clients.
---@param range number (optional) The maximum range at which the sound can be heard. Default is `10.0`. ---@param range number (optional) The maximum range at which the sound can be heard. Default is `10.0`.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0) --- playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
--- ``` --- ```
function playGameSound(bank, sound, coords, synced, range) function playGameSound(audioBank, soundSet, soundRef, coords, synced, range)
debugPrint("^6Debug^7: ^2Attempting to play: ^3"..sound.." ^7(^4"..bank.."^7')") debugPrint("^6Bridge^7: ^2Attempting to play: ^3"..soundRef.." ^7('^4"..audioBank.."^7')")
local range = range or 10.0 loadScriptBank(audioBank)
local soundId = GetSoundId() local range = range or 10.0
while not soundId do Wait(10) end local soundId = GetSoundId()
if type(coords) == "vector3" or type(coords) == "vector4" then while not soundId do Wait(10) end
debugPrint("^6Debug^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz)) if type(coords) == "vector3" or type(coords) == "vector4" then
PlaySoundFromCoord(soundId, sound, coords.x, coords.y, coords.z, bank, synced, range, 0) debugPrint("^6Bridge^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz))
else PlaySoundFromCoord(soundId, soundRef, coords.x, coords.y, coords.z, soundSet, synced, range, 0)
debugPrint("^6Debug^7: ^2Playing sound from Entity^7: ^4"..coords.."^7") else
PlaySoundFromEntity(soundId, sound, coords, bank, synced, 0) debugPrint("^6Bridge^7: ^2Playing sound from Entity^7: ^4"..coords.."^7")
end PlaySoundFromEntity(soundId, soundRef, coords, soundSet, synced, 1.0)
end
ReleaseScriptAudioBank(audioBank)
end end

View File

@@ -1,119 +1,165 @@
--- Creates a blip at specified coordinates with given properties. local blipTable = {}
--
-- This function adds a map blip at the provided coordinates and sets various display properties such as sprite, color, scale, and more. --- Creates a blip at specified coordinates with given properties.
-- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided. --
-- -- This function adds a map blip at the provided coordinates and sets various display properties such as sprite, color, scale, and more.
---@param data A table containing blip data and properties. -- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
-- - **coords**: A `vector3` containing x, y, z coordinates where the blip will be placed. --
-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`. ---@param data A table containing blip data and properties.
-- - **col** (optional): The color ID of the blip. Default is `5`. -- - **coords**: A `vector3` containing x, y, z coordinates where the blip will be placed.
-- - **scale** (optional): The scale of the blip. Default is `0.7`. -- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
-- - **disp** (optional): The display option of the blip. Default is `6`. -- - **col** (optional): The color ID of the blip. Default is `5`.
-- - **category** (optional): The category ID for the blip. -- - **scale** (optional): The scale of the blip. Default is `0.7`.
-- - **name**: The name of the blip, used for display on the map. -- - **disp** (optional): The display option of the blip. Default is `6`.
-- - **preview** (optional): A URL or image path for a preview image to display with the blip. -- - **category** (optional): The category ID for the blip.
-- -- - **name**: The name of the blip, used for display on the map.
---@return blip blipID The handle of the created blip. -- - **preview** (optional): A URL or image path for a preview image to display with the blip.
-- --
---@usage ---@return blip blipID The handle of the created blip.
-- ```lua --
-- local blipData = { ---@usage
-- coords = vector3(123.4, 567.8, 90.1), -- ```lua
-- sprite = 1, -- local blipData = {
-- col = 2, -- coords = vector3(123.4, 567.8, 90.1),
-- scale = 0.8, -- sprite = 1,
-- disp = 4, -- col = 2,
-- category = 7, -- scale = 0.8,
-- name = "My Blip", -- disp = 4,
-- preview = "http://example.com/preview.png" -- category = 7,
-- } -- name = "My Blip",
-- local blip = makeBlip(blipData) -- preview = "http://example.com/preview.png"
-- ``` -- }
function makeBlip(data) -- local blip = makeBlip(blipData)
local blip = AddBlipForCoord(vec3(data.coords.x, data.coords.y, data.coords.z)) -- ```
SetBlipCoords(blip, data.coords.x, data.coords.y, data.coords.z) -- Manually set blip coordinates again as sometimes it just refuses function makeBlip(data)
SetBlipAsShortRange(blip, true) local blip = nil
SetBlipSprite(blip, data.sprite or 106) if gameName == "rdr3" then
SetBlipColour(blip, data.col or 5) blip = BlipAddForCoords(1664425300, data.coords.x, data.coords.y, data.coords.z)
SetBlipScale(blip, data.scale or 0.7) SetBlipSprite(blip, data.sprite or `blip_shop_market_stall`)
SetBlipDisplay(blip, data.disp or 6) SetBlipScale(blip, data.scale or 0.2)
if data.category then SetBlipCategory(blip, data.category) end SetBlipName(blip, data.name)
BeginTextCommandSetBlipName('STRING') --BlipSetStyle(blip, data.col or `BLIP_STYLE_CREATOR_DEFAULT`)
AddTextComponentString(tostring(data.name)) else
EndTextCommandSetBlipName(blip) blip = AddBlipForCoord(vec3(data.coords.x, data.coords.y, data.coords.z))
-- Handle preview image if certain resources are running SetBlipCoords(blip, data.coords.x, data.coords.y, data.coords.z) -- Manually set blip coordinates again as sometimes it just refuses
if isStarted("fs_smallresources") or isStarted("blip_info") or isStarted("blipinfo") then SetBlipAsShortRange(blip, true)
if data.preview then SetBlipSprite(blip, data.sprite or 106)
local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "") SetBlipColour(blip, data.col or 5)
if data.preview:find("http") or data.preview:find("nui") then SetBlipScale(blip, data.scale or 0.7)
createDui(txname, data.preview, vec2(512, 256), scriptTxd) SetBlipDisplay(blip, data.disp or 6)
else if data.category then
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview) SetBlipCategory(blip, data.category)
end end
exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'scriptTxd', txname) BeginTextCommandSetBlipName('STRING')
exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false) AddTextComponentString(tostring(data.name))
end EndTextCommandSetBlipName(blip)
end -- Handle preview image if certain resources are running
debugPrint("^6Bridge^7: ^6Blip ^2created^7: '^6"..data.name.."^7' - '"..formatCoord(data.coords).."'") if isStarted("jim-blipcontroller") then
return blip if data.preview then
end local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
if data.preview:find("http") or data.preview:find("nui") then
--- Creates a blip attached to a specified entity with given properties. createDui(txname, data.preview, vec2(512, 256), scriptTxd)
-- else
-- This function adds a map blip attached to the provided entity and sets various display properties such as sprite, color, scale, and more. CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
-- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided. end
-- exports["jim-blipcontroller"]:ShowBlipInfo(blip, {
---@param data table A table containing blip data and properties. title = data.name,
-- - **entity**: The entity to which the blip will be attached. dict = getScript()..'scriptTxd',
-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`. tex = txname,
-- - **col** (optional): The color ID of the blip. Default is `5`. })
-- - **scale** (optional): The scale of the blip. Default is `0.7`. end
-- - **disp** (optional): The display option of the blip. Default is `6`. end
-- - **category** (optional): The category ID for the blip. end
-- - **name**: The name of the blip, used for display on the map. blipTable[blip] = blip
-- - **preview** (optional): A URL or image path for a preview image to display with the blip.
-- if DoesBlipExist(blip) then
-- debugPrint("^6Bridge^7: ^6Blip ^2created^7: '^6"..data.name.."^7' - '"..formatCoord(data.coords).."'")
---@return number blipID The handle of the created blip. else
---@usage print("Error making blip")
-- ```lua end
-- local blipData = { return blip
-- entity = myEntity, end
-- sprite = 1,
-- col = 2, --- Creates a blip attached to a specified entity with given properties.
-- scale = 0.8, --
-- disp = 4, -- This function adds a map blip attached to the provided entity and sets various display properties such as sprite, color, scale, and more.
-- category = 7, -- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
-- name = "Entity Blip", --
-- preview = "http://example.com/preview.png" ---@param data table A table containing blip data and properties.
-- } -- - **entity**: The entity to which the blip will be attached.
-- local blip = makeEntityBlip(blipData) -- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
-- ``` -- - **col** (optional): The color ID of the blip. Default is `5`.
function makeEntityBlip(data) -- - **scale** (optional): The scale of the blip. Default is `0.7`.
AddBlipForEntity(data.entity) -- - **disp** (optional): The display option of the blip. Default is `6`.
local blip = GetBlipFromEntity(data.entity) -- - **category** (optional): The category ID for the blip.
SetBlipAsShortRange(blip, true) -- - **name**: The name of the blip, used for display on the map.
SetBlipSprite(blip, data.sprite or 106) -- - **preview** (optional): A URL or image path for a preview image to display with the blip.
SetBlipColour(blip, data.col or 5) --
SetBlipScale(blip, data.scale or 0.7) --
SetBlipDisplay(blip, data.disp or 6) ---@return number blipID The handle of the created blip.
if data.category then SetBlipCategory(blip, data.category) end ---@usage
BeginTextCommandSetBlipName('STRING') -- ```lua
AddTextComponentString(tostring(data.name)) -- local blipData = {
EndTextCommandSetBlipName(blip) -- entity = myEntity,
-- Handle preview image if certain resources are running -- sprite = 1,
if isStarted("fs_smallresources") or isStarted("blip-info") or isStarted("blipinfo") then -- col = 2,
if data.preview then -- scale = 0.8,
local txname = string.gsub(tostring(data.name..'preview'), "[ ()~]", "") -- disp = 4,
if data.preview:find("http") or data.preview:find("nui") then -- category = 7,
createDui(txname, data.preview, vec2(512, 256), scriptTxd) -- name = "Entity Blip",
else -- preview = "http://example.com/preview.png"
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview) -- }
end -- local blip = makeEntityBlip(blipData)
exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'previewTxd', txname) -- ```
exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false) function makeEntityBlip(data)
end local blip = nil
end if gameName == "rdr3" then
debugPrint("^6Bridge^7: ^6Blip ^2created for Entity^7: '^6"..data.name.."^7'") blip = BlipAddForEntity(1664425300, data.entity)
return blip SetBlipSprite(blip, data.sprite or `blip_ambient_coach`)
SetBlipScale(blip, data.scale or 0.2)
SetBlipName(blip, data.name)
else
AddBlipForEntity(data.entity)
blip = GetBlipFromEntity(data.entity)
blipTable[blip] = blip
SetBlipAsShortRange(blip, true)
SetBlipSprite(blip, data.sprite or 106)
SetBlipColour(blip, data.col or 5)
SetBlipScale(blip, data.scale or 0.7)
SetBlipDisplay(blip, data.disp or 6)
if data.category then SetBlipCategory(blip, data.category) end
BeginTextCommandSetBlipName('STRING')
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 data.preview then
local txname = string.gsub(tostring(data.name..'preview'), "[ ()~]", "")
if data.preview:find("http") or data.preview:find("nui") then
createDui(txname, data.preview, vec2(512, 256), scriptTxd)
else
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
end
exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'previewTxd', txname)
exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false)
end
end
end
blipTable[blip] = blip
if DoesBlipExist(blip) then
debugPrint("^6Bridge^7: ^6Blip ^2created for Entity^7: '^6"..data.name.."^7'")
else
print("Error making blip")
end
return blip
end
if gameName == "rdr3" then
onResourceStop(function()
for k in pairs(blipTable) do
RemoveBlip(k)
end
end, true)
end end

View File

@@ -1,230 +1,246 @@
--- A table to keep track of all created Peds. --- A table to keep track of all created Peds.
local Peds = {} local Peds = {}
--- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area. --- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area.
-- --
-- This function sets up a circular area using `createCirclePoly`. When the player enters this area, a Ped is created using `makePed`. -- This function sets up a circular area using `createCirclePoly`. When the player enters this area, a Ped is created using `makePed`.
-- When the player exits the area, the Ped is deleted. -- When the player exits the area, the Ped is deleted.
-- --
---@param data table A table containing Ped data and properties. Should include at least `model` and `coords`. ---@param data table A table containing Ped data and properties. Should include at least `model` and `coords`.
---@param coords vector4 A `vector3` or `vector4` specifying the coordinates where the Ped will be placed. ---@param coords vector4 A `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`. ---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`. ---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
---@param scenario boolean (optional) String specifying the scenario the Ped should perform. ---@param scenario boolean (optional) String specifying the scenario the Ped should perform.
---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`. ---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`. ---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
-- --
---@usage ---@usage
-- ```lua -- ```lua
-- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true) -- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true)
-- ``` -- ```
function makeDistPed(data, coords, freeze, collision, scenario, anim, synced) function makeDistPed(data, coords, freeze, collision, scenario, anim, synced)
createCirclePoly({ local zoneCoords = type(data) == "table" and data.coords or coords
name = keyGen()..keyGen(), local randName = keyGen()..keyGen()
coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03), createCirclePoly({
radius = 50.0, name = randName,
onEnter = function() coords = vec3(zoneCoords.x, zoneCoords.y, zoneCoords.z - 1.03),
Peds[#Peds + 1] = makePed(data, coords, freeze, collision, scenario, anim, synced) radius = 50.0,
end, onEnter = function()
onExit = function() Peds[randName] = makePed(data, coords, freeze, collision, scenario, anim, synced)
DeletePed(Peds[#Peds]) end,
end, onExit = function()
debug = debugMode, DeletePed(Peds[randName])
}) end,
end debug = debugMode,
})
--- Creates a Ped (pedestrian character) with specified properties. end
--
-- This function creates a Ped at the given coordinates and applies appearance and clothing based on the provided data. --- Creates a Ped (pedestrian character) with specified properties.
-- --
-- If `data` is a table with `custom` properties, it customizes the Ped's appearance accordingly. -- This function creates a Ped at the given coordinates and applies appearance and clothing based on the provided data.
-- --
---@param data modelHash|table Either a string/model hash of the Ped model to use, or a table containing `model` and `custom` data. -- If `data` is a table with `custom` properties, it customizes the Ped's appearance accordingly.
---@param coords vector4 `vector3` or `vector4` specifying the coordinates where the Ped will be placed. --
---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`. ---@param data modelHash|table Either a string/model hash of the Ped model to use, or a table containing `model` and `custom` data.
---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`. ---@param coords vector4 `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
---@param scenario string (optional) String specifying the scenario the Ped should perform. ---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`. ---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`. ---@param scenario string (optional) String specifying the scenario the Ped should perform.
-- ---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
---@return ped entityID The handle of the created Ped. ---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
-- --
---@usage ---@return ped entityID The handle of the created Ped.
-- ```lua --
-- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true) ---@usage
-- ``` -- ```lua
function makePed(data, coords, freeze, collision, scenario, anim, synced) -- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true)
local ped = nil -- ```
local model = nil function makePed(data, coords, freeze, collision, scenario, anim, synced)
if type(data) == "table" then local ped = nil
model = data.model local model = nil
loadModel(data.model) if type(data) == "table" then
ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false) model = data.model
loadModel(data.model)
-- Inheritance ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)
-- Inheritance
-- Face Features SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)
for k, v in pairs({
"noseWidth", "noseHeight", "noseSize", "noseBoneHeight", "nosePeakHeight", "noseBoneTwist", -- Face Features
"eyebrowHeight", "eyebrowDepth", for k, v in pairs({
"cheekBoneHeight", "cheekBoneWidth", "cheeckWidth", "noseWidth", "noseHeight", "noseSize", "noseBoneHeight", "nosePeakHeight", "noseBoneTwist",
"eyeOpening", "lipThickness", "eyebrowHeight", "eyebrowDepth",
"jawWidth", "jawSize", "cheekBoneHeight", "cheekBoneWidth", "cheeckWidth",
"chinLowering", "chinLength", "chinSize", "chinHole", "eyeOpening", "lipThickness",
"neckThickness" "jawWidth", "jawSize",
}) do "chinLowering", "chinLength", "chinSize", "chinHole",
SetPedFaceFeature(ped, k - 1, data.custom[v]) "neckThickness"
end }) do
SetPedFaceFeature(ped, k - 1, data.custom[v])
-- Appearance end
SetPedComponentVariation(ped, 2, data.custom.Hair, 0, 0)
SetPedHairColor(ped, data.custom.HairTexture, data.custom.HairHighlight or 0) -- Appearance
SetPedHeadOverlay(ped, 2, data.custom.Eyebrows, data.custom.EyebrowsOpacity) SetPedComponentVariation(ped, 2, data.custom.Hair, 0, 0)
SetPedHeadOverlayColor(ped, 2, 1, data.custom.EyebrowsColor, 0) SetPedHairColor(ped, data.custom.HairTexture, data.custom.HairHighlight or 0)
SetPedEyeColor(ped, data.custom.Eyecolor) SetPedHeadOverlay(ped, 2, data.custom.Eyebrows, data.custom.EyebrowsOpacity)
SetPedHeadOverlay(ped, 4, data.custom.Makeup, data.custom.MakeupOpacity) SetPedHeadOverlayColor(ped, 2, 1, data.custom.EyebrowsColor, 0)
SetPedHeadOverlayColor(ped, 4, 1, data.custom.MakeupColor, 0) SetPedEyeColor(ped, data.custom.Eyecolor)
SetPedHeadOverlay(ped, 8, data.custom.Lipstick, data.custom.LipstickOpacity) SetPedHeadOverlay(ped, 4, data.custom.Makeup, data.custom.MakeupOpacity)
SetPedHeadOverlayColor(ped, 8, 1, data.custom.LipstickColor, 0) SetPedHeadOverlayColor(ped, 4, 1, data.custom.MakeupColor, 0)
SetPedHeadOverlay(ped, 1, data.custom.Beard, data.custom.BeardOpacity) SetPedHeadOverlay(ped, 8, data.custom.Lipstick, data.custom.LipstickOpacity)
SetPedHeadOverlayColor(ped, 1, 1, data.custom.BeardColor, 0) SetPedHeadOverlayColor(ped, 8, 1, data.custom.LipstickColor, 0)
SetPedHeadOverlay(ped, 1, data.custom.Beard, data.custom.BeardOpacity)
-- Clothes SetPedHeadOverlayColor(ped, 1, 1, data.custom.BeardColor, 0)
SetPedComponentVariation(ped, 1, data.custom.Mask, data.custom.MaskVariant, 0)
SetPedComponentVariation(ped, 7, data.custom.Scarf, data.custom.ScarfVariant, 0) -- Clothes
SetPedComponentVariation(ped, 11, data.custom.Jacket, data.custom.JacketVariant, 0) SetPedComponentVariation(ped, 1, data.custom.Mask, data.custom.MaskVariant, 0)
SetPedComponentVariation(ped, 8, data.custom.Shirt, data.custom.ShirtVariant, 0) SetPedComponentVariation(ped, 7, data.custom.Scarf, data.custom.ScarfVariant, 0)
SetPedComponentVariation(ped, 9, data.custom.Vest, data.custom.VestVariant, 0) SetPedComponentVariation(ped, 11, data.custom.Jacket, data.custom.JacketVariant, 0)
SetPedComponentVariation(ped, 5, data.custom.Bags, data.custom.BagsVariant, 0) SetPedComponentVariation(ped, 8, data.custom.Shirt, data.custom.ShirtVariant, 0)
SetPedComponentVariation(ped, 3, data.custom.Arms, data.custom.ArmsVariant, 0) SetPedComponentVariation(ped, 9, data.custom.Vest, data.custom.VestVariant, 0)
SetPedComponentVariation(ped, 4, data.custom.Pants, data.custom.PantsVariant, 0) SetPedComponentVariation(ped, 5, data.custom.Bags, data.custom.BagsVariant, 0)
SetPedComponentVariation(ped, 6, data.custom.Shoes, data.custom.ShoesVariant, 0) SetPedComponentVariation(ped, 3, data.custom.Arms, data.custom.ArmsVariant, 0)
SetPedComponentVariation(ped, 10, data.custom.Decal, data.custom.DecalVariant, 0) SetPedComponentVariation(ped, 4, data.custom.Pants, data.custom.PantsVariant, 0)
SetPedComponentVariation(ped, 6, data.custom.Shoes, data.custom.ShoesVariant, 0)
-- Accessories SetPedComponentVariation(ped, 10, data.custom.Decal, data.custom.DecalVariant, 0)
SetPedPropIndex(ped, 0, data.custom.Hat, data.custom.HatVariant, true)
SetPedPropIndex(ped, 1, data.custom.Glasses, data.custom.GlassesVariant, true) -- Accessories
SetPedPropIndex(ped, 0, data.custom.Hat, data.custom.HatVariant, true)
SetPedPropIndex(ped, 2, data.custom.Ear, data.custom.EarVariant, true) SetPedPropIndex(ped, 1, data.custom.Glasses, data.custom.GlassesVariant, true)
SetPedPropIndex(ped, 6, data.custom.Watches, data.custom.WatchesVariant, true)
SetPedPropIndex(ped, 7, data.custom.Bracelets, data.custom.BraceletsVariant, true) SetPedPropIndex(ped, 2, data.custom.Ear, data.custom.EarVariant, true)
else SetPedPropIndex(ped, 6, data.custom.Watches, data.custom.WatchesVariant, true)
model = data SetPedPropIndex(ped, 7, data.custom.Bracelets, data.custom.BraceletsVariant, true)
loadModel(model) else
ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false) model = data
end loadModel(model)
if gameName == "rdr3" then
SetEntityInvincible(ped, true) ped = CreatePed(model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
SetBlockingOfNonTemporaryEvents(ped, true) SetEntityVisible(ped, 1) -- SetEntityVisible
FreezeEntityPosition(ped, freeze and freeze or true) SetEntityAlpha(ped, 255, false) -- SetEntityAlpha
SetRandomOutfitVariation(ped, true) -- Invisible without
if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end else
if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
if anim then end
loadAnimDict(anim[1]) end
TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
end SetEntityInvincible(ped, true)
SetBlockingOfNonTemporaryEvents(ped, true)
debugPrint("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^5"..(model).."^7' | ^2Coord^7: "..formatCoord(coords)) FreezeEntityPosition(ped, freeze and freeze or true)
unloadModel(model)
Peds[#Peds + 1] = ped if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end
return ped if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end
end if anim then
loadAnimDict(anim[1])
--- Generates random Ped data by filling in missing customization options with random values. TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
-- end
-- This function takes in a data table that may have some customization options missing in `data.custom`. if DoesEntityExist(ped) then
-- debugPrint("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^5"..(model).."^7' | ^2Coord^7: "..formatCoord(coords))
-- It generates random values for any missing options and returns a new data table with complete customization. else
-- print("error ped")
---@param data table A table containing at least a `model` field, and possibly a `custom` table with customization options. end
-- unloadModel(model)
---@return generatedTable table A new table containing `model` and `custom` with all customization options filled. Peds[keyGen()..keyGen()] = ped
-- return ped
---@usage end
-- ```lua
-- local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} }) --- Generates random Ped data by filling in missing customization options with random values.
-- ``` --
function GenerateRandomPedData(data) -- This function takes in a data table that may have some customization options missing in `data.custom`.
local newTable = { --
model = data.model, -- It generates random values for any missing options and returns a new data table with complete customization.
custom = {}, --
} ---@param data table A table containing at least a `model` field, and possibly a `custom` table with customization options.
local isMale = data.model == `MP_M_Freemode_01` --
local randomTable = { ---@return generatedTable table A new table containing `model` and `custom` with all customization options filled.
-- Inheritance --
faceFather = math.random(0, 45), faceMother = math.random(0, 45), faceMix = (math.random(0, 9) / 10), ---@usage
skinFather = math.random(0, 45), skinMother = math.random(0, 45), skinMix = (math.random(0, 9) / 10), -- ```lua
raceShape = math.random(0, 45), raceSkin = math.random(0, 45), raceMix = (math.random(0, 9) / 10), -- local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} })
-- ```
-- Face Features function GenerateRandomPedData(data)
noseWidth = (math.random(0, 9) / 10), local newTable = {
noseHeight = (math.random(0, 9) / 10), model = data.model,
noseSize = (math.random(0, 9) / 10), custom = {},
noseBoneHeight = (math.random(0, 9) / 10), }
nosePeakHeight = (math.random(0, 9) / 10), local isMale = data.model == `MP_M_Freemode_01`
noseBoneTwist = (math.random(0, 9) / 10), local randomTable = {
-- Inheritance
eyebrowHeight = (math.random(0, 9) / 10), faceFather = math.random(0, 45), faceMother = math.random(0, 45), faceMix = (math.random(0, 9) / 10),
eyebrowDepth = (math.random(0, 9) / 10), skinFather = math.random(0, 45), skinMother = math.random(0, 45), skinMix = (math.random(0, 9) / 10),
raceShape = math.random(0, 45), raceSkin = math.random(0, 45), raceMix = (math.random(0, 9) / 10),
cheekBoneHeight = (math.random(0, 9) / 10),
cheekBoneWidth = (math.random(0, 9) / 10), -- Face Features
cheeckWidth = (math.random(0, 9) / 10), noseWidth = (math.random(0, 9) / 10),
noseHeight = (math.random(0, 9) / 10),
eyeOpening = (math.random(0, 9) / 10), noseSize = (math.random(0, 9) / 10),
lipThickness = (math.random(0, 9) / 10), noseBoneHeight = (math.random(0, 9) / 10),
nosePeakHeight = (math.random(0, 9) / 10),
jawWidth = (math.random(0, 9) / 10), noseBoneTwist = (math.random(0, 9) / 10),
jawSize = (math.random(0, 9) / 10),
eyebrowHeight = (math.random(0, 9) / 10),
chinLowering = (math.random(0, 9) / 10), eyebrowDepth = (math.random(0, 9) / 10),
chinLength = (math.random(0, 9) / 10),
chinSize = (math.random(0, 9) / 10), cheekBoneHeight = (math.random(0, 9) / 10),
chinHole = (math.random(0, 9) / 10), cheekBoneWidth = (math.random(0, 9) / 10),
cheeckWidth = (math.random(0, 9) / 10),
neckThickness = (math.random(0, 9) / 10),
eyeOpening = (math.random(0, 9) / 10),
-- Appearance lipThickness = (math.random(0, 9) / 10),
Hair = math.random(0, isMale and 147 or 261), HairTexture = math.random(0, 63), HairHighlight = math.random(0, 63),
Eyebrows = math.random(0, 33), jawWidth = (math.random(0, 9) / 10),
EyebrowsOpacity = 0.9, EyebrowsColor = 0, jawSize = (math.random(0, 9) / 10),
Eyecolor = math.random(0, 30),
Makeup = 0, MakeupOpacity = 0, MakeupColor = 0, chinLowering = (math.random(0, 9) / 10),
Lipstick = 0, LipstickOpacity = 0, LipstickColor = 0, chinLength = (math.random(0, 9) / 10),
Beard = isMale and math.random(0, 28) or -1, chinSize = (math.random(0, 9) / 10),
BeardOpacity = isMale and 0.9 or 0.0, BeardColor = 0, chinHole = (math.random(0, 9) / 10),
-- Clothing neckThickness = (math.random(0, 9) / 10),
Mask = math.random(0, 252), MaskVariant = 0,
Scarf = math.random(0, isMale and 249 or 198), ScarfVariant = 0, -- Appearance
Jacket = math.random(0, isMale and 634 or 713), JacketVariant = 0, Hair = math.random(0, isMale and 147 or 261), HairTexture = math.random(0, 63), HairHighlight = math.random(0, 63),
Shirt = math.random(0, isMale and 237 or 299), ShirtVariant = 0, Eyebrows = math.random(0, 33),
Vest = math.random(0, isMale and 81 or 91), VestVariant = 0, EyebrowsOpacity = 0.9, EyebrowsColor = 0,
Bags = math.random(0, isMale and 138 or 148), BagsVariant = 0, Eyecolor = math.random(0, 30),
Arms = math.random(0, isMale and 224 or 261), ArmsVariant = 0, Makeup = 0, MakeupOpacity = 0, MakeupColor = 0,
Pants = math.random(0, isMale and 255 or 275), PantsVariant = 0, Lipstick = 0, LipstickOpacity = 0, LipstickColor = 0,
Shoes = math.random(0, isMale and 157 or 199), ShoesVariant = 0, Beard = isMale and math.random(0, 28) or -1,
Decal = math.random(0, isMale and 238 or 253), DecalVariant = 0, BeardOpacity = isMale and 0.9 or 0.0, BeardColor = 0,
-- Accessories -- Clothing
Hat = math.random(0, isMale and 232 or 229), HatVariant = 0, Mask = math.random(0, 252), MaskVariant = 0,
Glasses = math.random(0, isMale and 68 or 71), GlassesVariant = 0, Scarf = math.random(0, isMale and 249 or 198), ScarfVariant = 0,
Ear = math.random(0, isMale and 51 or 40), EarVariant = 0, Jacket = math.random(0, isMale and 634 or 713), JacketVariant = 0,
Watches = math.random(0, isMale and 46 or 35), WatchesVariant = 0, Shirt = math.random(0, isMale and 237 or 299), ShirtVariant = 0,
Bracelets = math.random(0, isMale and 13 or 20), BraceletsVariant = 0, Vest = math.random(0, isMale and 81 or 91), VestVariant = 0,
} Bags = math.random(0, isMale and 138 or 148), BagsVariant = 0,
for option in pairs(randomTable) do Arms = math.random(0, isMale and 224 or 261), ArmsVariant = 0,
if not data.custom[option] then Pants = math.random(0, isMale and 255 or 275), PantsVariant = 0,
newTable.custom[option] = randomTable[option] Shoes = math.random(0, isMale and 157 or 199), ShoesVariant = 0,
debugPrint("^6Bridge^7: ^2Picking Random Ped option ^7[^5"..option.."^7]: ^6"..newTable.custom[option].."^7") Decal = math.random(0, isMale and 238 or 253), DecalVariant = 0,
else
newTable.custom[option] = data.custom[option] -- Accessories
end Hat = math.random(0, isMale and 232 or 229), HatVariant = 0,
end Glasses = math.random(0, isMale and 68 or 71), GlassesVariant = 0,
return newTable Ear = math.random(0, isMale and 51 or 40), EarVariant = 0,
end Watches = math.random(0, isMale and 46 or 35), WatchesVariant = 0,
Bracelets = math.random(0, isMale and 13 or 20), BraceletsVariant = 0,
--- Cleans up all created Peds when the resource stops. }
onResourceStop(function() for i = 1, #Peds do DeletePed(Peds[i]) end end, true) for option in pairs(randomTable) do
if not data.custom[option] then
newTable.custom[option] = randomTable[option]
debugPrint("^6Bridge^7: ^2Picking Random Ped option ^7[^5"..option.."^7]: ^6"..newTable.custom[option].."^7")
else
newTable.custom[option] = data.custom[option]
end
end
return newTable
end
--- Cleans up all created Peds when the resource stops.
onResourceStop(function()
for k in pairs(Peds) do
DeletePed(Peds[k])
end
end, true)

View File

@@ -1,90 +1,94 @@
local Props = {} local Props = {}
--- Creates a prop (object) in the game world at specified coordinates. --- Creates a prop (object) in the game world at specified coordinates.
--- ---
--- This function loads the model, creates the object, sets its heading, and freezes it if specified. --- This function loads the model, creates the object, sets its heading, and freezes it if specified.
--- ---
---@param data table A table containing prop data. ---@param data table A table containing prop data.
--- - **prop** `string`: The model name or hash of the prop to create. --- - **prop** `string`: The model name or hash of the prop to create.
--- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading). --- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`. ---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`. ---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
--- ---
---@return number entityID The handle of the created prop object. ---@return number entityID The handle of the created prop object.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- local propData = { --- local propData = {
--- prop = 'prop_chair_01a', --- prop = 'prop_chair_01a',
--- coords = vector4(123.4, 567.8, 90.1, 180.0) --- coords = vector4(123.4, 567.8, 90.1, 180.0)
--- } --- }
--- local prop = makeProp(propData, true, false) --- local prop = makeProp(propData, true, false)
--- ``` --- ```
function makeProp(data, freeze, synced) function makeProp(data, freeze, synced)
loadModel(data.prop) loadModel(data.prop)
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false) local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
SetEntityHeading(prop, data.coords.w + 180.0) SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
FreezeEntityPosition(prop, freeze or false) FreezeEntityPosition(prop, freeze or false)
debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords)) debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords))
SetModelAsNoLongerNeeded(data.prop) SetModelAsNoLongerNeeded(data.prop)
Props[#Props + 1] = prop Props[keyGen()..keyGen()] = prop
return prop return prop
end end
--- Creates a prop that appears when the player is within a certain distance. --- Creates a prop that appears when the player is within a certain distance.
--- ---
--- This function sets up a proximity area, and when the player enters it, the prop is created. --- This function sets up a proximity area, and when the player enters it, the prop is created.
--- When the player exits the area, the prop is destroyed. --- When the player exits the area, the prop is destroyed.
--- ---
---@param data table A table containing prop data. ---@param data table A table containing prop data.
--- - **prop** `string`: The model name or hash of the prop to create. --- - **prop** `string`: The model name or hash of the prop to create.
--- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading). --- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`. ---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`. ---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- local propData = { --- local propData = {
--- prop = 'prop_chair_01a', --- prop = 'prop_chair_01a',
--- coords = vector4(123.4, 567.8, 90.1, 180.0) --- coords = vector4(123.4, 567.8, 90.1, 180.0)
--- } --- }
--- makeDistProp(propData, true, false) --- makeDistProp(propData, true, false)
--- ``` --- ```
function makeDistProp(data, freeze, synced) function makeDistProp(data, freeze, synced, range)
local prop = nil local name = keyGen()..keyGen()
createCirclePoly({ createCirclePoly({
name = keyGen()..keyGen(), name = name,
coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03), coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
radius = 50.0, radius = range or 50.0,
onEnter = function() onEnter = function()
prop = makeProp(data, freeze, synced) Props[name] = makeProp(data, freeze, synced)
end, end,
onExit = function() onExit = function()
destroyProp(prop) destroyProp(Props[name])
end, end,
debug = debugMode, debug = debugMode,
}) })
end end
--- Destroys a prop, detaching it if attached to the player beforehand. --- Destroys a prop, detaching it if attached to the player beforehand.
--- ---
---@param entity number The handle of the prop entity to destroy. ---@param entity number The handle of the prop entity to destroy.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- destroyProp(prop) --- destroyProp(prop)
--- ``` --- ```
function destroyProp(entity) function destroyProp(entity)
if entity then if entity then
debugPrint("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'") debugPrint("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'")
if IsEntityAttachedToEntity(entity, PlayerPedId()) then if IsEntityAttachedToEntity(entity, PlayerPedId()) then
SetEntityAsMissionEntity(entity) SetEntityAsMissionEntity(entity)
DetachEntity(entity, true, true) DetachEntity(entity, true, true)
end end
DeleteObject(entity) DeleteObject(entity)
end end
end end
--- Cleans up all created props when the resource stops. --- Cleans up all created props when the resource stops.
onResourceStop(function() for i = 1, #Props do destroyProp(Props[i]) end end, true) onResourceStop(function()
for k in pairs(Props) do
destroyProp(Props[k])
end
end, true)

View File

@@ -1,73 +1,107 @@
local Vehicles = {} local Vehicles = {}
--- Creates a vehicle with the specified model and coordinates. --- Creates a vehicle with the specified model and coordinates.
--- ---
--- This function loads the vehicle model, creates the vehicle in the world at the given coordinates, sets initial properties, and returns the vehicle handle. --- This function loads the vehicle model, creates the vehicle in the world at the given coordinates, sets initial properties, and returns the vehicle handle.
--- ---
---@param model string|number The model name or hash of the vehicle to create. ---@param model string|number The model name or hash of the vehicle to create.
---@param coords vector4 The coordinates where the vehicle will be placed, including x, y, z, and w (heading). ---@param coords vector4 The coordinates where the vehicle will be placed, including x, y, z, and w (heading).
--- ---
---@return number entityID The handle of the created vehicle. ---@return number entityID The handle of the created vehicle.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0)) --- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0))
--- ``` --- ```
function makeVeh(model, coords) function makeVeh(model, coords)
loadModel(model) loadModel(model)
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false)
SetVehicleHasBeenOwnedByPlayer(veh, true) SetVehicleHasBeenOwnedByPlayer(veh, true)
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true) if gameName ~= "rdr3" then
Wait(100) SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
SetVehicleNeedsToBeHotwired(veh, false) Wait(100)
SetVehRadioStation(veh, 'OFF') SetVehicleNeedsToBeHotwired(veh, false)
SetVehicleFuelLevel(veh, 100.0) SetVehRadioStation(veh, 'OFF')
SetVehicleModKit(veh, 0) SetVehicleFuelLevel(veh, 100.0)
SetVehicleOnGroundProperly(veh) SetVehicleModKit(veh, 0)
end
debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords)) SetVehicleOnGroundProperly(veh)
unloadModel(model)
Vehicles[#Vehicles + 1] = veh debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords))
return veh unloadModel(model)
end Vehicles[#Vehicles + 1] = veh
return veh
--- Attempts to gain network control of a vehicle and set it as a mission entity. end
---
--- This function forces synchronization of a vehicle with other players by requesting network control and setting the vehicle as a mission entity. local distanceVehicles = {}
--- --- Creates a vehicle that spawns when the player enters a designated polyzone area.
---@param entity number The handle of the vehicle entity to push. ---
--- --- This function sets up a circular polyzone; when the player enters the zone, the vehicle is spawned,
---@usage --- and when the player exits, the vehicle is deleted.
--- ```lua ---
--- pushVehicle(vehicle) ---@param data table A table containing vehicle data.
--- ``` --- - **vehicle** `string`: The model name or hash of the vehicle to spawn.
function pushVehicle(entity) --- - **coords** `vector4`: The coordinates where the vehicle will be placed. Should include x, y, z, and w (heading).
SetVehicleModKit(entity, 0) ---@param freeze boolean (optional) Whether to freeze the vehicle in place. Defaults to `false`.
if entity ~= 0 and DoesEntityExist(entity) then ---@param synced boolean (optional) Whether the vehicle should be synced across clients. Defaults to `false`.
if not NetworkHasControlOfEntity(entity) then function makeDistVehicle(data, radius, onEnter, onExit)
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.") local vehicle = nil
NetworkRequestControlOfEntity(entity) local zoneId = keyGen() .. keyGen()
local timeout = 2000 local zone = createCirclePoly({
while timeout > 0 and not NetworkHasControlOfEntity(entity) do name = zoneId,
Wait(100) coords = vec3(data.coords.x, data.coords.y, data.coords.z),
timeout -= 100 radius = radius,
end onEnter = function()
if NetworkHasControlOfEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.") end vehicle = makeVeh(data.model, data.coords)
end if onEnter then
if not IsEntityAMissionEntity(entity) then debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onEnter^7() ^2running^7")
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.") onEnter(vehicle)
SetEntityAsMissionEntity(entity, true, true) end
local timeout = 2000 end,
while timeout > 0 and not IsEntityAMissionEntity(entity) do onExit = function()
Wait(100) deleteVehicle(vehicle)
timeout -= 100 if onExit then
end debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onExit^7() ^2running^7")
if IsEntityAMissionEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") end onExit(vehicle)
end end
end end,
end debug = debugMode,
})
--- Cleans up all created vehicles when the resource stops. distanceVehicles[zoneId] = { zone = zone, vehicle = vehicle }
onResourceStop(function(r) return zoneId
for i = 1, #Vehicles do DeleteVehicle(Vehicles[i]) end end
--- Removes a specific distance-based vehicle spawning zone.
---
---@param zoneId string The unique identifier of the zone to remove.
function removeDistVehicleZone(zoneId)
if distanceVehicles[zoneId].zone then
removePolyZone(distanceVehicles[zoneId].zone) -- Adjust this if your polyzone library uses a different removal method.
if distanceVehicles[zoneId].vehicle then
deleteVehicle(distanceVehicles[zoneId].vehicle)
end
distanceVehicles[zoneId] = nil
print("Removed polyzone for zoneId: " .. zoneId)
else
print("No zone found with zoneId: " .. zoneId)
end
end
--- Deletes a spawned vehicle.
---
---@param vehicle number The handle of the vehicle entity to delete.
function deleteVehicle(vehicle)
if vehicle then
debugPrint("^6Bridge^7: ^2Destroying Vehicle^7: '^6" .. vehicle .. "^7'")
if IsEntityAttachedToEntity(vehicle, PlayerPedId()) then
SetEntityAsMissionEntity(vehicle)
DetachEntity(vehicle, true, true)
end
DeleteVehicle(vehicle)
end
end
--- Cleans up all created vehicles when the resource stops.
onResourceStop(function(r)
for i = 1, #Vehicles do DeleteVehicle(Vehicles[i]) end
end) end)

View File

@@ -1,209 +1,279 @@
local inProgress = false local inProgress = false
local storedPID = nil
--- Displays a progress bar using the configured progress bar system.
--- --- Displays a progress bar using the configured progress bar system.
--- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta). ---
--- It supports shared progress bars between players, animations, camera effects, and more. --- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
--- --- It supports shared progress bars between players, animations, camera effects, and more.
---@param data table A table containing the progress bar configuration. ---
--- - **label** (`string`): The text label to display on the progress bar. ---@param data table A table containing the progress bar configuration.
--- - **time** (`number`): The duration of the progress bar in milliseconds. --- - **label** (`string`): The text label to display on the progress bar.
--- - **dict** (`string`, optional): The animation dictionary to use. --- - **time** (`number`): The duration of the progress bar in milliseconds.
--- - **anim** (`string`, optional): The animation name to play. --- - **dict** (`string`, optional): The animation dictionary to use.
--- - **task** (`string`, optional): The task scenario to perform. --- - **anim** (`string`, optional): The animation name to play.
--- - **flag** (`number`, optional): The animation flag. --- - **task** (`string`, optional): The task scenario to perform.
--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`. --- - **flag** (`number`, optional): The animation flag.
--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`. --- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
--- - **icon** (`string`, optional): The icon to display (for qb progress bar). --- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
--- - **cam** (`number`, optional): The camera handle to use. --- - **icon** (`string`, optional): The icon to display (for qb progress bar).
--- - **shared** (`table`, optional): Data for shared progress bars. --- - **cam** (`number`, optional): The camera handle to use.
--- - **pid** (`number`): The player ID to share the progress bar with. --- - **shared** (`table`, optional): Data for shared progress bars.
--- - **label** (`string`): The label to display on the shared progress bar. --- - **pid** (`number`): The player ID to share the progress bar with.
--- --- - **label** (`string`): The label to display on the shared progress bar.
--- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled. ---
--- --- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
---@usage ---
--- ```lua ---@usage
--- local success = progressBar({ --- ```lua
--- label = "Processing...", --- local success = progressBar({
--- time = 5000, --- label = "Processing...",
--- dict = "amb@world_human_hang_out_street@female_hold_arm@base", --- time = 5000,
--- anim = "base", --- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
--- flag = 49, --- anim = "base",
--- cancel = true, --- flag = 49,
--- }) --- cancel = true,
--- ``` --- })
function progressBar(data) --- ```
local ped = PlayerPedId() function progressBar(data)
if data.shared then local ped = PlayerPedId()
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7") if data.shared then
storedPID = data.shared.pid debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
TriggerServerEvent(getScript()..":server:sharedProg:Start", data) storedPID = data.shared.pid
end TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
local result = nil end
if data.cam then startTempCam(data.cam) end local result = nil
if Config.System.ProgressBar == "ox" then if data.cam then startTempCam(data.cam) end
if exports[OXLibExport]:progressBar({ if Config.System.ProgressBar == "ox" then
duration = debugMode and 1000 or data.time, local options = {
label = data.label, duration = debugMode and 1000 or data.time,
useWhileDead = data.dead or false, label = data.label,
canCancel = data.cancel and data.cancel or true, position = data.position or "bottom",
anim = { useWhileDead = data.dead or false,
dict = data.dict, canCancel = data.cancel or true,
clip = data.anim, anim = {
flag = (data.flag == 8 and 32 or data.flag) or nil, dict = data.dict,
scenario = data.task clip = data.anim,
}, flag = (data.flag == 8 and 32 or data.flag) or nil,
disable = { scenario = data.task
combat = true },
}, disable = {
}) then combat = data.combat or true,
result = true move = data.disableMovement or false,
else car = data.disableMovement or false,
result = false mouse = data.mouse or false
end },
}
elseif Config.System.ProgressBar == "qb" then if data.prop and data.prop.model then
Core.Functions.Progressbar("progbar", options.prop = {
data.label, model = data.prop.model,
debugMode and 1000 or data.time, pos = data.prop.pos or vec3(0, 0, 0),
data.dead or false, rot = data.prop.rot or vec3(0, 0, 0),
data.cancel or true, bone = data.prop.bone or 0
{ disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true }, }
{ animDict = data.dict, anim = data.anim, flags = data.flag or 32, task = data.task }, {}, {}, end
function() if data.propTwo and data.propTwo.model then
result = true options.propTwo = {
end, function() model = data.propTwo.model,
result = false pos = data.propTwo.pos or vec3(0, 0, 0),
end, data.icon) rot = data.propTwo.rot or vec3(0, 0, 0),
bone = data.propTwo.bone or 0
elseif Config.System.ProgressBar == "esx" then }
ESX.Progressbar(data.label, debugMode and 1000 or data.time, { end
FreezePlayer = true, if data.progressType == "circle" then
animation = { if exports[OXLibExport]:progressCircle(options) then
type = data.anim, result = true
dict = data.dict, else
scenario = data.task, result = false
}, end
onFinish = function() end
result = true if not data.progressType or data.progressType == "bar" then
end, if exports[OXLibExport]:progressBar(options) then
onCancel = function() result = true
result = false else
end result = false
}) end
end
elseif Config.System.ProgressBar == "gta" then
local wait = debugMode and 1000 or data.time elseif Config.System.ProgressBar == "qb" then
inProgress = true Core.Functions.Progressbar("progbar",
if not (data.dead or false) then data.label,
lockInv(true) debugMode and 1000 or data.time,
displaySpinner(data.label) data.dead or false,
if data.dict then data.cancel or true,
playAnim(data.dict, data.anim, -1, (data.flag == 8 and 32 or data.flag) or nil) {
end disableMovement = data.disableMovement or false,
if data.task then disableCarMovement = data.disableMovement or false,
TaskStartScenarioInPlace(ped, data.task, -1, true) disableMouse = data.disableMouse or false,
end disableCombat = data.disableCombat or true,
while inProgress and wait > 0 do },
wait -= 15 {
local waitTimer = 0 animDict = data.dict,
DisablePlayerFiring(ped, true) anim = data.anim,
DisableControlAction(0, 25, true) -- Disable aim flags = data.flag or 32,
DisableControlAction(0, 21, true) -- Disable sprint task = data.task
DisableControlAction(0, 30, true) -- Disable move left/right },
DisableControlAction(0, 31, true) -- Disable move forward/back {}, {},
DisableControlAction(0, 36, true) -- Disable stealth function()
if data.cam ~= nil then result = true
DisableControlAction(0, 1, true) -- Disable look left/right end, function()
DisableControlAction(0, 2, true) -- Disable look up/down result = false
DisableControlAction(0, 106, true) -- Disable vehicle mouse control end, data.icon)
end
if data.cancel then elseif Config.System.ProgressBar == "esx" then
if IsControlJustReleased(0, 202) or IsControlJustReleased(0, 77) then -- Cancel key (Backspace or Delete) ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
inProgress = false FreezePlayer = true,
waitTimer = 1500 animation = {
displaySpinner(Loc[Config.Lan].error["cancel"]) type = data.anim,
end dict = data.dict,
end scenario = data.task,
Wait(waitTimer) },
end onFinish = function()
inProgress = false result = true
if data.dict then stopAnim(data.dict, data.anim, ped) end end,
ClearPedTasks(ped) onCancel = function()
end result = false
stopSpinner() end
result = (wait <= 0) })
end
elseif Config.System.ProgressBar == "lation" then
while result == nil do Wait(10) end if exports.lation_ui:progressBar({
label = data.label,
-- Cleanup description = nil,
FreezeEntityPosition(ped, false) duration = debugMode and 1000 or data.time,
lockInv(false) icon = data.icon,
if data.cam then stopTempCam(data.cam) end useWhileDead = data.dead or false,
if result == false and data.shared then disable = {
debugPrint("^6Bridge^7: ^2Sending cancel to ^6"..storedPID.."^7") combat = data.combat or true,
TriggerServerEvent(getScript().."server:sharedProg:cancel", storedPID) move = data.disableMovement or false,
end car = data.disableMovement or false,
storedPID = nil },
return result anim = {
end dict = data.dict,
clip = data.anim,
--- Stops the current progress bar. },
--- prop = {
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup. model = data.prop and data.prop.model,
function stopPropgressBar() pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
if Config.System.ProgressBar == "ox" then rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
exports[OXLibExport]:cancelProgress() bone = data.prop and (data.prop.bone or 0)
elseif Config.System.ProgressBar == "qb" then }
TriggerEvent("progressbar:client:cancel") }) then
elseif Config.System.ProgressBar == "gta" then result = true
inProgress = false else
BusyspinnerOff() result = true
end end
end
elseif Config.System.ProgressBar == "red" then
-- System to handle sending/sharing progress bars between players -- -- Currently only uses jim-redui if you choose this option
-- For example, healing someone -- if exports["jim_bridge"]:redProgressBar({
label = data.label,
local storedPID = nil time = debugMode and 1000 or data.time,
dict = data.dict,
--- Server event handler for starting a shared progress bar. anim = data.anim,
--- This event is triggered when a player wants to start a progress bar on another player. flag = data.flag or 32,
--- It adjusts the data to prevent loops and sends the data to the target client. task = data.task,
RegisterNetEvent(getScript()..":server:sharedProg:Start", function(data) disableMovement = data.disableMovement or false,
local pid = data.shared.pid -- Get player ID from the client cancel = data.cancel or true,
data.label = data.shared.label -- Set progress bar label to the shared label }) then
data.cancel = false -- Make it so it can't be canceled result = true
data.dead = true -- Allow progress bar even if player is dead else
data.shared = nil -- Remove shared info to prevent loops result = false
data.anim = nil -- Remove animation so players don't share it end
debugPrint("^6Bridge^7: ^6"..source.." ^2is sending shared progressBar to player^7, ^6"..pid.."^7")
TriggerClientEvent(getScript()..":client:sharedProg:Start", pid, data) elseif Config.System.ProgressBar == "gta" then
end) if exports["jim_bridge"]:gtaProgressBar({
label = data.label,
--- Client event handler for starting a shared progress bar. time = debugMode and 1000 or data.time,
--- This event is triggered when the server wants the client to start a shared progress bar. dict = data.dict,
RegisterNetEvent(getScript()..":client:sharedProg:Start", function(data) anim = data.anim,
debugPrint("^6Bridge^7: ^2You have been sent a progressBar^7") flag = data.flag or 32,
progressBar(data) task = data.task,
end) disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
--- Server event handler for canceling a shared progress bar. }) then
--- This event is triggered when a progress bar is canceled and the server needs to notify the other player. result = true
RegisterNetEvent(getScript()..":server:sharedProg:Cancel", function(pid) else
debugPrint("^6Bridge^7: ^2Sending cancel progressBar to ^6"..pid.."^7") result = false
TriggerClientEvent(getScript()..":client:sharedProg:Cancel", pid) end
end)
end
--- Client event handler for canceling a shared progress bar.
--- This event is triggered when the server wants the client to cancel a shared progress bar. while result == nil do
RegisterNetEvent(getScript()..":client:sharedProg:Cancel", function() Wait(10)
debugPrint("^6Bridge^7: ^2Receiving cancel progressBar^7") end
stopPropgressBar()
end) -- Cleanup
FreezeEntityPosition(ped, false)
--- Cleans up when the resource stops. lockInv(false)
--- This event handler ensures that any active spinners or progress bars are stopped when the resource is stopped. if data.cam then
onResourceStop(function() stopSpinner() end, true) stopTempCam(data.cam)
end
if result == false and data.shared then
debugPrint("^6Bridge^7: ^2Sending cancel to ^6"..storedPID.."^7")
TriggerServerEvent(getScript().."server:sharedProg:cancel", storedPID)
end
storedPID = nil
if result == false then
currentToken = nil
TriggerServerEvent(getScript()..":clearAuthToken")
end
if result == true and data.request then
TriggerServerEvent(getScript()..":clearAuthToken")
currentToken = triggerCallback(AuthEvent)
end
return result
end
--- Stops the current progress bar.
---
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
function stopProgressBar()
if Config.System.ProgressBar == "ox" then
exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "lation" then
exports.lation_ui:cancelProgress()
elseif Config.System.ProgressBar == "gta" or Config.System.ProgressBar == "red" then
exports["jim_bridge"]:stopProgressBar()
end
end
-- System to handle sending/sharing progress bars between players --
-- For example, healing someone --
--- 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.
--- It adjusts the data to prevent loops and sends the data to the target client.
RegisterNetEvent(getScript()..":server:sharedProg:Start", function(data)
local pid = data.shared.pid -- Get player ID from the client
data.label = data.shared.label -- Set progress bar label to the shared label
data.cancel = false -- Make it so it can't be canceled
data.dead = true -- Allow progress bar even if player is dead
data.shared = nil -- Remove shared info to prevent loops
data.anim = nil -- Remove animation so players don't share it
debugPrint("^6Bridge^7: ^6"..source.." ^2is sending shared progressBar to player^7, ^6"..pid.."^7")
TriggerClientEvent(getScript()..":client:sharedProg:Start", pid, data)
end)
--- Client event handler for starting a shared progress bar.
--- This event is triggered when the server wants the client to start a shared progress bar.
RegisterNetEvent(getScript()..":client:sharedProg:Start", function(data)
debugPrint("^6Bridge^7: ^2You have been sent a progressBar^7")
progressBar(data)
end)
--- Server event handler for canceling a shared progress bar.
--- This event is triggered when a progress bar is canceled and the server needs to notify the other player.
RegisterNetEvent(getScript()..":server:sharedProg:Cancel", function(pid)
debugPrint("^6Bridge^7: ^2Sending cancel progressBar to ^6"..pid.."^7")
TriggerClientEvent(getScript()..":client:sharedProg:Cancel", pid)
end)
--- Client event handler for canceling a shared progress bar.
--- This event is triggered when the server wants the client to cancel a shared progress bar.
RegisterNetEvent(getScript()..":client:sharedProg:Cancel", function()
debugPrint("^6Bridge^7: ^2Receiving cancel progressBar^7")
stopProgressBar()
end)

157
shared/metaHandlers.lua Normal file
View File

@@ -0,0 +1,157 @@
--[[
Player Metadata Utilities Module
----------------------------------
This module provides functions for retrieving and setting metadata for players
across different frameworks (QB, ESX, OXCore). It also registers server callbacks
for getting and setting metadata.
]]
-------------------------------------------------------------
-- Player Retrieval
-------------------------------------------------------------
--- Retrieves the player object using the active core export.
---
--- @param source number The server ID of the player.
--- @return table|nil table The player object, or nil if no supported core is detected.
---
--- @usage
--- ```lua
--- local player = GetPlayer(playerId)
--- ```
function GetPlayer(source)
if isStarted(QBExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() QBExport")
return exports[QBExport]:GetCoreObject().Functions.GetPlayer(source)
elseif isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() QBOXExport")
return exports[QBXExport]:GetCoreObject().Functions.GetPlayer(source)
elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() ESXExport")
return ESX.GetPlayerFromId(source)
elseif isStarted(OXCoreExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() OXCoreExport")
return exports[OXCoreExport]:GetPlayer(source)
elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() RSGExport")
return exports[RSGExport]:GetCoreObject().Functions.GetPlayer(source)
end
return nil
end
-------------------------------------------------------------
-- Metadata Retrieval
-------------------------------------------------------------
--- Retrieves metadata from a player object.
---
--- If called client-side (player is nil), it triggers a server callback to retrieve metadata.
---
--- @param player table|nil The player object; if nil, metadata is retrieved via a server callback.
--- @param key string The metadata key to retrieve.
--- @return any The value of the requested metadata, or nil if not found.
---
--- @usage
--- ```lua
--- local myMeta = GetMetadata(player, "myKey")
--- ```
function GetMetadata(player, key)
if not player then
debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key)
return triggerCallback(getScript()..":server:GetMetadata", key)
else
if isStarted(QBExport) or isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^3GetMetadata^7() QBExport/QBXExport", key)
return player.PlayerData.metadata[key]
elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^3GetMetadata^7() ESXExport", key)
return player.getMeta(key)
elseif isStarted(OXCoreExport) then
debugPrint("^6Bridge^7: ^3GetMetadata^7() OXCoreExport", key)
return player.get(key)
elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^3GetMetadata^7() RSGExport", key)
return player.PlayerData.metadata[key]
end
end
return nil
end
-- Register a server callback for retrieving metadata.
createCallback(getScript()..":server:GetMetadata", function(source, key)
debugPrint("^6Bridge^7: ^3GetMetadata Callback^7 from source: "..tostring(source)..", key: "..tostring(key))
local player = GetPlayer(source)
if not player then
print("Error getting metadata: player not found for source "..tostring(source))
return
end
if type(key) == "table" then
local Metadata = {}
for _, k in ipairs(key) do
Metadata[k] = GetMetadata(player, k)
end
return Metadata
elseif type(key) == "string" then
return GetMetadata(player, key)
end
end)
-------------------------------------------------------------
-- Metadata Setting
-------------------------------------------------------------
--- Sets metadata on a player object.
---
--- The function updates the player's metadata using the active core export.
---
--- @param player table The player object.
--- @param key string The metadata key to set.
--- @param value any The new value for the metadata key.
---
--- @usage
--- ```lua
--- SetMetadata(player, "myKey", "newValue")
--- ```
function SetMetadata(player, key, value)
debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata for key: "..key)
if isStarted(QBExport) or isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using QBExport/QBXExport")
player.Functions.SetMetaData(key, value)
elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using ESXExport")
player.setMeta(key, value)
elseif isStarted(OXCoreExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using OXCoreExport")
player.set(key, value)
elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using RSGExport")
player.Functions.SetMetaData(key, value)
end
end
-- Register a server callback for setting metadata.
createCallback(getScript()..":server:SetMetadata", function(source, key, value)
debugPrint("SetMetadata callback triggered for source:", source, "key:", key, "value:", value)
local player = GetPlayer(source)
--[[if not player then
print("Error setting metadata: player not found for source "..tostring(source))
return false
end]]
SetMetadata(player, key, value)
print("Metadata set successfully.", key)
return true
end)

View File

@@ -1,88 +1,147 @@
-- NOTIFICATIONS -- --[[
-- This function is widely used to display notifications to the player, can be used server side or client side -- Notifications Module
----------------------
--- Displays notifications to the player using the configured notification system. This module provides a unified interface for displaying notifications using various
--- notification systems. The active system is determined by the Config.System.Notify setting.
--- This function supports multiple notification systems based on the `Config.System.Notify` setting.
--- It can be triggered from both client-side and server-side scripts. Depending on the configuration, Supported systems include:
--- it utilizes different exports or events to display the notification. • okok
--- • qb
---@param title string|nil The title of the notification. Optional, used by certain notification systems. • ox
---@param message string The main message content of the notification. • gta (default)
---@param type string The type/category of the notification (e.g., "success", "error", "info"). • esx
---@param src number|nil Optional. The server ID of the player to send the notification to. If `nil`, the notification is sent to the caller. ]]
---
---@usage --- Displays notifications to the player using the configured notification system.
--- ```lua ---
--- -- Client-side usage without specifying a player (shows to the current player) --- Supports multiple notification systems based on Config.System.Notify. Can be triggered from both
--- triggerNotify("Success", "You have completed the task!", "success") --- client and server contexts.
--- ---
--- -- Server-side usage specifying a player by their server ID --- @param title string|nil The notification title (optional for some systems).
--- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId) --- @param message string The main message content.
--- ``` --- @param type string The notification type ("success", "error", "info").
function triggerNotify(title, message, type, src) --- @param src number|nil Optional server ID; if provided, the notification is sent to that player.
if Config.System.Notify == "okok" then ---
if not src then TriggerEvent('okokNotify:Alert', title, message, 6000, type) --- @usage
else TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type) end --- ```lua
elseif Config.System.Notify == "qb" then --- -- Client-side usage without specifying a player (shows to the current player)
if not src then TriggerEvent("QBCore:Notify", message, type) --- triggerNotify("Success", "You have completed the task!", "success")
else TriggerClientEvent("QBCore:Notify", src, message, type) end ---
elseif Config.System.Notify == "ox" then --- -- Server-side usage specifying a player by their server ID
if not src then TriggerEvent('ox_lib:notify', {title = title, description = message, type = type or "success"}) --- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId)
else TriggerClientEvent('ox_lib:notify', src, { type = type or "success", title = title, description = message }) end --- ```
elseif Config.System.Notify == "gta" then function triggerNotify(title, message, type, src)
if not src then TriggerEvent(getScript()..":DisplayGTANotify", title, message) if not Config.System or not Config.System.Notify then debugPrint("Notify triggered but not set up") return end
else TriggerClientEvent(getScript()..":DisplayGTANotify", src, title, message) end if Config.System.Notify == "okok" then
elseif Config.System.Notify == "esx" then if not src then
if not src then exports["esx_notify"]:Notify(type, 4000, message) TriggerEvent('okokNotify:Alert', title, message, 6000, type)
else TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, title, message) end else
end TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type)
end end
elseif Config.System.Notify == "qb" then
--- Registers a server-side event to display ESX notifications to clients. if not src then
--- TriggerEvent("QBCore:Notify", message, type)
--- This event listens for `DisplayESXNotify` and triggers the ESX notification on the client side. else
--- TriggerClientEvent("QBCore:Notify", src, message, type)
--- @param type string The type/category of the notification (e.g., "success", "error", "info"). end
--- @param title string The title of the notification. elseif Config.System.Notify == "ox" then
--- @param text string The main message content of the notification. if not src then
--- TriggerEvent('ox_lib:notify', { title = title, description = message, type = type or "success" })
--- @usage else
--- ```lua TriggerClientEvent('ox_lib:notify', src, { title = title, description = message, type = type or "success" })
--- -- Server-side event trigger end
--- TriggerClientEvent(getScript()..":DisplayESXNotify", playerId, "success", "Achievement Unlocked", "You have unlocked a new achievement!") elseif Config.System.Notify == "gta" then
--- ``` if not src then
RegisterNetEvent(getScript()..":DisplayESXNotify", function(type, title, text) exports.jim_bridge:Notify(title, message, type)
exports["esx_notify"]:Notify(type, 4000, text) else
end) TriggerClientEvent("jim-bridge:Notify", src, title, message, type)
end
--- Displays default GTA-style text notifications. elseif Config.System.Notify == "esx" then
--- if not src then
--- This event handles displaying text-based notifications using GTA's native functions. exports["esx_notify"]:Notify(type, 4000, message)
--- It supports specific scenarios by assigning different icons based on the script name. else
--- TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message)
---@param title string The title or identifier for the notification, used to select the appropriate icon. end
---@param text string The main message content of the notification. elseif Config.System.Notify == "lation" then
--- if not src then
---@usage exports.lation_ui:notify({
--- ```lua title = title,
--- -- Client-side event trigger message = message,
--- TriggerEvent(getScript()..":DisplayGTANotify", "taxiname", "Taxi service has arrived.") type = type or "success",
--- ``` })
RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text) else
local iconTable = {} TriggerClientEvent("lation_ui:notify", src, {
if getScript() == "jim-npcservice" then title = title,
iconTable = { message = message,
[Loc[Config.Lan].notify["taxiname"]] = "CHAR_TAXI", type = type or "success",
[Loc[Config.Lan].notify["limoname"]] = "CHAR_CASINO", })
[Loc[Config.Lan].notify["ambiname"]] = "CHAR_CALL911", end
[Loc[Config.Lan].notify["pilotname"]] = "CHAR_DEFAULT",
[Loc[Config.Lan].notify["planename"]] = "CHAR_BOATSITE2", elseif Config.System.Notify == "red" then
[Loc[Config.Lan].notify["heliname"]] = "CHAR_BOATSITE2", if isStarted("jim-redui") then
} if not src then
end TriggerEvent("jim-redui:Notify", title, message, type)
BeginTextCommandThefeedPost("STRING") else
AddTextComponentSubstringKeyboardDisplay(text) TriggerClientEvent("jim-redui:Notify", src, title, message, type)
EndTextCommandThefeedPostMessagetext(iconTable[title] or "CHAR_DEFAULT", iconTable[title] or "CHAR_DEFAULT", true, 1, title, nil, text) end
EndTextCommandThefeedPostTicker(true, false) end
end) end
end
-------------------------------------------------------------
-- ESX Notifications
-------------------------------------------------------------
--- Registers a server-side event to display ESX notifications to clients.
---
--- Listens for DisplayESXNotify events and triggers the ESX notification on the client.
---
--- @param type string The notification type.
--- @param title string The notification title.
--- @param text string The notification message.
---
--- @usage
--- ```lua
--- TriggerClientEvent(getScript()..":DisplayESXNotify", playerId, "success", "New achievement unlocked!")
--- ```
RegisterNetEvent(getScript()..":DisplayESXNotify", function(type, text)
exports["esx_notify"]:Notify(type, 4000, text)
end)
-------------------------------------------------------------
-- GTA-style Notifications
-------------------------------------------------------------
--- Displays GTA-style text notifications using native GTA functions.
---
--- Selects an appropriate icon based on the current script (if applicable) and renders the notification.
---
--- @param title string The notification title/identifier (used to select an icon).
--- @param text string The notification message.
---
--- @usage
--- ```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)

278
shared/phones.lua Normal file
View File

@@ -0,0 +1,278 @@
--[[
Phone Mails Module
------------------
This module handles sending phone mails using different phone systems.
Supported systems include:
- gksphone
- yflip-phone
- qs-smartphone
- qs-smartphone-pro
- roadphone
- lb-phone
- qb-phone
- jpr-phonesystem
]]
--- Sends a phone mail using the detected phone system.
--- The function iterates through a prioritized list of supported phone systems.
--- Once an active system is found (via `isStarted`), the corresponding mail function is executed.
---
--- @param data table A table containing the mail data.
--- - subject (string): The email subject.
--- - sender (string): The sender identifier.
--- - message (string): The email body content.
--- - actions (table|nil): Optional action buttons for the email.
--- @usage
--- sendPhoneMail({
--- subject = "Welcome!",
--- sender = "Admin",
--- message = "Thank you for joining our server.",
--- actions = {
--- { label = "Reply", action = replyFunction }
--- }
--- })
function sendPhoneMail(data)
-- Define each supported phone system and its corresponding mail-sending function.
local phoneSystems = {
{ name = "gksphone",
send = function(mailData)
exports["gksphone"]:SendNewMail(mailData)
end,
},
{ name = "yflip-phone",
send = function(mailData)
TriggerServerEvent(getScript()..":yflip:SendMail", mailData)
end,
},
{ name = "qs-smartphone",
send = function(mailData)
TriggerServerEvent('qs-smartphone:server:sendNewMail', mailData)
end,
},
{ name = "qs-smartphone-pro",
send = function(mailData)
TriggerServerEvent('phone:sendNewMail', mailData)
end,
},
{ name = "roadphone",
send = function(mailData)
-- Convert HTML line breaks to newlines for roadphone.
mailData.message = mailData.message:gsub("%<br>", "\n")
exports["roadphone"]:sendMail(mailData)
end,
},
{ name = "lb-phone",
send = function(mailData)
-- Convert HTML line breaks to newlines for lb-phone.
mailData.message = mailData.message:gsub("%<br>", "\n")
TriggerServerEvent(getScript()..":lbphone:SendMail", mailData)
end,
},
{ name = "qb-phone",
send = function(mailData)
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)
end,
},
{ name = "ef-phone",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
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")
phone.send(data)
return true
end
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
-------------------------------------------------------------
--- Handles sending mail for lb-phone.
--- Listens for the `lbphone:SendMail` event and sends an email using lb-phone's API.
---
--- @event lbphone:SendMail
--- @param data table The mail data.
--- - subject (string): The email subject.
--- - message (string): The email content.
--- - buttons (table|nil): Optional action buttons (mapped from data.actions if present).
RegisterNetEvent(getScript()..":lbphone:SendMail", function(data)
local src = source
local phoneNumber = exports["lb-phone"]:GetEquippedPhoneNumber(src)
local emailAddress = exports["lb-phone"]:GetEmailAddress(phoneNumber)
-- Map actions to buttons if provided.
data.buttons = data.actions or data.buttons
exports["lb-phone"]:SendMail({
to = emailAddress,
subject = data.subject,
message = data.message,
actions = data.buttons,
})
end)
--- Handles sending mail for yflip-phone.
--- Listens for the `yflip:SendMail` event and sends an email using yflip-phone's API.
---
--- @event yflip:SendMail
--- @param data table The mail data.
--- - subject (string): The email subject.
--- - sender (string): The sender identifier.
--- - message (string): The email content.
--- - buttons (table|nil): Optional action buttons.
RegisterNetEvent(getScript()..":yflip:SendMail", function(data)
local src = source
exports["yflip-phone"]:SendMail({
title = data.subject,
sender = data.sender,
senderDisplayName = data.sender,
content = data.message,
actions = data.buttons,
}, 'source', src)
end)
--- Handles sending mail for jpr-phonesystem.
--- Listens for the `jpr:SendMail` event and sends an email using jpr-phonesystem's API.
---
--- @event jpr:SendMail
--- @param data table The mail data.
--- - subject (string): The email subject.
--- - sender (string): The sender identifier.
--- - message (string): The email content.
--- - buttons (table|nil): Optional action buttons.
RegisterNetEvent(getScript()..":jpr:SendMail", function(data)
local src = source
local Player = Core.Functions.GetPlayer(src)
TriggerEvent('jpr-phonesystem:server:sendEmail', {
Assunto = data.subject, -- Email subject
Conteudo = data.message, -- Email content
Enviado = data.sender, -- Sender information
Destinatario = Player.PlayerData.citizenid, -- Recipient identifier
Event = {}, -- Optional event details
})
end)

File diff suppressed because it is too large Load Diff

View File

@@ -1,116 +1,140 @@
-- This automatically detects what polyzone script it should use to create a polyzone -- --[[
-- if ox_lib is detected, it will automatically use that instead of PolyZone -- PolyZone Management Module
-- createPoly({ name = 'name', debug = true, points = { vec2(), vec2() }, onEnter = function() end, onExit = function() end, }) ----------------------------
--- This module automatically detects the available polyzone library (ox_lib or PolyZone)
--- Creates a polygonal zone using the detected polyzone library (ox_lib or PolyZone). and creates polygonal and circular zones accordingly. It also provides a function to remove
--- previously created zones.
--- This function automatically detects whether `ox_lib` or `PolyZone` is active and creates a polygonal zone accordingly.
--- It supports setting up entry and exit callbacks for the zone. Functions:
--- • createPoly(data) - Creates a polygonal zone.
---@param data table A table containing the zone configuration. • createCirclePoly(data) - Creates a circular zone.
--- - **name** (`string`): The name of the zone. • removePolyZone(Location) - Removes a created zone.
--- - **debug** (`boolean`): Whether to enable debug mode for the zone. ]]
--- - **points** (`table`): A list of `vec2` points defining the polygon.
--- - **onEnter** (`function`): Callback function to execute when a player enters the zone. -------------------------------------------------------------
--- - **onExit** (`function`): Callback function to execute when a player exits the zone. -- Polygonal Zone Creation
--- -------------------------------------------------------------
---@return table|nil table Returns the created zone object or `nil` if creation failed.
--- --- Creates a polygonal zone using the detected polyzone library (ox_lib or PolyZone).
---@usage ---
--- ```lua --- Automatically checks which polyzone script is active. When using ox_lib, it converts the provided
--- createPoly({ --- 2D points to 3D (setting a constant z value) and sets a thickness value. For PolyZone, it creates the zone
--- name = 'testZone', --- and attaches onEnter and onExit callbacks.
--- debug = true, ---
--- points = { vec2(100.0, 100.0), vec2(200.0, 100.0), vec2(200.0, 200.0), vec2(100.0, 200.0) }, --- @param data table Zone configuration table with the following keys:
--- onEnter = function() print("Entered Test Zone") end, --- - name (string): The zone's identifier.
--- onExit = function() print("Exited Test Zone") end, --- - debug (boolean): Whether debug mode is enabled.
--- }) --- - points (table): A list of vec2 points defining the polygon.
--- ``` --- - onEnter (function): Callback when a player enters the zone.
function createPoly(data) --- - onExit (function): Callback when a player exits the zone.
local Location = nil ---
if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone --- @return table|nil table Returns the created zone object or nil if creation failed.
debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name) ---
for i = 1, #data.points do ---@usage
data.points[i] = vec3(data.points[i].x, data.points[i].y, 12.0) ---```lua
end ---createPoly({
data.thickness = 1000 --- name = 'testZone',
Location = lib.zones.poly(data) --- debug = true,
elseif isStarted("PolyZone") then --- points = { vec2(100.0, 100.0), vec2(200.0, 100.0), vec2(200.0, 200.0), vec2(100.0, 200.0) },
debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4PolyZone^7': "..data.name) --- onEnter = function() print("Entered Test Zone") end,
Location = PolyZone:Create(data.points, { name = data.name, debugPoly = data.debug }) --- onExit = function() print("Exited Test Zone") end,
Location:onPlayerInOut(function(isPointInside) ---})
if isPointInside then data.onEnter() else data.onExit() end ---```
end) function createPoly(data)
else local Location = nil
print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7") if isStarted(OXLibExport) then
end debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name)
return Location -- Convert 2D points to 3D with a fixed Z coordinate (e.g., 12.0)
end for i = 1, #data.points do
data.points[i] = vec3(data.points[i].x, data.points[i].y, 12.0)
--- Creates a circular zone using the detected polyzone library (ox_lib or PolyZone). end
--- data.thickness = 1000 -- Set a default thickness value
--- This function automatically detects whether `ox_lib` or `PolyZone` is active and creates a circular zone accordingly. Location = lib.zones.poly(data)
--- It supports setting up entry and exit callbacks for the zone. elseif isStarted("PolyZone") then
--- debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4PolyZone^7': "..data.name)
---@param data table A table containing the circular zone configuration. Location = PolyZone:Create(data.points, { name = data.name, debugPoly = data.debug })
--- - **name** (`string`): The name of the circular zone. Location:onPlayerInOut(function(isPointInside)
--- - **coords** (`vector3`): The center coordinates of the circle. if isPointInside then data.onEnter() else data.onExit() end
--- - **radius** (`number`): The radius of the circle.
--- - **onEnter** (`function`): Callback function to execute when a player enters the zone. end)
--- - **onExit** (`function`): Callback function to execute when a player exits the zone. else
--- print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7")
---@return table|nil table Returns the created circular zone object or `nil` if creation failed. end
--- return Location
---@usage end
--- ```lua
--- createCirclePoly({ -------------------------------------------------------------
--- name = 'circleZone', -- Circular Zone Creation
--- coords = vector3(150.0, 150.0, 20.0), -------------------------------------------------------------
--- radius = 50.0,
--- onEnter = function() print("Entered Circle Zone") end, --- Creates a circular zone using the detected polyzone library (ox_lib or PolyZone).
--- onExit = function() print("Exited Circle Zone") end, ---
--- }) --- When using ox_lib, it creates a sphere zone. For PolyZone, it creates a CircleZone and attaches
--- ``` --- onEnter and onExit callbacks.
function createCirclePoly(data) ---
local Location = nil --- @param data table Zone configuration with the following keys:
if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone --- - name (string): The zone's identifier.
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name) --- - coords (vector3): The center of the circle.
Location = lib.zones.sphere(data) --- - radius (number): The radius of the circle.
elseif isStarted("PolyZone") then --- - onEnter (function): Callback when a player enters the zone.
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7PolyZone ".. data.name) --- - onExit (function): Callback when a player exits the zone.
Location = CircleZone:Create(data.coords, data.radius, { name = data.name, debugPoly = debugMode }) ---
Location:onPlayerInOut(function(isPointInside) --- @return table|nil table Returns the created circular zone object or nil if creation failed.
if isPointInside then ---
data.onEnter() --- @usage
else --- ```lua
data.onExit() --- createCirclePoly({
end --- name = 'circleZone',
end) --- coords = vector3(150.0, 150.0, 20.0),
else --- radius = 50.0,
print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7") --- onEnter = function() print("Entered Circle Zone") end,
end --- onExit = function() print("Exited Circle Zone") end,
debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius) --- })
return Location --- ```
end function createCirclePoly(data)
local Location = nil
--- Removes a previously created polyzone. if isStarted(OXLibExport) then
--- debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name)
--- This function detects the active polyzone library (`ox_lib` or `PolyZone`) and removes the specified zone accordingly. Location = lib.zones.sphere(data)
--- elseif isStarted("PolyZone") then
--- @param Location table The zone object to be removed. debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7PolyZone ".. data.name)
--- Location = CircleZone:Create(data.coords, data.radius, { name = data.name, debugPoly = debugMode })
--- @usage Location:onPlayerInOut(function(isPointInside)
--- ```lua if isPointInside then
--- local zone = createPoly({...}) data.onEnter()
--- -- Later in the code else
--- removePolyZone(zone) data.onExit()
--- ``` end
function removePolyZone(Location) end)
if isStarted(OXLibExport) then -- if it finds ox_lib, use it instead of PolyZone else
debugPrint("^6Bridge^7: ^2Removing ^2poly with ^7"..OXLibExport) print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3starter^1.^2lua^7")
Location:remove() end
elseif isStarted("PolyZone") then debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
debugPrint("^6Bridge^7: ^2poly with ^7PolyZone") return Location
Location:destroy() end
end
-------------------------------------------------------------
-- PolyZone Removal Function
-------------------------------------------------------------
--- Removes a previously created polyzone.
---
--- Detects the active polyzone library and calls the appropriate removal method.
---
--- @param Location table The zone object to be removed.
---
--- @usage
--- ```lua
--- local zone = createPoly({...})
---
--- removePolyZone(zone)
--- ```
function removePolyZone(Location)
if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Removing ^2poly with ^7"..OXLibExport)
Location:remove()
elseif isStarted("PolyZone") then
debugPrint("^6Bridge^7: ^2poly with ^7PolyZone")
Location:destroy()
end
end end

View File

@@ -1,77 +1,77 @@
local cacheOrigScale = {} local cacheOrigScale = {}
local initialOffset = {} local initialOffset = {}
--- Sets the scale of an entity. --- Sets the scale of an entity.
--- ---
--- This function scales an entity by adjusting its forward, right, and up vectors. --- This function scales an entity by adjusting its forward, right, and up vectors.
--- It also applies an initial offset to maintain the entity's position relative to the ground. --- It also applies an initial offset to maintain the entity's position relative to the ground.
--- ---
---@param entity number The entity ID to scale. ---@param entity number The entity ID to scale.
---@param scale number The scale factor to apply to the entity. ---@param scale number The scale factor to apply to the entity.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- -- Scale an entity to twice its original size --- -- Scale an entity to twice its original size
--- SetEntityScale(entityId, 2.0) --- SetEntityScale(entityId, 2.0)
--- ``` --- ```
function SetEntityScale(entity, scale) function SetEntityScale(entity, scale)
local forward, right, up = GetEntityMatrix(entity) local forward, right, up = GetEntityMatrix(entity)
if not cacheOrigScale[entity] then if not cacheOrigScale[entity] then
cacheOrigScale[entity] = { cacheOrigScale[entity] = {
forward = forward, forward = forward,
right = right, right = right,
up = up up = up
} }
end end
local minDim, maxDim = GetModelDimensions(GetEntityModel(entity)) local minDim, maxDim = GetModelDimensions(GetEntityModel(entity))
local originalHeight = maxDim.z - minDim.z local originalHeight = maxDim.z - minDim.z
local newHeight = originalHeight * scale local newHeight = originalHeight * scale
initialOffset[entity] = (newHeight - originalHeight) / 3 initialOffset[entity] = (newHeight - originalHeight) / 3
local forwardTemp = cacheOrigScale[entity].forward * scale local forwardTemp = cacheOrigScale[entity].forward * scale
local rightTemp = cacheOrigScale[entity].right * scale local rightTemp = cacheOrigScale[entity].right * scale
local upTemp = cacheOrigScale[entity].up * scale local upTemp = cacheOrigScale[entity].up * scale
-- Apply the initial offset to the current position -- Apply the initial offset to the current position
local currentPosition = GetEntityCoords(entity) local currentPosition = GetEntityCoords(entity)
local newPosition = vector3(currentPosition.x, currentPosition.y, currentPosition.z + initialOffset[entity]) local newPosition = vector3(currentPosition.x, currentPosition.y, currentPosition.z + initialOffset[entity])
SetEntityMatrix(entity, forwardTemp, rightTemp, upTemp, currentPosition) SetEntityMatrix(entity, forwardTemp, rightTemp, upTemp, currentPosition)
end end
--- Resets the scale of an entity to its original values. --- Resets the scale of an entity to its original values.
--- ---
--- This function restores an entity's original forward, right, and up vectors, --- This function restores an entity's original forward, right, and up vectors,
--- effectively undoing any scaling applied by `SetEntityScale`. --- effectively undoing any scaling applied by `SetEntityScale`.
--- ---
---@param entity number The entity ID to reset. ---@param entity number The entity ID to reset.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
--- -- Reset the scale of an entity --- -- Reset the scale of an entity
--- resetScale(entityId) --- resetScale(entityId)
--- ``` --- ```
function resetScale(entity) function resetScale(entity)
if cacheOrigScale[entity] then if cacheOrigScale[entity] then
SetEntityMatrix(entity, cacheOrigScale[entity].forward, cacheOrigScale[entity].right, cacheOrigScale[entity].up, GetEntityCoords(entity)) SetEntityMatrix(entity, cacheOrigScale[entity].forward, cacheOrigScale[entity].right, cacheOrigScale[entity].up, GetEntityCoords(entity))
cacheOrigScale[entity] = nil cacheOrigScale[entity] = nil
end end
end end
--[[ --[[
CreateThread(function() CreateThread(function()
-- Example usage: -- Example usage:
-- local prop = makeProp({prop = "v_res_r_figcat", coords = vec4(-1025.88, -1417.58, 5.43, 76.30)}, false, false) -- local prop = makeProp({prop = "v_res_r_figcat", coords = vec4(-1025.88, -1417.58, 5.43, 76.30)}, false, false)
-- local ped = makePed(`a_c_cat_01`, vec4(-1022.42, -1429.97, 13.79, 68.36), true, false, nil) -- local ped = makePed(`a_c_cat_01`, vec4(-1022.42, -1429.97, 13.79, 68.36), true, false, nil)
-- SetEntityCollision(prop, false, true) -- SetEntityCollision(prop, false, true)
SetEntityScale(prop, 12) SetEntityScale(prop, 12)
--[[CreateThread(function() --[[CreateThread(function()
while true do while true do
Wait(1000) Wait(1000)
resetScale(prop) resetScale(prop)
Wait(1000) Wait(1000)
end end
end) end)
end) end)
]] ]]

View File

@@ -1,61 +0,0 @@
function makeInstructionalButtons(info)
local build = RequestScaleformMovie("instructional_buttons")
while not HasScaleformMovieLoaded(build) do Wait(0) end
DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
BeginScaleformMovieMethod(build, "CLEAR_ALL")
EndScaleformMovieMethod()
BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
ScaleformMovieMethodAddParamInt(200)
EndScaleformMovieMethod()
for i = 1, #info do
BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
ScaleformMovieMethodAddParamInt(i - 1)
for k = 1, #info[i].keys do
ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
end
BeginTextCommandScaleformString("STRING")
AddTextComponentSubstringKeyboardDisplay(info[i].text)
EndTextCommandScaleformString()
EndScaleformMovieMethod()
end
BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS")
EndScaleformMovieMethod()
BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR")
ScaleformMovieMethodAddParamInt(0)
ScaleformMovieMethodAddParamInt(0)
ScaleformMovieMethodAddParamInt(0)
ScaleformMovieMethodAddParamInt(80)
EndScaleformMovieMethod()
DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0)
end
-- Testing showing variables on the screen instead of only in f8
function debugScaleForm(textTable, loc)
if debugMode then
-- Define the display position (top left corner)
local loc = loc or vec2(0.05, 0.65)
-- Calculate dynamic height based on the number of lines in the textTable
local lineHeight = 0.025 -- Height of each line of text
local totalHeight = #textTable * lineHeight -- Dynamic height based on number of lines
local boxPadding = 0.01 -- Padding to add around the text inside the box
local size = vec2(0.18, totalHeight + boxPadding * 2) -- Width remains fixed, height is dynamic
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255)
for i = 1, #textTable do
local textLine = textTable[i]
SetTextScale(0.30, 0.30)
BeginTextCommandDisplayText("STRING")
AddTextComponentSubstringKeyboardDisplay(textLine)
EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
end
end
end

View File

@@ -1,277 +1,306 @@
BigMessage = {} --[[
BigMessage.__index = BigMessage BigMessage Module
-----------------
function BigMessage:new() This module provides a flexible way to display large, attention-grabbing messages
local self = setmetatable({}, BigMessage) on screen using a Scaleform movie ("MP_BIG_MESSAGE_FREEMODE"). It supports multiple
self.scaleform = nil message types (mission passed, colored shard, old-style, simple shard, rank-up, weapon purchased,
self.startTime = 0 and large multiplayer messages), including customizable transitions and durations.
self.duration = 0 ]]
self.transition = "TRANSITION_OUT"
self.transitionDuration = 0.15 BigMessage = {}
self.transitionPreventAutoExpansion = false BigMessage.__index = BigMessage
self.transitionExecuted = false
self.manualDispose = false --- Creates a new BigMessage instance.
self.isDisplaying = false --- @return table table A new BigMessage object.
return self function BigMessage:new()
end local self = setmetatable({}, BigMessage)
self.scaleform = nil
function BigMessage:Load() self.startTime = 0
if self.scaleform then return end self.duration = 0
self.scaleform = RequestScaleformMovie("MP_BIG_MESSAGE_FREEMODE") self.transition = "TRANSITION_OUT"
while not HasScaleformMovieLoaded(self.scaleform) do self.transitionDuration = 0.15
Wait(0) self.transitionPreventAutoExpansion = false
end self.transitionExecuted = false
end self.manualDispose = false
self.isDisplaying = false
-- Dispose of the scaleform return self
function BigMessage:Dispose() end
if not self.scaleform then return end
--- Loads the Scaleform movie if it has not been loaded yet.
if self.manualDispose then function BigMessage:Load()
BeginScaleformMovieMethod(self.scaleform, self.transition) if self.scaleform then return end
ScaleformMovieMethodAddParamBool(false) self.scaleform = RequestScaleformMovie("MP_BIG_MESSAGE_FREEMODE")
ScaleformMovieMethodAddParamFloat(self.transitionDuration) while not HasScaleformMovieLoaded(self.scaleform) do
ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion) Wait(0)
EndScaleformMovieMethod() end
end
Wait((self.transitionDuration * 0.5) * 1000)
--- Disposes of the Scaleform movie.
self.manualDispose = false --- If manualDispose is true, executes a transition before disposing.
end function BigMessage:Dispose()
if not self.scaleform then return end
self.startTime = 0
self.transitionExecuted = false if self.manualDispose then
SetScaleformMovieAsNoLongerNeeded(self.scaleform) BeginScaleformMovieMethod(self.scaleform, self.transition)
self.scaleform = nil ScaleformMovieMethodAddParamBool(false)
self.isDisplaying = false ScaleformMovieMethodAddParamFloat(self.transitionDuration)
end ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
EndScaleformMovieMethod()
function BigMessage:Update()
if not self.scaleform then return end -- Wait a fraction of the transition duration (in milliseconds)
DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0) Wait((self.transitionDuration * 0.5) * 1000)
self.manualDispose = false
if self.manualDispose then return end end
if self.startTime ~= 0 and (GetGameTimer() - self.startTime) > self.duration then self.startTime = 0
if not self.transitionExecuted then self.transitionExecuted = false
BeginScaleformMovieMethod(self.scaleform, self.transition) SetScaleformMovieAsNoLongerNeeded(self.scaleform)
ScaleformMovieMethodAddParamBool(false) self.scaleform = nil
ScaleformMovieMethodAddParamFloat(self.transitionDuration) self.isDisplaying = false
ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion) end
EndScaleformMovieMethod()
self.transitionExecuted = true --- Updates the display by drawing the Scaleform movie fullscreen.
self.duration = self.duration + ((self.transitionDuration * 0.5) * 1000) function BigMessage:Update()
else if not self.scaleform then return end
self:Dispose()
end DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
end
end if self.manualDispose then return end
function BigMessage:SetTransition(transition, duration, preventAutoExpansion) if self.startTime ~= 0 and (GetGameTimer() - self.startTime) > self.duration then
self.transition = transition or "TRANSITION_OUT" if not self.transitionExecuted then
self.transitionDuration = duration or 0.4 BeginScaleformMovieMethod(self.scaleform, self.transition)
self.transitionPreventAutoExpansion = preventAutoExpansion or true ScaleformMovieMethodAddParamBool(false)
end ScaleformMovieMethodAddParamFloat(self.transitionDuration)
ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
function BigMessage:StartUpdate() EndScaleformMovieMethod()
if self.isDisplaying then return end self.transitionExecuted = true
self.isDisplaying = true -- Extend duration slightly for smooth transition
CreateThread(function() self.duration = self.duration + ((self.transitionDuration * 0.5) * 1000)
while self.isDisplaying do else
Wait(0) self:Dispose()
self:Update() end
end end
end) end
end
--- Sets the transition properties for disposing the message.
--- Displays a mission passed message. --- @param transition string The transition function name (default: "TRANSITION_OUT").
--- --- @param duration number The duration for the transition (default: 0.4).
--- @param msg string The main message to display. --- @param preventAutoExpansion boolean Whether to prevent auto-expansion (default: true).
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000. function BigMessage:SetTransition(transition, duration, preventAutoExpansion)
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. self.transition = transition or "TRANSITION_OUT"
function BigMessage:ShowMissionPassedMessage(msg, duration, manualDispose) self.transitionDuration = duration or 0.4
duration = duration or 5000 self.transitionPreventAutoExpansion = preventAutoExpansion or true
self:Load() end
self.startTime = GetGameTimer()
self.manualDispose = manualDispose or false --- Starts a thread to continuously update the HUD until the message is done.
function BigMessage:StartUpdate()
BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE") if self.isDisplaying then return end
ScaleformMovieMethodAddParamPlayerNameString(msg)
ScaleformMovieMethodAddParamPlayerNameString("") self.isDisplaying = true
ScaleformMovieMethodAddParamInt(100) CreateThread(function()
ScaleformMovieMethodAddParamBool(true) while self.isDisplaying do
ScaleformMovieMethodAddParamInt(0) Wait(0)
ScaleformMovieMethodAddParamBool(true) self:Update()
EndScaleformMovieMethod() end
end)
self.duration = duration end
self:StartUpdate()
end --- Displays a mission passed message.
--- @param msg string The message to display.
--- Displays a colored shard message. --- @param duration number|nil The duration (in milliseconds) to display the message (default: 5000).
--- --- @param manualDispose boolean|nil Whether to manually dispose the Scaleform after display (default: false).
--- @param msg string The main message to display. --- @usage
--- @param desc string The description text. --- ```lua
--- @param textColor number The color index for the text. --- BigMessage:ShowMissionPassedMessage("MISSION PASSED", 5000)
--- @param bgColor number The color index for the background. --- ```
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000. function BigMessage:ShowMissionPassedMessage(msg, duration, manualDispose)
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. duration = duration or 5000
function BigMessage:ShowColoredShard(msg, desc, textColor, bgColor, duration, manualDispose) self:Load()
duration = duration or 5000 self.startTime = GetGameTimer()
self:Load() self.manualDispose = manualDispose or false
self.startTime = GetGameTimer()
self.manualDispose = manualDispose or false BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
ScaleformMovieMethodAddParamPlayerNameString(msg)
BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CENTERED_MP_MESSAGE") ScaleformMovieMethodAddParamPlayerNameString("")
ScaleformMovieMethodAddParamPlayerNameString(msg) ScaleformMovieMethodAddParamInt(100)
ScaleformMovieMethodAddParamPlayerNameString(desc) ScaleformMovieMethodAddParamBool(true)
ScaleformMovieMethodAddParamInt(bgColor) ScaleformMovieMethodAddParamInt(0)
ScaleformMovieMethodAddParamInt(textColor) ScaleformMovieMethodAddParamBool(true)
EndScaleformMovieMethod() EndScaleformMovieMethod()
self.duration = duration self.duration = duration
self:StartUpdate() self:StartUpdate()
end end
--- Displays an old-style mission passed message. --- Displays a colored shard message.
--- --- @param msg string The main message.
--- @param msg string The main message to display. --- @param desc string The description text.
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000. --- @param textColor number The text color index.
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. --- @param bgColor number The background color index.
--- --- @param duration number|nil Duration in milliseconds (default: 5000).
--- @return void --- @param manualDispose boolean|nil Whether to manually dispose the Scaleform (default: false).
function BigMessage:ShowOldMessage(msg, duration, manualDispose) function BigMessage:ShowColoredShard(msg, desc, textColor, bgColor, duration, manualDispose)
duration = duration or 5000 duration = duration or 5000
self:Load() self:Load()
self.startTime = GetGameTimer() self.startTime = GetGameTimer()
self.manualDispose = manualDispose or false self.manualDispose = manualDispose or false
BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE") BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CENTERED_MP_MESSAGE")
ScaleformMovieMethodAddParamPlayerNameString(msg) ScaleformMovieMethodAddParamPlayerNameString(msg)
EndScaleformMovieMethod() ScaleformMovieMethodAddParamPlayerNameString(desc)
ScaleformMovieMethodAddParamInt(textColor)
self.duration = duration ScaleformMovieMethodAddParamInt(bgColor)
self:StartUpdate() EndScaleformMovieMethod()
end
self.duration = duration
--- Displays a simple shard message. self:StartUpdate()
--- end
--- @param msg string The main message to display.
--- @param subtitle string The subtitle text. --- Displays an old-style mission passed message.
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000. --- @param msg string The message.
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. --- @param duration number|nil Duration in milliseconds (default: 5000).
--- --- @param manualDispose boolean|nil Whether to manually dispose (default: false).
--- @return void function BigMessage:ShowOldMessage(msg, duration, manualDispose)
function BigMessage:ShowSimpleShard(msg, subtitle, duration, manualDispose) duration = duration or 5000
duration = duration or 5000 self:Load()
self:Load() self.startTime = GetGameTimer()
self.startTime = GetGameTimer() self.manualDispose = manualDispose or false
self.manualDispose = manualDispose or false
BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CREW_RANKUP_MP_MESSAGE") ScaleformMovieMethodAddParamPlayerNameString(msg)
ScaleformMovieMethodAddParamPlayerNameString(msg) EndScaleformMovieMethod()
ScaleformMovieMethodAddParamPlayerNameString(subtitle)
EndScaleformMovieMethod() self.duration = duration
self:StartUpdate()
self.duration = duration end
self:StartUpdate()
end --- Displays a simple shard message.
--- @param msg string The main message.
--- Displays a rank-up message. --- @param subtitle string The subtitle text.
--- --- @param duration number|nil Duration in milliseconds (default: 5000).
--- @param msg string The main message to display. --- @param manualDispose boolean|nil Whether to manually dispose (default: false).
--- @param subtitle string The subtitle text. function BigMessage:ShowSimpleShard(msg, subtitle, duration, manualDispose)
--- @param rank number The rank level achieved. duration = duration or 5000
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000. self:Load()
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. self.startTime = GetGameTimer()
function BigMessage:ShowRankupMessage(msg, subtitle, rank, duration, manualDispose) self.manualDispose = manualDispose or false
duration = duration or 5000
self:Load() BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CREW_RANKUP_MP_MESSAGE")
self.startTime = GetGameTimer() ScaleformMovieMethodAddParamPlayerNameString(msg)
self.manualDispose = manualDispose or false ScaleformMovieMethodAddParamPlayerNameString(subtitle)
EndScaleformMovieMethod()
BeginScaleformMovieMethod(self.scaleform, "SHOW_BIG_MP_MESSAGE")
ScaleformMovieMethodAddParamPlayerNameString(msg) self.duration = duration
ScaleformMovieMethodAddParamPlayerNameString(subtitle) self:StartUpdate()
ScaleformMovieMethodAddParamInt(rank) end
ScaleformMovieMethodAddParamPlayerNameString("")
ScaleformMovieMethodAddParamPlayerNameString("") --- Displays a rank-up message.
EndScaleformMovieMethod() --- @param msg string The main message.
--- @param subtitle string The subtitle text.
self.duration = duration --- @param rank number The rank level achieved.
self:StartUpdate() --- @param duration number|nil Duration in milliseconds (default: 5000).
end --- @param manualDispose boolean|nil Whether to manually dispose (default: false).
function BigMessage:ShowRankupMessage(msg, subtitle, rank, duration, manualDispose)
--- Displays a weapon purchased message. duration = duration or 5000
--- self:Load()
--- @param bigMessage string The main message to display. self.startTime = GetGameTimer()
--- @param weaponName string The name of the weapon purchased. self.manualDispose = manualDispose or false
--- @param weaponHash number The hash identifier of the weapon.
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000. BeginScaleformMovieMethod(self.scaleform, "SHOW_BIG_MP_MESSAGE")
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. ScaleformMovieMethodAddParamPlayerNameString(msg)
function BigMessage:ShowWeaponPurchasedMessage(bigMessage, weaponName, weaponHash, duration, manualDispose) ScaleformMovieMethodAddParamPlayerNameString(subtitle)
duration = duration or 5000 ScaleformMovieMethodAddParamInt(rank)
self:Load() ScaleformMovieMethodAddParamPlayerNameString("")
self.startTime = GetGameTimer() ScaleformMovieMethodAddParamPlayerNameString("")
self.manualDispose = manualDispose or false EndScaleformMovieMethod()
BeginScaleformMovieMethod(self.scaleform, "SHOW_WEAPON_PURCHASED") self.duration = duration
ScaleformMovieMethodAddParamPlayerNameString(bigMessage) self:StartUpdate()
ScaleformMovieMethodAddParamPlayerNameString(weaponName) end
ScaleformMovieMethodAddParamInt(weaponHash)
ScaleformMovieMethodAddParamPlayerNameString("") --- Displays a weapon purchased message.
ScaleformMovieMethodAddParamInt(100) --- @param bigMessage string The main message.
EndScaleformMovieMethod() --- @param weaponName string The name of the weapon purchased.
--- @param weaponHash number The weapon hash.
self.duration = duration --- @param duration number|nil Duration in milliseconds (default: 5000).
self:StartUpdate() --- @param manualDispose boolean|nil Whether to manually dispose (default: false).
end function BigMessage:ShowWeaponPurchasedMessage(bigMessage, weaponName, weaponHash, duration, manualDispose)
duration = duration or 5000
--- Displays a large multiplayer message. self:Load()
--- self.startTime = GetGameTimer()
--- @param msg string The main message to display. self.manualDispose = manualDispose or false
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000.
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. BeginScaleformMovieMethod(self.scaleform, "SHOW_WEAPON_PURCHASED")
function BigMessage:ShowMpMessageLarge(msg, duration, manualDispose) ScaleformMovieMethodAddParamPlayerNameString(bigMessage)
duration = duration or 5000 ScaleformMovieMethodAddParamPlayerNameString(weaponName)
self:Load() ScaleformMovieMethodAddParamInt(weaponHash)
self.startTime = GetGameTimer() ScaleformMovieMethodAddParamPlayerNameString("")
self.manualDispose = manualDispose or false ScaleformMovieMethodAddParamInt(100)
EndScaleformMovieMethod()
BeginScaleformMovieMethod(self.scaleform, "SHOW_CENTERED_MP_MESSAGE_LARGE")
ScaleformMovieMethodAddParamPlayerNameString(msg) self.duration = duration
ScaleformMovieMethodAddParamPlayerNameString("") self:StartUpdate()
ScaleformMovieMethodAddParamInt(100) end
ScaleformMovieMethodAddParamBool(true)
ScaleformMovieMethodAddParamInt(100) --- Displays a large multiplayer message.
EndScaleformMovieMethod() --- @param msg string The main message.
--- @param duration number|nil Duration in milliseconds (default: 5000).
BeginScaleformMovieMethod(self.scaleform, "TRANSITION_IN") --- @param manualDispose boolean|nil Whether to manually dispose (default: false).
EndScaleformMovieMethod() function BigMessage:ShowMpMessageLarge(msg, duration, manualDispose)
duration = duration or 5000
self.duration = duration self:Load()
self:StartUpdate() self.startTime = GetGameTimer()
end self.manualDispose = manualDispose or false
--- Displays a "Wasted" multiplayer message. BeginScaleformMovieMethod(self.scaleform, "SHOW_CENTERED_MP_MESSAGE_LARGE")
--- ScaleformMovieMethodAddParamPlayerNameString(msg)
--- @param msg string The main message to display. ScaleformMovieMethodAddParamPlayerNameString("")
--- @param subtitle string The subtitle text. ScaleformMovieMethodAddParamInt(100)
--- @param duration number|nil The duration in milliseconds the message should be displayed. Defaults to 5000. ScaleformMovieMethodAddParamBool(true)
--- @param manualDispose boolean|nil Whether to manually dispose of the scaleform after the message. Defaults to false. ScaleformMovieMethodAddParamInt(100)
function BigMessage:ShowMpWastedMessage(msg, subtitle, duration, manualDispose) EndScaleformMovieMethod()
duration = duration or 5000
self:Load() BeginScaleformMovieMethod(self.scaleform, "TRANSITION_IN")
self.startTime = GetGameTimer() EndScaleformMovieMethod()
self.manualDispose = manualDispose or false
self.duration = duration
BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_WASTED_MP_MESSAGE") self:StartUpdate()
ScaleformMovieMethodAddParamPlayerNameString(msg) end
ScaleformMovieMethodAddParamPlayerNameString(subtitle)
EndScaleformMovieMethod() --- Displays a "Wasted" multiplayer message.
--- @param msg string The main message.
self.duration = duration --- @param subtitle string The subtitle text.
self:StartUpdate() --- @param duration number|nil Duration in milliseconds (default: 5000).
end --- @param manualDispose boolean|nil Whether to manually dispose (default: false).
function BigMessage:ShowMpWastedMessage(msg, subtitle, duration, manualDispose)
duration = duration or 5000
self:Load()
self.startTime = GetGameTimer()
self.manualDispose = manualDispose or false
BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_WASTED_MP_MESSAGE")
ScaleformMovieMethodAddParamPlayerNameString(msg)
ScaleformMovieMethodAddParamPlayerNameString(subtitle)
EndScaleformMovieMethod()
self.duration = duration
self:StartUpdate()
end
--- Starts the update loop for displaying the message.
function BigMessage:StartUpdate()
if self.isDisplaying then return end
self.isDisplaying = true
CreateThread(function()
while self.isDisplaying do
Wait(0)
self:Update()
end
end)
end
-- Create an instance of BigMessage and return it.
BigMessage = BigMessage:new()
return BigMessage return BigMessage

View File

@@ -1,116 +1,135 @@
CountdownHandler = {} --[[
CountdownHandler.__index = CountdownHandler CountdownHandler Module
-------------------------
function CountdownHandler:new() This module provides a countdown HUD using a Scaleform movie ("COUNTDOWN").
local self = setmetatable({}, CountdownHandler) It handles loading, updating, and disposing of the scaleform, as well as
self.scaleform = nil playing sounds and displaying messages for each countdown tick.
self.renderCountdown = false
self.colour = { r = 255, g = 255, b = 255, a = 255 } TriggerNetEvent(getScript()..":startCountdown", 5, 25)
return self ]]
end
CountdownHandler = {}
function CountdownHandler:Load() CountdownHandler.__index = CountdownHandler
if self.scaleform then return end
self.scaleform = RequestScaleformMovie("COUNTDOWN") --- Creates a new CountdownHandler instance.
while not HasScaleformMovieLoaded(self.scaleform) do --- @return table table A new CountdownHandler object.
Wait(0) function CountdownHandler:new()
end local self = setmetatable({}, CountdownHandler)
end self.scaleform = nil
self.renderCountdown = false
function CountdownHandler:Dispose() self.colour = { r = 255, g = 255, b = 255, a = 255 }
if self.scaleform then return self
SetScaleformMovieAsNoLongerNeeded(self.scaleform) end
self.scaleform = nil
end --- Loads the "COUNTDOWN" scaleform movie.
end function CountdownHandler:Load()
if self.scaleform then
function CountdownHandler:Update() return
if self.scaleform then end
DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0) self.scaleform = RequestScaleformMovie("COUNTDOWN")
end while not HasScaleformMovieLoaded(self.scaleform) do
end Wait(0)
end
function CountdownHandler:ShowMessage(message) end
local r, g, b, a = self.colour.r, self.colour.g, self.colour.b, self.colour.a
--- Disposes of the currently loaded scaleform movie.
BeginScaleformMovieMethod(self.scaleform, "SET_MESSAGE") function CountdownHandler:Dispose()
ScaleformMovieMethodAddParamPlayerNameString(message) if self.scaleform then
ScaleformMovieMethodAddParamInt(r) SetScaleformMovieAsNoLongerNeeded(self.scaleform)
ScaleformMovieMethodAddParamInt(g) self.scaleform = nil
ScaleformMovieMethodAddParamInt(b) end
ScaleformMovieMethodAddParamBool(true) end
EndScaleformMovieMethod()
--- Updates the HUD by drawing the scaleform movie fullscreen.
BeginScaleformMovieMethod(self.scaleform, "FADE_MP") function CountdownHandler:Update()
ScaleformMovieMethodAddParamPlayerNameString(message) if self.scaleform then
ScaleformMovieMethodAddParamInt(r) DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
ScaleformMovieMethodAddParamInt(g) end
ScaleformMovieMethodAddParamInt(b) end
EndScaleformMovieMethod()
end --- Displays a message on the countdown HUD.
--- @param message string The message to display.
--- Starts the countdown with the specified number and HUD color. function CountdownHandler:ShowMessage(message)
--- local r, g, b, a = self.colour.r, self.colour.g, self.colour.b, self.colour.a
--- @param number number|nil The starting number for the countdown. Defaults to 3.
--- @param hudColour number|nil The HUD color index. Defaults to 18. -- Set the message in the scaleform.
--- BeginScaleformMovieMethod(self.scaleform, "SET_MESSAGE")
--- @return boolean `true` when the countdown has finished. ScaleformMovieMethodAddParamPlayerNameString(message)
--- ScaleformMovieMethodAddParamInt(r)
--- @usage ScaleformMovieMethodAddParamInt(g)
--- ```lua ScaleformMovieMethodAddParamInt(b)
--- -- Start a countdown of 5 seconds with HUD color 25 ScaleformMovieMethodAddParamBool(true)
--- if CountdownHandler:Start(5, 25) then EndScaleformMovieMethod()
--- print("Countdown Complete")
--- end -- Trigger a fade effect (optional).
--- ``` BeginScaleformMovieMethod(self.scaleform, "FADE_MP")
function CountdownHandler:Start(number, hudColour) ScaleformMovieMethodAddParamPlayerNameString(message)
local finished = false ScaleformMovieMethodAddParamInt(r)
number = number or 3 ScaleformMovieMethodAddParamInt(g)
hudColour = hudColour or 18 ScaleformMovieMethodAddParamInt(b)
EndScaleformMovieMethod()
local r, g, b, a = GetHudColour(hudColour) end
self.colour = { r = r, g = g, b = b, a = a }
--- Starts the countdown HUD.
self:Load() --- @param number number|nil The starting number for the countdown (default: 3).
--- @param hudColour number|nil The HUD colour index (default: 18).
self.renderCountdown = true --- @return boolean boolean True when the countdown has finished.
CreateThread(function() --- @usage
while self.renderCountdown do --- ```lua
Wait(0) --- if CountdownHandler:Start(5, 25) then
self:Update() --- -- When run in an if statement, the script will wait until its finished to continue
end --- print("Countdown Complete")
end) --- end
--- ```
-- Begin the countdown function CountdownHandler:Start(number, hudColour)
CreateThread(function() local finished = false
local currentNumber = number number = number or 3
while currentNumber > 0 do hudColour = hudColour or 18
-- Play countdown sound
playSound("Count") -- Get HUD colour using framework function; alternatives could be added here.
self:ShowMessage(tostring(currentNumber)) local r, g, b, a = GetHudColour(hudColour)
Wait(1000) self.colour = { r = r, g = g, b = b, a = a }
currentNumber = currentNumber - 1
end self:Load()
playSound("Go")
self.renderCountdown = true
self:ShowMessage("GO") CreateThread(function()
finished = true while self.renderCountdown do
Wait(0)
Wait(1000) self:Update()
self.renderCountdown = false end
self:Dispose() end)
finished = true
end) -- Countdown logic
while not finished do Wait(10) end CreateThread(function()
return true local currentNumber = number
end while currentNumber > 0 do
playSound("Count")
-- Create an instance of CountdownHandler self:ShowMessage(tostring(currentNumber))
CountdownHandler = CountdownHandler:new() Wait(1000)
currentNumber = currentNumber - 1
-- Optional: Register an event to start the countdown end
RegisterNetEvent(getScript()..":startCountdown", function(number, hudColour)
CountdownHandler:Start(number, hudColour) playSound("Go")
end) self:ShowMessage("GO")
finished = true
Wait(1000)
self.renderCountdown = false
self:Dispose()
finished = true
end)
while not finished do Wait(10) end
return true
end
-- Create a singleton instance of CountdownHandler.
CountdownHandler = CountdownHandler:new()
-- Register an event to start the countdown.
RegisterNetEvent(getScript()..":startCountdown", function(number, hudColour)
CountdownHandler:Start(number, hudColour)
end)
return CountdownHandler return CountdownHandler

View File

@@ -1,41 +1,44 @@
-------------------------------------------------------------
--- Displays debug information on the player's screen. -- Debug Text Display Functionality
--- -------------------------------------------------------------
--- This function renders a semi-transparent box with multiple lines of text for debugging purposes.
--- It is controlled by the `debugMode` flag and can be positioned dynamically on the screen. --- Draws debug text on the screen if debugMode is enabled.
--- ---
--- @param textTable table A table containing strings to display. --- Calculates a background rectangle based on the number of text lines and renders each line on-screen.
--- @param loc vector2|nil Optional. The screen position to display the debug box. Defaults to `vec2(0.05, 0.65)`. ---
--- --- @param textTable table An array of strings to display.
--- @usage --- @param loc vector2 (Optional) Top-left coordinate for the text box (default: vec2(0.05, 0.65)).
--- ```lua ---
--- debugScaleForm({ --- @usage
--- "Player Position: X=123.45 Y=678.90 Z=12.34", --- ```lua
--- "Current Action: Running", ---CreateThread(function()
--- }) --- while true do
--- ``` --- debugScaleForm({
function debugScaleForm(textTable, loc) --- "Line 1: Debug info",
if debugMode then --- "Line 2: More info"
-- Define the display position (top left corner) --- })
local loc = loc or vec2(0.05, 0.65) --- Wait(0)
--- end
-- Calculate dynamic height based on the number of lines in the textTable ---end)
local lineHeight = 0.025 -- Height of each line of text --- ```
local totalHeight = #textTable * lineHeight -- Dynamic height based on number of lines function debugScaleForm(textTable, loc)
local boxPadding = 0.01 -- Padding to add around the text inside the box if debugMode then
local size = vec2(0.18, totalHeight + boxPadding * 2) -- Width remains fixed, height is dynamic loc = loc or vec2(0.05, 0.65)
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255) local lineHeight = 0.025 -- Height per line.
local totalHeight = #textTable * lineHeight
for i = 1, #textTable do local boxPadding = 0.01 -- Padding around the text.
local textLine = textTable[i] local size = vec2(0.18, totalHeight + boxPadding * 2)
SetTextScale(0.30, 0.30) -- Draw background rectangle.
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 200)
BeginTextCommandDisplayText("STRING")
AddTextComponentSubstringKeyboardDisplay(textLine) -- Render each line of text.
for i = 1, #textTable do
EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01) SetTextScale(0.30, 0.30)
end BeginTextCommandDisplayText("STRING")
end AddTextComponentSubstringKeyboardDisplay(textTable[i])
end EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
end
end
end

View File

@@ -1,50 +1,110 @@
--- Creates instructional buttons using the detected polyzone library (ox_lib or PolyZone). -------------------------------------------------------------
--- -- Instructional Buttons Functionality
--- This function generates instructional buttons on the player's screen based on the provided information. -------------------------------------------------------------
--- It supports different polyzone libraries by automatically detecting which one is active.
--- --- Loads and draws instructional buttons on-screen using a scaleform movie.
---@param info table A table containing the instructional buttons configuration. ---
--- - **keys** (`table`): A list of control keys to display. --- Requests the "instructional_buttons" scaleform, clears previous data, sets clear space,
--- - **text** (`string`): The description text for the buttons. --- creates data slots for each button option provided in `info`, and then draws the scaleform fullscreen.
--- ---
---@usage --- @param info table An array of tables, where each table represents a button option:
--- ```lua --- - keys (table): An array of key codes (e.g., {38, 29}) to display.
--- makeInstructionalButtons({ --- - text (string): The label for the button.
--- { keys = { 38 }, text = "Interact" }, ---
--- { keys = { 47 }, text = "Pick Up" }, --- @usage
--- }) --- ```lua
--- ``` ---CreateThread(function()
function makeInstructionalButtons(info) --- while true do
local build = RequestScaleformMovie("instructional_buttons") --- makeInstructionalButtons({
while not HasScaleformMovieLoaded(build) do Wait(0) end --- { keys = {38, 29}, text = "Open Menu" },
--- { keys = {45}, text = "Close Menu" }
DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0) --- })
BeginScaleformMovieMethod(build, "CLEAR_ALL") --- Wait(0)
EndScaleformMovieMethod() --- end
BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE") ---end)
ScaleformMovieMethodAddParamInt(200) --- ```
EndScaleformMovieMethod() function makeInstructionalButtons(info)
local build = RequestScaleformMovie("instructional_buttons")
for i = 1, #info do while not HasScaleformMovieLoaded(build) do Wait(0) end
BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
ScaleformMovieMethodAddParamInt(i - 1) -- Draw the scaleform fullscreen (initial draw).
for k = 1, #info[i].keys do DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
end -- Clear previous instructions.
BeginTextCommandScaleformString("STRING") BeginScaleformMovieMethod(build, "CLEAR_ALL")
AddTextComponentSubstringKeyboardDisplay(info[i].text) EndScaleformMovieMethod()
EndTextCommandScaleformString()
EndScaleformMovieMethod() -- Set clear spacing between buttons.
end BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
ScaleformMovieMethodAddParamInt(200)
BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS") EndScaleformMovieMethod()
EndScaleformMovieMethod()
BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR") -- Add each button option to the scaleform.
ScaleformMovieMethodAddParamInt(0) for i = 1, #info do
ScaleformMovieMethodAddParamInt(0) BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
ScaleformMovieMethodAddParamInt(0) ScaleformMovieMethodAddParamInt(i - 1)
ScaleformMovieMethodAddParamInt(80) for k = 1, #info[i].keys do
EndScaleformMovieMethod() ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
end
DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0) BeginTextCommandScaleformString("STRING")
end AddTextComponentSubstringKeyboardDisplay(info[i].text)
EndTextCommandScaleformString()
EndScaleformMovieMethod()
end
-- Draw the instructional buttons.
BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS")
EndScaleformMovieMethod()
-- Set a translucent black background.
BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR")
ScaleformMovieMethodAddParamInt(0)
ScaleformMovieMethodAddParamInt(0)
ScaleformMovieMethodAddParamInt(0)
ScaleformMovieMethodAddParamInt(80)
EndScaleformMovieMethod()
-- Final full-screen draw with full opacity.
DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0)
end
-- EXPERIMENTAL --
-- RedM Button Prompts --
-- Creates the promot, then shows it, this needs to be run in a loop
local promptGroups = {}
function makeRedInstructionalButtons(info, title)
if not promptGroups[title] then -- Create group if not exists
promptGroups[title] = {
title = CreateVarString(10, 'LITERAL_STRING', title),
id = GetRandomIntInRange(0, 0xffffff),
prompts = {},
}
for i = 1, #info do
promptGroups[title].prompts[i] = {
keys = info[i].keys,
text = info[i].text,
}
local keyTitle = CreateVarString(10, 'LITERAL_STRING', info[i].text)
-- Create one prompt per entry
local promptSet = UiPromptRegisterBegin()
-- Register all keys for this prompt
for k = 1, #info[i].keys do
PromptSetControlAction(promptSet, info[i].keys[k])
end
PromptSetText(promptSet, keyTitle)
PromptSetEnabled(promptSet, true)
PromptSetVisible(promptSet, true)
PromptSetGroup(promptSet, promptGroups[title].id)
PromptRegisterEnd(promptSet)
end
end
PromptSetActiveGroupThisFrame(promptGroups[title].id, promptGroups[title].title)
end
onResourceStop(function()
for k, v in pairs(promptGroups) do
print("^5Bridge^7: ^2Removing Prompt Group^7: ^3" .. k .. "^7")
PromptDelete(promptGroups[k].id, 1)
end
end, true)

View File

@@ -0,0 +1,115 @@
-------------------------------------------------------------
-- 3D Text Rendering
-------------------------------------------------------------
--- Draws 3D text at specified world coordinates.
---
--- Configures text properties, draws the text, and displays a background rectangle behind it.
---
--- @param coord table A vector3 with x, y, and z coordinates.
--- @param text string The text to display.
--- @param highlight boolean (Optional) If true, highlights parts of the text.
---
--- @usage
--- ```lua
--- CreateThread(function()
--- while true do
--- DrawText3D(vector3(100, 200, 300), "Hello World", true)
--- Wait(0)
--- 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
-------------------------------------------------------------
-- Additional UI Helpers
-------------------------------------------------------------
--- Displays a help message on the screen.
---
--- @param text string The message to display.
---
--- @usage
--- ```lua
--- DisplayHelpMsg("Press E to interact")
--- ```
function DisplayHelpMsg(text)
BeginTextCommandDisplayHelp("STRING")
AddTextComponentScaleform(text)
EndTextCommandDisplayHelp(0, true, false, -1)
end
--- Displays a "Saving/Loading" spinner with a custom message.
---
--- @param text string The message to display alongside the spinner.
---
--- @usage
--- ```lua
--- displaySpinner("Saving data...")
--- ```
function displaySpinner(text)
BeginTextCommandBusyspinnerOn('STRING')
AddTextComponentSubstringPlayerName(text)
EndTextCommandBusyspinnerOn(4)
end
--- Stops the "Saving/Loading" spinner.
---
--- This function should only be called client-side.
---
--- @usage
--- ```lua
--- stopSpinner()
--- ```
function stopSpinner()
if not isServer() then
BusyspinnerOff()
end
end

View File

@@ -1,60 +1,78 @@
function createTimerHud(title, data, alpha) --- Creates and displays a timer HUD on the screen.
loadTextureDict("timerbars") --- Draws a title (if provided) and a series of timer bars from the supplied data.
---
local loc = vec2(0.89, 0.90) --- @param title string|nil Optional title to display at the top of the HUD.
alpha = alpha or 255 -- Default to fully opaque if alpha is not provided --- @param data table A table of timer bar entries. Each entry should include:
--- - stat (string): The statistic name.
if title then --- - value (string): The value to display.
local x = loc.x+0.037 --- - multi (number|nil): Optional, indicates multiple checkpoints (e.g., progress levels).
local y = 0.1 --- @param alpha number|nil Optional alpha value (transparency) for the HUD; defaults to 255.
---
DrawSprite("timerbars", "all_black_bg", x, y, 0.12, 0.05, 0.0, 255, 255, 255, alpha) --- @usage
SetTextScale(0.80, 0.80) --- ```lua
SetTextWrap(0.75, 0.985) --- createTimerHud("Timer", {
SetTextJustification(2) --- { stat = "Health", value = "85%" },
SetTextFont(4) --- { stat = "Armor", value = "50%", multi = 2 },
SetTextColour(255, 255, 255, alpha) --- { stat = "Stamina", value = "100%" },
BeginTextCommandDisplayText("STRING") --- }, 255)
AddTextComponentSubstringKeyboardDisplay("~y~"..title) --- ```
EndTextCommandDisplayText(x+0.06, y - 0.026) function createTimerHud(title, data, alpha)
end loadTextureDict("timerbars")
local displayIndex = 0 local loc = vec2(0.89, 0.90)
for i = #data, 1, -1 do alpha = alpha or 255 -- Default to fully opaque if alpha is not provided
local space = 0.044 * displayIndex
if title then
DrawSprite("timerbars", "all_black_bg", loc.x+0.02, loc.y - space, 0.15, 0.04, 0.0, 255, 255, 255, alpha) local x = loc.x+0.037
SetTextScale(0.0, 0.35) local y = 0.1
SetTextWrap(0.5, 0.92)
SetTextJustification(2) DrawSprite("timerbars", "all_black_bg", x, y, 0.12, 0.05, 0.0, 255, 255, 255, alpha)
SetTextColour(255, 255, 255, alpha) SetTextScale(0.80, 0.80)
BeginTextCommandDisplayText("STRING") SetTextWrap(0.75, 0.985)
AddTextComponentSubstringKeyboardDisplay(data[i].stat:upper()) SetTextJustification(2)
EndTextCommandDisplayText(loc.x-0.05, (loc.y - space) - 0.0125) SetTextFont(4)
SetTextColour(255, 255, 255, alpha)
SetTextScale(0.55, 0.55) BeginTextCommandDisplayText("STRING")
SetTextWrap(0.85, 0.98 - (data[i].multi and 0.026 or 0.0)) AddTextComponentSubstringKeyboardDisplay("~y~"..title)
SetTextFont(4) EndTextCommandDisplayText(x+0.06, y - 0.026)
SetTextJustification(2) end
SetTextColour(255, 255, 255, alpha)
if data[i].multi then local displayIndex = 0
local startX = 0.071 for i = #data, 1, -1 do
DrawSprite("timerbars", "circle_checkpoints", local space = 0.044 * displayIndex
loc.x + startX, (loc.y - space)+0.005,
0.011, 0.018, 0.0, 255, 191, 0, 200) DrawSprite("timerbars", "all_black_bg", loc.x+0.02, loc.y - space, 0.15, 0.04, 0.0, 255, 255, 255, alpha)
SetTextScale(0.0, 0.35)
DrawSprite("timerbars", "circle_checkpoints", SetTextWrap(0.5, 0.92)
loc.x + (startX + 0.008), (loc.y - space)+0.005, SetTextJustification(2)
0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 1 and 200 or 75) SetTextColour(255, 255, 255, alpha)
BeginTextCommandDisplayText("STRING")
DrawSprite("timerbars", "circle_checkpoints", AddTextComponentSubstringKeyboardDisplay(data[i].stat:upper())
loc.x + (startX + 0.016), (loc.y - space)+0.005, EndTextCommandDisplayText(loc.x-0.05, (loc.y - space) - 0.0125)
0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 2 and 200 or 75)
end SetTextScale(0.55, 0.55)
BeginTextCommandDisplayText("STRING") SetTextWrap(0.85, 0.98 - (data[i].multi and 0.026 or 0.0))
AddTextComponentSubstringKeyboardDisplay(data[i].value) SetTextFont(4)
EndTextCommandDisplayText(loc.x - 0.02, (loc.y - space) - 0.017) SetTextJustification(2)
displayIndex += 1 SetTextColour(255, 255, 255, alpha)
end if data[i].multi then
makeInstructionalButtons({ { text = "Exit", keys = { 194 }}}) local startX = 0.071
DrawSprite("timerbars", "circle_checkpoints",
loc.x + startX, (loc.y - space)+0.005,
0.011, 0.018, 0.0, 255, 191, 0, 200)
DrawSprite("timerbars", "circle_checkpoints",
loc.x + (startX + 0.008), (loc.y - space)+0.005,
0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 1 and 200 or 75)
DrawSprite("timerbars", "circle_checkpoints",
loc.x + (startX + 0.016), (loc.y - space)+0.005,
0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 2 and 200 or 75)
end
BeginTextCommandDisplayText("STRING")
AddTextComponentSubstringKeyboardDisplay(data[i].value)
EndTextCommandDisplayText(loc.x - 0.02, (loc.y - space) - 0.017)
displayIndex += 1
end
makeInstructionalButtons({ { text = "Exit", keys = { 194 }}})
end end

266
shared/shops.lua Normal file
View File

@@ -0,0 +1,266 @@
-------------------------------------------------------------
-- Selling Menu and Animation
-------------------------------------------------------------
--- Opens a selling menu with available items and prices.
---
--- @param data table Contains selling menu data:
--- - sellTable (`table`) Table with Header and Items (item names and prices).
--- - ped (optional) (`number`) Ped entity involved.
--- - onBack (optional) (`function`) Callback for returning.
--- @usage
--- ```lua
--- sellMenu({
--- sellTable = {
--- Header = "Sell Items",
--- Items = {
--- ["gold_ring"] = 100,
--- ["diamond"] = 500,
--- },
--- },
--- ped = pedEntity,
--- onBack = function() print("Returning to previous menu") end,
--- })
--- ```
function sellMenu(data)
local origData = data
local Menu = {}
if data.sellTable.Items then
local itemList = {}
for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
local _, hasTable = hasItem(itemList)
for k, v in pairsByKeys(data.sellTable.Items) do
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
if type(v) == "table" then
Menu[#Menu + 1] = {
arrow = true,
header = k,
txt = "Amount of items: "..countTable(v.Items),
onSelect = function()
v.onBack = function()
sellMenu(origData)
end
v.sellTable = data.sellTable[k]
sellMenu(v)
end,
}
end
end
end
openMenu(Menu, {
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,
})
end
--- Plays the selling animation and processes the sale transaction.
---
--- Checks if the player has the item, plays animations, triggers the server event for selling,
--- and then calls the onBack callback if provided.
---
--- @param data table Contains:
--- `- item: The item to sell.
--- `- price: Price per item.
--- `- ped (optional): Ped entity involved.
--- `- onBack (optional): Callback to call on completion.
---@usage
--- ```lua
--- sellAnim({
--- item = "gold_ring",
--- price = 100,
--- ped = pedEntity,
--- onBack = function() sellMenu(data) end,
--- })
--- ```
function sellAnim(data)
if not hasItem(data.item, 1) then
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error")
return
end
-- Remove any attached clipboard objects.
for _, obj in pairs(GetGamePool('CObject')) do
for _, model in pairs({ `p_cs_clipboard` }) do
if GetEntityModel(obj) == model and IsEntityAttachedToEntity(data.ped, obj) then
DeleteObject(obj)
DetachEntity(obj, 0, 0)
SetEntityAsMissionEntity(obj, true, true)
Wait(100)
DeleteEntity(obj)
end
end
end
TriggerServerEvent(getScript().."Sellitems", data)
lookEnt(data.ped)
local dict = "mp_common"
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
end
--- Server event handler for processing item sales.
--- Removes sold items from inventory and funds the player based on the sale.
RegisterNetEvent(getScript().."Sellitems", function(data)
local src = source
local hasItems, hasTable = hasItem(data.item, 1, src)
if hasItems then
removeItem(data.item, hasTable[data.item].count, src)
fundPlayer((hasTable[data.item].count * data.price), "cash", src)
else
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src)
end
end)
-------------------------------------------------------------
-- Shop Interface
-------------------------------------------------------------
--- Opens a shop interface for the player.
---
--- Checks job/gang restrictions, then uses the active inventory system to open the shop.
--- @param data table Contains:
--- - shop (`string`) The shop identifier.
--- - items (`table`) The items available in the shop.
--- - coords (`vector3`) where the shop is located.
--- - job/gang (optional) (`string`) Job or gang requirements.
---@usage
--- ```lua
--- openShop({
--- shop = "weapon_shop",
--- items = weaponShopItems,
--- coords = vector3(100.0, 200.0, 300.0),
--- job = "police",
--- })
--- ```
function openShop(data)
jsonPrint(data)
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
if Config.General.JimShops then
TriggerServerEvent("jim-shops:ShopOpen", "shop", data.items.label, data.items)
elseif isStarted(OXInv) then
exports[OXInv]:openInventory('shop', { type = data.shop })
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
TriggerServerEvent(getScript()..':server:openServerShop', data.shop)
TriggerServerEvent("inventory:server:OpenInventory", "shop", data.items.label, data.items)
elseif isStarted(PSInv) then
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)
end
lookEnt(data.coords)
end
RegisterNetEvent(getScript()..':server:openServerShop', function(data)
if isStarted(QBInv) and checkExportExists(QBInv, "OpenShop") then
exports[QBInv]:OpenShop(source, data)
end
if isStarted(PSInv) and checkExportExists(PSInv, "OpenShop") then
exports[PSInv]:OpenShop(source, data)
end
if isStarted(TgiannInv) then
exports[TgiannInv]:OpenShop(source, data)
end
if isStarted(RSGInv) then
exports[RSGInv]:OpenShop(source, data)
end
end)
--- Registers a shop with the active inventory system.
--- Supports either OXInv or QBInv (with QBInvNew flag).
---
--- @param name string Unique shop identifier.
--- @param label string Display name for the shop.
--- @param items table List of available shop items.
--- @param society string|nil (Optional) Society identifier for shared shops.
--- @usage
--- ```lua
--- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons")
--- ```
function registerShop(name, label, items, society)
local shopResource = ""
if isStarted(OXInv) then
shopResource = OXInv
exports[OXInv]:RegisterShop(name, {
name = label,
inventory = items,
society = society,
})
elseif isStarted(TgiannInv) then
shopResource = TgiannInv
exports[TgiannInv]:RegisterShop(name, items)
elseif isStarted(QBInv) and checkExportExists(QBInv, "CreateShop") then
shopResource = QBInv
exports[QBInv]:CreateShop({
name = name,
label = label,
slots = #items,
items = items,
society = society,
})
elseif isStarted(PSInv) and checkExportExists(PSInv, "CreateShop") then
shopResource = PSInv
exports[PSInv]:CreateShop({
name = name,
label = label,
slots = #items,
items = items,
society = society,
})
elseif isStarted(RSGInv) then
shopResource = RSGInv
exports[RSGInv]:CreateShop({
name = name,
label = label,
slots = #items,
items = items,
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

58
shared/skillcheck.lua Normal file
View File

@@ -0,0 +1,58 @@
local activeSkillCheck = false
function skillCheck(data)
local result = false
if Config.System.skillCheck == "qb" then
local Skillbar = exports["qb-minigames"]:Skillbar()
if Skillbar then
result = true
else
result = false
end
elseif Config.System.skillCheck == "ox" then
local Skillbar = exports[OXLibExport]:skillCheck(
{
"easy",
"easy",
"easy"
},
{
"1",
"2",
"3",
"4"
})
if Skillbar then
result = true
else
result = false
end
elseif Config.System.skillCheck == "gta" then
exports.jim_bridge:skillCheck()
elseif Config.System.skillCheck == "lation" then
local Skillbar = exports.lation_ui:skillCheck("",
{
"easy",
"easy",
"easy"
},
{
"1",
"2",
"3",
"4"
})
if Skillbar then
result = true
else
result = false
end
else
result = true
end
return result
end

203
shared/societybank.lua Normal file
View File

@@ -0,0 +1,203 @@
--[[
Society Banking Module
------------------------
This module provides functions to interact with society bank accounts across
different banking systems. Supported systems include:
• qb-banking
• esx_society *testing*
• Renewed-Banking
• fd_banking
• okokBanking
• Tgiann-bank
]]
--- Retrieves the current balance of a society's bank account.
--- @param society string The identifier of the society.
--- @return number number The current account balance.
--- @usage
--- ```lua
--- local balance = getSocietyAccount("police")
--- print("Police account balance: $"..balance)
--- ```
function getSocietyAccount(society)
local bankScript, amount = "", 0
if society == nil or society == "none" then return amount end
if isStarted("qb-banking") then
bankScript = "qb-banking"
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
amount = exports["qb-banking"]:GetAccountBalance(society)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- -- Since esx_society does not have a native client export for retrieving money,
-- -- we use a server callback to get the final amount.
-- amount = triggerCallback(getScript()..":getESXSocietyAccount", society) or 0
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
amount = exports["Renewed-Banking"]:getAccountMoney(society)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
amount = exports["fd_banking"]:GetAccount(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
print("^1Error^7: ^3GetSocietyAccount^7: ^2No supported banking script found")
else
debugPrint("^6Bridge^7: ^3"..bankScript.."^7: ^2Retrieved account ^7'^6"..society.."^7' ($"..amount..")")
end
return amount
end
--- Deducts funds from a society's bank account.
--- @param society string The identifier of the society.
--- @param amount number The amount of money to remove.
--- @usage
--- ```lua
--- chargeSociety("police", 1000)
--- ```
function chargeSociety(society, amount)
local bankScript, newAmount = "", 0
if isStarted("qb-banking") then
bankScript = "qb-banking"
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null"
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null"
end
end
exports["qb-banking"]:RemoveMoney(society, amount)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- TriggerEvent("esx_society:withdrawMoney", society, amount)
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
exports['Renewed-Banking']:removeAccountMoney(society, amount)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
exports["fd_banking"]:RemoveMoney(society, amount)
elseif isStarted("okokBanking") then
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
print("^1Error^7: ^3ChargeSociety^7: ^2No supported banking script found")
else
newAmount = getSocietyAccount(society)
debugPrint("^6Bridge^7: ^3"..bankScript.."^7: ^2Removing $"..amount.." from account '^6"..society.."^7' ($"..newAmount..")")
end
end
--- Adds funds to a society's bank account.
--- @param society string The identifier of the society.
--- @param amount number The amount of money to add.
--- @usage
--- ```lua
--- fundSociety("police", 500)
--- ```
function fundSociety(society, amount)
local bankScript, newAmount, success = "", 0, false
if isStarted("qb-banking") then
bankScript = "qb-banking"
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
exports["qb-banking"]:AddMoney(society, amount)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- -- Use the esx_society event to deposit money.
-- TriggerServerEvent('esx_society:depositMoney', society, amount)
-- -- Use callback to return the updated balance.
-- newAmount = triggerCallback(getScript()..":getESXSocietyAccount", society) or 0
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
exports['Renewed-Banking']:addAccountMoney(society, amount)
newAmount = exports["Renewed-Banking"]:getAccountMoney(society)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
exports["fd_banking"]:AddMoney(society, amount)
elseif isStarted("okokBanking") then
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
print("^1Error^7: ^3FundSociety^7: ^2No supported banking script found")
else
newAmount = getSocietyAccount(society)
debugPrint("^6Bridge^7: ^3"..bankScript.."^7: ^2Adding ^7$"..amount.." ^2to account ^7'^6"..society.."^7' ($"..newAmount..")")
end
end
-- other
if isStarted("esx_society") then
createCallback(getScript()..":getESXSocietyAccount", function(source, society)
-- Example query adjust table/field names to match your esx_society implementation.
local result = MySQL.scalar.await('SELECT money FROM society_money WHERE society = ?', { society })
return result or 0
end)
end

View File

@@ -1,271 +1,700 @@
if isServer() then --[[
createCallback(getScript()..':server:GetStashItems', function(source, stashName) local stash = getStash(stashName) return stash end) Stash Management Module
end -------------------------
This module handles stash-related operations including:
local stashCache ={} • Retrieving stash items (from server or local cache).
function GetStashTimeout(stashName, stop) • Checking for required items in stashes.
if stop then stashCache = {} return end • Opening stashes using different inventory systems.
local stash = stashCache[stashName] • Removing items from stashes.
if not stash then • Checking if a stash has specific items.
stashCache[stashName] = { items = {}, timeout = 0 } ]]
stash = stashCache[stashName]
end -- Global variable to hold the current stash (used in callbacks).
if #stash.items > 0 then return true end local stash
if stash.timeout <= 0 then
stash.items = triggerCallback(getScript()..':server:GetStashItems', stashName) -- If running on the server, create a callback to retrieve stash items.
stash.timeout = 10000 if isServer() then
CreateThread(function() createCallback(getScript()..':server:GetStashItems', function(source, stashName)
while stash.timeout > 0 do stash.timeout -= 1000 Wait(1000) end stash = getStash(stashName)
stashCache[stashName] = nil return stash
end) end)
end end
return false -- Local cache for stashes.
end local stashCache = {}
function checkHasItem(stashes, itemTable) --- Retrieves (or updates) a local stash cache entry with a timeout.
if not stashes then return hasItem(itemTable), nil end --- When the cache is empty or expired, it triggers a server callback to update the items.
if type(stashes) == "table" then ---
local succeses = 0 --- @param stashName string The name of the stash.
local itemCount = 0 --- @param stop boolean (Optional) If true, clears the entire stash cache.
for _, item in pairs(itemTable) do itemCount += 1 end --- @return boolean True if items exist in cache (and recheck is skipped), false otherwise.
for _, name in pairs(stashes) do ---
GetStashTimeout(name) --- @usage
for item, amount in pairs(itemTable) do --- ```lua
debugPrint("^6Bridge^7: ^2Checking"..(name and " ^7'^6"..name.."^7'" or "").." ^2ingredients^7 - ^6"..item.."^7") --- local cached = GetStashTimeout("playerStash")
if stashhasItem(stashCache[name].items, item, amount) then --- ```
succeses += 1 function GetStashTimeout(stashName, stop)
if succeses == itemCount then return true, name end if stop then
end stashCache = {}
end return
end end
else
debugPrint("^6Bridge^7: ^2Checking"..(stashes and " ^7'^6"..stashes.."^7'" or "").." ^2ingredients^7 - ^6"..k.."^7") -- Retrieve cache for this stash, or initialize if not present.
GetStashTimeout(stashes) stash = stashCache[stashName]
return stashhasItem(stashCache[stashes].items, itemTable), stashes if not stash then
end debugPrint("^6Bridge^7: ^2Local Stash ^7'^3"..stashName.."^7'^2 cache ^1not ^2found^7, ^2need to grab from server^7")
stashCache[stashName] = { items = {}, timeout = 0 }
return false, nil stash = stashCache[stashName]
end else
debugPrint("^6Bridge^7: ^2Local Stash for ^7'^3"..stashName.."^7'^2 cache found^7")
end
-- Stash Items
function openStash(data) -- If there are already items in cache, skip recheck.
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end if countTable(stashCache[stashName].items) > 0 then
if isStarted(OXInv) then debugPrint("^6Bridge^7: '^3"..stashName.."^7' ^2Items found in local cache, skipping server recheck")
exports[OXInv]:openInventory('stash', data.stash) return true
elseif isStarted(CodeMInv) then end
exports[CodeMInv]:OpenStash(data.stash, StashWeight, 100)
elseif isStarted(QBInv) then -- If timeout has expired, update the stash items from the server.
if QBInvNew then if stashCache[stashName].timeout <= 0 then
TriggerServerEvent(getScript()..':server:OpenStashQB', { stashName = data.stash, label = data.label, maxweight = data.maxWeight or 600000, slots = data.slots or 40 }) stashCache[stashName].items = triggerCallback(getScript()..':server:GetStashItems', stashName)
else stashCache[stashName].timeout = 10000 -- Timeout in milliseconds.
TriggerEvent("inventory:client:SetCurrentStash", data.stash) CreateThread(function()
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions) while stashCache[stashName] and stashCache[stashName].timeout > 0 do
end stashCache[stashName].timeout -= 1000
else Wait(1000)
TriggerEvent("inventory:client:SetCurrentStash", data.stash) end
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions) debugPrint("^6Bridge^7: ^2Local Stash ^7'^3"..stashName.."^7'^2 cache timed out^7, ^3Clearing^7")
end stashCache[stashName] = nil
lookEnt(data.coords) end)
end end
return false
RegisterNetEvent(getScript()..':server:OpenStashQB', function(data) end
exports[QBInv]:OpenInventory(source, data.stashName, data)
end) --- Checks if the specified stashes have the required items.
---
function getStash(stashName) local stashResource = "" --- If multiple stashes are provided (as a table), it iterates over each until all required items are found.
if type(stashName) ~= "string" then return print("Stash name was not a string %s(%s)", stashName, type(stashName)) end ---
local stashItems, items = {}, {} --- @param stashes string|table Either a single stash name or a table of stash names.
if isStarted(OXInv) then stashResource = OXInv --- @param itemTable table A table where keys are item names and values are the required amounts.
stashItems = exports[OXInv]:Inventory(stashName).items --- @return boolean, string|nil `boolean, string` true and the stash name if found, otherwise false and nil.
---
elseif isStarted(QSInv) then stashResource = QSInv --- @usage
stashItems = exports[QSInv]:GetStashItems(stashName) --- ```lua
--- local found, stashName = checkStashItem({"playerStash", "storageStash"}, { iron = 2, wood = 5 })
elseif isStarted(CoreInv) then stashResource = CoreInv --- ```
stashItems = exports[CoreInv]:getInventory(stashName) function checkStashItem(stashes, itemTable)
if not stashes then
elseif isStarted(CodeMInv) then stashResource = CodeMInv return hasItem(itemTable), nil
stashItems = exports[CodeMInv]:GetInventoryItems('Stash', stashName) end
elseif isStarted(OrigenInv) then stashResource = OrigenInv if type(stashes) == "table" then
stashItems = exports[OrigenInv]:GetStashItems(stashName) debugPrint("^6Bridge^7: ^2Checking multiple stashes for ingredients^7")
-- Iterate over each provided stash name.
elseif isStarted(PSInv) then stashResource = PSInv for _, name in pairs(stashes) do
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName }) GetStashTimeout(name)
if result then stashItems = json.decode(result) end if stashhasItem(stashCache[name].items, itemTable, nil) then
elseif isStarted(QBInv) then stashResource = QBInv return true, name
local result = MySQL.scalar.await("SELECT items FROM "..(QBInvNew and "inventories" or "stashitem").." WHERE identifier = ?", { stashName }) end
if result then stashItems = json.decode(result) end end
end else
debugPrint("^6Bridge^7: ^2Checking "..(stashes and " '^3"..stashes.."^7'" or "").." ingredients")
debugPrint("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7"..stashResource) GetStashTimeout(stashes)
if stashItems then return stashhasItem(stashCache[stashes].items, itemTable), stashes
for _, item in pairs(stashItems) do end
local itemInfo = Items[item.name:lower()]
if itemInfo then return false, nil
local indexNum = #items+1 -- Added to help recreate missing slot numbers end
items[(item.slot and item.slot) or indexNum] = {
name = itemInfo.name or nil, -------------------------------------------------------------
amount = tonumber(item.amount) or tonumber(item.count), -- Stash Opening Functions
info = item.info or "", -------------------------------------------------------------
label = itemInfo.label or nil,
description = itemInfo.description or "", --- Opens a stash using the active inventory system.
weight = itemInfo.weight or nil, ---
type = itemInfo.type or nil, --- Checks for job or gang restrictions before opening the stash.
unique = itemInfo.unique or nil, ---
useable = itemInfo.useable or nil, --- @param data table A table containing stash data:
image = itemInfo.image or nil, --- - stash (string): The stash identifier.
slot = (item.slot and item.slot) or indexNum, --- - label (string): Display label.
metadata = (item.metadata and item.metadata) or nil, --- - maxWeight (number|nil): Maximum weight (default 600000).
} --- - slots (number|nil): Number of slots (default 40).
end --- - stashOptions (table|nil): Additional options for the stash.
end --- - job/gang (string|nil): Restriction for access.
debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for ^7'^6"..stashName.."^7' ^2retrieved^7") --- - coords (vector3): Coordinates to "look" at.
end ---
return items --- @usage
end --- ```lua
--- openStash({ stash = "playerStash", label = "Player Stash", coords = vector3(100, 200, 30) })
function stashRemoveItem(stashItems, stashName, items) local amount = amount and amount or 1 --- ```
-- print("stashItems: "..json.encode(stashItems, { indent = true})) function openStash(data)
-- print("stashName: "..json.encode(stashName, { indent = true})) if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
-- print("items: "..json.encode(items, { indent = true}))
if isStarted(OXInv) then if isStarted(OXInv) then
for k, v in pairs(items) do exports[OXInv]:openInventory('stash', data.stash)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
if type(stashName) == "table" then elseif isStarted(CoreInv) then
for _, name in pairs(stashName) do TriggerServerEvent('core_inventory:server:openInventory', data.stash, 'stash')
local success = exports[OXInv]:RemoveItem(name, k, v)
if success then elseif isStarted(CodeMInv) then
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v) TriggerServerEvent('codem-inventory:server:openstash', data.stash, data.slots, data.maxWeight, data.label)
break
end elseif isStarted(OrigenInv) then
end exports[OrigenInv]:openInventory('stash', data.stash, { label = data.label })
else
exports[OXInv]:RemoveItem(stashName, k, v) elseif isStarted(TgiannInv) then
end TriggerServerEvent(getScript()..':server:openServerStash', {
end stashName = data.stash,
label = data.label,
elseif isStarted(QSInv) then maxweight = data.maxWeight or 600000,
for k, v in pairs(items) do slots = data.slots or 40
for l in pairs(stashItems) do })
if stashItems[l].name == k then
if (stashItems[l].amount - v) <= 0 then elseif isStarted(JPRInv) then
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v) if QBInvNew then
stashItems[l] = nil TriggerServerEvent(getScript()..':server:openServerStash', {
else stashName = data.stash,
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v) label = data.label,
exports[QSInv]:RemoveItemIntoStash(stashName, k, v, l) maxweight = data.maxWeight or 600000,
end slots = data.slots or 40
end })
end else
end TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
elseif isStarted(CoreInv) then slots = data.slots or 50,
for k, v in pairs(items) do maxWeight = data.maxWeight or 600000
exports[CoreInv]:removeItemExact(stashName, k, v) })
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CoreInv, k, v) end
end
elseif isStarted(QBInv) then
elseif isStarted(CodeMInv) then if QBInvNew then
for k, v in pairs(items) do TriggerServerEvent(getScript()..':server:openServerStash', {
for l in pairs(stashItems) do stashName = data.stash,
if stashItems[l].name == k then label = data.label,
if (stashItems[l].amount - v) <= 0 then maxweight = data.maxWeight or 600000,
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v) slots = data.slots or 40
stashItems[l] = nil })
else else
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CodeMInv, k, v) TriggerEvent("inventory:client:SetCurrentStash", data.stash)
stashItems[l].amount -= v TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
end slots = data.slots or 50,
end maxWeight = data.maxWeight or 600000
end })
end end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
elseif isStarted(PSInv) then
elseif isStarted(OrigenInv) then if QBInvNew then
for k, v in pairs(items) do TriggerServerEvent(getScript()..':server:openServerStash', {
exports[OrigenInv]:RemoveFromStash(stashName, k, v) stashName = data.stash,
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OrigenInv, k, v) label = data.label,
end maxweight = data.maxWeight or 600000,
slots = data.slots or 40
elseif isStarted(PSInv) then })
for k, v in pairs(items) do else
for l in pairs(stashItems) do TriggerEvent("ps-inventory:client:SetCurrentStash", data.stash)
if stashItems[l].name == k then TriggerEvent("inventory:client:SetCurrentStash", data.stash)
if (stashItems[l].amount - v) <= 0 then TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v) slots = data.slots or 50,
stashItems[l] = nil maxWeight = data.maxWeight or 600000
else })
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v) end
stashItems[l].amount -= v
end elseif isStarted(RSGInv) then
end TriggerServerEvent(getScript()..':server:openServerStash', {
end stashName = data.stash,
end label = data.label,
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'") maxweight = data.maxWeight or 600000,
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) }) slots = data.slots or 40
elseif isStarted(QBInv) then })
if QBInvNew then
for k, v in pairs(items) do else
exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting') --Fallback to these commands
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v) TriggerEvent("inventory:client:SetCurrentStash", data.stash)
end TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName[1].."^7'") slots = data.slots or 50,
MySQL.Async.insert('INSERT INTO inventories (identifier, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) }) maxWeight = data.maxWeight or 600000
else })
for k, v in pairs(items) do end
for l in pairs(stashItems) do
if stashItems[l].name == k then lookEnt(data.coords)
if (stashItems[l].amount - v) <= 0 then end
if Config.System.Debug then
print("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
end -- Wrapper function for opening stash from the server.
stashItems[l] = nil -- Messy but not much else I can do about it.
else RegisterNetEvent(getScript()..":server:openServerStash", function(data)
if Config.System.Debug then local src = source
print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v) if isStarted(TgiannInv) then
end exports[TgiannInv]:OpenInventory(source, 'stash', data.stashName, data)
stashItems[l].amount -= v end
end if isStarted(JPRInv) then
end exports[JPRInv]:OpenInventory(source, data.stashName, data)
end end
end if isStarted(QBInv) then
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'") exports[QBInv]:OpenInventory(source, data.stashName, data)
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) }) end
end if isStarted(PSInv) then
else exports[PSInv]:OpenInventory(source, data.stashName, data)
print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7") end
end if isStarted(RSGInv) then
end exports[RSGInv]:OpenInventory(source, data.stashName, data)
RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem) end
end)
function stashhasItem(stashItems, items, amount)
local invs = {OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, QBInv, PSInv} function clearStash(stashId)
local foundInv = "" if isStarted(JPRInv) then
for _, inv in ipairs(invs) do debugPrint("^5Bridge^7: ^2Clearing ^3"..JPRInv.."^2 Stash^7", stashId)
if isStarted(inv) then MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
foundInv = inv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6") ['stash'] = stashId,
break ['items'] = json.encode({})
end })
end
elseif isStarted(QBInv) then
if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end debugPrint("^5Bridge^7: ^2Clearing ^3"..QBInv.."^2 Stash^7", stashId)
local hasTable = {} if QBInvNew then
for item, amount in pairs(items) do exports[QBInv]:ClearStash(stashId)
local count = 0 else
for _, itemData in pairs(stashItems) do MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
if itemData and (itemData.name == item) then ['stash'] = stashId,
count += (itemData.amount or 1) ['items'] = json.encode({})
end })
end end
local debugMsg = string.format("^6Bridge^7: ^3stashHasItem^7[^6%s^7]: %s '%s' ^3%d^7/^3%d^7", foundInv, (count >= amount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, amount) elseif isStarted(OXInv) then
debugPrint(debugMsg) debugPrint("^5Bridge^7: ^2Clearing ^3"..OXInv.."^2 Stash^7", stashId)
exports[OXInv]:ClearInventory(stashId)
hasTable[item] = { hasItem = (count >= amount), count = count }
end elseif isStarted(PSInv) then
for k, v in pairs(hasTable) do if v.hasItem == false then return false, hasTable end end debugPrint("^5Bridge^7: ^2Clearing ^3"..PSInv.."^2 Stash^7", stashId)
return true, hasTable MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
end ['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
-------------------------------------------------------------
--- Retrieves stash items from the active inventory system.
---
--- This function converts the raw stash items into a standardized table using the global Items lookup.
---
--- @param stashName string The identifier for the stash.
--- @return stashTable table A table of items from the stash.
---
--- @usage
--- ```lua
--- local items = getStash("playerStash")
--- ```
function getStash(stashName)
local stashResource = ""
if type(stashName) ~= "string" then
print("^6Bridge^7: ^2Stash name was not a string ^3"..stashName.."^7(^3"..type(stashName).."^7)")
return {}
end
local stashItems, items = {}, {}
if isStarted(OXInv) then
stashResource = OXInv
local stash = exports[OXInv]:Inventory(stashName)
-- Add fallback if ox can't find the stash and returns a boolean
stashItems = type(stash) == "table" and stash.items or {}
elseif isStarted(QSInv) then
stashResource = QSInv
stashItems = exports[QSInv]:GetStashItems(stashName)
elseif isStarted(CoreInv) then
stashResource = CoreInv
stashItems = exports[CoreInv]:getInventory(stashName)
elseif isStarted(CodeMInv) then
stashResource = CodeMInv
stashItems = exports[CodeMInv]:GetStashItems(stashName)
elseif isStarted(OrigenInv) then
stashResource = OrigenInv
stashItems = exports[OrigenInv]:getInventory(stashName)
elseif isStarted(TgiannInv) then
stashResource = TgiannInv
stashItems = exports[TgiannInv]:GetSecondaryInventoryItems("stash", stashName)
elseif isStarted(PSInv) then
stashResource = PSInv
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
local result = exports[QBInv]:GetInventory(stashName) or {}
stashItems = result.items or {}
else
local result = MySQL.scalar.await("SELECT items FROM stashitems WHERE stash = ?", { stashName })
if result then stashItems = json.decode(result) end
end
elseif isStarted(RSGInv) then
stashResource = RSGInv
stashItems = exports[RSGInv]:GetInventory(stashName)
end
debugPrint("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7"..stashResource)
if stashItems then
for _, item in pairs(stashItems) do
local itemInfo = Items[item.name:lower()]
if itemInfo then
local indexNum = #items + 1 -- Fallback index if slot is missing.
items[(item.slot or indexNum)] = {
name = itemInfo.name or nil,
amount = tonumber(item.amount) or tonumber(item.count),
info = item.info or "",
label = itemInfo.label or nil,
description = itemInfo.description or "",
weight = itemInfo.weight or nil,
type = itemInfo.type or nil,
unique = itemInfo.unique or nil,
useable = itemInfo.useable or nil,
image = itemInfo.image or nil,
slot = (item.slot and item.slot) or indexNum,
metadata = (item.metadata and item.metadata) or nil,
}
end
end
debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for '^6"..stashName.."^7' retrieved")
end
jsonPrint(items)
return items
end
-------------------------------------------------------------
-- Stash Item Removal Function
-------------------------------------------------------------
--- Removes items from a stash using the active inventory system.
---
--- Iterates over the provided items and adjusts the stash contents accordingly.
---
--- @param stashItems table The current stash items.
--- @param stashName string|table The stash identifier (or table of identifiers).
--- @param items table A table of items to remove (keys are item names, values are amounts).
---
--- @usage
--- ```lua
--- stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 })
--- ```
function stashRemoveItem(stashItems, stashName, items)
if type(stashName) ~= "table" then
stashName = { stashName }
end
if isStarted(OXInv) then
for k, v in pairs(items) do
for _, name in pairs(stashName) do
local success = exports[OXInv]:RemoveItem(name, k, v)
if success then
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
break
end
end
end
elseif isStarted(QSInv) then
for k, v in pairs(items) do
exports[QSInv]:RemoveItemIntoStash(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QSInv, k, v)
end
elseif isStarted(CoreInv) then
for k, v in pairs(items) do
exports[CoreInv]:removeItemExact(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CoreInv, k, v)
end
elseif isStarted(CodeMInv) then
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 ^7"..CodeMInv, k, v)
stashItems[l].amount -= v
end
end
end
end
exports[CodeMInv]:UpdateStash(stashName[1], stashItems)
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3CodeM^2 stash ^7'^6"..stashName.."^7'")
elseif isStarted(OrigenInv) then
for k, v in pairs(items) do
exports[OrigenInv]:RemoveFromStash(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OrigenInv, k, v)
end
elseif isStarted(TgiannInv) then
for k, v in pairs(items) do
local itemData = exports[TgiannInv]:GetItemByNameFromSecondaryInventory("stash", stashName[1], k)
exports[TgiannInv]:RemoveItemFromSecondaryInventory("stash", stashName[1], k, v, itemData.slot, nil)
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
exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
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
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 "..QBInv, k, v)
stashItems[l].amount -= v
end
end
end
end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^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)
})
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
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 ^7"..PSInv, k, v)
stashItems[l].amount -= v
end
end
end
end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3PS^2 stash ^7'^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(RSGInv) then
for k, v in pairs(items) do
exports[RSGInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..RSGInv, k, v)
end
else
print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3starter^1.^2lua^7")
end
end
RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
-------------------------------------------------------------
-- Stash Item Availability Check
-------------------------------------------------------------
--- Checks whether a stash has the required amount of specific items.
---
--- It iterates through the provided items and tallies available quantities.
---
--- @param stashItems table The items available in the stash.
--- @param items string|table The item name or table of required items (key: item, value: amount).
--- @param amount number (Optional) The required amount (if a single item is provided).
--- @return boolean, table (`boolean, string`) Returns true (and a table with counts) if all items are available; false otherwise.
---
--- @usage
--- ```lua
--- local hasAll, details = stashhasItem(currentStashItems, { iron = 2, wood = 5 })
--- ```
function stashhasItem(stashItems, items, amount)
local invs = { OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv, QBInv, PSInv, RSGInv }
local foundInv = ""
for _, inv in ipairs(invs) do
if isStarted(inv) then
foundInv = inv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
break
end
end
-- Ensure items is a table.
if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
local hasTable = {}
for item, requiredAmount in pairs(items) do
local count = 0
for _, itemData in pairs(stashItems) do
if itemData and (itemData.name == item) then
count += (itemData.amount or 1)
end
end
local debugMsg = string.format("^6Bridge^7: ^3stashHasItem^7[^6%s^7]: %s '%s' ^3%d^7/^3%d^7", foundInv, (count >= requiredAmount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, requiredAmount)
debugPrint(debugMsg)
hasTable[item] = { hasItem = (count >= requiredAmount), count = count }
end
for k, v in pairs(hasTable) do if v.hasItem == false then return false, hasTable end end
return true, hasTable
end
--- Registers a stash with the active inventory system.
--- Supports either OXInv or QSInv.
---
--- @param name string Unique stash identifier.
--- @param label string Display name for the stash.
--- @param slots number|nil (Optional) Number of slots (default 50).
--- @param weight number|nil (Optional) Maximum weight (default 4000000).
--- @param owner string|nil (Optional) Owner identifier for personal stashes.
--- @param coords table|nil (Optional) Coordinates for the stash location.
--- @usage
--- ```lua
--- registerStash(
--- "playerStash",
--- "Player Stash",
--- 100,
--- 8000000,
--- "player123",
--- { x = 100.0, y = 200.0, z = 30.0 }
--- )
--- ```
function registerStash(name, label, slots, weight, owner, coords)
local stashResource = ""
if isStarted(OXInv) then
stashResource = OXInv
exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil)
elseif isStarted(OrigenInv) then
stashResource = OrigenInv
exports["origen_inventory"]:registerStash(name, label, slots or 50, weight or 4000000)
elseif isStarted(TgiannInv) then
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
--- Registers an event to create an OX stash from the server.
--- When triggered, it calls registerStash with the provided parameters.
---
--- @event server:makeOXStash
--- @param name string Unique stash identifier.
--- @param label string Display name for the stash.
--- @param slots number|nil (Optional) Number of slots.
--- @param weight number|nil (Optional) Maximum weight.
--- @param owner string|nil (Optional) Owner identifier.
--- @param coords table|nil (Optional) Stash coordinates.
--- @usage
--- ```lua
--- TriggerEvent(getScript()..":server:makeOXStash", name, label, slots, weight, owner, coords, token)
--- ```
RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords, token)
local src = source or nil
if src then
if not checkToken(src, token, "stash", name) then
return
end
end
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

@@ -1,404 +1,526 @@
-- This is for experimental targets based on GTA in-world text prompts -- --[[
local TextTargets = {} Experimental GTA In-World Text Prompts Targets Module
local Keys = { -------------------------------------------------------
[322] = "ESC", [288] = "F1", [289] = "F2", [170] = "F3", [166] = "F5", This module handles the creation, removal, and management of in-world text targets
[167] = "F6", [168] = "F7", [169] = "F8", [56] = "F9", [57] = "F10", for interacting with entities and zones using GTA text prompts. It supports multiple
[243] = "~", [157] = "1", [158] = "2", [160] = "3", [164] = "4", [165] = "5", targeting systems: OX Target, QB Target, or a fallback using DrawText3D.
[159] = "6", [161] = "7", [162] = "8", [163] = "9", [84] = "-", [83] = "=",
[177] = "BACKSPACE", [37] = "TAB", Available functionalities:
[44] = "Q", [32] = "W", [38] = "E", [45] = "R", [245] = "T", [246] = "Y", • createEntityTarget - Creates a target for a specific entity.
[303] = "U", [199] = "P", • createBoxTarget - Creates a box-shaped zone target.
[39] = "[", [40] = "]", [18] = "ENTER", [137] = "CAPS", • createCircleTarget - Creates a circular zone target.
[34] = "A", [8] = "S", [9] = "D", [23] = "F", [47] = "G", • createModelTarget - Creates a target for specified models.
[74] = "H", [311] = "K", [182] = "L", [21] = "LEFTSHIFT", • removeEntityTarget - Removes a target from an entity.
[20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N", • removeZoneTarget - Removes a zone target.
[244] = "M", [82] = ",", [81] = "."
} Fallback: If no targeting system is detected (or if disabled via Config.System.DontUseTarget),
the module uses DrawText3D prompts. This is experimental and may not work as expected.
-- Target Creation -- ]]
-- Target Entities, this is more based on qb-target's style of target creation, and translates those into ox or qb-target code --
local targetEntities = {} -------------------------------------------------------------
-- Utility Data & Tables
--- Creates a target for an entity with specified options and interaction distance. -------------------------------------------------------------
---
--- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets)
--- based on the server configuration. It translates qb-target style options into the appropriate format -- Tables for storing created targets for the fallback system and zone management.
--- for the detected targeting system. local TextTargets = {} -- For fallback DrawText3D targets.
--- local targetEntities = {} -- For entity targets.
---@param entity number The entity ID to create a target for. local boxTargets = {} -- For box-shaped zone targets.
---@param opts table A table of option configurations for the target. local circleTargets = {} -- For circular zone targets.
--- - **icon** (`string`): The icon to display for the option.
--- - **label** (`string`): The label text for the option. -------------------------------------------------------------
--- - **item** (`string|nil`): (Optional) The item associated with the option. -- Entity Target Creation
--- - **job** (`string|nil`): (Optional) The job required to interact with the option. -------------------------------------------------------------
--- - **gang** (`string|nil`): (Optional) The gang required to interact with the option.
--- - **action** (`function|nil`): (Optional) The function to execute when the option is selected. --- Creates a target for an entity with specified options and interaction distance.
---@param dist number The interaction distance for the target. --- Supports different targeting systems (OX Target, QB Target, or custom DrawText3D)
--- --- based on the server configuration.
---@usage ---
--- ```lua --- @param entity number The entity ID for which the target is created.
--- createEntityTarget(entityId, { --- @param opts table Array of option tables. Each option should include:
--- { icon = "fas fa-car", label = "Open Vehicle", action = openVehicle }, --- - icon (string): The icon to display.
--- { icon = "fas fa-lock", label = "Lock Vehicle", action = lockVehicle } --- - label (string): The text label for the option.
--- }, 2.5) --- - item (string|nil): (Optional) An associated item.
--- ``` --- - job (string|nil): (Optional) The job required to interact.
function createEntityTarget(entity, opts, dist) --- - gang (string|nil): (Optional) The gang required to interact.
targetEntities[#targetEntities + 1] = entity --- - action (function|nil): (Optional) The function executed on selection.
local entityCoords = GetEntityCoords(entity) --- @param dist number The interaction distance for the target.
if Config.System.DontUseTarget then ---
debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6DrawText ^7"..entity) --- @usage
local existingTarget = nil --- ```lua
for key, target in pairs(TextTargets) do ---createEntityTarget(entityId, {
if #(target.coords - entityCoords) < 0.01 then -- Adjust the threshold for coordinate matching --- {
existingTarget = target --- action = function()
break --- openStorage()
end --- end,
end --- icon = "fas fa-box",
--- job = "police",
if existingTarget then --- label = "Open Storage",
-- Combine options --- },
local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Extend key table as needed ---}, 2.0)
for i = 1, #opts do --- ```
local key = keyTable[#existingTarget.options + i] function createEntityTarget(entity, opts, dist)
opts[i].key = key -- Store the target entity for later cleanup.
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label targetEntities[#targetEntities + 1] = entity
existingTarget.options[#existingTarget.options + 1] = opts[i]
end -- Fallback: Use DrawText3D if targeting systems are disabled or unavailable.
else if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
-- Create new target debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
local tempText = {} exports.jim_bridge:createEntityTarget(entity, opts, dist)
local keyTable = { 38, 29, 303, 45, 46, 47, 48 }
for i = 1, #opts do elseif isStarted(OXTargetExport) then
opts[i].key = keyTable[i] debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
tempText[#tempText + 1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label local options = {}
end for i = 1, #opts do
TextTargets[entity] = { coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z), buttontext = tempText, options = opts, dist = dist } options[i] = {
end icon = opts[i].icon,
elseif isStarted(OXTargetExport) then label = opts[i].label,
debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..OXTargetExport.." ^7"..entity) items = opts[i].item or nil,
local options = {} groups = opts[i].job or opts[i].gang,
for i = 1, #opts do onSelect = opts[i].action,
options[i] = { distance = dist,
icon = opts[i].icon, canInteract = opts[i].canInteract or nil,
label = opts[i].label, }
item = opts[i].item or nil, end
groups = opts[i].job or opts[i].gang, exports[OXTargetExport]:addLocalEntity(entity, options)
onSelect = opts[i].action,
canInteract = function(_, distance) elseif isStarted(QBTargetExport) then
return distance < dist and true or false debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..QBTargetExport.." ^2for entity ^7"..entity)
end local options = { options = opts, distance = dist }
} exports[QBTargetExport]:AddTargetEntity(entity, options)
end
exports[OXTargetExport]:addLocalEntity(entity, options) end
elseif isStarted(QBTargetExport) then end
debugPrint("^6Bridge^7: ^2Creating new ^3Entity^2 target with ^6"..QBTargetExport.." ^7"..entity)
local options = { options = opts, distance = dist } -------------------------------------------------------------
exports[QBTargetExport]:AddTargetEntity(entity, options) -- Box Zone Target Creation
end -------------------------------------------------------------
end
--- Creates a box-shaped target zone with specified options and interaction distance.
local boxTargets = {} --- Supports different targeting systems based on the server configuration.
---@param data table A table containing the box zone configuration.
--- Creates a box-shaped target zone with specified options and interaction distance. --- - name (`string`): The name identifier for the zone.
--- --- - coords (`vector3`): The center coordinates of the box.
--- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets) --- - width (`number`): The width of the box.
--- based on the server configuration. It translates qb-target style options into the appropriate format --- - height (`number`): The height of the box.
--- for the detected targeting system. --- - options (`table`): A table with additional options:
--- --- - heading (`number`): The rotation angle of the box.
---@param data table A table containing the box zone configuration. --- - debugPoly (`boolean`): Whether to enable debug mode for the zone.
--- - **name** (`string`): The name identifier for the zone. ---
--- - **coords** (`vector3`): The center coordinates of the box. ---@param opts table A table of option configurations for the target.
--- - **width** (`number`): The width of the box. --- - icon (`string`): The icon to display for the option.
--- - **height** (`number`): The height of the box. --- - label (`string`): The label text for the option.
--- - **options** (`table`): A table with additional options: --- - item (`string|nil`): (Optional) The item associated with the option.
--- - **heading** (`number`): The rotation angle of the box. --- - job (`string|nil`): (Optional) The job required to interact with the option.
--- - **debugPoly** (`boolean`): Whether to enable debug mode for the zone. --- - gang (`string|nil`): (Optional) The gang required to interact with the option.
--- --- - onSelect (`function|nil`): (Optional) The function to execute when the option is selected.
---@param opts table A table of option configurations for the target. ---@param dist number The interaction distance for the target.
--- - **icon** (`string`): The icon to display for the option. ---
--- - **label** (`string`): The label text for the option. ---@return string|table name identifier or target object of the created zone.
--- - **item** (`string|nil`): (Optional) The item associated with the option. ---
--- - **job** (`string|nil`): (Optional) The job required to interact with the option. ---@usage
--- - **gang** (`string|nil`): (Optional) The gang required to interact with the option. ---```lua
--- - **onSelect** (`function|nil`): (Optional) The function to execute when the option is selected. ---createBoxTarget(
---@param dist number The interaction distance for the target. --- {
--- --- 'storageBox',
---@return string|table name identifier or target object of the created zone. --- vector3(100.0, 200.0, 30.0),
--- --- 2.0,
---@usage --- 2.0,
--- ```lua --- {
--- createBoxTarget({ --- name = 'storageBox',
--- name = 'storageBox', --- heading = 100.0,
--- coords = vector3(100.0, 200.0, 30.0), --- debugPoly = true,
--- width = 2.0, --- minZ = 27.0,
--- height = 2.0, --- maxZ = 32.0,
--- options = { heading = 0, debugPoly = false } --- },
--- }, { --- },
--- { icon = "fas fa-box", label = "Open Storage", action = openStorage } ---{
--- }, 1.5) --- {
--- ``` --- action = function()
function createBoxTarget(data, opts, dist) --- openStorage()
if Config.System.DontUseTarget then --- end,
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^7"..data[1]) --- icon = "fas fa-box",
local existingTarget = nil --- job = "police",
for key, target in pairs(TextTargets) do --- label = "Open Storage",
if #(target.coords - data[2]) < 0.01 then -- Adjust the threshold as needed for coordinate precision --- },
existingTarget = target ---}, 2.0)
break ---```
end function createBoxTarget(data, opts, dist)
end if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
local keyTable = { 38, 29, 303, 45, 46, 47, 48 } debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^2 for zone ^7"..data[1])
return exports.jim_bridge:createZoneTarget(data, opts, dist)
if existingTarget then
-- Combine options elseif isStarted(OXTargetExport) then
for i = 1, #opts do debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local key = keyTable[#existingTarget.options + i] local options = {}
opts[i].key = key for i = 1, #opts do
existingTarget.buttontext[#existingTarget.buttontext+1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label options[i] = {
existingTarget.options[#existingTarget.options+1] = opts[i] icon = opts[i].icon,
end label = opts[i].label,
else items = opts[i].item or nil,
-- Create new target groups = opts[i].job or opts[i].gang,
local tempText = {} onSelect = opts[i].onSelect or opts[i].action,
for i = 1, #opts do distance = dist,
opts[i].key = keyTable[i] canInteract = opts[i].canInteract or nil,
tempText[#tempText+1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label }
end end
TextTargets[data[1]] = { coords = data[2], buttontext = tempText, options = opts, dist = 1.5 } if not data[5].useZ then
end local z = data[2].z + math.abs(data[5].maxZ - data[5].minZ) / 2
return data[1] data[2] = vec3(data[2].x, data[2].y, z)
elseif isStarted(OXTargetExport) then end
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^7"..data[1]) local target = exports[OXTargetExport]:addBoxZone({
local options = {} coords = data[2],
for i = 1, #opts do size = vec3(data[4], data[3], (data[5].useZ or not data[5].maxZ) and data[2].z or math.abs(data[5].maxZ - data[5].minZ)),
options[i] = { rotation = data[5].heading,
icon = opts[i].icon, debug = data[5].debugPoly,
label = opts[i].label, options = options
item = opts[i].item or nil, })
groups = opts[i].job or opts[i].gang, boxTargets[#boxTargets + 1] = target
onSelect = opts[i].onSelect or opts[i].action, return target
canInteract = function(_, distance)
return distance < dist and true or false elseif isStarted(QBTargetExport) then
end debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^2for zone ^7"..data[1])
} local options = { options = opts, distance = dist }
end local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
if not data[5].useZ then boxTargets[#boxTargets + 1] = target
local z = data[2].z + math.abs(data[5].maxZ - data[5].minZ) / 2 return data[1]
data[2] = vec3(data[2].x, data[2].y, z)
end end
local target = exports[OXTargetExport]:addBoxZone({ end
coords = data[2],
size = vec3(data[4], data[3], (data[5].useZ or not data[5].maxZ) and data[2].z or math.abs(data[5].maxZ - data[5].minZ)), -------------------------------------------------------------
rotation = data[5].heading, -- Circle Zone Target Creation
debug = data[5].debugPoly, -------------------------------------------------------------
options = options
}) --- Creates a circular target zone with specified options and interaction distance.
boxTargets[#boxTargets+1] = target --- Supports different targeting systems based on server configuration.
return target ---
elseif isStarted(QBTargetExport) then ---@param data table A table containing the circle zone configuration.
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^7"..data[1]) --- - name (`string`): The name identifier for the zone.
local options = { options = opts, distance = dist } --- - coords (`vector3`): The center coordinates of the circle.
local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options) --- - radius (`number`): The radius of the circle.
boxTargets[#boxTargets+1] = target --- - options (`table`): A table with additional options:
return data[1] --- - debugPoly (`boolean`): Whether to enable debug mode for the zone.
end ---
end ---@param opts table A table of option configurations for the target.
--- - icon (`string`): The icon to display for the option.
local circleTargets = {} --- - label (`string`): The label text for the option.
--- - item (`string|nil`): (Optional) The item associated with the option.
--- Creates a circular target zone with specified options and interaction distance. --- - job (`string|nil`): (Optional) The job required to interact with the option.
--- --- - gang (`string|nil`): (Optional) The gang required to interact with the option.
--- This function supports different targeting systems (OX Target, QB Target, or custom DrawText3D targets) --- - onSelect (`function|nil`): (Optional) The function to execute when the option is selected.
--- based on the server configuration. It translates qb-target style options into the appropriate format ---@param dist number The interaction distance for the target.
--- for the detected targeting system. ---
--- ---@return string|table name identifier or target object of the created zone.
---@param data table A table containing the circle zone configuration. ---
--- - **name** (`string`): The name identifier for the zone. ---@usage
--- - **coords** (`vector3`): The center coordinates of the circle. --- ```lua
--- - **radius** (`number`): The radius of the circle. --- createCircleTarget({
--- - **options** (`table`): A table with additional options: --- name = 'centralPark',
--- - **debugPoly** (`boolean`): Whether to enable debug mode for the zone. --- coords = vector3(200.0, 300.0, 40.0),
--- --- radius = 50.0,
---@param opts table A table of option configurations for the target. --- options = { debugPoly = false }
--- - **icon** (`string`): The icon to display for the option. --- }, {
--- - **label** (`string`): The label text for the option. --- { icon = "fas fa-tree", label = "Relax", action = relaxAction }
--- - **item** (`string|nil`): (Optional) The item associated with the option. --- }, 2.0)
--- - **job** (`string|nil`): (Optional) The job required to interact with the option. --- ```
--- - **gang** (`string|nil`): (Optional) The gang required to interact with the option. function createCircleTarget(data, opts, dist)
--- - **onSelect** (`function|nil`): (Optional) The function to execute when the option is selected. if Config.System.DontUseTarget then
---@param dist number The interaction distance for the target. debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6DrawText ^2for zone ^7"..data[1])
--- return exports.jim_bridge:createZoneTarget(data, opts, dist)
---@return string|table name identifier or target object of the created zone.
--- elseif isStarted(OXTargetExport) then
---@usage debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
--- ```lua local options = {}
--- createCircleTarget({ for i = 1, #opts do
--- name = 'centralPark', options[i] = {
--- coords = vector3(200.0, 300.0, 40.0), icon = opts[i].icon,
--- radius = 50.0, label = opts[i].label,
--- options = { debugPoly = false } items = opts[i].item or nil,
--- }, { groups = opts[i].job or opts[i].gang,
--- { icon = "fas fa-tree", label = "Relax", action = relaxAction } onSelect = opts[i].onSelect or opts[i].action,
--- }, 2.0) distance = dist,
--- ``` canInteract = opts[i].canInteract or nil,
function createCircleTarget(data, opts, dist) }
if Config.System.DontUseTarget then end
debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6DrawText ^7"..data[1]) local target = exports[OXTargetExport]:addSphereZone({
local existingTarget = nil coords = data[2],
for key, target in pairs(TextTargets) do radius = data[3],
if #(target.coords - data[2]) < 0.01 then -- Adjust the threshold for precision debug = data[4].debugPoly,
existingTarget = target options = options
break })
end circleTargets[#circleTargets + 1] = target
end return target
elseif isStarted(QBTargetExport) then
if existingTarget then debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..QBTargetExport.." ^2for zone ^7"..data[1])
-- Combine options local options = { options = opts, distance = dist }
local keyTable = { 38, 29, 303, 45, 46, 47, 48 } -- Extend key table as needed local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options)
for i = 1, #opts do circleTargets[#circleTargets + 1] = target
local key = keyTable[#existingTarget.options + i] return data[1]
opts[i].key = key end
existingTarget.buttontext[#existingTarget.buttontext+1] = " ~b~[~w~"..Keys[key].."~b~] ~w~"..opts[i].label end
existingTarget.options[#existingTarget.options+1] = opts[i]
end -------------------------------------------------------------
else -- Model Target Creation
-- Create new target -------------------------------------------------------------
local tempText = ""
local keyTable = { 38, 29, 303, 45, 46, 47, 48 } --- Creates a target for models with specified options and interaction distance.
for i = 1, #opts do --- Supports different targeting systems (OX Target, QB Target) based on server configuration.
opts[i].key = keyTable[i] ---
tempText[#tempText+1] = " ~b~[~w~"..Keys[opts[i].key].."~b~] ~w~"..opts[i].label --- @param models table Array of model identifiers.
end --- @param opts table Array of option tables (same structure as in createEntityTarget).
TextTargets[data[1]] = { coords = data[2], buttontext = tempText, options = opts, dist = dist } --- @param dist number The interaction distance for the target.
end ---
return data[1] --- @usage
elseif isStarted(OXTargetExport) then --- ```lua
debugPrint("^6Bridge^7: ^2Creating new ^3Sphere^2 target with ^6"..OXTargetExport.." ^7"..data[1]) ---createModelTarget({ model1, model2 },
local options = {} ---{
for i = 1, #opts do --- {
options[i] = { --- action = function()
icon = opts[i].icon, --- openStorage()
label = opts[i].label, --- end,
item = opts[i].item or nil, --- icon = "fas fa-box",
groups = opts[i].job or opts[i].gang, --- job = "police",
onSelect = opts[i].onSelect or opts[i].action, --- label = "Open Storage",
canInteract = function(_, distance) --- },
return distance < dist and true or false ---}, 2.0)
end ---```
} function createModelTarget(models, opts, dist)
end if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
local target = exports[OXTargetExport]:addSphereZone({ return exports.jim_bridge:createModelTarget(models, opts, dist)
coords = data[2],
radius = data[3], elseif isStarted(OXTargetExport) then
debug = data[4].debugPoly, debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..OXTargetExport)
options = options local options = {}
}) for i = 1, #opts do
circleTargets[#circleTargets+1] = target options[i] = {
return target icon = opts[i].icon,
elseif isStarted(QBTargetExport) then label = opts[i].label,
debugPrint("^6Bridge^7: ^2Creating new ^3Circle^2 target with ^6"..QBTargetExport.." ^7"..data[1]) items = opts[i].item or nil,
local options = { options = opts, distance = dist } groups = opts[i].job or opts[i].gang,
local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options) onSelect = opts[i].action,
circleTargets[#circleTargets+1] = target distance = dist,
return data[1] canInteract = opts[i].canInteract or nil,
end }
end end
exports[OXTargetExport]:addModel(models, options)
-- Simple function to remove an entity target created within the script -- elseif isStarted(QBTargetExport) then
--- Removes a previously created entity target. debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..QBTargetExport)
--- local options = { options = opts, distance = dist }
--- This function removes the target associated with the specified entity based on the active targeting system. exports[QBTargetExport]:AddTargetModel(models, options)
--- end
--- @param entity number The entity ID whose target should be removed. end
---
--- @usage -------------------------------------------------------------
--- removeEntityTarget(entityId) -- Target Removal Functions
function removeEntityTarget(entity) -------------------------------------------------------------
if isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveTargetEntity(entity) end
if isStarted(OXTargetExport) then exports[OXTargetExport]:removeLocalEntity(entity, nil) end --- Removes a previously created entity target.
if Config.System.DontUseTarget then TextTargets[entity] = nil end ---
end --- @param entity number The entity ID whose target should be removed.
---
-- Simple function to remove circle or box targets in the script -- --- @usage
--- Removes a previously created zone target. --- ```lua
--- --- removeEntityTarget(entityId)
--- This function removes the target associated with the specified zone based on the active targeting system. --- ```
--- function removeEntityTarget(entity)
--- @param target string|table The name identifier or target object of the zone to remove. if isStarted(QBTargetExport) then
--- exports[QBTargetExport]:RemoveTargetEntity(entity)
--- @usage end
--- ```lua if isStarted(OXTargetExport) then
--- removeZoneTarget('centralPark') exports[OXTargetExport]:removeLocalEntity(entity, nil)
--- removeZoneTarget(targetObject) end
--- ``` if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
function removeZoneTarget(target) exports.jim_bridge:removeEntityTarget(entity)
if isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(target) end end
if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(target, true) end end
if Config.System.DontUseTarget then TextTargets[target] = nil end
end --- Removes a previously created zone target.
---
-- If no target script is found, default to DrawText3D targets -- * experimental * --- @param target string|table The name identifier or target object of the zone to remove.
if Config.System.DontUseTarget and not isServer() then ---
CreateThread(function() --- @usage
while true do --- ```lua
local pedCoords = GetEntityCoords(PlayerPedId()) --- removeZoneTarget('centralPark')
local camCoords = GetGameplayCamCoord() --- removeZoneTarget(targetObject)
local camRotation = GetGameplayCamRot(2) -- Get camera rotation in degrees --- ```
local camForwardVector = RotationToDirection(camRotation) -- Convert rotation to direction vector function removeZoneTarget(target)
if isStarted(QBTargetExport) then
local closestTarget = nil exports[QBTargetExport]:RemoveZone(target)
local closestDist = math.huge end
if isStarted(OXTargetExport) then
for k, v in pairs(TextTargets) do exports[OXTargetExport]:removeZone(target, true)
local targetCoords = v.coords end
local dist = #(pedCoords - targetCoords) if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
local vecToTarget = targetCoords - camCoords exports.jim_bridge:removeZoneTarget(target)
end
-- Normalize the vector to the target end
local vecToTargetNormalized = normalizeVector(vecToTarget)
--- Removes a previously created model target.
-- Dot product to check if facing the target ---
local dot = camForwardVector.x * vecToTargetNormalized.x + camForwardVector.y * vecToTargetNormalized.y + camForwardVector.z * vecToTargetNormalized.z --- @param model table The model ID whose target should be removed.
---
local isFacingTarget = dot > 0.5 -- Adjust threshold as needed --- @usage
--- ```lua
if dist <= v.dist and isFacingTarget then --- removeModelTarget(model)
if dist < closestDist then --- ```
closestDist = dist function removeModelTarget(model)
closestTarget = v if isStarted(QBTargetExport) then
end exports[QBTargetExport]:RemoveTargetModel(model, "Test")
end end
end if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeModel(model, nil)
for k, v in pairs(TextTargets) do end
local isClosest = (v == closestTarget) if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
if #(pedCoords - v.coords) <= v.dist then exports.jim_bridge:removeZoneTarget(target)
for i = 1, #v.options do end
if IsControlJustPressed(0, v.options[i].key) and isClosest then end
if v.options[i].onSelect then v.options[i].onSelect() end -------------------------------------------------------------
if v.options[i].action then v.options[i].action() end -- Fallback: DrawText3D Targets (Experimental)
end -------------------------------------------------------------
end
DrawText3D(vec3(v.coords.x, v.coords.y, v.coords.z + 0.7), concatenateText(v.buttontext), isClosest) -- If no targeting system is detected and this is a client script, use DrawText3D for targets.
end if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) and not isServer() then
end CreateThread(function()
Wait(0) while true do
end local ped = PlayerPedId()
end) local pedCoords = GetEntityCoords(ped)
end local camCoords = GetGameplayCamCoord()
local camRot = GetGameplayCamRot(2)
-- If the current loaded script is stopped, automatically remove targets -- local camForward = RotationToDirection(camRot)
onResourceStop(function()
for i = 1, #targetEntities do local closestTarget = nil
if isStarted(OXTargetExport) then exports[OXTargetExport]:removeLocalEntity(targetEntities[i], nil) local closestDist = math.huge
elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveTargetEntity(targetEntities[i]) end local targetEntity = nil
end
for i = 1, #boxTargets do -- Shallow copy for safety
if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(boxTargets[i], true) local targetsCopy = {}
elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(boxTargets[i].name) end for k, v in pairs(TextTargets) do
end targetsCopy[k] = v
for i = 1, #circleTargets do end
if isStarted(OXTargetExport) then exports[OXTargetExport]:removeZone(circleTargets[i], true)
elseif isStarted(QBTargetExport) then exports[QBTargetExport]:RemoveZone(circleTargets[i].name) end -- Detect models and update coords
end for _, target in pairs(targetsCopy) do
if target.models then
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
-- 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
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 + handle input
for _, target in pairs(targetsCopy) do
if target.coords and #(pedCoords - target.coords) <= target.dist then
local isClosest = (target == closestTarget)
for _, opt in ipairs(target.options) do
if IsControlJustPressed(0, opt.key) and isClosest then
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
-- 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
Wait(0)
end
end)
end
function ShowFloatingHelpNotification(coord, text, highlight)
AddTextEntry("FloatingText", text)
SetFloatingHelpTextWorldPosition(1, coord.x, coord.y, coord.z)
SetFloatingHelpTextStyle(1, 1, 62, -1, 3, 0)
BeginTextCommandDisplayHelp("FloatingText")
EndTextCommandDisplayHelp(2, false, false, -1)
end
function updateCachedText(target)
target.text = table.concat(target.buttontext, "\n")
end
-------------------------------------------------------------
-- Cleanup on Resource Stop
-------------------------------------------------------------
-- When the current resource stops, remove all targets.
onResourceStop(function()
-- Remove entity targets.
for i = 1, #targetEntities do
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeLocalEntity(targetEntities[i], nil)
elseif isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveTargetEntity(targetEntities[i])
end
end
-- Remove box zone targets.
for i = 1, #boxTargets do
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeZone(boxTargets[i], true)
elseif isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveZone(boxTargets[i].name)
end
end
-- Remove circle zone targets.
for i = 1, #circleTargets do
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeZone(circleTargets[i], true)
elseif isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveZone(circleTargets[i].name)
end
end
end, true) end, true)

View File

@@ -1,250 +1,302 @@
-- Get Vehicle Info -- --[[
local lastCar = nil Vehicle Info & Properties Module
local carInfo = {} ----------------------------------
This module provides utilities for:
--- Searches the 'Vehicles' table for a specific vehicle's 'name', 'price', and 'class'. - Retrieving vehicle information from a Vehicles table.
--- - Getting and setting vehicle properties using the active framework.
--- This function checks if the provided vehicle is different from the last searched vehicle. - Comparing vehicle property differences.
--- If it's a new vehicle, it retrieves its model and searches the 'Vehicles' table for matching entries. - Synchronizing vehicle properties across clients.
--- It populates the `carInfo` table with the vehicle's name, price, and class. - Managing network control of vehicles.
--- If the vehicle is not found in the table, it defaults to using the vehicle's display name and sets the price to 0. - Finding the closest vehicle to a given position.
--- ]]
---@param vehicle number The entity ID of the vehicle to search for.
--- -- Cached vehicle info to avoid unnecessary re-searches.
---@return table|nil table containing the vehicle's `name`, `price`, and `class`, or `nil` if the vehicle is invalid. local lastCar, carInfo = nil, {}
---
---@usage --- Searches the 'Vehicles' table for a specific vehicle's details.
--- ```lua --- If the vehicle differs from the last searched, it retrieves its model and updates the carInfo table.
--- local info = searchCar(vehicleEntity) --- The table includes the vehicle's name, price, and class information.
--- print(info.name, info.price, info.class) ---
--- ``` --- @param vehicle number The entity ID of the vehicle to search for.
function searchCar(vehicle) --- @return table|nil table A table containing the vehicle's details or nil if the vehicle is invalid.
if lastCar ~= vehicle then -- If same car, use previous info ---
lastCar = vehicle --- @usage
carInfo = {} --- ```lua
local model = GetEntityModel(vehicle) --- local info = searchCar(vehicleEntity)
local classlist = { --- print(info.name, info.price, info.class.name, info.class.index)
"Compacts", --1 --- ```
"Sedans", --2 function searchCar(vehicle)
"SUVs", --3 if lastCar ~= vehicle then -- If same car, use previous info
"Coupes", --4 lastCar = vehicle
"Muscle", --5 carInfo = {}
"Sports Classics", --6 local model = GetEntityModel(vehicle)
"Sports", --7 local classlist = {
"Super", --8 "Compacts", --1
"Motorcycles", --9 "Sedans", --2
"Off-road", --10 "SUVs", --3
"Industrial", --11 "Coupes", --4
"Utility", --12 "Muscle", --5
"Vans", --13 "Sports Classics", --6
"Cycles", --14 "Sports", --7
"Boats", --15 "Super", --8
"Helicopters", --16 "Motorcycles", --9
"Planes", --17 "Off-road", --10
"Service", --18 "Industrial", --11
"Emergency", --19 "Utility", --12
"Military", --20 "Vans", --13
"Commercial", --21 "Cycles", --14
"Trains", --22 "Boats", --15
} "Helicopters", --16
if Vehicles then "Planes", --17
for k, v in pairs(Vehicles) do "Service", --18
if tonumber(v.hash) == model or GetHashKey(v.hash) == model or GetHashKey(v.model) == model then "Emergency", --19
debugPrint("^6Bridge^7: ^2Vehicle info found in^7 ^4Vehicles^7 ^2table^7: ^6"..(v.hash and v.hash or v.model).. " ^7(^6"..Vehicles[k].name.."^7)") "Military", --20
carInfo.name = Vehicles[k].name.." "..Vehicles[k].brand "Commercial", --21
carInfo.price = Vehicles[k].price "Trains", --22
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle) "Open Wheel", --23
break }
end if Vehicles then
end for k, v in pairs(Vehicles) do
if tonumber(v.hash) == model or GetHashKey(v.hash) == model or GetHashKey(v.model) == model then
if not carInfo.name then debugPrint("^6Bridge^7: ^2Vehicle info found in^7 ^4Vehicles^7 ^2table^7: ^6"..(v.hash and v.hash or v.model).. " ^7(^6"..Vehicles[k].name.."^7)")
debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)") carInfo.name = Vehicles[k].name.." "..Vehicles[k].brand
carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model)) carInfo.price = Vehicles[k].price
carInfo.price = 0 carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle) break
end end
return carInfo end
else
if not carInfo.name then if not carInfo.name then
debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)") debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model)) carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
carInfo.price = 0 carInfo.price = 0
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle) carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
end end
end return carInfo
else else
return carInfo if not carInfo.name then
end debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
end carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
carInfo.price = 0
-- Vehicle Properties -- carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
end
--- Retrieves the properties of a given vehicle. end
--- else
--- This function fetches the vehicle's properties based on the active framework (QBCore or ox). return carInfo
--- It utilizes the framework's native functions or events to obtain the vehicle's mod list and other details. end
--- end
--- @param vehicle number The entity ID of the vehicle.
--- -------------------------------------------------------------
--- @return table|nil table containing the vehicle's properties, or `nil` if the vehicle is invalid or the framework is not detected. -- Vehicle Properties Functions
--- -------------------------------------------------------------
--- @usage
--- ```lua --- Retrieves the properties of a given vehicle using the active framework.
--- local props = getVehicleProperties(vehicleEntity) ---
--- if props then --- @param vehicle number The entity ID of the vehicle.
--- -- Manipulate vehicle properties --- @return table|nil table A table containing the vehicle's properties or nil if invalid.
--- end ---
--- ``` --- @usage
function getVehicleProperties(vehicle) --- ```lua
local properties = {} --- local props = getVehicleProperties(vehicleEntity)
if vehicle == nil then return nil end --- if props then
if isStarted(QBExport) and not isStarted(QBXExport) then --- -- Use vehicle properties
properties = Core.Functions.GetVehicleProperties(vehicle) --- end
debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]") --- ```
elseif isStarted(OXLibExport) then function getVehicleProperties(vehicle)
properties = lib.getVehicleProperties(vehicle) if not vehicle then return nil end
debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
end local properties = {}
return properties if isStarted(QBExport) and not isStarted(QBXExport) then
end properties = Core.Functions.GetVehicleProperties(vehicle)
debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
--- Sets the properties of a given vehicle.
--- elseif isStarted(OXLibExport) then
--- This function applies the provided properties to the vehicle using the active framework's functions or events. properties = lib.getVehicleProperties(vehicle)
--- It first retrieves the current properties and checks for differences before applying the new ones. debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
--- end
---@param vehicle number The entity ID of the vehicle. return properties
---@param props table The properties to set on the vehicle. end
---
---@usage --- Sets the properties of a given vehicle if changes are detected.
--- ```lua --- It compares the current properties with the new ones and applies the update using the active framework.
--- setVehicleProperties(vehicleEntity, newProperties) ---
--- ``` --- @param vehicle number The entity ID of the vehicle.
function setVehicleProperties(vehicle, props) --- @param props table The new properties to apply.
local oldProps = getVehicleProperties(vehicle) ---
if checkDifferences(vehicle, props) then --- @usage
--if debugMode then debugDifferences(vehicle, props) end --- ```lua
if not DoesEntityExist(vehicle) then --- setVehicleProperties(vehicleEntity, newProperties)
print(("Unable to set vehicle properties for '%s' (entity does not exist)"):format(vehicle)) --- ```
end function setVehicleProperties(vehicle, props)
if isStarted(QBExport) and not isStarted(QBXExport) then if checkDifferences(vehicle, props) then
Core.Functions.SetVehicleProperties(vehicle, props) if not DoesEntityExist(vehicle) then
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]") print("Unable to set vehicle properties for '"..vehicle.."' (^1entity does not exist^7)")
else end
TriggerServerEvent(getScript()..":ox:setVehicleProperties", VehToNet(vehicle), props)
end if isStarted(QBExport) and not isStarted(QBXExport) then
else Core.Functions.SetVehicleProperties(vehicle, props)
debugPrint("^6Bridge^7: ^2No Changes Found ^7 [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]") debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
end
end elseif isStarted(OXLibExport) then
lib.setVehicleProperties(vehicle, props, false)
--- Checks for differences between the current and new vehicle properties. debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
--- end
--- This function compares each property of the vehicle to determine if any changes have been made. else
--- It logs the differences for debugging purposes. debugPrint("^6Bridge^7: ^2No Changes Found ^7 [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
--- end
---@param vehicle number The entity ID of the vehicle. end
---@param newProps table The new properties to compare against the current ones.
--- --- Checks for differences between the current and new vehicle properties.
---@return boolean `true` if differences are found, `false` otherwise. --- Compares properties using JSON encoding for deep comparison and logs differences.
--- ---
---@usage --- @param vehicle number The entity ID of the vehicle.
--- ```lua --- @param newProps table The new properties to compare.
--- if checkDifferences(vehicleEntity, newProperties) then --- @return boolean `true` if differences are found; `false` otherwise.
--- setVehicleProperties(vehicleEntity, newProperties) ---
--- end --- @usage
--- ``` --- ```lua
function checkDifferences(vehicle, newProps) --- if checkDifferences(vehicleEntity, newProperties) then
local oldProps = getVehicleProperties(vehicle) --- setVehicleProperties(vehicleEntity, newProperties)
debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7") --- end
local allow = false --- ```
for k in pairs(oldProps) do function checkDifferences(vehicle, newProps)
if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then local oldProps = getVehicleProperties(vehicle)
allow = true debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7")
debugPrint("^6Bridge^7: ^5Old ^7[^3"..k.."^7] - "..json.encode(oldProps[k], { indent = true })) local differencesFound = true
debugPrint("^6Bridge^7: ^5New ^7[^3"..k.."^7] - "..json.encode(newProps[k], { indent = true }))
end for k in pairs(oldProps) do
end if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then
return allow differencesFound = true
end debugPrint("^6Bridge^7: ^5Old ^7[^3"..k.."^7] - "..json.encode(oldProps[k], { indent = true }))
debugPrint("^6Bridge^7: ^5New ^7[^3"..k.."^7] - "..json.encode(newProps[k], { indent = true }))
--- Handles setting vehicle properties received from the server. end
--- end
--- This event listens for the `ox:setVehicleProperties` event and applies the received properties to the vehicle.
--- return differencesFound
---@event end
---@param netId number The network ID of the vehicle.
---@param props table The properties to set on the vehicle. -------------------------------------------------------------
--- -- Vehicle Properties Synchronization
---@usage -------------------------------------------------------------
--- -- Server-side: TriggerClientEvent(getScript()..":ox:setVehicleProperties", netId, properties)
RegisterNetEvent(getScript()..":ox:setVehicleProperties", function(netId, props) --- Event handler for setting vehicle properties received from the server.
local vehicle = NetworkGetEntityFromNetworkId(netId) --- Listens for the `ox:setVehicleProperties` event and applies the properties.
local value = props ---
Entity(vehicle).state[getScript()..':setVehicleProperties'] = value --- @event `getScript()..ox:setVehicleProperties`
end) --- @param netId number The network ID of the vehicle.
--- @param props table The new vehicle properties.
--- Handles state bag changes for setting vehicle properties. RegisterNetEvent(getScript()..":ox:setVehicleProperties", function(netId, props)
--- local vehicle = NetworkGetEntityFromNetworkId(netId)
--- This handler listens for changes to the vehicle's state bag and applies the new properties accordingly. local value = props
--- Entity(vehicle).state[getScript()..':setVehicleProperties'] = value
---@param bagName string The name of the state bag. end)
---@param key string The key that changed.
---@param value table The new value of the state. --- Handles state bag changes for updating vehicle properties.
--- --- When the state bag changes, the new properties are applied to the vehicle.
---@usage ---
--- -- Automatically handled when the state bag changes --- @param bagName string The state bag's name.
AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagName, _, value) --- @param key string The key that changed.
if not value or not GetEntityFromStateBagName then return end --- @param value table The new state value.
local entity = GetEntityFromStateBagName(bagName) AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagName, _, value)
local networked = not bagName:find('localEntity') if not value or not GetEntityFromStateBagName then return end
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..entity.."^7] - [^3"..GetEntityModel(entity).."^7] - [^3"..value.plate.."^7]") local entity = GetEntityFromStateBagName(bagName)
local networked = not bagName:find('localEntity')
if networked and NetworkGetEntityOwner(entity) ~= cache.playerId then return end debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..entity.."^7] - [^3"..GetEntityModel(entity).."^7] - [^3"..value.plate.."^7]")
if lib.setVehicleProperties(entity, value) then if networked then return end
Entity(entity).state:set('setVehicleProperties', nil, true)
end if lib.setVehicleProperties(entity, value) then
end) Entity(entity).state:set('setVehicleProperties', nil, true)
end
--- Pushes a vehicle to other players by syncing it. end)
---
--- 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. -- Vehicle Control Functions
--- -------------------------------------------------------------
---@param entity number The entity ID of the vehicle to push.
--- --- This function ensures that the vehicle is controlled by the current player and is set as a mission entity.
---@usage --- It requests network control and sets the vehicle accordingly to synchronize changes across clients.
--- ```lua ---
--- pushVehicle(vehicleEntity) ---@param entity number The entity ID of the vehicle to push.
--- ``` ---
function pushVehicle(entity) ---@usage
SetVehicleModKit(entity, 0) --- ```lua
if entity ~= 0 and DoesEntityExist(entity) then --- pushVehicle(vehicleEntity)
if not NetworkHasControlOfEntity(entity) then --- ```
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.") function pushVehicle(entity)
NetworkRequestControlOfEntity(entity) SetVehicleModKit(entity, 0)
local timeout = 2000 if entity ~= 0 and DoesEntityExist(entity) then
while timeout > 0 and not NetworkHasControlOfEntity(entity) do -- Request network control if not already controlled.
Wait(100) if not NetworkHasControlOfEntity(entity) then
timeout = timeout - 100 debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.")
end NetworkRequestControlOfEntity(entity)
if NetworkHasControlOfEntity(entity) then local timeout = 2000
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.") while timeout > 0 and not NetworkHasControlOfEntity(entity) do
end Wait(100)
end timeout = timeout - 100
if not IsEntityAMissionEntity(entity) then end
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' &2entity^7.") if NetworkHasControlOfEntity(entity) then
SetEntityAsMissionEntity(entity, true, true) debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network now has control of the entity^7.")
local timeout = 2000 end
while timeout > 0 and not IsEntityAMissionEntity(entity) do end
Wait(100)
timeout = timeout - 100 -- Set as mission entity if not already set.
end if not IsEntityAMissionEntity(entity) then
if IsEntityAMissionEntity(entity) then debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.")
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") SetEntityAsMissionEntity(entity, true, true)
end local timeout = 2000
end while timeout > 0 and not IsEntityAMissionEntity(entity) do
end Wait(100)
end timeout = timeout - 100
end
if IsEntityAMissionEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.")
end
end
end
end
--- Finds the closest vehicle to the specified coordinates.
--- The function uses different APIs based on whether a source is provided.
---
--- @param coords table|vector3 (Optional) The reference coordinates. If nil, uses the player's position.
--- @param src boolean (Optional) If true, uses GetPlayerPed(source) and GetAllVehicles.
--- @return number|number closestVehicle|closestDistance The closest vehicle entity and its distance.
---
--- @usage
--- ```lua
--- local closestVeh, distance = getClosestVehicle({ x = 100, y = 200, z = 30 }, src)
--- ```
function getClosestVehicle(coords, src)
local ped, vehicles, closestDistance, closestVehicle
if src then
ped = GetPlayerPed(src)
vehicles = GetAllVehicles()
else
ped = PlayerPedId()
vehicles = GetGamePool('CVehicle')
end
local closestDistance, closestVehicle = -1, -1
if coords then
if type(coords) == 'table' then
coords = vec3(coords.x, coords.y, coords.z)
end
else
coords = GetEntityCoords(ped)
end
for i = 1, #vehicles, 1 do
local vehicleCoords = GetEntityCoords(vehicles[i])
local distance = #(vehicleCoords - coords)
if closestDistance == -1 or distance < closestDistance then
closestDistance = distance
closestVehicle = vehicles[i]
end
end
return closestVehicle, closestDistance
end

View File

@@ -1,47 +0,0 @@
-- Version check for jim_bridge --
function CheckBridgeVersion()
if isServer() then
local currentVersion = "^3"..GetResourceMetadata("jim_bridge", 'version'):gsub("%.", "^7.^3").."^7"
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, newestVersion, headers)
if not newestVersion then print("^1Currently unable to run a version check for ^7'^3jim_bridge^7' ("..currentVersion.."^7)") return end
newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
print(newestVersion == currentVersion and "^7'^3jim_bridge^7' - ^6You are running the latest version.^7 ("..currentVersion..")" or "^7'^3jim_bridge^7' - ^1You are currently running an outdated version^7, ^1please update^7!")
end)
end
end
--CheckBridgeVersion()
-- Print Script names
function capitalize(str)
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
end
local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
-- Loaded script Version Check, requires CheckVersion() to be placed in a server file
function CheckVersion()
if isServer() then
local currentVersion = "^3"..GetResourceMetadata(getScript(), 'version'):gsub("%.", "^7.^3").."^7"
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..getScript()..'.txt', function(err, newestVersion, headers)
if not newestVersion then
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..getScript()..'/master/version.txt', function(err, freeVersion, headers)
if not freeVersion then print("^1Currently unable to run a version check for ^7'^3"..getScript().."^7' ("..currentVersion.."^7)") return end
local currentVersion = "^3"..GetResourceMetadata(getScript(), 'version'):gsub("%.", "^7.^3").."^7"
freeVersion = "^3"..freeVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..freeVersion)
print(freeVersion == currentVersion and "^7'^3"..getScript().."^7' - ^6You are running the latest version.^7 ("..currentVersion..")" or "^7'^3"..getScript().."^7' - ^1You are currently running an outdated version^7, ^1please update^7!")
end)
else
newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3"):gsub("%\r", "").."^7"
print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..newestVersion)
print(newestVersion == currentVersion and '^6You are running the latest version.^7 ('..currentVersion..')' or "^1You are currently running an outdated version^7, ^1please update^7!")
end
end)
end
end
--CheckVersion()

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

@@ -1,260 +1,49 @@
-- Phone Mails
--- Registers a command with the active command system.
--- Sends a phone mail using the detected phone system. --- This function supports multiple command systems (OXLib, qb-core, ESX Legacy).
--- ---
--- This function detects the active phone resource (e.g., gksphone, yflip-phone, qb-phone, etc.) --- @param command string The name of the command to register.
--- and sends a mail using the appropriate method for that phone system. --- @param options table A table containing command options.
--- --- - help (`string`): The help description for the command.
--- @param data table A table containing the mail data. --- - params (`table`): A table of parameters for the command.
--- - **subject** (`string`): The subject of the email. --- - callback (`function`): The function to execute when the command is called.
--- - **sender** (`string`): The sender of the email. --- - autocomplete (`function|nil`): (Optional) A function for autocompletion.
--- - **message** (`string`): The body content of the email. --- - restrictedGroup (`string|nil`): (Optional) The user group required to execute the command.
--- - **actions** (`table|nil`): (Optional) Action buttons associated with the email. ---
--- --- @usage
--- @usage --- ```lua
--- ```lua --- registerCommand("greet", {
--- sendPhoneMail({ --- "Greets the player",
--- subject = "Welcome!", --- { name = "name", help = "Name of the player to greet" },
--- sender = "Admin", --- function(source, args) print("Hello, "..args[1].."!") end,
--- message = "Thank you for joining our server.", --- nil,
--- actions = { --- "admin"
--- { label = "Reply", action = replyFunction } --- })
--- } --- ```
--- }) function registerCommand(command, options)
--- ``` local commandResource = ""
function sendPhoneMail(data) local phoneResource = "" if isStarted(OXLibExport) then
if isStarted("gksphone") then phoneResource = "gksphone" commandResource = OXLibExport
exports["gksphone"]:SendNewMail(data) lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4])
elseif isStarted("yflip-phone") then phoneResource = "yflip-phone" elseif isStarted(QBExport) and not isStarted(QBXExport) then
TriggerServerEvent(getScript()..":yflip:SendMail", data) commandResource = QBExport
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
elseif isStarted("qs-smartphone") then phoneResource = "qs-smartphone"
TriggerServerEvent('qs-smartphone:server:sendNewMail', data) elseif isStarted(RSGExport) then
commandResource = RSGExport
elseif isStarted("qs-smartphone-pro") then phoneResource = "qs-smartphone-pro" Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
TriggerServerEvent('phone:sendNewMail', data)
elseif isStarted(ESXExport) then
elseif isStarted("roadphone") then phoneResource = "roadphone" commandResource = ESXExport
data.message = data.message:gsub("%<br>", "\n") ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError)
exports['roadphone']:sendMail(data) options[4](xPlayer.source, args, showError)
end, false, { help = options[1] })
elseif isStarted("lb-phone") then phoneResource = "lb-phone"
data.message = data.message:gsub("%<br>", "\n") end
TriggerServerEvent(getScript()..":lbphone:SendMail", data) if commandResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^4"..commandResource.."^7:", "^7/"..command)
elseif isStarted("qb-phone") then phoneResource = "qb-phone" else
TriggerServerEvent('qb-phone:server:sendNewMail', data) print("^1ERROR^7: ^1Couldn't find supported framework to register command^7:", "/"..command)
end
elseif isStarted("jpr-phonesystem") then phoneResource = "jpr-phonesystem"
TriggerServerEvent(getScript()..":jpr:SendMail", data)
end
if phoneResource ~= "" then debugPrint("^6Bridge^7[^3"..phoneResource.."^7]: ^2Sending mail to player")
else print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7 - ^2No supported phone found") end
end
--- Handles sending mail for lb-phone.
---
--- This event listens for the `lbphone:SendMail` event and sends an email using lb-phone's API.
---
--- @event
--- @param data table The mail data.
--- - **subject** (`string`): The subject of the email.
--- - **message** (`string`): The body content of the email.
--- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
---
--- @usage
--- ```
--- -- Server-side:
--- TriggerClientEvent(getScript()..":lbphone:SendMail", data)
--- ```
RegisterNetEvent(getScript()..":lbphone:SendMail", function(data)
local src = source
local phoneNumber = exports["lb-phone"]:GetEquippedPhoneNumber(src)
local emailAddress = exports["lb-phone"]:GetEmailAddress(phoneNumber)
if data.actions then data.buttons = data.actions end
exports["lb-phone"]:SendMail({
to = emailAddress,
subject = data.subject,
message = data.message,
actions = data.buttons,
})
end)
--- Handles sending mail for yflip-phone.
---
--- This event listens for the `yflip:SendMail` event and sends an email using yflip-phone's API.
---
--- @event
--- @param data table The mail data.
--- - **subject** (`string`): The subject of the email.
--- - **sender** (`string`): The sender of the email.
--- - **message** (`string`): The body content of the email.
--- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
---
--- @usage
--- ```lua
--- -- Server-side:
--- TriggerClientEvent(getScript()..":yflip:SendMail", data)
--- ```
RegisterNetEvent(getScript()..":yflip:SendMail", function(data)
local src = source
exports["yflip-phone"]:SendMail({
title = data.subject,
sender = data.sender,
senderDisplayName = data.sender,
content = data.message,
actions = data.buttons,
}, 'source', src)
end)
--- Handles sending mail for jpr-phonesystem.
---
--- This event listens for the `jpr:SendMail` event and sends an email using jpr-phonesystem's API.
---
--- @event
--- @param data table The mail data.
--- - **subject** (`string`): The subject of the email.
--- - **sender** (`string`): The sender of the email.
--- - **message** (`string`): The body content of the email.
--- - **buttons** (`table|nil`): (Optional) Action buttons associated with the email.
---
--- @return void
---
--- @usage
--- ```lua
--- -- Server-side:
--- TriggerClientEvent(getScript()..":jpr:SendMail", data)
--- ```
RegisterNetEvent(getScript()..":jpr:SendMail", function(data)
local src = source
local Player = Core.Functions.GetPlayer(src)
TriggerEvent('jpr-phonesystem:server:sendEmail', {
Assunto = data.subject, -- Subject
Conteudo = data.message, -- Content
Enviado = data.sender, -- Submitted by
Destinatario = Player.PlayerData.citizenid, -- Target
Event = {}, -- Optional
})
end)
-- Server-Side Functions for Registering Commands, Stashes, and Shops
--- Registers a command with the active command system.
---
--- This function detects whether the server is using OXLib or qb-core for command registration
--- and registers the command accordingly.
---
--- @param command string The name of the command to register.
--- @param options table A table containing command options.
--- - **help** (`string`): The help description for the command.
--- - **params** (`table`): A table of parameters for the command.
--- - **callback** (`function`): The function to execute when the command is called.
--- - **autocomplete** (`function|nil`): (Optional) A function for autocompletion.
--- - **restrictedGroup** (`string|nil`): (Optional) The user group required to execute the command.
---
--- @usage
--- ````lua
--- -- Server Side:
--- registerCommand("greet", {
--- "Greets the player",
--- { name = "name", help = "Name of the player to greet" },
--- function(source, args) print("Hello, " .. args[1] .. "!") end,
--- nil,
--- "admin"
--- })
--- ```
function registerCommand(command, options)
if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^7"..OXLibExport, command)
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)
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] and options[5] or nil)
end
end
--- Registers a stash with the active inventory system.
---
--- This function detects whether the server is using OXInv or QSInv and registers the stash accordingly.
---
--- @param name string The unique identifier for the stash.
--- @param label string The display name for the stash.
--- @param slots number|nil (Optional) The number of slots in the stash. Defaults to 50.
--- @param weight number|nil (Optional) The maximum weight the stash can hold. Defaults to 4,000,000.
--- @param owner string|nil (Optional) The owner identifier for personal stashes.
--- @param coords table|nil (Optional) The coordinates for the stash location.
---
--- @usage
--- ```lua
--- registerStash("playerStash", "Player Stash", 100, 8000000, "player123", { x = 100.0, y = 200.0, z = 30.0 })
--- ```
function registerStash(name, label, slots, weight, owner, coords)
if isStarted(OXInv) then
debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Stash^7:", name, label, owner or nil)
exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil)
elseif isStarted(QSInv) then
debugPrint("^6Bridge^7: ^2Registering ^3QS ^2Stash^7:", name, label)
exports[QSInv]:RegisterStash(name, slots or 50, weight or 4000000)
end
end
--- Registers a shop with the active inventory system.
---
--- This function detects whether the server is using OXInv or QBInv and registers the shop accordingly.
---
--- @param name string The unique identifier for the shop.
--- @param label string The display name for the shop.
--- @param items table The list of items available in the shop.
--- @param society string|nil (Optional) The society identifier for shared shops.
---
--- @usage
--- ```lua
--- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons")
--- ```
function registerShop(name, label, items, society)
if isStarted(OXInv) then
debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Store^7:", name, label)
exports[OXInv]:RegisterShop(
name, {
name = label,
inventory = items,
society = society,
}
)
elseif isStarted(QBInv) and QBInvNew then
debugPrint("^6Bridge^7: ^2Registering ^3QB ^2Store^7:", name, label)
print(json.encode(items, {indent = true}))
exports[QBInv]:CreateShop({
name = name,
label = label,
slots = #items,
items = items,
society = society,
})
end
end
-- Server-Side Event Registration
if isServer() then
--- Registers an event to create an OX stash from the server.
---
--- @event
--- @param name string The unique identifier for the stash.
--- @param label string The display name for the stash.
--- @param slots number|nil (Optional) The number of slots in the stash.
--- @param weight number|nil (Optional) The maximum weight the stash can hold.
--- @param owner string|nil (Optional) The owner identifier for personal stashes.
--- @param coords table|nil (Optional) The coordinates for the stash location.
---
--- @usage
--- ```lua
--- -- Server-side:
--- TriggerEvent(getScript()..":server:makeOXStash", name, label, slots, weight, owner, coords)
--- ```
RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords)
registerStash(name, label, slots, weight, owner, coords)
end)
end end

View File

@@ -1,89 +1,236 @@
Exports = { gameName = not IsDuplicityVersion() and GetCurrentGameName()
QBExport = "qb-core",
QBXExport = "qbx_core", Exports = {
ESXExport = "es_extended", QBExport = "qb-core",
OXCoreExport = "ox_core", QBXExport = "qbx_core",
ESXExport = "es_extended",
OXInv = "ox_inventory", OXCoreExport = "ox_core",
QBInv = "qb-inventory",
PSInv = "ps-inventory", OXInv = "ox_inventory",
QSInv = "qs-inventory", QBInv = "qb-inventory",
CoreInv = "core_inventory", PSInv = "ps-inventory",
CodeMInv = "codem-inventory", QSInv = "qs-inventory",
OrigenInv = "origen_inventory", CoreInv = "core_inventory",
CodeMInv = "codem-inventory",
OXLibExport = "ox_lib", OrigenInv = "origen_inventory",
TgiannInv = "tgiann-inventory",
QBMenuExport = "qb-menu", JPRInv = "jpr-inventory",
QBTargetExport = "qb-target", OXLibExport = "ox_lib",
OXTargetExport = "ox_target"
} QBMenuExport = "qb-menu",
-- Required variables QBTargetExport = "qb-target",
debugMode = Config.System.Debug OXTargetExport = "ox_target",
QBInvNew = true -- REDM
RSGExport = "rsg-core",
InventoryWeight = 120000 RSGInv = "rsg-inventory"
}
-- Load files here into the invoking script
for _, v in pairs({ -- This is a specific load order -- Required variables
'helpers.lua', -- needs to be first debugMode = Config.System.Debug
'_loaders.lua',
QBInvNew = true
'_eventDebug.lua',
'coreloader.lua', -- needs to be second to load all core related stuff before everything else InventoryWeight = 120000
'callback.lua',
-- [[ Server.Cfg Convar Check ]]--
'duifunctions.lua', -- Check server convars for hard set defaults, otherwise it uses per script configurations
if Config and Config.System then
-- Native Scaleforms if Config.System.Debug then
'scaleforms/bigMessageInstance.lua', if GetConvar("jim_DisableDebug", "false") == "true" then
'scaleforms/countDownHandler.lua', debugMode = false
'scaleforms/debugScaleform.lua', end
'scaleforms/instructionalButtons.lua', if GetConvar("jim_DisableEventDebug", "false") == "true" then
'scaleforms/timerBars.lua', Config.System.EventDebug = false
end
-- Required functions end
'make/loaders.lua',
'make/makeBlip.lua', Config.System.Menu = GetConvar("jim_menuScript", Config.System.Menu)
'make/makePed.lua', Config.System.Notify = GetConvar("jim_notifyScript", Config.System.Notify or "gta")
'make/makeProp.lua', Config.System.ProgressBar = GetConvar("jim_progressBarScript", Config.System.ProgressBar or "gta")
'make/makeVeh.lua', Config.System.drawText = GetConvar("jim_drawTextScript", Config.System.drawText or "gta")
'make/cameras.lua', Config.System.skillCheck = GetConvar("jim_skillCheckScript", Config.System.skillCheck or "gta")
'make/progressBars.lua',
if GetConvar("jim_dontUseTarget", "false") == "true" then
'wrapperfunctions.lua', Config.System.DontUseTarget = true
'polyZone.lua', end
'itemcontrol.lua', end
'playerfunctions.lua',
'jobfunctions.lua', -- Load the core files here instead of in fxmanifests
-- Forcefully loads the files and quietly, compared to fxmanifest complaining if they don't exist when you don't need them all
-- Interactions local filesToLoad = {
'targets.lua', Shared = {
'contextmenus.lua', [Exports.OXLibExport] = { "init.lua" },
'input.lua', [Exports.OXCoreExport] = { "lib/init.lua" },
'notify.lua', [Exports.ESXExport] = { "imports.lua" },
'drawText.lua', [Exports.QBXExport] = { "modules/playerdata.lua" },
},
-- Crafting / Shops / Stashes Client = {
'crafting.lua', ["PolyZone"] = { "client.lua", "BoxZone.lua", "EntityZone.lua", "CircleZone.lua", "ComboZone.lua" },
'stashcontrol.lua', ["warmenu"] = { "warmenu.lua", },
},
-- Kind of "other" }
'isAnimal.lua',
'scaleEntity.lua', -- Shared framework file loader
'vehicles.lua', for Type, ScriptTable in pairs(filesToLoad) do
'effects.lua', if Type == "Client" and IsDuplicityVersion() then
'versioncheck.lua' goto continue
}) do else
if debugMode then for scriptName, files in pairs(ScriptTable) do
print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...") local state = GetResourceState(scriptName)
end -- if the resource is started, load the file
local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v))) if state == "started" then
fileLoader() if scriptName == Exports.OXLibExport and GetResourceState(Exports.OXCoreExport):find("start") then
if debugMode then if debugMode then print("OX_Core found, skipping OX_Lib loading") end
print("^5Success^7: ^2loaded file^7: 'jim_bridge/shared/"..v.."'!") goto skip
end 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: '"..scriptName.."/"..file.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
local fileLoader = assert(load(LoadResourceFile(scriptName, (file)), ('@@'..scriptName..'/'..file)))
fileLoader()
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
if state == "uninitialized" or state == "stopped" then
print("^5CoreLoader^7: ^3"..scriptName.." ^1 found but it wasn't started^7.")
print("^1Check your ^3server^7.^3cfg ^1load order^7")
end
-- debugging only, if the script is missing, warn the user
if state == "missing" then
if debugMode then
print("^5CoreLoader^7: ^3"..scriptName.." ^2not found^7, ^2skipping")
end
end
end
end
::continue::
end
-- Load files here into the invoking script
for _, v in pairs({ -- This is a specific load order
'helpers.lua', -- needs to be first
'_loaders.lua',
'_eventDebug.lua',
'callback.lua',
'coreloader.lua',
'duifunctions.lua',
-- Native Scaleforms
'scaleforms/scaleform_basic.lua',
'scaleforms/bigMessageInstance.lua',
'scaleforms/countDownHandler.lua',
'scaleforms/debugScaleform.lua',
'scaleforms/instructionalButtons.lua',
'scaleforms/timerBars.lua',
-- Required functions
'make/loaders.lua',
'make/makeBlip.lua',
'make/makePed.lua',
'make/makeProp.lua',
'make/makeVeh.lua',
'make/cameras.lua',
'make/progressBars.lua',
'wrapperfunctions.lua',
'polyZone.lua',
'inventories.lua',
'itemcontrol.lua',
'playerfunctions.lua',
'metaHandlers.lua',
'jobfunctions.lua',
'societybank.lua',
'phones.lua',
-- Interactions
'targets.lua',
'contextmenus.lua',
'input.lua',
'notify.lua',
'drawText.lua',
'skillcheck.lua',
-- Crafting / Shops / Stashes
'crafting.lua',
'shops.lua',
'stashcontrol.lua',
-- Kind of "other"
'isAnimal.lua',
'scaleEntity.lua',
'vehicles.lua',
'effects.lua',
--'warmenu.lua',
'_authToken.lua',
-- Do version check last
'_scriptversioncheck.lua'
}) do
if debugMode then
--print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
end
local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v)))
fileLoader()
if debugMode then
print("^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(PSInv, "version", 0), "1.0.6") then
print("^6Bridge^7: ^4"..PSInv.."^2 Version above ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1true^7")
QBInvNew = true
else
print("^6Bridge^7: ^4"..PSInv.."^2 Version ^1below ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
end
end end

View File

@@ -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)

363
ui_modules/progressbar.lua Normal file
View File

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

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,14 @@
1.2 2.0.20
- Add Full support for lation_ui
- Add "Open Wheel" to searchCar()
- Increase timeout for cache timer from 5 seconds to 2 minutes
- Fix GTA progressbar erroring on playAnim()
- Add Igredient location indicator when crafting
- Fix JPRInv checks breaking early when caching framework info
- Enhance doesItemExist() and add getItemLabel() function
- Add Support for old qb-inventory config loading
- Fix typo in cancarry when using Tgiann Inv
- Add support for single progressBar when crafting
https://github.com/jimathy/jim_bridge