Compare commits

...

364 Commits

Author SHA1 Message Date
Jim Shield
f09e34b1b5 Version Bump
`2.0.21` - `2.0.22``

- Support for qs-interface
- Fix polyzone minZ + maxZ handling
- Fix ox_target zone location to match qb-target
- Fix "Waiting for script start" loop bug
- Add condition for fading in entities
- Fix typo breaking scripts on gta skillbar fail
- Fix typo for gksphone invoice event
- Fix latio_ui TextUI placement
- Remove registerShop debug message
- Fix typo in print when removing codem stash item
- Fix function typo from origen inv documenation
2025-07-27 20:00:42 +01:00
Jim Shield
1146639d6f Update notify-discord.yml 2025-07-27 18:50:08 +01:00
Jim Shield
a9ac2e8897 Fix typo for orgien_inv getInventory 2025-07-24 21:49:37 +01:00
Jim Shield
1a1960f5a8 Fix entity blips looking for wrong preview script 2025-07-24 21:48:39 +01:00
Jim Shield
996b135973 Improve ox_target zone creation locations 2025-07-24 19:53:30 +01:00
Jim Shield
9337238832 fix typo in debugprint for codem stash staving 2025-07-23 19:53:07 +01:00
Jim Shield
ee049a953c Make ox_lib polyzone conversion code more accurate 2025-07-23 16:46:10 +01:00
Jim Shield
adfe3e8e08 Remove registerShop debug message
People keep thinking this is a massive error, its literally just stating that there isn't a script found to register a shop. not all inventories have these
2025-07-22 16:00:27 +01:00
Jim Shield
71c9c99db7 fix location typo for lation_ui drawtext 2025-07-22 13:04:46 +01:00
Jim Shield
dd66083f45 fix typo for gks sendPhoneInvoice function
when creating a `phone invoice` the function was checking if `gks-phone` was started, but I believe the script folder name is default `gksphone`
2025-07-22 11:44:32 +01:00
Jim Shield
9ddd6422bd fix typo breaking gta skillcheck if failed 2025-07-22 01:25:14 +01:00
Jim Shield
7d24c274cd Fix ox_lib polyzone maxZ and minZ math 2025-07-20 23:20:43 +01:00
Jim Shield
3911824fc6 Add resourceName sendback for onResource functions 2025-07-17 12:47:36 +01:00
Jim Shield
9729d2fef1 Add condition for stopping entity fade in 2025-07-17 12:40:43 +01:00
Jim Shield
3cb4005889 Merge pull request #51 from oosayeroo/main 2025-07-17 12:39:48 +01:00
Jim Shield
bf759ebaf8 Fix inventory config check to check "starting" scripts 2025-07-17 12:38:31 +01:00
Jim Shield
3d0eb3df65 fix PolyZone support for minZ maxZ 2025-07-17 12:37:41 +01:00
oosayeroo
fe09d0f773 qs notify + progressbar + makeVeh edit 2025-07-16 08:18:55 +01:00
Jim Shield
880d2b0233 Version Bump
`2.0.20` - `2.0.21`
2025-07-14 11:29:29 +01:00
Jim Shield
5865d2eeb2 increase authToken timeout from 5 sec to 1 min 2025-07-13 17:41:06 +01:00
Jim Shield
528735abd1 fix anim flag on lation_ui progressbars 2025-07-12 22:24:28 +01:00
Jim Shield
d4df995c71 fix lation_ui progressbars returning true when cancelled 2025-07-12 13:05:10 +01:00
Jim Shield
3b13fa663e Merge pull request #49 from derTweaky/patch-1 2025-07-12 11:31:16 +01:00
derTweaky
e973c64ffd Update makePed.lua
Typo fix
2025-07-12 12:27:31 +02:00
Jim Shield
6ecf10c084 fix inventory config loading if vector4 is present 2025-07-11 21:16:45 +01:00
Jim Shield
79e765fbb0 Stop crafting trying to check blank stash names
Skimmed through the stash checking and editing functions and added more "security" for handling nil or blank stash names
2025-07-11 13:33:44 +01:00
Jim Shield
d4a08d5e2b Rewrite waitForSharedLoad() to fix issues 2025-07-10 22:41:28 +01:00
Jim Shield
eeb3f40018 fix break when trying to fade ents too far away 2025-07-10 22:16:26 +01:00
Jim Shield
e6b4c1d13c Add fade in animations for entity spawning 2025-07-09 15:10:53 +01:00
Jim Shield
6ecbc3392a make breaktool() smarter and add useToolDegrade() 2025-07-09 15:09:41 +01:00
Jim Shield
7f10c394de make cache timeout optional 2025-07-09 15:09:14 +01:00
Jim Shield
fae099fd93 fix progressbar looking for missing function 2025-07-08 11:20:52 +01:00
Jim Shield
64d43b54e9 fix getPlayer() checking users not logged in yet 2025-07-08 00:48:39 +01:00
Jim Shield
d4abdbc93f fix typo stopping mysql cache loading on esx 2025-07-07 17:56:23 +01:00
Jim Shield
b595453efe Rename metahandler functions
Realised I named the player and item metadata functions too closely and made it confusing, this separates them better
2025-07-07 17:47:04 +01:00
Jim Shield
d255034c02 Increase timer again on cache timeout
Also moved around the cache timers to be a bit more accurate (timers start when the cache creation is triggered)
2025-07-07 02:11:36 +01:00
Jim Shield
d6fd2cc28c Fix toggleDuty() for ESX 2025-07-07 00:09:34 +01:00
Jim Shield
7c47de5dc0 Version Bump
`2.0.19` - `2.0.20`
2025-07-06 19:16:01 +01:00
Jim Shield
c1ab2b3505 Support only one progressBar when crafting 2025-07-06 19:13:10 +01:00
Jim Shield
1665ab41de fix multicraft menu for lation_ui 2025-07-06 19:01:10 +01:00
Jim Shield
6d397b59bf fix onExit support for lation_ui 2025-07-05 20:52:08 +01:00
Jim Shield
9dfbcbe878 fix typo breaking tgiann CanCarryItem export check 2025-07-05 13:24:19 +01:00
Jim Shield
ca0970b650 Add fallback config support for QBInv inventory
Enhanced the inventory config loader to support both old and new config file structures for QBInv by introducing a fallback mechanism. The loader now attempts multiple config paths to improve compatibility with different versions.
2025-07-05 11:11:51 +01:00
Jim Shield
02218a4902 enhance dosItemExist() and add getItemLabel() 2025-07-05 00:33:26 +01:00
Jim Shield
a900044306 Fix hang when using jpr-inventory
The script was randomly looping the config file check, if it hit qb-inventory first it would end the check and break the script, changed it to skip instead and fixed it
2025-07-04 23:44:37 +01:00
Jim Shield
7382a534a0 I THINK jim_bridge now fully supports lation_ui 2025-07-04 18:56:19 +01:00
Jim Shield
3b76b15bb4 Add starting support for lation_ui
Added support for:
- Menus
- Draw Text
- Notifications
- Skillchecks
- ProgressBar

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

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

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

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

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

---
Also slipped in an `experimental` function that grabs inventory config files to check their set InventoryWeight and InventorySlots to automatically apply it for other functions
---
2025-06-26 20:03:12 +01:00
Jim Shield
9d74e7cb46 Update setMetadata server event
Before it was JUST for durability but fixed it to work for all item metadata
2025-06-26 19:57:03 +01:00
Jim Shield
fea72bba2e hotfix ox_lib progressbars stopping movement 2025-06-23 12:54:24 +01:00
Jim Shield
b364f14260 Version Bump
`2.0.17` - `2.0.18`
2025-06-22 22:16:17 +01:00
Jim Shield
8077ec4f46 Add functions to try and get players inv weights 2025-06-22 20:03:12 +01:00
Jim Shield
e5277574df Rework Auto detection of QBInvNew
It's messy but hopefully it will work as expected
2025-06-22 17:45:33 +01:00
Jim Shield
e0563dd4ce Add extra triggerevent for ps-inventory
They keep changing their event names 🤷‍♀️
added a duplicate with `ps-` at the start in hopes this triggers things correctly when opening a stash
2025-06-22 17:45:04 +01:00
Jim Shield
1987cf3df3 Fix IsAnimal checks getting confused 2025-06-22 16:51:19 +01:00
Jim Shield
9f5145c3b2 Update progressBars.lua 2025-06-22 16:38:02 +01:00
Jim Shield
5921cf8adc ProgressBar default variable fixes 2025-06-22 16:14:32 +01:00
Jim Shield
f154b45076 Enhance built in ui_modules to support RedM 2025-06-21 22:34:03 +01:00
Jim Shield
2c41272c36 Remove ClearStash export call for ps-inv 2025-06-21 18:28:50 +01:00
McKlean
57d4beafed Commit[crafting]: commented print causing errors
- debugPrint was causing crafting to error
2025-06-21 16:32:49 +01:00
McKlean
bdb526bd1e Commit[Billing]: added okok Billing
- Added OKOK's CreateInvoice as a choice for the billing
2025-06-21 14:52:09 +01:00
Jim Shield
bb9dbe248f Update callback.lua 2025-06-21 13:56:49 +01:00
Jim Shield
caf2e7fbed add support for esx_society to openBossMenu() 2025-06-21 13:29:42 +01:00
Jim Shield
d0b8fb16d6 fix for if ox_inv can't find an item when trying to get image 2025-06-21 13:29:01 +01:00
Jim Shield
2e57e66095 Fix confusion with old and new inv shop exports 2025-06-20 23:33:44 +01:00
Jim Shield
05fc856e86 small tidy up 2025-06-20 21:53:44 +01:00
Jim Shield
74e6a679e2 Tidy up onPlayerLoaded for all frameworks 2025-06-20 21:32:08 +01:00
Jim Shield
17db5aa31a Fix QBCore OnPlayerUnload event not triggering 2025-06-20 21:12:53 +01:00
Jim Shield
387c97de71 Fix jpr erroring when clearStash is triggered 2025-06-20 20:56:45 +01:00
Jim Shield
de45491c8c move exploit protection to new file
Moves Auth Token stuff to separate file because it doesn't really need to be in `itemcontrol.lua`

Helps me keep track of it better too

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

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

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

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

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

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

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

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

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

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

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

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

making my scripts think they didn't exist

This fixes that
2025-06-06 19:36:12 +01:00
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
Jim Shield
e72fad0bfa Upload beta 1.2 2025-02-21 13:47:15 +00:00
Jim Shield
61a582a330 Remove old files 2025-02-21 13:45:08 +00:00
Jim Shield
80c357986d Merge pull request #22 from jimathy/1.0.13
1.0.14
2024-02-25 11:49:30 +00:00
Jim Shield
6c2b0b487f Merge branch 'main' into 1.0.13 2024-02-25 11:49:13 +00:00
Jim Shield
ad3d2b534f update version.txt to 14 2024-02-23 15:51:52 +00:00
Jim Shield
a4e9333ab8 1.0.14 2024-02-23 15:47:08 +00:00
Jim Shield
e08b349e31 Version Bump 2024-02-20 12:44:25 +00:00
Jim Shield
2bf7a28508 fix typo 2024-02-20 12:43:51 +00:00
Jim Shield
e6bee2ae2e dui request callback 2024-02-19 17:57:51 +00:00
Jim Shield
fc832d73f9 dui request callback 2024-02-19 17:57:15 +00:00
Jim Shield
e8e9b53f53 port dui texture changes (needs mutliscripting) 2024-02-19 17:53:23 +00:00
Jim Shield
7304376e90 getDurability updates 2024-02-19 17:52:19 +00:00
Jim Shield
7e9914afda grav inv fixes 2024-02-19 17:50:19 +00:00
Jim Shield
89f6f6deb0 add jpr-phonesystem support 2024-02-16 22:45:22 +00:00
Jim Shield
faa4a123fd Merge pull request #21 from SirOMGitsYOU/patch-2
add jpr-phonesystem support
2024-02-16 22:45:07 +00:00
Jim Shield
f1ae24d0fe separate create dui event 2024-02-16 22:42:13 +00:00
Peter
4670d87c96 add jpr-phonesystem support
Update wrapper.lua - Added code in for JPR-Phonesystem, Not sure if I should have called the QBCore export a different way, JPR-Phone system only supports qbcore and esx, 

Info for mails taken directly from their docs https://joaos-organization-3.gitbook.io/jpresources-documentation/installation/phone-system/events-and-commands#phone-mails
2024-02-15 10:45:50 +00:00
Jim Shield
4848b51ecc Update README.md 2024-02-14 17:34:04 +00:00
Jim Shield
2aa0cc9d5d Start adding manual durability changes 2024-02-14 00:19:36 +00:00
Jim Shield
5e3ace804d Adjust ox/qb target name creation 2024-02-14 00:11:08 +00:00
Jim Shield
42b109a5f1 readability 2024-02-09 17:02:45 +00:00
Jim Shield
5a8afb516b Update README.md 2024-02-09 16:51:20 +00:00
Jim Shield
ef2771cc83 Update README.md 2024-02-09 16:49:29 +00:00
Jim Shield
942c11af1d Update README.md
Start adding documentation
2024-02-09 00:46:20 +00:00
Jim Shield
b291e3f4b6 Update functions.lua 2024-02-08 21:54:28 +00:00
Jim Shield
46e3f18edc Merge pull request #20 from jimathy/1.0.12
1.0.12
2024-02-08 18:58:37 +00:00
Jim Shield
0886d8a4b6 Version Bump 2024-02-08 18:39:30 +00:00
Jim Shield
6a23e4ac91 Move loadModel time reset 2024-02-08 18:39:02 +00:00
Jim Shield
f7b7718679 remove unused variable 2024-02-08 18:38:22 +00:00
Jim Shield
9ae034da77 remove unused variable 2024-02-08 18:38:11 +00:00
Jim Shield
4d1b2abd3d blip_info support preview imgs 2024-02-08 18:37:14 +00:00
Jim Shield
f8e3f64907 Origen Inv Fixes 2024-02-08 18:35:54 +00:00
Jim Shield
3702835958 Added sellMenu() 2024-02-08 18:34:17 +00:00
Jim Shield
23c2e1472e Merge pull request #19 from jimathy/1.0.11
1.0.11
2024-01-30 19:21:33 +00:00
Jim Shield
6367c9ed3b remove loadmodel debug timeout print 2024-01-30 18:47:23 +00:00
Jim Shield
2d299a6a4a chargePlayer & fundPlayer server events/fixes 2024-01-30 18:40:26 +00:00
Jim Shield
ced629d4dc dupewarn info addition 2024-01-30 18:39:51 +00:00
Jim Shield
714121d272 crafting menu workarounds for missing info 2024-01-30 18:38:57 +00:00
Jim Shield
14c140cf85 load model and prop fixes 2024-01-29 22:23:23 +00:00
Jim Shield
881aec7c04 remove ox_lib model check as it was erroring out 2024-01-29 13:27:00 +00:00
Jim Shield
85b31e7e1d getRandomReward added for reward item support 2024-01-29 13:23:02 +00:00
Jim Shield
66c9287202 v1.0.11 - Version Bump 2024-01-28 19:24:19 +00:00
Jim Shield
f0f59b8568 remove debug print 2024-01-28 19:19:58 +00:00
Jim Shield
e03efa6687 update stopAnim to choose ped 2024-01-28 19:19:24 +00:00
Jim Shield
5a22651961 fix ConsumeSuccess and other typos 2024-01-28 19:18:55 +00:00
Jim Shield
236689452e custom stash slot sizes at "register" 2024-01-28 19:17:57 +00:00
Jim Shield
b686f267e1 Remove debug print 2024-01-28 01:44:53 +00:00
Jim Shield
096e323409 Merge pull request #18 from jimathy/1.0.10
1.0.10
2024-01-27 14:27:44 +00:00
Jim Shield
b71ec14615 sendPhoneMail - mutli phone support 2024-01-27 12:55:56 +00:00
Jim Shield
5a2304b785 quick linting 2024-01-26 20:31:46 +00:00
Jim Shield
6f413c555d add basic canCarry function 2024-01-26 20:31:10 +00:00
Jim Shield
fbc92aea41 update common functions to be smurter 2024-01-26 19:02:02 +00:00
Jim Shield
7b31343991 Merge pull request #17 from jimathy/1.0.9
1.0.9
2024-01-25 18:42:19 +00:00
Jim Shield
872cb39715 Fix crafting from pockets and add debugs 2024-01-25 18:40:42 +00:00
Jim Shield
dd35674307 i hate github 2024-01-25 18:31:34 +00:00
Jim Shield
1d102e94b4 Fix loading of ESX Core shared info 2024-01-25 18:31:00 +00:00
Jim Shield
c18932443a Testing fixing stashes 2024-01-25 17:32:40 +00:00
Jim Shield
7563b4abdf Merge pull request #16 from SirOMGitsYOU/patch-1
Fix Typo
2024-01-25 10:56:55 +00:00
Peter
20509e5cc2 Fix Typo
Update wrapper.lua - Fixed a typo on line 141 prevents this update from loading.
2024-01-24 23:33:39 +00:00
Jim Shield
43e30e95bc Tidy up and make ESX timeout longer 2024-01-24 17:10:44 +00:00
Jim Shield
9efff1060b Merge pull request #15 from jimathy/1.0.8
1.0.8 - Fixes crafting issues
2024-01-24 01:13:39 +00:00
Jim Shield
3b608e2b4f Update wrapper.lua 2024-01-24 01:13:30 +00:00
Jim Shield
bfed10c019 more fixes and optmization 2024-01-23 22:39:46 +00:00
Jim Shield
4bb29b8f14 cheeky little core_skills addition 2024-01-23 22:05:07 +00:00
Jim Shield
6eca0dd1d1 Cut down code and optimize crafting functions 2024-01-23 22:04:30 +00:00
Jim Shield
cc96bcde34 Fix stashCrafting not finding items 2024-01-23 22:03:27 +00:00
Jim Shield
ab61e40567 Merge pull request #14 from jimathy/1.0.7
1.0.7
2024-01-23 19:02:13 +00:00
Jim Shield
e17e2fb865 bit'o linting 2024-01-23 19:01:03 +00:00
Jim Shield
a6bd82a41c Fixes for ox showMenu support 2024-01-23 19:00:39 +00:00
Jim Shield
fac495c0ef Change how core information is grabbed 2024-01-23 19:00:04 +00:00
Jim Shield
15bea85bfb Version Bump v1.0.7 2024-01-23 18:59:02 +00:00
Jim Shield
f889080bc0 Merge pull request #13 from jimathy/1.0.6
1.0.6
2024-01-23 00:37:55 +00:00
Jim Shield
7d62358558 ox input default "select" 2024-01-23 00:37:18 +00:00
Jim Shield
f7a7e89c56 lib.showMenu support 2024-01-23 00:36:04 +00:00
Jim Shield
5f6720d37b Merge pull request #12 from solareon/patch-1
fix: job crafting restrictions
2024-01-23 00:34:15 +00:00
solareon
e541201a08 fix: skip amount and job tables 2024-01-22 22:39:16 +00:00
Solareon
2bf04f69cf fix: hasjob
Fixes broken crafting because the wrapper function is called hasJob with a capital J while the variable is called hasjob
2024-01-22 12:50:03 -07:00
Jim Shield
8479aea1b0 Origen inv experimental support 2024-01-22 17:14:56 +00:00
Jim Shield
d17a1da5b2 CoreInv Fully Supported 2024-01-22 16:55:58 +00:00
Jim Shield
f1ff06c098 CoreInv hasItem events updated 2024-01-22 15:31:04 +00:00
Jim Shield
df785043e8 CoreInv add/remove Item fixes 2024-01-22 14:51:51 +00:00
Jim Shield
5ae4799a5f add auto check for ps/lj-inv 2024-01-22 14:41:38 +00:00
Jim Shield
8cf4a6bd23 Version Bump - 1.0.6 2024-01-22 14:35:52 +00:00
Jim Shield
3e067c8492 Merge pull request #9 from jimathy/1.0.5
1.0.5
2024-01-21 22:28:03 +00:00
Jim Shield
6847639c65 Fix version checking system being dumb 2024-01-21 17:24:32 +00:00
Jim Shield
0592c3ebc8 Fix stashRemoveItem for QS-Inv 2024-01-21 17:24:10 +00:00
Jim Shield
2ca1cbd110 fxmanifest.lua - Version Bump v1.0.5 2024-01-21 17:20:15 +00:00
Jim Shield
8642733b78 version.txt - Version bump - 1.0.5 2024-01-21 17:17:55 +00:00
Jim Shield
5a4293182c version number plz work 2024-01-21 17:10:32 +00:00
Jim Shield
6075878038 Update version.txt 2024-01-21 00:56:19 +00:00
Jim Shield
7eaeac886f Merge pull request #8 from jimathy/1.0.4
1.0.4
2024-01-21 00:27:08 +00:00
Jim Shield
fe27a1ca1c speed playAnim up + add default input message 2024-01-21 00:25:42 +00:00
Jim Shield
0caea0b473 advanced checking and handling of setVehicle 2024-01-20 14:28:10 +00:00
Jim Shield
370c77433b Version Bump - v1.0.4 2024-01-20 13:01:26 +00:00
Jim Shield
ac85d35097 Merge pull request #7 from jimathy/1.0.3
1.0.3
2024-01-19 23:58:42 +00:00
Jim Shield
85c3e06910 update addItem/removeItem prints 2024-01-19 23:57:22 +00:00
Jim Shield
7d0736bfc2 Change how bridge decides what get/setvehicleproperties to use 2024-01-19 22:33:35 +00:00
Jim Shield
b0b011ac56 update readme -main 2024-01-19 20:25:44 +00:00
Jim Shield
23091c2e73 add spam free ensureNetToVeh() 2024-01-19 20:25:04 +00:00
Jim Shield
ef2ba3ea7a tidy up new prints more 2024-01-19 20:24:25 +00:00
Jim Shield
df60f3b2b1 tidy prints for setVehicleProperties 2024-01-19 18:13:22 +00:00
Jim Shield
4cc001d2f0 fix typo in stashhasitem 2024-01-19 18:10:05 +00:00
Jim Shield
e2bb32d9de Optimize Crafting/Multicraft Menu + better hasItem prints 2024-01-19 14:46:45 +00:00
Jim Shield
725125a274 add debug prints to new ox setVehicleProperties call 2024-01-19 14:46:09 +00:00
Jim Shield
dcf2018ec6 check for destroyProp if received any info 2024-01-19 13:02:09 +00:00
Jim Shield
e5316f45aa Rewrite + Optimize hasItem() & stashhasItem() 2024-01-19 13:00:50 +00:00
Jim Shield
02c202d075 Version Bump - v1.0.3 2024-01-19 12:53:45 +00:00
Jim Shield
9e9fb14f55 Merge pull request #3 from bandgeekndb/stashoptions
Allow for passing through stash options when opening a stash
2024-01-19 12:50:10 +00:00
Jim Shield
61e0d777a8 Attempt to fix ox_lib's setVehicleProperites + 1.0.2 2024-01-18 20:21:22 +00:00
Nicholas Blew
63c2d5d83d Allow for passing through stash options when opening a stash 2024-01-18 02:20:20 -05:00
89 changed files with 15221 additions and 2661 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: Send commit payload to Discord Bot
run: |
curl -X POST http://${{ secrets.DISCORDBOT }}:3000/github-commits \
-H "Content-Type: application/json" \
-d '${{ toJson(github.event) }}'

135
README.md
View File

@@ -1,47 +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 multiple updates
- Having certain functions in one place(this script) makes it easier to update, enchance and fix things
- 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)
All the next updates of my scripts will use this script and be added as a dependancy
------
It was a tough decision to put it up on github instead of tebex and encrypted
But I want this script to grow with help of others who know more about other cores
---
The installation of this script is simple
- it just needs to start before any script that requires it
- it can start before core scripts if you want
- for `qb-core` I personally place this script in `resources > [standalone]`
---
### Support for different exports and scripts
In exports.lua is the list of script folder names
This is for people who have customised/renamed scripts
eg. for people who use `ps-inventory`, this is mainly based on qb-inventory
so you need to rename
```lua
QBInv = "qb-inventory",
```
to
```lua
QBInv = "ps-inventory",
```
This will now use events from `ps-inventory` and use it through out the scripts.
# 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

@@ -1,506 +0,0 @@
if IsDuplicityVersion() then
if GetResourceState(OXLibExport):find("start") then
createCallback(GetCurrentResourceName()..':server:GetStashItems', function(source, stashName) local stash = getStash(stashName) return stash end)
else
createCallback(GetCurrentResourceName()..':server:GetStashItems', function(source, cb, stashName) local stash = getStash(stashName) cb(stash) end)
end
end
local timeout = 0
local timing = false
local stashItems = {}
function GetStashTimeout(stashName, stop)
if stop then stashItems = {} timing = false timeout = 0 return end
if stashItems[1] then return true else
if timeout <= 0 then
stashItems = triggerCallback(GetCurrentResourceName()..':server:GetStashItems', stashName)
timeout = 15000
if not timing then
CreateThread(function()
timing = true
while timeout > 0 do timeout -= 1000 Wait(1000) end
timing = false stashItems = {} timeout = 0
end)
end
end
return false
end
end
local CraftLock = false
function craftingMenu(data) local hasjob = false
if CraftLock then return end
if data.stashName and not GetStashTimeout(data.stashName) then
--triggerNotify(nil, "Chacking", "success")
end
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
local Menu = {}
local Recipes = data.craftable.Recipes
for i = 1, #Recipes do
for k, v in pairs(Recipes[i]) do
if k ~= "amount" and k ~= "job" and k ~= "gang" then
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
if Recipes[i].job then hasjob = false
for l, b in pairs(Recipes[i].job) do
hasjob = hasJob(l, nil, b)
if hasJob == true then break end
end
end
local setheader, settext = "", ""
local disable = false
local checktable = {}
if Recipes[i].job and hasjob == false then else
for l, b in pairs(Recipes[i][tostring(k)]) do
if not Items[l] then print("^3Error^7: ^2Script can't find ingredient item in Shared Items - ^1"..l.."^7") return end
settext = settext..(settext ~= "" and br or "")..Items[l].label..(b > 1 and " x"..b or "")
if data.stashName then checktable[l] = stashhasItem(stashItems, l, b)
else checktable[l] = hasItem(l, b) end
Wait(0)
end
for _, v in pairs(checktable) do
if not v then
disable = true
break
end
end
setheader = Items[tostring(k)].label..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")..(not disable and " ✔️" or "")
Menu[#Menu + 1] = {
isMenuHeader = disable,
icon = invImg(tostring(k)),
header = setheader, txt = settext,
onSelect = function()
local transdata = { item = k, craft = data.craftable.Recipes[i], craftable = data.craftable, coords = data.coords, stashName = data.stashName, onBack = data.onBack }
if Config.Crafting.MultiCraft then
multiCraft(transdata)
else
makeItem(transdata)
end
end,
}
end
end
end
end
openMenu(Menu, { header = data.craftable.Header, onBack = data.onBack or nil, canClose = true, onExit = function() end, })
lookEnt(data.coords)
end
function multiCraft(data) local Menu = {}
local success = Config.Crafting.MultiCraftAmounts
if data.stashName and not GetStashTimeout(data.stashName) then
--triggerNotify(nil, "Refreshing stashinfo", "success")
end
Menu[#Menu+1] = {
isMenuHeader = true,
icon = invImg(data.item),
header = Items[data.item].label,
}
for k in pairsByKeys(success) do
local settext = ""
for l, b in pairs(data.craft[data.item]) do
if data.stashName then
success[k] = stashhasItem(stashItems, l, (b * k))
else
success[k] = hasItem(l, (b * k))
end
settext = settext..(settext ~= "" and br or "")..Items[l].label..(b*k > 1 and "- x"..b*k or "")
Wait(0)
end
Menu[#Menu + 1] = {
isMenuHeader = not success[k],
header = "Craft - x"..k * data.craft.amount,
txt = settext,
onSelect = function ()
makeItem({item = data.item, craft = data.craft, craftable = data.craftable, amount = k, coords = data.coords, stashName = data.stashName, onBack = data.onBack })
end,
}
end
openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end, })
end
function makeItem(data)
if not CraftLock then CraftLock = true else return end
local bartime, bartext, animDict, anim = nil, nil, nil, nil
if data.craftable.progressBar then
bartime = data.craftable.progressBar.time
bartext = data.craftable.progressBar.label
else
bartime = 5000
bartext = Loc[Config.Lan].progressbar["progress_make"]
end
animDict = data.craftable.Anims.animDict or "amb@prop_human_parking_meter@male@idle_a"
anim = data.craftable.Anims.anim or "idle_a"
local amount = (data.amount and data.amount ~= 1) and data.amount or 1
local crafted = true
local crafting = true
local cam = createTempCam(PlayerPedId(), data.coords)
startTempCam(cam)
for i = 1, amount do
for _, v in pairs(data.craft) do
if type(v) == "table" then
for l, b in pairs(v) do
if crafting and progressBar({
label = "Using "..b.." "..Items[l].label,
time = 1000,
cancel = true,
dict = 'pickup_object',
anim = "putdown_low",
flag = 48,
icon = l,
}) then
--TriggerEvent('inventory:client:ItemBox', Items[l], "use", b) -- Show item box for each item
else
crafted = false
crafting = false
break
end
Wait(200)
end
if crafted then
if crafting and progressBar({
label = bartext..Items[data.item].label,
time = bartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 8,
icon = data.item,
}) then
TriggerServerEvent(GetCurrentResourceName()..":Crafting:GetItem", data.item, data.craft, data.stashName)
else
crafting = false
break
end
end
end
end
Wait(500)
end
stopTempCam()
CraftLock = false
lockInv(false)
craftingMenu(data)
ClearPedTasks(PlayerPedId())
end
RegisterNetEvent(GetCurrentResourceName()..":Crafting:GetItem", function(ItemMake, craftable, stashName)
local src = source
local amount = 1
if stashName then
local stashItems = getStash(stashName)
if craftable["amount"] then amount = craftable["amount"] end
local itemRemove = {}
for k, v in pairs(craftable[ItemMake]) do
for l, b in pairs(stashItems) do
if k == b.name then
itemRemove[k] = v
end
end
end
stashRemoveItem(stashItems, stashName, itemRemove)
else
if craftable then
if craftable["amount"] then amount = craftable["amount"] end
for k, v in pairs(craftable[ItemMake]) do
TriggerEvent(GetCurrentResourceName()..":server:toggleItem", false, tostring(k), v, src)
end
end
end
TriggerEvent(GetCurrentResourceName()..":server:toggleItem", true, ItemMake, amount, src)
end)
--[[SHOPS]]--
function openShop(data)
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
if GetResourceState(OXInv):find("start") then
exports[OXInv]:openInventory('shop', { type = data.shop })
else
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
lookEnt(data.coords)
end
-- Client & Server side
function hasItem(items, amount, src) local amount, count = amount and amount or 1, 0
if src then
if GetResourceState(OXInv):find("start") then
local item = exports[OXInv]:GetItem(src, items)
local amount = (amount or 1)
if item.count >= amount then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..item.count.."^7/^3"..amount.." "..tostring(items)) end
return true
else if Config.System.Debug then
print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
return false
end
elseif GetResourceState(QSInv):find("start") then
for _, itemData in pairs(exports[QSInv]:GetInventory(src)) do
if itemData and (itemData.name == items) then
--if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
count += (itemData.amount or 1)
end
end
if count >= amount then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
return true
else
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
return false
end
elseif GetResourceState(CoreInv):find("start") then
local item = exports[CoreInv]:getItems('primary-'..src, items)
if item.amount >= amount then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..item.amount.."^7/^3"..amount.." "..tostring(items)) end
return true
else if Config.System.Debug then
print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
return false
end
elseif GetResourceState(CodeMInv):find("start") then
local success = exports["codem-inventory"]:CheckItemValid(src, items, amount)
if success then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..amount.." "..tostring(items)) end
return true
end
elseif GetResourceState(CodeMInv):find("start") then
for _, itemData in pairs(exports[CodeMInv]:GetUserInventory(src)) do
if itemData and (itemData.name == items) then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
count += (itemData.amount or 1)
end
end
if count >= amount then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
return true
else
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
return false
end
else
for _, itemData in pairs(Core.Functions.GetPlayer(src).PlayerData.items) do
if itemData and (itemData.name == items) then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
count += (itemData.amount or 1)
end
end
if count >= amount then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
return true
else
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
return false
end
end
else
if GetResourceState(OXInv):find("start") then
local count = tonumber(exports[OXInv]:Search('count', items))
local amount = (amount or 1)
if count >= amount then if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end return true
else if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end return false end
elseif GetResourceState(QSInv):find("start") then
for _, itemData in pairs(exports[QSInv]:getUserInventory()) do
if itemData and (itemData.name == items) then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
count += (itemData.amount or 1)
end
end
if count >= amount then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
return true
else
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
return false
end
elseif GetResourceState(CoreInv):find("start") then
for _, itemData in pairs(exports[CoreInv]:getInventory('primary-'..GetPlayerServerId(PlayerPedId()))) do
if itemData and (itemData.name == items) then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
count += (itemData.amount or 1)
end
end
if count >= amount then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
return true
else
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
return false
end
elseif GetResourceState(CodeMInv):find("start") then
local success = exports[CodeMInv]:CheckItemValid(items, amount)
if success then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..amount.." "..tostring(items)) end
return true
end
elseif GetResourceState(QBInv):find("start") then
--if tonumber(GetResourceMetadata(QBInv, 'version')) <= 1.2 then
-- return exports[QBInv]:HasItem(items, amount)
--else
for _, itemData in pairs(Core.Functions.GetPlayerData().items) do
if itemData and (itemData.name == items) then
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
count += (itemData.amount or 1)
end
end
if count >= amount then
if Config.System.Debug then
print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items))
end
return true
else
if Config.System.Debug then
print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7")
end
return false
end
-- end
end
end
end
-- Stash Items
function openStash(data)
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
if GetResourceState(OXInv):find("start") then
exports[OXInv]:openInventory('stash', data.stash)
elseif GetResourceState(CodeMInv):find("start") then
exports[CodeMInv]:OpenStash(data.stash, 400000, 100)
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash)
end
lookEnt(data.coords)
end
function getStash(stashName)
local stashItems, items = {}, {}
if GetResourceState(OXInv):find("start") then
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7ox_inventory") end
stashItems = exports[OXInv]:Inventory(stashName).items
elseif GetResourceState(QSInv):find("start") then
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7qs-inventory") end
stashItems = exports[QSInv]:GetStashItems(stashName)
elseif GetResourceState(CodeMInv):find("start") then
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7qs-inventory") end
stashItems = exports[CodeMInv]:GetInventoryItems('Stash', stashName)
elseif GetResourceState(QBInv):find("start") then
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7qb-inventory") end
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
if result then stashItems = json.decode(result) end
end
if stashItems then
for _, item in pairs(stashItems) do
local itemInfo = Items[item.name:lower()]
if itemInfo then
items[item.slot] = {
name = itemInfo["name"],
amount = tonumber(item.amount) or tonumber(item.count),
info = item.info ~= nil and item.info or "",
label = itemInfo["label"],
description = itemInfo["description"] ~= nil and itemInfo["description"] or "",
weight = itemInfo["weight"],
type = itemInfo["type"],
unique = itemInfo["unique"],
useable = itemInfo["useable"],
image = itemInfo["image"],
slot = item.slot,
}
end
end
if Config.System.Debug then print("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for ^7'^6"..stashName.."^7' ^2retrieved^7") end
end
return items
end
function stashRemoveItem(stashItems, stashName, items) local amount = amount and amount or 1
if GetResourceState(OXInv):find("start") then
for k, v in pairs(items) do
exports[OXInv]:RemoveItem(stashName, k, v)
if Config.System.Debug then print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v) end
end
elseif GetResourceState(QSInv):find("start") then
for k, v in pairs(items) do
exports[QSInv]:RemoveItemIntoStash(stashName, k, v)
if Config.System.Debug then print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QSInv, k, v) end
end
elseif GetResourceState(CodeMInv):find("start") 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
if Config.System.Debug then
print("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
end
stashItems[l] = nil
else
if Config.System.Debug then
print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CodeMInv, k, v)
end
stashItems[l].amount -= v
end
end
end
end
if Config.System.Debug then
print("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
end
elseif GetResourceState(QBInv):find("start") 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
if Config.System.Debug then
print("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
end
stashItems[l] = nil
else
if Config.System.Debug then
print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
end
stashItems[l].amount -= v
end
end
end
end
if Config.System.Debug then
print("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
end
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) })
else
print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
end
end
RegisterNetEvent(GetCurrentResourceName()..":server:stashRemoveItem", stashRemoveItem)
function stashhasItem(stashItems, item, amount) local amount, count = amount and amount or 1, 0
for k, itemData in pairs(stashItems) do
if itemData and (itemData.name == item) then
--if Config.System.Debug then
-- print("^6Bridge^7: ^3stashHasItem^7: ^2Item^7: '^3"..tostring(item).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)")
--end
count += (itemData.amount or 1)
end
end
if count >= amount then
if Config.System.Debug then
print("^6Bridge^7: ^3stashHasItem^7: ^2Items ^3"..item.." ^5FOUND^7 x^3"..count.."^7/^3"..amount.."^7") end
return
true
else
if Config.System.Debug then print("^6Bridge^7: ^3stashHasItem^7: ^2Items ^1NOT FOUND^7 "..json.encode(item)) end
return false
end
end

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)
```
- **setPlayerMetadata(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
setPlayerMetadata(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)
```

View File

@@ -1,19 +0,0 @@
Exports = {
QBExport = "qb-core",
QBXExport = "qbx_core",
ESXExport = "es_extended",
OXCoreExport = "ox_core",
OXInv = "ox_inventory",
QBInv = "qb-inventory",
QSInv = "qs-inventory",
CoreInv = "core-inventory",
CodeMInv = "codem-inventory",
OXLibExport = "ox_lib",
QBMenuExport = "qb-menu",
QBTargetExport = "qb-target",
OXTargetExport = "ox_target"
}

462
frameworkCache.lua Normal file
View File

@@ -0,0 +1,462 @@
--[[
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).."s)"
end
startTimer("Cache") startTimer("Items") startTimer("Vehicles") startTimer("Jobs") startTimer("InvWeight") startTimer("InvSlots")
-- Helper function to check if resource exists in server (instead of if it is already started)
local function checkExists(resourceName)
return GetResourceState(resourceName):find("start") or GetResourceState(resourceName):find("stopped")
end
-- Ensure oxmysql resource is loaded
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) then
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua')))
fileLoader()
end
if checkExists(Exports.OXCoreExport) then
-- 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
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 ---
---------------------
-- 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 -----
---------------------
-- 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
local function getInventoryConfig(resource, data)
if data.convars then
return function(path)
local key = path[1]
local convar = key == "MaxWeight" and data.convars.weight or key == "MaxSlots" and data.convars.slots
return convar and GetConvarInt(convar.key, convar.default) or nil, "Unsupported convar path: " .. table.concat(path, ".")
end
end
local content = LoadResourceFile(resource, data.file)
if not content then return nil, "Failed to load file" end
local env = {
GetConvar = GetConvar,
vector3 = vector3,
vector4 = vector4,
Citizen = Citizen,
GetResourceState = GetResourceState,
exports = exports,
DependencyCheck = function() return nil end,
}
local fn, err = load(content, '@'..data.file, 't', env)
--local success, err = xpcall(fn, function(e)
-- print(debug.traceback("Error while executing qs-inventory config:\n" .. tostring(e), 2))
--end)
if not fn then return nil, "Failed to compile config: " .. err end
if not pcall(fn) then return nil, "Error executing config file" end
local cfg = env.Config or env.config
if not cfg then return nil, "Config table not found" end
return function(path)
local ref = cfg
for _, k in ipairs(path) do
if type(ref) ~= "table" then return nil, "Path invalid at: " .. tostring(k) end
ref = ref[k]
end
return ref
end
end
-- Inventory table
local invWeightTable = {
[Exports.OXInv] = { convars = {
weight = { key = "inventory:weight", default = 30000 },
slots = { key = "inventory:slots", default = 40 }
}},
[Exports.QBInv] = {
fallback = {
{ file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } }, -- old version
{ file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } }, -- new version
}
},
[Exports.JPRInv] = { file = "configs/main_config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
[Exports.PSInv] = { file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
[Exports.QSInv] = { file = "config/config.lua", path = { "InventoryWeight", "weight" }, slotPath = { "InventoryWeight", "slots" } },
[Exports.TgiannInv] = { file = "configs/config.lua", path = { "slotsMaxWeights", "player", "maxWeight" }, slotPath = { "slotsMaxWeights", "player", "slots" } },
[Exports.CodeMInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
[Exports.RSGInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
--[Exports.OrigenInv] = { file = "config.lua", path = { "MaxWeight" } },
}
-- Run config detection
local invResource = ""
for script, data in pairs(invWeightTable) do
if checkExists(script) then
if script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end
while GetResourceState(script) ~= "started" and GetResourceState(script) ~= "stopped" do
Wait(100)
print("Waiting for script start")
end
local attempts = data.fallback or { data }
local lookup, used, err
for _, option in ipairs(attempts) do
local try, e = getInventoryConfig(script, option)
if try then
lookup = try
used = option
break
end
err = e
end
if not lookup then
print(("^1ERROR^7: ^1Config loader failed from ^5%s^7: ^1%s^7"):format(script, err or "unknown"))
break
end
local function resolve(label, path)
local val, perr = lookup(path)
if val then
cache["Inventory"..label] = val
return
end
local warnType = label == "Weight" and "^1ERROR" or "^3WARNING"
print(("%s^7: ^1Failed to get ^7Inventory%s ^1from ^5%s^7: ^1%s^7"):format(warnType, label, script, perr or "unknown"))
end
resolve("Weight", used.path or { "MaxWeight" })
resolve("Slots", used.slotPath or { "MaxSlots" })
invResource = script:gsub("-", "^7-^4"):gsub("_", "^7_^4")
break
end
::skip::
end
endTimer("InvWeight")
endTimer("InvSlots")
CreateThread(function()
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"])
end
if cache.InventorySlots then
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7"..timers["InvSlots"])
end
print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7"..timers["Items"])
print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7"..timers["Vehicles"])
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7"..timers["Jobs"])
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7"..timers["Jobs"])
endTimer("Cache")
print("^6FrameworkCache^7: ^2Cache Ready ^7"..timers["Cache"])
cacheReady = true
end)
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,866 +0,0 @@
onDuty = false
function jobCheck(job)
canDo = true
if not hasJob(job) or not onDuty then
triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"])
canDo = false
end
return canDo
end
local time = 100
function loadModel(model)
if not HasModelLoaded(model) then if Config.System.Debug then print("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'") end
while not HasModelLoaded(model) do if time > 0 then time = time - 1 RequestModel(model)
else time = 1000 print("^6Bridge^7: ^3LoadModel^7: ^2Timed out loading model ^7'^6"..model.."^7'") break end
Wait(10) end
end
end
function unloadModel(model) if Config.System.Debug then print("^6Bridge^7: ^2Removing Model^7: '^6"..model.."^7'") end SetModelAsNoLongerNeeded(model) end
function loadAnimDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..dict.."^7'") end while not HasAnimDictLoaded(dict) do RequestAnimDict(dict) Wait(5) end end
function unloadAnimDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Removing Anim Dictionary^7: '^6"..dict.."^7'") end RemoveAnimDict(dict) end
function loadPtfxDict(dict) if Config.System.Debug then if not HasNamedPtfxAssetLoaded(dict) then print("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..dict.."^7'") end end while not HasNamedPtfxAssetLoaded(dict) do RequestNamedPtfxAsset(dict) Wait(5) end end
function unloadPtfxDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'") end RemoveNamedPtfxAsset(dict) end
function loadTextureDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'") end while not HasStreamedTextureDictLoaded(dict) do RequestNamedPtfxAsset(dict) Wait(5) end end
function countTable(table) local i = 0 for keys in pairs(table) do i += 1 end return i end
function pairsByKeys(t) local a = {} for n in pairs(t) do a[#a+1] = n end table.sort(a) local i = 0 local iter = function() i += 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter end
function playAnim(animDict, animName, duration, flag, ped)
loadAnimDict(animDict)
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, 1.0, -1.0, duration or 30000, flag or 50, 1, false, false, false)
end
function stopAnim(animDict, animName)
StopAnimTask(PlayerPedId(), animName, animDict)
unloadAnimDict(animDict)
end
function makeVeh(model, coords)
loadModel(model)
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false)
SetVehicleHasBeenOwnedByPlayer(veh, true)
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
Wait(100)
SetVehicleNeedsToBeHotwired(veh, false)
SetVehRadioStation(veh, 'OFF')
SetVehicleFuelLevel(veh, 100.0)
SetVehicleModKit(veh, 0)
SetVehicleOnGroundProperly(veh)
if Config.System.Debug then
local coords = { string.format("%.2f", coords.x), string.format("%.2f", coords.y), string.format("%.2f", coords.z), (string.format("%.2f", coords.w or 0.0)) }
print("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..(model).."^7' | ^2Coord^7: ^5vec4^7(^6"..(coords[1]).."^7, ^6"..(coords[2]).."^7, ^6"..(coords[3]).."^7, ^6"..(coords[4]).."^7)")
end
unloadModel(model)
return veh
end
local Peds = {}
local Props = {}
function makePed(model, coords, freeze, collision, scenario, anim, synced)
loadModel(model)
local ped = CreatePed(0, model, coords.x, coords.y, coords.z-1.03, coords.w, synced and synced or true, false)
SetEntityInvincible(ped, true)
SetBlockingOfNonTemporaryEvents(ped, true)
FreezeEntityPosition(ped, freeze and freeze or true)
if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end
if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end
if anim then
loadAnimDict(anim[1])
TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
end
if Config.System.Debug then
local coords = { string.format("%.2f", coords.x), string.format("%.2f", coords.y), string.format("%.2f", coords.z), (string.format("%.2f", coords.w or 0.0)) }
print("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^6"..(model).."^7' | ^2Coord^7: ^5vec4^7(^6"..(coords[1]).."^7, ^6"..(coords[2]).."^7, ^6"..(coords[3]).."^7, ^6"..(coords[4]).."^7)")
end
unloadModel(model)
Peds[#Peds+1] = ped
return ped
end
function makeProp(data, freeze, synced)
loadModel(data.prop)
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced or false, synced or false, false)
SetEntityHeading(prop, data.coords.w + 180.0)
FreezeEntityPosition(prop, freeze and freeze or 0)
if Config.System.Debug then
local coords = { string.format("%.2f", data.coords.x), string.format("%.2f", data.coords.y), string.format("%.2f", data.coords.z), (string.format("%.2f", data.coords.w or 0.0)) }
print("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..(data.prop).."^7' | ^2Coord^7: ^5vec4^7(^6"..(coords[1]).."^7, ^6"..(coords[2]).."^7, ^6"..(coords[3]).."^7, ^6"..(coords[4]).."^7)")
end
unloadModel(data.prop)
Props[#Props+1] = prop
return prop
end
function cv(amount)
local formatted = amount or "0"
while true do formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') if (k==0) then break end Wait(0) end
return formatted
end
function DrawText3D(x, y, z, text)
SetTextScale(0.35, 0.35)
SetTextFont(4)
SetTextProportional(true)
SetTextColour(255, 255, 255, 215)
BeginTextCommandDisplayText("STRING")
SetTextCentre(true)
AddTextComponentSubstringPlayerName(text)
SetDrawOrigin(x,y,z, 0)
EndTextCommandDisplayText(0.0, 0.0)
local factor = string.len(text) / 370
DrawRect(0.0, 0.0 + 0.0125, 0.017 + factor, 0.03, 0, 0, 0, 75)
ClearDrawOrigin()
end
AddEventHandler('onResourceStop', function(r)
if r ~= GetCurrentResourceName() then return end
stopSpinner()
for i = 1, #Peds do DeletePed(Peds[i]) end
for i = 1, #Props do destroyProp(Props[i]) end
end)
function instantLookEnt(ent, ent2)
local p1 = GetEntityCoords(ent, true)
local p2 = GetEntityCoords(ent2, true)
local dx = p2.x - p1.x
local dy = p2.y - p1.y
local heading = GetHeadingFromVector_2d(dx, dy)
SetEntityHeading( ent, heading )
end
function lookEnt(entity) local ped = PlayerPedId()
if entity then
if type(entity) == "vector3" or type(entity) == "vector4" then
if not IsPedHeadingTowardsPosition(ped, entity.xyz, 30.0) then
TaskTurnPedToFaceCoord(ped, entity.xyz, 1500)
if Config.System.Debug then print("^6Bridge^7: ^2Turning Player to^7: '^6"..json.encode(entity).."^7'") end
Wait(1500)
end
else
if DoesEntityExist(entity) then
if not IsPedHeadingTowardsPosition(ped, GetEntityCoords(entity), 30.0) then
TaskTurnPedToFaceCoord(ped, GetEntityCoords(entity), 1500)
if Config.System.Debug then print("^6Bridge^7: ^2Turning Player to^7: '^6"..entity.."^7'") end
Wait(1500)
end
end
end
end
end
function destroyProp(entity)
if Config.System.Debug then print("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'") end
if IsEntityAttachedToEntity(entity, PlayerPedId()) then
SetEntityAsMissionEntity(entity)
DetachEntity(entity, true, true)
end
DeleteObject(entity)
end
function pushVehicle(entity)
SetVehicleModKit(entity, 0)
if entity ~= 0 and DoesEntityExist(entity) then
if not NetworkHasControlOfEntity(entity) then
if Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.") end
NetworkRequestControlOfEntity(entity)
local timeout = 2000
while timeout > 0 and not NetworkHasControlOfEntity(entity) do
Wait(100)
timeout = timeout - 100
end
if NetworkHasControlOfEntity(entity) and Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.") end
end
if not IsEntityAMissionEntity(entity) then
if Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' &2entity^7.") end
SetEntityAsMissionEntity(entity, true, true)
local timeout = 2000
while timeout > 0 and not IsEntityAMissionEntity(entity) do
Wait(100)
timeout = timeout - 100
end
if IsEntityAMissionEntity(entity) and Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") end
end
end
end
function makeBlip(data)
local blip = AddBlipForCoord(data.coords)
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)
if Config.System.Debug then print("^6Bridge^7: ^6Blip ^2created for location^7: '^6"..data.name.."^7'") end
return blip
end
function makeEntityBlip(data)
AddBlipForEntity(data.entity)
local blip = GetBlipFromEntity(data.entity)
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)
if Config.System.Debug then print("^6Bridge^7: ^6Blip ^2created for Entity^7: '^6"..data.name.."^7'") end
return blip
end
function lockInv(toggle)
FreezeEntityPosition(PlayerPedId(), toggle)
LocalPlayer.state:set("inv_busy", toggle, true)
TriggerEvent('inventory:client:busy:status', toggle)
TriggerEvent('canUseInventoryAndHotbar:toggle', not toggle)
end
function createTempCam(ent, coords)
local cam = nil
if Config.Crafting.craftCam then
if Config.System.Debug then
triggerNotify(nil, "ModCam Created", "success")
end
local camCoords = nil
if type(ent) ~= "vector3" then
camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.2, -0.3, 0.8)
else
camCoords = ent
end
--local pedCoords = GetEntityCoords(PlayerPedId())
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
return cam
end
function keyGen()
local charset = {
"q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m",
"Q","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M",
"1","2","3","4","5","6","7","8","9","0"
}
local GeneratedID = ""
for i = 1, 3 do GeneratedID = GeneratedID..charset[math.random(1, #charset)] end
return GeneratedID
end
function startTempCam(cam)
if Config.Crafting.craftCam then
SetCamActive(cam, true)
RenderScriptCams(true, true, 1000, true, true)
end
end
function stopTempCam()
if Config.Crafting.craftCam then
CreateThread(function()
Wait(1000)
RenderScriptCams(false, true, 500, true, true)
DestroyAllCams()
end)
end
end
local inProgress = false
function progressBar(data)
local result = nil
if data.cam then
startTempCam(data.cam)
end
if Config.System.ProgressBar == "ox" then
if exports[OXLibExport]:progressBar({
duration = Config.System.Debug and 1000 or data.time,
label = data.label,
useWhileDead = data.dead and data.dead or false,
canCancel = data.cancel and data.cancel or true,
anim = {
dict = data.dict,
clip = data.anim,
flag = (data.flag == 8 and 32 or data.flag) or nil,
scenario = data.task
},
disable = {
combat = true
},
}) then
result = true
lockInv(false)
if data.cam then stopTempCam(data.cam) end
else
result = false
lockInv(false)
if data.cam then stopTempCam(data.cam) end
end
elseif Config.System.ProgressBar == "qb" then
Core.Functions.Progressbar("mechbar",
data.label,
Config.System.Debug and 1000 or data.time,
data.dead and data.dead or false,
data.cancel or true,
{ disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true, },
{ animDict = data.dict, anim = data.anim, flags = data.flag and data.flag or 32, task = data.task }, {}, {},
function()
result = true
lockInv(false)
if data.cam then
stopTempCam(data.cam)
end
end, function()
result = false
lockInv(false)
if data.cam then
stopTempCam(data.cam)
end
end, data.icon)
elseif Config.System.ProgressBar == "esx" then
ESX.Progressbar(data.label, Config.System.Debug and 1000 or data.time, {
FreezePlayer = true,
animation ={
type =data.anim,
dict = data.dict,
scenario = data.task,
},
onFinish = function()
result = true
FreezeEntityPosition(PlayerPedId(), false)
lockInv(false)
if data.cam then
stopTempCam(data.cam)
end
end, onCancel = function()
result = false
FreezeEntityPosition(PlayerPedId(), false)
lockInv(false)
if data.cam then
stopTempCam(data.cam)
end
end
})
elseif Config.System.ProgressBar == "gta" then
local wait, inProgress = (Config.System.Debug and 1000 or data.time), true
--local wait = data.time
inProgress = true
if not (data.dead and data.dead or false) then
lockInv(true)
displaySpinner(data.label)
local ped = PlayerPedId()
if data.dict then
playAnim(data.dict, data.anim, -1, (data.flag == 8 and 32 or data.flag) or nil)
end
if data.task then
TaskStartScenarioInPlace(ped, data.task, -1, true)
end
while inProgress and wait > 0 do wait -= 15
local waitTimer = 0
DisablePlayerFiring(ped, true)
DisableControlAction(0, 25, true)
DisableControlAction(0, 21, true)
DisableControlAction(0, 30, true)
DisableControlAction(0, 31, true)
DisableControlAction(0, 36, true)
if data.cam ~= nil then
DisableControlAction(0, 1, true)
DisableControlAction(0, 2, true)
DisableControlAction(0, 106, true)
end
if data.cancel then
if IsControlJustReleased(0, 202) or IsControlJustReleased(0, 77) then
inProgress = false
waitTimer = 1500
displaySpinner(Loc[Config.Lan].error["cancel"])
end
end
Wait(waitTimer)
end
inProgress = false
if data.cam then stopTempCam(data.cam) end
if data.dict then stopAnim(data.dict, data.anim, ped) end
ClearPedTasks(ped)
end
lockInv(false)
stopSpinner()
result = (wait <= 0)
end
while result == nil do Wait(10) end
return result
end
function displaySpinner(text)
BeginTextCommandBusyspinnerOn('STRING')
AddTextComponentSubstringPlayerName(text)
EndTextCommandBusyspinnerOn(4)
end
function stopSpinner() if not IsDuplicityVersion() then BusyspinnerOff() end end
function stopPropgressBar()
if Config.System.ProgressBar == "ox" then
exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "gta" then
inProgress = false
BusyspinnerOff()
end
end
-- [[ OTHER ]] --
function washHands(data)
lookEnt(data.coords)
local cam = createTempCam(PlayerPedId(), data.coords)
if progressBar({
label = Loc[Config.Lan].progressbar["progress_washing"],
time = 5000,
cancel = true,
dict = "mp_arresting",
anim = "a_uncuff",
flag = 32,
icon = "fas fa-hand-holding-droplet",
cam = cam
}) then
triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
else
triggerNotify(nil, Loc[Config.Lan].error["cancel"], 'error')
end
ClearPedTasks(PlayerPedId())
end
function useToilet(data)
if data.urinal then
if progressBar({
label = "Using Urinal",
time = 5000,
cancel = true,
dict = "misscarsteal2peeing",
anim = "peeing_loop",
flag = 32
}) then
TriggerServerEvent(GetCurrentResourceName().."server:Urinal")else
lockInv(false)
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(GetCurrentResourceName().."server:Urinal")
ClearPedTasks(PlayerPedId())
else
lockInv(false)
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error')
end
end
end
RegisterNetEvent(GetCurrentResourceName()..":server:Urinal", function()
local src = source
local Player = getPlayer(src)
local thirstamt = math.random(10,30)
local thirst = Player.thirst - thirstamt
setThirst(src, getPlayer(src).thirst - thirst)
end)
RegisterNetEvent(GetCurrentResourceName()..":server:setNeed", function(type, amount) local src = source
if type == "thirst" then
setThirst(src, amount)
elseif type == "thirst" then
setHunger(src, amount)
end
end)
function setThirst(src, thirst)
if GetResourceState(ESXExport):find("start") then
TriggerClientEvent('esx_status:add', src, 'thirst', thirst)
elseif GetResourceState(QBExport):find("start") or GetResourceState(QBXExport):find("start") then
local Player = QBCore.Functions.GetPlayer(src)
Player.Functions.SetMetaData('thirst', thirst)
TriggerClientEvent("hud:client:UpdateNeeds", src, thirst, Player.PlayerData.metadata.thirst)
end
end
function setHunger(src, hunger)
if GetResourceState(ESXExport):find("start") then
TriggerClientEvent('esx_status:add', src, 'hunger', hunger)
elseif GetResourceState(QBExport):find("start") or GetResourceState(QBXExport):find("start") then
local Player = QBCore.Functions.GetPlayer(src)
Player.Functions.SetMetaData('hunger', hunger)
TriggerClientEvent("hud:client:UpdateNeeds", src, hunger, Player.PlayerData.metadata.hunger)
end
end
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
-- [[CONSUME]] --
function ConsumeSuccess(itemName, type)
ExecuteCommand("e c")
toggleItem(false, itemName, 1)
if GetResourceState(ESXEport):find("start") then
if Items[itemName].hunger then
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "hunger", Items[itemName].hunger * 10000)
end
if Items[itemName].thirst then
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "thirst", Items[itemName].thirst * 10000)
end
else
if Items[itemName].hunger then
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "hunger", Core.Functions.GetPlayerData().metadata["hunger"] + Items[itemName].hunger)
end
if Items[itemName].thirst then
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "thirst", Core.Functions.GetPlayerData().metadata["thirst"] + Items[itemName].thirst)
end
end
if type == "alcohol" then alcoholCount += 1
if alcoholCount > 1 and alcoholCount < 4 then TriggerEvent("evidence:client:SetStatus", "alcohol", 200) elseif alcoholCount >= 4 then TriggerEvent("evidence:client:SetStatus", "heavyalcohol", 200) AlienEffect() end
end
if Config.RewardItem == itemName then toggleItem(true, Config.RewardPool[math.random(1, #Config.RewardPool)], 1) end
end
function addItem(item, amount, info)
TriggerServerEvent(GetCurrentResourceName()..":server:toggleItem", true, item, amount)
end
function removeItem(item, amount)
TriggerServerEvent(GetCurrentResourceName()..":server:toggleItem", false, item, amount)
end
RegisterNetEvent(GetCurrentResourceName()..":server:toggleItem", function(give, item, amount, newsrc)
local src = newsrc or source
if Config.System.Debug then
print("^6Bridge^7: ^3toggleItem ^2triggered^7: ^6"..(tostring(give) == "true" and "addItem" or "removeItem").."^7 - ['^8"..tostring(item).."^7'] x"..(tostring(amount) or "1"))
end
local remamount = (amount and amount or 1)
if item == nil then return end
if give == 0 or give == false then
if hasItem(item, amount and amount or 1, src) then -- check if you still have the item
if GetResourceState(OXInv):find("start") then
local success = exports[OXInv]:RemoveItem(src, item, (amount and amount or 1), nil)
if Config.System.Debug then
print("^6Bridge^7: ^1Removing ^2from Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) - '"..OXInv)
end
elseif GetResourceState(QSInv):find("start") then
local success = exports[QSInv]:RemoveItem(src, item, amount)
if Config.System.Debug then
print("^6Bridge^7: ^1Removing ^2from Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) - '"..QSInv)
end
elseif GetResourceState(CoreInv):find("start") then
local success = exports[CoreInv]:removeItemExact('primary-'..src, item, amount)
if Config.System.Debug then
print("^6Bridge^7: ^1Removing ^2from Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) - '"..CoreInv)
end
elseif GetResourceState(CodeMInv):find("start") then
local success = exports[CodeMInv]:RemoveItem(src, item, amount)
if Config.System.Debug then
print("^6Bridge^7: ^1Removing ^2from Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) - '"..CodeMInv)
end
elseif GetResourceState(QBInv):find("start") then
while remamount > 0 do
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then end
remamount -= 1
end
if Config.Crafting.showItemBox then
TriggerClientEvent('inventory:client:ItemBox', src, Items[item], "remove", (amount and amount or 1))
end
if Config.System.Debug then
print("^6Bridge^7: ^1Removing ^2from Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) - '"..QBInv)
end
else
print("^4Error^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
end
else
dupeWarn(src, item) -- if not boot the player
end
else
local amount = amount and amount or 1
if GetResourceState(OXInv):find("start") then
local success = exports[OXInv]:AddItem(src, item, amount or 1, nil)
if Config.System.Debug then
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^7"..OXInv)
end
elseif GetResourceState(QSInv):find("start") then
local success = exports[QSInv]:AddItem(src, item, amount)
if Config.System.Debug then
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^7"..QSInv)
end
elseif GetResourceState(CoreInv):find("start") then
local success = exports[CoreInv]:addItem('primary-'..src, item, amount)
if Config.System.Debug then
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^7"..CoreInv)
end
elseif GetResourceState(CodeMInv):find("start") then
local success = exports[CodeMInv]:AddItem(src, item, amount)
if Config.System.Debug then
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^7"..CodeMInv)
end
elseif GetResourceState(QBInv):find("start") then
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amount or 1) then
--if Config.Crafting.showItemBox then
TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "add", amount and amount or 1)
--end
end
if Config.System.Debug then
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^7"..QBInv)
end
else
print("^4Error^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
end
end
end)
--Item Exploit protection
function dupeWarn(src, item)
print("^5DupeWarn^7: (^1"..tostring(src).."^7) ^2Tried to remove item ^7('^3"..item.."^7')^2 but it wasn't there^7")
if Config.System.Debug == false then
DropPlayer(src, src.." ^1Kicked for attempting to duplicate items")
end
print("^5DupeWarn:^7: (^1"..tostring(src).."^7) ^2Dropped from server - exploit protection detected an item not being found in players inventory^7")
end
function toggleDuty()
if GetResourceState(QBExport):find("start") or GetResourceState(QBXExport):find("start") then
TriggerServerEvent("QBCore:ToggleDuty")
else
onDuty = not onDuty
if onDuty then
triggerNotify(nil, "Now on duty", "success")
else
triggerNotify(nil, "Now off duty", "success")
end
end
end
local function CheckVersion()
if IsDuplicityVersion() then
local currentVersion = "^3"..GetResourceMetadata(GetCurrentResourceName(), 'version'):gsub("%.", "^7.^3").."^7"
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..GetCurrentResourceName()..'.txt', function(err, newestVersion, headers)
if not newestVersion then
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..GetCurrentResourceName()..'/master/version.txt', function(err, freeVersion, headers)
if not freeVersion then print("^1Currently unable to run a version check for ^7'^3"..GetCurrentResourceName().."^7' ("..currentVersion.."^7)") return end
local currentVersion = "^3"..GetResourceMetadata(GetCurrentResourceName(), 'version'):gsub("%.", "^7.^3").."^7"
freeVersion = "^3"..freeVersion:sub(1, -2):gsub("%.", "^7.^3").."^7"
print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..freeVersion)
print(freeVersion == currentVersion and "^7'^3"..GetCurrentResourceName().."^7' - ^6You are running the latest version.^7 ("..currentVersion..")" or "^7'^3"..GetCurrentResourceName().."^7' - ^1You are currently running an outdated version^7, ^1please update^7!")
end)
else
newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3").."^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()
--Screen Effects
local alienEffect = false
function AlienEffect()
if alienEffect then return else alienEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3AlienEffect^7() ^2activated") end
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
Wait(math.random(5000, 8000))
local Ped = PlayerPedId()
local animDict = "MOVE_M@DRUNK@VERYDRUNK"
loadAnimDict(animDict)
SetPedCanRagdoll(Ped, true)
ShakeGameplayCam('DRUNK_SHAKE', 2.80)
SetTimecycleModifier("Drunk")
SetPedMovementClipset(Ped, animDict, 1)
SetPedMotionBlur(Ped, true)
SetPedIsDrunk(Ped, true)
Wait(1500)
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
Wait(13500)
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
Wait(120500)
ClearTimecycleModifier()
ResetScenarioTypesEnabled()
ResetPedMovementClipset(Ped, 0)
SetPedIsDrunk(Ped, false)
SetPedMotionBlur(Ped, false)
AnimpostfxStopAll()
ShakeGameplayCam('DRUNK_SHAKE', 0.0)
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
Wait(math.random(45000, 60000))
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
AnimpostfxStop("DrugsMichaelAliensFightIn")
AnimpostfxStop("DrugsMichaelAliensFight")
AnimpostfxStop("DrugsMichaelAliensFightOut")
alienEffect = false
if Config.System.Debug then print("^5Debug^7: ^3AlienEffect^7() ^2stopped") end
end
local weedEffect = false
function WeedEffect()
if weedEffect then return else weedEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3WeedEffect^7() ^2activated") end
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
Wait(math.random(3000, 20000))
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
Wait(math.random(15000, 20000))
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
AnimpostfxStop("DrugsMichaelAliensFightIn")
AnimpostfxStop("DrugsMichaelAliensFight")
AnimpostfxStop("DrugsMichaelAliensFightOut")
weedEffect = false
if Config.System.Debug then print("^5Debug^7: ^3WeedEffect^7() ^2stopped") end
end
local trevorEffect = false
function TrevorEffect()
if trevorEffect then return else trevorEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3TrevorEffect^7() ^2activated") end
AnimpostfxPlay("DrugsTrevorClownsFightIn", 3.0, 0)
Wait(3000)
AnimpostfxPlay("DrugsTrevorClownsFight", 3.0, 0)
Wait(30000)
AnimpostfxPlay("DrugsTrevorClownsFightOut", 3.0, 0)
AnimpostfxStop("DrugsTrevorClownsFight")
AnimpostfxStop("DrugsTrevorClownsFightIn")
AnimpostfxStop("DrugsTrevorClownsFightOut")
trevorEffect = false
if Config.System.Debug then print("^5Debug^7: ^3TrevorEffect^7() ^2stopped") end
end
local turboEffect = false
function TurboEffect()
if turboEffect then return else turboEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3TurboEffect^7() ^2activated") end
AnimpostfxPlay('RaceTurbo', 0, true)
SetTimecycleModifier('rply_motionblur')
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
Wait(30000)
StopGameplayCamShaking(true)
SetTransitionTimecycleModifier('default', 0.35)
Wait(1000)
ClearTimecycleModifier()
AnimpostfxStop('RaceTurbo')
turboEffect = false
if Config.System.Debug then print("^5Debug^7: ^3TurboEffect^7() ^2stopped") end
end
local rampageEffect = false
function RampageEffect()
if rampageEffect then return else rampageEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3RampageEffect^7() ^2activated") end
AnimpostfxPlay('Rampage', 0, true)
SetTimecycleModifier('rply_motionblur')
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
Wait(30000)
StopGameplayCamShaking(true)
SetTransitionTimecycleModifier('default', 0.35)
Wait(1000)
ClearTimecycleModifier()
AnimpostfxStop('Rampage')
rampageEffect = false
if Config.System.Debug then print("^5Debug^7: ^3RampageEffect^7() ^2stopped") end
end
local focusEffect = false
function FocusEffect()
if focusEffect then return else focusEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3FocusEffect^7() ^2activated") end
Wait(1000)
AnimpostfxPlay('FocusIn', 0, true)
Wait(30000)
AnimpostfxStop('FocusIn')
focusEffect = false
if Config.System.Debug then print("^5Debug^7: ^3FocusEffect^7() ^2stopped") end
end
local NightVisionEffect = false
function NightVisionEffect()
if NightVisionEffect then return else NightVisionEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3NightVisionEffect^7() ^2activated") end
SetNightvision(true)
Wait(math.random(3000, 4000)) -- FEEL FREE TO CHANGE THIS
SetNightvision(false)
SetSeethrough(false)
NightVisionEffect = false
if Config.System.Debug then print("^5Debug^7: ^3NightVisionEffect^7() ^2stopped") end
end
local thermalEffect = false
function ThermalEffect()
if thermalEffect then return else thermalEffect = true end
if Config.System.Debug then print("^5Debug^7: ^3ThermalEffect^7() ^2activated") end
SetNightvision(true)
SetSeethrough(true)
Wait(math.random(2000, 3000)) -- FEEL FREE TO CHANGE THIS
SetNightvision(false)
SetSeethrough(false)
thermalEffect = false
if Config.System.Debug then print("^5Debug^7: ^3ThermalEffect^7() ^2stopped") end
end
--Built-in Buff effects
local healEffect = false
function HealEffect(data)
if healEffect then return end
if Config.System.Debug then print("^5Debug^7: ^3HealEffect^7() ^2activated") end
healEffect = true
local count = (data[1] / 1000)
while count > 0 do
Wait(1000)
count -= 1
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + data[2])
end
healEffect = false
if Config.System.Debug then print("^5Debug^7: ^3HealEffect^7() ^2stopped") end
end
local staminaEffect = false
function StaminaEffect(data)
if staminaEffect then return end
if Config.System.Debug then print("^5Debug^7: ^3StaminaEffect^7() ^2activated") end
staminaEffect = true
local startStamina = (data[1] / 1000)
SetRunSprintMultiplierForPlayer(PlayerId(), 1.49)
while startStamina > 0 do
Wait(1000)
if math.random(5, 100) < 10 then RestorePlayerStamina(PlayerId(), data[2]) end
startStamina -= 1
if math.random(5, 100) < 51 then end
end
startStamina = 0
SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
staminaEffect = false
if Config.System.Debug then print("^5Debug^7: ^3StaminaEffect^7() ^2stopped") end
end
function StopEffects() -- Used to clear up any effects stuck on screen
if Config.System.Debug then print("^5Debug^7: ^2All screen effects stopped") end
ShakeGameplayCam('DRUNK_SHAKE', 0.0)
SetPedToRagdoll(PlayerPedId(), 5000, 1000, 1, 0, 0, 0)
ClearTimecycleModifier()
ResetScenarioTypesEnabled()
ResetPedMovementClipset(PlayerPedId(), 0)
SetPedIsDrunk(PlayerPedId(), false)
SetPedMotionBlur(PlayerPedId(), false)
SetNightvision(false)
SetSeethrough(false)
AnimpostfxStop("DrugsMichaelAliensFightIn")
AnimpostfxStop("DrugsMichaelAliensFight")
AnimpostfxStop("DrugsMichaelAliensFightOut")
AnimpostfxStop("DrugsTrevorClownsFight")
AnimpostfxStop("DrugsTrevorClownsFightIn")
AnimpostfxStop("DrugsTrevorClownsFightOut")
AnimpostfxStop('RaceTurbo')
AnimpostfxStop('FocusIn')
AnimpostfxStop('Rampage')
end

View File

@@ -1,14 +1,27 @@
name "Jim_Bridge"
author "Jimathy"
version "1.0.1"
version "2.0.22"
description "Framework Bridge By Jimathy"
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.'
games { 'gta5', 'rdr3' }
lua54 'yes'
files {
'exports.lua',
'functions.lua',
'wrapper.lua',
'crafting.lua',
'starter.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 AuthEvent
end
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent)
authCooldown[src] = true
SetTimeout(60000, function() -- 1 minute cooldown
authCooldown[src] = nil
end)
if not receivedEvent[src] then
receivedEvent[src] = true
return AuthEvent
else
print("^1Auth^7: ^1Player ^7"..src.." ^1tried to request auth token more than once^7")
return ""
end
end)
RegisterNetEvent(getScript()..":clearAuthEventRequest", function()
local src = source
debugPrint("^1Auth^7: ^2Manually clearing Auth Event for Player Source^7:", src, AuthEvent)
receivedEvent[src] = nil
end)
-- Multiuse function to check if the generated client token is valid
function checkToken(src, token, genType, name)
if token == nil then
debugPrint("^1Auth^7: ^1No token recieved^7")
if genType == "stash" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1without an auth token^7")
elseif genType == "item" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1without an auth token^7")
end
return false
else
debugPrint("^1Auth^7: ^2Auth token received^7, ^2checking against server cache^7..")
if token ~= validTokens[src] then
debugPrint("^1Auth^7: ^1Tokens don't match! ^7", token, validTokens[src])
if genType == "stash" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1with an incorrect auth token^7")
elseif genType == "item" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1with an incorrect auth token^7")
end
return false
else
debugPrint("^1Auth^7: ^2Client and Server Auth tokens match^7!", token, validTokens[src])
validTokens[src] = nil
return true
end
end
end
else
onPlayerLoaded(function()
debugPrint("^1Auth^7: ^2Requesting ^3Auth Event^7")
AuthEvent = triggerCallback(getScript()..":callback:GetAuthEvent")
end, true)
onPlayerUnload(function()
debugPrint("^1Auth^7: ^2Clearing Auth Event^7")
TriggerServerEvent(getScript()..":clearAuthEventRequest")
end, true)
end

149
shared/_eventDebug.lua Normal file
View File

@@ -0,0 +1,149 @@
-- IN NO WAY PERFECT -- ** Experimental debugging
function toggleDebug()
Config.System.Debug = not Config.System.Debug
print("Debug Prints = "..tostring(Config.System.Debug))
end
exports("toggleDebug", toggleDebug)
function getDebug() return Config.System.Debug end
exports("getDebug", getDebug)
local origRegisterNetEvent = RegisterNetEvent
local origTriggerEvent = TriggerEvent
local origTriggerServerEvent = TriggerServerEvent
local origTriggerClientEvent = TriggerClientEvent
local origExecuteCommand = ExecuteCommand
local origRegisterCommand = RegisterCommand
local origPairs = pairs
local origiPairs = ipairs
function getDebugInfo(info)
local info = info
local level = 2
if info and info.short_src:match("scheduler.lua") then
repeat
info = debug.getinfo(level, "nSl")
level += 1
local found = false
for _, v in pairs({
"deffered.lua",
"scheduler.lua",
"_eventDebug.lua",
"targets.lua",
"init.lua",
"MySQL.lua",
"helpers.lua",
}) do
if info and info.short_src:match(v) then
found = true
end
end
until not info or (info.short_src and found == false)
end
return " ^7[^3"..(info and info.short_src:match("^.+/(.+)$") or "unknown").."^7:^3"..(info and info.currentline or "unknown").."^7]"
end
--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)
if Config.System.EventDebug 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")))
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")))
end
end
origRegisterNetEvent(name, funct)
end
function TriggerEvent(name, ...)
local data = {...}
if Config.System.EventDebug 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")))
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")))
end
for i, value in ipairs(data) do
if value then
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))
end
end end
origTriggerEvent(name, ...)
end
function TriggerServerEvent(name, ...) -- Client side, trigger a server event
local data = {...}
if Config.System.EventDebug 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")))
else
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Server ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
end
for i, value in ipairs(data) do
if value then
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))
end
end end
origTriggerServerEvent(name, ...)
end
function TriggerClientEvent(name, ...) -- Server side, trigger a client event
local data = {...}
if Config.System.EventDebug 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")))
else
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Client ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
end
for i, value in ipairs(data) do
if value then
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))
end
end end
origTriggerClientEvent(name, ...)
end
function RegisterCommand(command, funct, restrict)
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")))
end
origRegisterCommand(command, funct, restrict)
end
function ExecuteCommand(comm) -- Client side, execute /command
if Config.System.EventDebug then
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3ExecuteCommand^7: /"..comm..getDebugInfo(debug.getinfo(2, "nSl")))
end
origExecuteCommand(comm)
end
function pairs(tbl)
if not tbl then
print("^1Error^7: ^1nil ^2for ^3pairs^7(), ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
return origPairs({})
end
return origPairs(tbl)
end
function ipairs(tbl)
local tbl = tbl
if not tbl 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")))
end
tbl = {}
end
return pairsByKeys(tbl) -- change to pairsByKeys for less errors
end
--[[
local origPrint = print
function print(...)
origPrint(getDebugInfo(debug.getinfo(2, "nSl"))..":")
origPrint(...)
end
]]

227
shared/_loaders.lua Normal file
View File

@@ -0,0 +1,227 @@
--[[
Player & Resource Event Utility Functions
-------------------------------------------
This module provides functions to:
• Execute code when the player character is loaded or unloaded.
• Execute code on resource start and stop.
• Wait for the player to be logged in before proceeding.
]]
-------------------------------------------------------------
-- Player Loaded and Unloaded Events
-------------------------------------------------------------
--- 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).
---
--- @param func function The function to execute when the player is loaded.
--- @param onStart boolean (optional) If true, also execute on resource start. Default is false.
--- @usage
--- ```lua
--- onPlayerLoaded(function()
--- print("Player logged in")
--- -- Your initialization code here.
--- end, true)
--- ```
function onPlayerLoaded(func, onStart)
local onPlayerFramework = ""
local loaded = false
if onStart then
onResourceStart(function()
if not waitForLogin() then return end
loaded = true
debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 executed through ^3onPlayerLoaded^7()")
Wait(2000)
func()
end, true)
end
if not loaded then
local tempFunc = function()
Wait(2000)
debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded")
func()
end
if isStarted(QBExport) or isStarted(QBXExport) then
onPlayerFramework = QBExport
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', tempFunc)
elseif isStarted(ESXExport) then
onPlayerFramework = ESXExport
RegisterNetEvent('esx:playerLoaded', function()
if waitForSharedLoad() then
tempFunc()
end
end
)
elseif isStarted(OXCoreExport) then
onPlayerFramework = OXCoreExport
RegisterNetEvent('ox:playerLoaded', tempFunc)
elseif isStarted(RSGExport) then
onPlayerFramework = RSGExport
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
end
if onPlayerFramework ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^7()^2 with ^3" .. onPlayerFramework.."^7")
else
print("^4ERROR^7: No supported core detected for onPlayerLoaded - Check starter.lua")
end
end
end
--- Executes a function when the player character is unloaded.
--- @param func function The function to execute when the player unloads.
--- @usage
--- ```lua
--- onPlayerUnload(function()
--- print("Player has logged out of their character")
--- -- Your cleanup code here.
--- end)
--- ```
function onPlayerUnload(func)
debugPrint("^6Bridge^7: ^2Registering ^3onPlayerUnload^7()")
RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() func() end)
RegisterNetEvent('ox:playerLogout', function() func() end)
RegisterNetEvent('RSGCore:Client:OnPlayerUnload', function() func() end)
RegisterNetEvent('esx:onPlayerLogout', function() func() end)
end
-------------------------------------------------------------
-- Resource Start and Stop Events
-------------------------------------------------------------
--- Executes a function when the resource starts.
--- @param func function The function to execute.
--- @param thisScript boolean (optional) If true, only runs when this resource starts (default true).
--- @usage
--- ```lua
--- onResourceStart(function()
--- print("Script ensured")
--- -- Initialization code on resource start.
--- end, true)
--- ```
local hasPrinted = false
function onResourceStart(func, thisScript)
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^7()")
AddEventHandler('onResourceStart', function(resourceName)
if getScript() == resourceName and (thisScript or true) then
if waitForSharedLoad() then
if not hasPrinted then
debugPrint("^6Bridge^7: ^2Shared Load Detected^7.")
hasPrinted = true
end
func(resourceName)
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(resourceName)
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 = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
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 (timeout and GetGameTimer() < 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
Wait(1000)
loop += 1
end
if Jobs and Items and Vehicles then
debugPrint("^6Bridge^7: ^2Jobs, Items, and Vehicles Loaded^7.")
return true
else
print("^4Error^7: ^1Timeout reached while waiting for shared load^7.")
return false
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()

89
shared/callback.lua Normal file
View File

@@ -0,0 +1,89 @@
--- 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.
---
---@param callbackName string The name of the callback to register.
---@param funct function The function to be called when the callback is triggered.
---
---@usage
--- ```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)
--- ```
function createCallback(callbackName, funct)
if isServer() then
if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..OXLibExport.." ^3Callback^7:", callbackName)
lib.callback.register(callbackName, funct)
else
local adaptedFunction = function(source, cb, ...)
local result = funct(source, ...)
cb(result)
end
if isStarted(QBExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
Core = Core or exports[QBExport]:GetCoreObject()
Core.Functions.CreateCallback(callbackName, adaptedFunction)
elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
ESX.RegisterServerCallback(callbackName, adaptedFunction)
else
print("^1ERROR^7: ^1Can't find any supported framework to register callback with^7: "..callbackName)
end
end
end
end
--- Triggers a server callback and returns the result.
---
--- This function triggers a server callback using the appropriate framework's method and awaits the result.
---
---@param callbackName string The name of the callback to trigger.
---@param ... any Additional arguments to pass to the callback.
---
---@return any any The result returned by the callback function.
---
---@usage
--- ```lua
--- local result = triggerCallback('myCallback')
--- jsonPrint(result)
---
--- local result = triggerCallback("callback:checkVehicleOwned", plate)
--- print(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

321
shared/contextmenus.lua Normal file
View File

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

104
shared/coreloader.lua Normal file
View File

@@ -0,0 +1,104 @@
Items, Vehicles, Jobs, Gangs = nil, nil, nil, nil
-- Shared Exports Initialization
Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv
OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
Exports.OXLibExport or "",
Exports.QBXExport or "",
Exports.QBExport or "",
Exports.ESXExport or "",
Exports.OXCoreExport or ""
OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
Exports.OXInv or "",
Exports.QBInv or "",
Exports.PSInv or "",
Exports.QSInv or "",
Exports.CoreInv or "",
Exports.CodeMInv or "",
Exports.OrigenInv or "",
Exports.TgiannInv or "",
Exports.JPRInv or ""
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
QBMenuExport = Exports.QBMenuExport or ""
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
if isStarted(QBXExport) or isStarted(QBExport) then
Core = Core or exports[QBExport]:GetCoreObject()
elseif isStarted(RSGExport) then
Core = Core or exports[RSGExport]:GetCoreObject()
end
if IsDuplicityVersion() then
local cache = nil
local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
-- Wait until jim_bridge is started and export is available
while not cache and (timeout and GetGameTimer() < timeout) do
if GetResourceState("jim_bridge"):find("start") then
local success, result = pcall(function()
return exports["jim_bridge"]:GetSharedData()
end)
if success and result then
cache = result
end
end
Wait(100)
end
if timeout and not cache then
print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
return
end
Items = cache.Items
Vehicles = cache.Vehicles
Jobs = cache.Jobs
Gangs = cache.Gangs
InventoryWeight = cache.InventoryWeight or InventoryWeight
InventorySlots = cache.InventorySlots or 40
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
--print(countTable(Items), countTable(Vehicles), countTable(Jobs))
else
-- Shared bridge cache - prevent duplicate listeners
exports["jim_bridge"]:GetBridgeCache(function(cache)
Items = cache.Items or {}
Vehicles = cache.Vehicles or {}
Jobs = cache.Jobs or {}
Gangs = cache.Gangs or {}
InventoryWeight = cache.InventoryWeight or InventoryWeight
InventorySlots = cache.InventorySlots or 50
CreateThread(function()
if isStarted(ESXExport) then
for _, v in pairs(Vehicles) do
Vehicles[v.model] = {
model = v.model,
hash = v.hash,
price = v.price,
name = v.name,
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
}
end
end
if isStarted(OXInv) then
for k, v in pairs(Items) do
local tempInfo = exports[OXInv]:Items(k)
Items[k].image = k..".png" -- Set default image
if tempInfo and tempInfo.client then -- if client info, check for hard set image
Items[k].image = (tempInfo.client.image) and tempInfo.client.image:gsub("nui://"..OXInv.."/web/images/", "") or Items[k].image
Items[k].hunger = tempInfo.client.hunger
Items[k].thirst = tempInfo.client.thirst
end
end
end
end)
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
end)
end

543
shared/crafting.lua Normal file
View File

@@ -0,0 +1,543 @@
--[[
Crafting, Selling, and Shop Module
-----------------------------------
This module provides functions for opening crafting menus, handling multi-crafting,
performing the crafting process (with animations and progress bars), selling items,
and opening shop interfaces. It integrates with various inventory and menu systems,
and uses server callbacks to check item carry capacity.
]]
-------------------------------------------------------------
-- Global Variables
-------------------------------------------------------------
CraftLock = false
-- helper filter table for crafting menus
local excludeKeys = {
amount = true, metadata = true, description = true, info = true,
job = true, gang = true, oneUse = true, slot = true,
blueprintRef = true, craftingLevel = true, craftedItems = true,
hasCrafted = true, exp = true, anim = true, time = true,
}
-------------------------------------------------------------
-- Crafting Menu
-------------------------------------------------------------
--- Opens the crafting menu based on provided data.
--- Checks job restrictions, builds the recipe menu, and opens the menu.
---
--- @param data table Crafting menu configuration containing:
--- - craftable (`table`) Table with Header, Recipes, Anims, and (optionally) craftedItems.
--- - coords (`vector3`) The coordinates where the crafting menu is being opened.
--- - stashTable|stashName (`string\table`) Name(s) of the stash for checking item availability.
--- - job|gang (`string`) Job or gang requirements.
--- - onBack (optional): Function to call when returning.
---
--- @usage
--- ```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,
--- })
function craftingMenu(data)
if CraftLock then return end
-- Job or gang check; exit if not authorized.
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
-- Display a temporary "thinking" notification.
if Config.System.Menu == "jim" then
triggerNotify(nil, "Thinking", "info")
else
openMenu({ { header = "Thinking...", icon = "fas fa-hourglass-end", isMenuHeader = true } }, { header = "Crafting Menu" } )
end
-- Normalize stash name.
data.stashName = data.stashTable or data.stashName
local Menu = {}
local Recipes = data.craftable.Recipes
local craftedItems = {}
local tempCarryTable = {}
-- Build a table of all required ingredients (default quantity is 1).
for i = 1, #Recipes do
for k in pairs(Recipes[i]) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
tempCarryTable[k] = Recipes[i].amount or 1
end
end
end
-- Check if the player can carry the required items (server callback).
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
local usingStash = data.stashName ~= nil and data.stashName ~= ""
Menu[#Menu+1] = {
icon = usingStash and "fas fa-boxes-stacked" or "fas fa-person",
header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"),
disabled = true,
}
-- Process each recipe to create menu entries.
for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
for k, _ in pairs(Recipes[i]) do
if not excludeKeys[k] then
local hasjob = true
if Recipes[i].job then
for l, b in pairs(Recipes[i].job) do
hasjob = hasJob(l, nil, b)
if hasjob then break end
end
end
if hasjob then
local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil)
local itemTable = {}
local metaTable = {}
-- Build ingredient details.
for l, b in pairs(Recipes[i][tostring(k)]) do
local label = Items[l] and Items[l].label or "error - "..l
local hasItem = checkStashItem(data.stashName, { [l] = b })
local missingMark = not hasItem and "" or " "
settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark
metaTable[Items[l] and Items[l].label or "error - "..l] = b
itemTable[l] = b
end
while not canCarryTable do Wait(0) end
disable = not checkStashItem(data.stashName, itemTable)
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k))
..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️"
local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or ""
setheader = isNew .. setheader .. statusEmoji
Menu[#Menu + 1] = {
arrow = isOx() and (not disable and canCarryTable[k]),
isMenuHeader = disable or not canCarryTable[k],
icon = invImg((metadata and metadata.image) or tostring(k)),
image = invImg((metadata and metadata.image) or tostring(k)),
header = setheader,
txt = settext or nil,
metadata = metaTable,
onSelect = (not disable and canCarryTable[k]) and function()
local transdata = {
item = k,
craft = data.craftable.Recipes[i],
craftable = data.craftable,
coords = data.coords,
stashName = data.stashName,
onBack = data.onBack,
metadata = metadata,
}
if Config.Crafting.MultiCraft then
multiCraft(transdata)
else
makeItem(transdata)
end
end or nil,
}
end
end
--Wait(0)
end
end
openMenu(Menu, {
header = data.craftable.Header,
headertxt = data.craftable.Headertxt,
onBack = data.onBack or nil,
canClose = true,
onExit = data.onExit or (function() end),
})
lookEnt(data.coords)
end
-------------------------------------------------------------
-- Multi-Craft Menu
-------------------------------------------------------------
--- Opens a menu for selecting the quantity to craft.
---
--- Presents the player with multiple crafting quantities based on Config.Crafting.MultiCraftAmounts.
---
--- @param data table Crafting configuration containing:
--- - 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.
---
--- @usage
--- ```lua
--- multiCraft({
--- item = "weapon_pistol",
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
--- craftable = craftingOptions,
--- coords = vector3(100,200,300),
--- stashName = "crafting_stash",
--- onBack = function() craftingMenu(data) end,
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- })
--- ```
function multiCraft(data)
local max = 0
local stashName = nil
local maxCreation = 100
-- Generate item table to check against stash or inventory
-- takes into account the ingredients needed for multiple items
local multiItemTable = {}
for i = 1, maxCreation do
multiItemTable[i] = {}
for l, b in pairs(data.craft[data.item]) do
multiItemTable[i][l] = (b * i)
end
end
for i = 1, maxCreation do
-- if its received a stash name, check if the items are in the stash
if data.stashName then
local hasItems, stashname = checkStashItem(data.stashName, multiItemTable[i])
if hasItems == true then
max += 1
stashName = stashname
else
break
end
else
-- if not check the players inventory for the items
local has, _ = hasItem(multiItemTable[i], nil, nil)
if has then
max += 1
else
break
end
end
end
local carryMax = triggerCallback(getScript()..":server:getMaxCarryCraft", {
item = data.item,
max = max
})
local dialog = createInput(data.craftable.Header..(Config.System.Menu == "qb" and ": "..br.."How many to craft? "..br.."Max: "..carryMax or ""), {
((Config.System.Menu == "ox" or Config.System.Menu == "lation") and {
type = "slider",
label = "How many to craft? "..br.."Max: "..carryMax,
required = true,
default = 1,
min = 1,
max = carryMax
}) or nil,
((Config.System.Menu == "qb") and {
type = "number",
name = "amount",
isRequired = true,
default = 1,
}) or nil,
})
if dialog then
if Config.System.Menu == "ox" then
end
if Config.System.Menu == "qb" then
dialog["amount"] = tonumber(dialog["amount"])
if dialog["amount"] > carryMax or dialog["amount"] < 1 or dialog["amount"] == nil or dialog["amount"] == "" then
triggerNotify(nil, "Invalid Amount", "error")
craftingMenu(data)
return
end
end
makeItem({
item = data.item,
craft = data.craft,
craftable = data.craftable,
amount = dialog["amount"] or dialog[1],
coords = data.coords,
stashName = stashName or nil,
onBack = data.onBack,
metadata = data.metadata,
})
end
end
-------------------------------------------------------------
-- Crafting Process
-------------------------------------------------------------
--- Initiates the crafting process for a specified item.
---
--- Plays crafting animations, shows progress bars, removes ingredients, and triggers item creation.
---
--- @param data table Crafting configuration containing:
--- - item `string`) The item to craft.
--- - craft (`table`) The crafting recipe.
--- - craftable (`table`) Crafting options.
--- - amount (`number`) (optional): Quantity to craft (default 1).
--- - 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.
---
--- @usage
--- ```lua
--- makeItem({
--- item = "weapon_pistol",
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
--- craftable = craftingOptions,
--- amount = 2,
--- coords = vector3(100,200,300),
--- stashName = "crafting_stash",
--- onBack = function() craftingMenu(data) end,
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- })
--- ```
function makeItem(data)
if CraftLock then return end
CraftLock = true
data.stashName = data.stashTable or data.stashName
local bartime = (data.craftable.progressBar and data.craftable.progressBar.time) or 5000
local bartext = (data.craftable.progressBar and data.craftable.progressBar.label)
or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"])
or "Making "
local animDict = (data.craftable.Anims and data.craftable.Anims.animDict) or "amb@prop_human_parking_meter@male@idle_a"
local anim = (data.craftable.Anims and data.craftable.Anims.anim) or "idle_a"
local craftAmount = (data.amount and data.amount ~= 1) and data.amount or 1
local metadata = data.metadata or nil
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
local canReturn = true
local crafted, crafting = true, true
local cam = createTempCam(PlayerPedId(), data.coords)
startTempCam(cam)
-- Calculate total bartime if SingleProgress is enabled
local totalBartime = bartime * craftAmount
-- Run ingredient check and usage separately first
for i = 1, craftAmount do
for k, v in pairs(data.craft) do
if not excludeKeys[k] and type(v) == "table" then
for l, b in pairs(v) do
if isInventoryOpen() then
print("^1Error^7: ^2Inventory is open, you tried to break things")
stopTempCam()
ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(data) end
CraftLock = false
return
end
if crafting and progressBar({
label = "Using "..b.." "..Items[l].label,
time = 1000,
cancel = true,
dict = 'pickup_object',
anim = "putdown_low",
flag = 49,
icon = l,
}) then
TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b)
else
crafted, crafting = false, false
break
end
Wait(200)
end
end
end
end
if not crafted then
stopTempCam()
ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(data) end
CraftLock = false
return
end
-- Handle SingleProgress option
if Config.Crafting.SingleProgress then
local craftProp = nil
if prop then
craftProp = makeProp({ prop = prop.model, coords = vec4(0, 0, 0, 0), true, true })
AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true)
end
if data.sound then
local s = data.sound
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label).." x"..craftAmount,
time = totalBartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
request = true,
}) then
data.craft.amount = craftAmount
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil -- clear client cached token
-- handle metadata and experience in a single go
if data.craft["hasCrafted"] ~= nil then
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give * craftAmount
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false
end
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
end
if craftProp then destroyProp(craftProp) end
else
-- Run the original loop for multiple progress bars
for i = 1, craftAmount do
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label),
time = bartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
request = true,
}) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil
if data.craft["hasCrafted"] ~= nil then
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
else
break
end
end
end
Wait(500)
stopTempCam()
CraftLock = false
if canReturn then craftingMenu(data) end
ClearPedTasks(PlayerPedId())
end
-------------------------------------------------------------
-- Server Event Handler: Crafted Item
-------------------------------------------------------------
--- Server event handler for giving the crafted item to the player.
---
--- Removes required ingredients from the player's inventory or stash,
--- then adds the crafted item to their inventory.
---
--- @param ItemMake string The item being crafted.
--- @param craftable table The crafting recipe and details.
--- @param stashName string|table The stash name(s) to remove ingredients from.
--- @param metadata table (optional) Metadata for the crafted item.
--- @usage
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata, token)
local src = source
-- debugPrint("^6Bridge^7: ^4"..getScript().."^7:^4Crafting^7:^4GetItem ^2invoked by^7: "..GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1ERROR^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return
end
if not checkToken(src, token, "item", ItemMake) then
return
end
local hasItems, hasTable = hasItem(ItemMake, 1, src)
if stashName then
local itemRemove = {}
if type(stashName) == "table" then
for _, name in pairs(stashName) do
stashItems = getStash(name)
for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do
if k == b.name then
itemRemove[k] = v
end
end
end
end
else
stashItems = getStash(stashName)
for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do
if k == b.name then
itemRemove[k] = v
end
end
end
end
stashRemoveItem(stashItems, stashName, itemRemove)
else
if craftable then
for k, v in pairs(craftable[ItemMake] or {}) do
removeItem(tostring(k), v, src)
end
end
end
addItem(ItemMake, craftable.amount or 1, metadata, src)
-- Optionally, add experience here:
-- for example:
-- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
end)

115
shared/drawText.lua Normal file
View File

@@ -0,0 +1,115 @@
--[[
Text Drawing Module
---------------------
This module provides functions to display and hide text on screen using
various frameworks: QB, OX, GTA, and ESX.
]]
--- Displays text on the screen using the configured draw text system.
---
--- Depending on Config.System.drawText, this function will use different methods to
--- display text along with optional images/icons.
---
--- @param image string|nil Optional image/icon identifier to display with the text.
--- @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).
--- @param oxStyleTable table|nil Optional table specifying style parameters for the OX text UI.
---
---@usage
--- ```lua
--- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
--- ```
function drawText(image, input, style, oxStyleTable)
local text = ""
if not radarTable then radarTable = {} end
if Config.System.drawText == "qb" then
-- Concatenate lines for QB system with HTML line breaks.
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
exports[QBExport]:DrawText(text, 'left')
elseif Config.System.drawText == "ox" then
-- Append newline spacing to each input line.
local inputnum = countTable(input)
for k, v in pairs(input) do
input[k] = v..(inputnum ~= k and " \n" or "")
end
lib.showTextUI(table.concat(input), { icon = (image and radarTable[image] or image) or nil, position = 'left-center', style = oxStyleTable })
elseif Config.System.drawText == "lation" then
local inputnum = countTable(input)
for k, v in pairs(input) do
input[k] = v..(inputnum ~= k and " \n" or "")
end
exports.lation_ui:showText({
--title = " ",
description = table.concat(input),
keybind = nil,
icon = (image and radarTable[image] or image) or nil,
iconColor = '#3B82F6',
position = 'left-center'
})
elseif Config.System.drawText == "gta" then
-- Concatenate input lines and apply GTA style formatting.
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

161
shared/duifunctions.lua Normal file
View File

@@ -0,0 +1,161 @@
if gameName ~= "rdr3" then
--[[
DUI Module (Experimental)
--------------------------
This module handles the creation, modification, and removal of custom DUI (Display UI)
elements using runtime textures. It supports both client and server functionality to update DUI
images dynamically.
]]
-- Create a runtime texture dictionary on the client if not running on the server.
scriptTxd = not isServer() and CreateRuntimeTxd(getScript()..'scriptTxd') or nil
customDUIList = {}
-------------------------------------------------------------
-- DUI Client Functions
-------------------------------------------------------------
--- Creates or updates a DUI element.
---
--- @param name string The unique name for the DUI element.
--- @param http string The URL to load into the DUI.
--- @param size table A table with .x and .y fields specifying the DUI dimensions.
--- @param txd table The runtime texture dictionary where the DUI texture will be created.
--- @usage
--- ```lua
--- createDui("logo", "https://example.com/logo.png", { x = 512, y = 256 }, scriptTxd)
--- ```
function createDui(name, http, size, txd)
if not customDUIList[name] then
local newDui = CreateDui(http, math.floor(size.x), math.floor(size.y))
while not GetDuiHandle(newDui) do Wait(0) end
CreateRuntimeTextureFromDuiHandle(txd, name, GetDuiHandle(newDui))
customDUIList[name] = newDui
SetDuiUrl(customDUIList[name], http)
else
SetDuiUrl(customDUIList[name], http)
end
end
--- Opens a DUI selection input allowing the user to change the DUI image URL.
---
--- @param data table A table containing DUI data:
--- - name: The key name in the DUI list.
--- - texn: The texture name.
--- - texd: The texture dictionary.
--- - size: A table with .x and .y dimensions.
---
--- @usage
--- ```lua
--- DuiSelect({ name = "logo", texn = "logoTex", texd = "someTxd", size = { x = 512, y = 256 } })
--- ```
function DuiSelect(data)
local imagePreview = "![test]("..data.url..")"
--local imagePreview = "<center>- Current Image -<br>" ..
-- "<img src="..data.url.." width=150px><br>" ..
-- "Size: ["..math.floor(data.size.x)..", "..math.floor(data.size.y).."]<br><br>"
local dialog = createInput(imagePreview, {
{ type = "text", text = "dui_url", name = "url", isRequired = true },
})
if dialog then
data.url = dialog.url or dialog[1]
-- Scan URL for valid image extension and banned words.
local searchList = { "png", "jpg", "jpeg", "gif", "webp", "bmp" }
local banList = { "porn" }
local searchFound = false
for _, ext in pairs(searchList) do
if string.find(tostring(data.url), ext) then
searchFound = true
break
end
end
for _, banned in pairs(banList) do
if string.find(tostring(data.url), banned) then
searchFound = false
print("BANNED WORD: "..banned)
break
end
end
if searchFound then
TriggerServerEvent(getScript()..":Server:ChangeDUI", data)
end
end
end
--- Client event handler to update DUI elements.
RegisterNetEvent(getScript()..":Client:ChangeDUI", function(data)
debugPrint("^6Bridge^7: ^2Receiving new DUI ^7- ^6"..data.url.."^7")
if tostring(data.url) ~= "-" then
createDui(data.texn, tostring(data.url), data.size, scriptTxd)
AddReplaceTexture(tostring(data.texd), tostring(data.texn), getScript().."scriptTxd", tostring(data.texn))
end
end)
--- Client event handler to clear DUI elements.
RegisterNetEvent(getScript()..":Client:ClearDUI", function(data)
if customDUIList[tostring(data.texn)] then
RemoveReplaceTexture(tostring(data.texd), tostring(data.texn))
if IsDuiAvailable(customDUIList[tostring(data.texn)]) then
SetDuiUrl(customDUIList[data.name], nil)
end
end
end)
-------------------------------------------------------------
-- DUI Server Functions
-------------------------------------------------------------
--- Server event handler to change DUI settings.
--- If no URL is provided, resets to the preset value.
RegisterNetEvent(getScript()..":Server:ChangeDUI", function(data)
if not data.url then
debugPrint("^6Bridge^7: ^2Preset: ^6"..tostring(data.preset).."^7")
data.url = data.preset
else
for k, v in pairs(Locations[data.name].duiList) do
if v.tex.texn == data.texn then
Locations[data.name].duiList[k].url = data.url
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

190
shared/effects.lua Normal file
View File

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

871
shared/helpers.lua Normal file
View File

@@ -0,0 +1,871 @@
--[[
Utility Functions for Resource Management and Debugging
----------------------------------------------------------
This script provides a set of utility functions for managing resources,
debugging, and handling common tasks in the game environment.
It includes functions to check resource states, generate unique keys,
format numbers and coordinates, handle JSON data, perform raycasts, and more.
]]
-------------------------------------------------------------
-- Resource and Environment Checks
-------------------------------------------------------------
--- Checks if a specific resource is started.
--- @param script string The name of the resource.
--- @return boolean boolean True if the resource state contains "start", false otherwise.
---@usage
--- ```lua
--- if isStarted("myResource") then
--- print("Resource is running")
--- end
--- ```
function isStarted(script)
if not script then
print("^1Error^7: ^1Tried to check if ^3nil^2 was started^7, ^1returning ^4false^7")
return false
end
return GetResourceState(script):find("start") ~= nil
end
local scriptName = nil
--- Retrieves the current resource name, caching it for efficiency.
--- @return string string The current resource name.
--- @usage
--- ```lua
--- local currentScript = getScript()
--- print("Current script:", currentScript)
--- ```
function getScript()
if not scriptName then scriptName = GetCurrentResourceName() end
return scriptName
end
--- Determines if the current context is the server.
--- @return boolean boolean True if running on the server, false otherwise.
---@usage
--- ```lua
--- if isServer() then
--- -- Server-specific code
--- else
--- -- Client-specific code
--- end
--- ```
function isServer()
return IsDuplicityVersion()
end
function checkExportExists(resource, export)
if not resource or not export then return false end
local exists = false
local ok, result = pcall(function()
exists = type(exports[resource][export]) == "function"
end)
if ok and exists then
debugPrint(("^3Export Check^7: ^5exports^7['^5%s^7']:^5%s^7() ^2Exists^7"):format(resource, export))
return true
else
debugPrint(("^3Export Check^7: ^5exports^7['^5%s^7']:^5%s^7() ^1Doesn^7'^1t Exist^7"):format(resource, export))
return false
end
end
-------------------------------------------------------------
-- Debugging and JSON Utilities
-------------------------------------------------------------
--- Prints debug messages if debugMode is enabled.
--- Concatenates all arguments and prints them with debug info.
--- @param ... any One or more values to print.
--- @usage
--- ```lua
--- debugPrint("Player has joined:", playerName)
--- ```
function debugPrint(...)
if debugMode then
local args = {...}
local output = table.concat(args, " ")
print(output, getDebugInfo(debug.getinfo(2, "nSl")))
end
end
--- Prints event-related debug messages if event debugging is enabled.
--- @param ... any One or more values to print.
--- @usage
--- ```lua
--- eventPrint("Event triggered:", eventName)
--- ```
function eventPrint(...)
if Config.System.EventDebug then
print(...)
end
end
--- Returns the keys of a table in sorted order.
--- @param tbl table The table to sort keys for.
--- @return table table A sorted array of keys.
function getSortedKeys(tbl)
local keys = {}
for k in pairs(tbl) do keys[#keys + 1] = k end
table.sort(keys, function(a, b)
local numA, numB = tonumber(a), tonumber(b)
if numA and numB then return numA < numB else return tostring(a) < tostring(b) end
end)
return keys
end
--- Recursively colorizes a table for debug printing.
--- @param tbl table The table to colorize.
--- @return table table A new table with colorized keys and values.
--- ```lua
--- local colorizedData = colorizeTable(myTable)
--- jsonPrint(colorizedData)
--- ```
function colorizeTable(tbl)
local newData, sortedKeys = {}, getSortedKeys(tbl)
for _, k in ipairs(sortedKeys) do
local v = tbl[k]
newData["^6"..tostring(k).."^7"] =
(type(v) == "table" and colorizeTable(v))
or (tostring(type(v)):find("vector") and formatCoord(v))
or "^2"..tostring(v).."^7"
end
return newData
end
--- Encodes a table into an ordered JSON string with indentation.
--- @param data table The table to encode.
--- @param indent string The indentation string (e.g., " ").
--- @param level number The current level of indentation.
--- @return string The formatted JSON string.
--- @usage
--- ```lua
--- local jsonString = encodeOrderedJSON(myTable, " ", 0)
--- print(jsonString)
--- ```
function encodeOrderedJSON(data, indent, level)
local jsonParts, prefix, sortedKeys = {"{"}, string.rep(indent, level), getSortedKeys(data)
for i, k in ipairs(sortedKeys) do
jsonParts[#jsonParts + 1] = (i > 1 and ",\n" or "\n")..prefix..indent..json.encode(k)..": "
jsonParts[#jsonParts + 1] = (type(data[k]) == "table") and encodeOrderedJSON(data[k], indent, level + 1) or json.encode(data[k])
end
jsonParts[#jsonParts + 1] = "\n"..prefix.."}"
return table.concat(jsonParts)
end
--- Prints a table as a colorized and ordered JSON string if debugMode is enabled.
--- @param data table The table to print.
--- @usage
--- ```lua
--- jsonPrint(myTable)
--- ```
function jsonPrint(data)
if debugMode then
print(encodeOrderedJSON(colorizeTable(data), " ", 0), getDebugInfo(debug.getinfo(2, "nSl")))
end
end
--- Retrieves the current time formatted for debug prints.
--- @return string string The formatted time string, e.g., "^7(14:23:45)".
--- @usage
--- ```lua
--- local currentTime = GetPrintTime()
--- debugPrint("Current Time:", currentTime)
--- ```
function GetPrintTime()
if isServer() then
local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
else
local _, _, _, hour, min, sec = GetLocalTime()
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
end
end
--- Generates a unique 3-character alphanumeric key.
--- @return string string The generated key.
--- @usage
--- ```lua
--- local uniqueKey = keyGen()
--- print("Generated Key:", uniqueKey)
--- ```
function keyGen()
local charset = {
"q","w","e","r","t","y","u","i","o","p",
"a","s","d","f","g","h","j","k","l",
"z","x","c","v","b","n","m",
"Q","W","E","R","T","Y","U","I","O","P",
"A","S","D","F","G","H","J","K","L",
"Z","X","C","V","B","N","M",
"1","2","3","4","5","6","7","8","9","0"
}
local GeneratedID = ""
for i = 1, 3 do
GeneratedID = GeneratedID..charset[math.random(1, #charset)]
end
return GeneratedID
end
-------------------------------------------------------------
-- Formatting and Vector Math Functions
-------------------------------------------------------------
--- Formats a number with commas as thousand separators.
--- @param amount number The number to format.
--- @return string string The formatted number.
--- @usage
--- ```lua
--- local formattedNumber = cv(1000000) -- "1,000,000"
--- print(formattedNumber)
--- ``
function cv(amount)
local formatted = tostring(amount or "0")
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
if k == 0 then break end
Wait(0)
end
return formatted
end
--- Formats a coordinate vector for debug printing.
--- @param coord table A vector3 or vector4 with x, y, z (and optional w).
--- @return string string The formatted coordinate string.
--- @usage
--- ```lua
--- local formattedCoord = formatCoord(vector3(100.0, 200.0, 300.0))
--- debugPrint("Player Position:", formattedCoord)
--- ```
function formatCoord(coord)
local vecType = type(coord):gsub("tor", "")
local components = {
[1] = coord.x and ("^6"..string.format("%.1f", coord.x)) or "",
[2] = coord.y and ("^7, ^6"..string.format("%.1f", coord.y)) or "",
[3] = coord.z and ("^7, ^6"..string.format("%.1f", coord.z)) or "",
[4] = coord.w and ("^7, ^6"..string.format("%.1f", coord.w)) or "",
}
return "^5"..vecType.."^7("..components[1]..components[2]..components[3]..components[4].."^7)"
end
--- Calculates the center point of a list of coordinates.
--- @param tbl table An array of vector3 coordinates.
--- @return vector3 vector3 The center coordinate.
--- @usage
--- ```lua
--- local center = getCenterOfZones({vector3(100, 200, 300), vector3(110, 210, 310)})
--- print("Center of Zones:", center)
--- ```
function getCenterOfZones(tbl)
local totalX, totalY, totalZ = 0, 0, 0
for _, coord in ipairs(tbl) do
totalX = totalX + coord.x
totalY = totalY + coord.y
totalZ = totalZ + coord.z
end
local count = #tbl
return vector3(totalX / count, totalY / count, totalZ / count)
end
--- Counts the number of keys in a table.
--- @param tbl table The table to count.
--- @return number number The key count.
--- @usage
--- ```lua
--- local count = countTable(myTable)
--- print("Number of keys:", count)
--- ```
function countTable(tbl)
local i = 0
for _ in pairs(tbl) do i += 1 end
return i
end
--- Returns an iterator over a table's keys in sorted order.
--- @param t table The table to iterate over.
--- @return function An iterator function for sorted keys.
--- @usage
--- ```lua
--- for k, v in pairsByKeys(myTable) do
--- print(k, v)
--- end
--- ```
function pairsByKeys(t)
if not t then
print("^1Error^7: ^3Nil ^2table recieved for ^3pairsByKeys^7(), ^2setting to ^7{} ^2to prevent break^7")
t = {}
end
local a = {} for n in pairs(t) do a[#a+1] = n end table.sort(a) local i = 0 local iter = function() i += 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter
end
--- Creates a new table with consecutive numerical indices sorted by the 'id' field.
--- @param originalTable table The table containing entries with an 'id' field.
--- @return table table A sorted table with consecutive indices.
--- @usage
--- ```lua
--- local sortedTable = createConsecutiveTable(originalTable)
--- for i, entry in ipairs(sortedTable) do
--- print(i, entry)
--- end
--- ```
function createConsecutiveTable(originalTable)
local sortedEntries = {}
for _, entry in pairs(originalTable) do table.insert(sortedEntries, entry) end
table.sort(sortedEntries, function(a, b) return a.id < b.id end)
local newTable = {}
for newIndex, entry in ipairs(sortedEntries) do
entry.id = newIndex
newTable[newIndex] = entry
end
return newTable
end
--- Concatenates a table of strings into a single string separated by newlines.
--- @param tbl table The table containing strings.
--- @return string string The concatenated string.
--- @usage
--- ```lua
--- local combinedText = concatenateText({"Line 1", "Line 2", "Line 3"})
--- print(combinedText)
--- ```
function concatenateText(tbl)
local result = ""
for i = 1, #tbl do
result = result..tbl[i]..(i < #tbl and "\n" or "")
end
return result
end
--- Converts a rotation (degrees) to a direction vector.
--- @param rot vector3 A vector3 with rotation values.
--- @return vector3 vector3 The forward direction vector.
--- @usage
--- ```lua
--- local direction = RotationToDirection({ z = 90 })
--- print(direction)
--- ```
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
--- Creates a basic progress bar string.
--- @param percentage number Completion percentage (0-100).
--- @return string string The progress bar (e.g., "█████░░░░░").
--- @usage
--- ```lua
--- local bar = basicBar(50) -- "█████░░░░░"
--- print(bar)
--- ```
function basicBar(percentage)
local perc = math.ceil(percentage)
local total = 10
local filled = math.floor((perc / 100) * total)
local empty = total - filled
return string.rep("", filled)..string.rep("", empty)
end
--- Normalizes a 3D vector.
--- @param vec vector3 A vector3 table.
--- @return vector3 vector3 A normalized vector.
--- @usage
--- ```lua
--- local normalizedVec = normalizeVector(vector3(1, 2, 3))
--- print(normalizedVec)
--- ```
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
-------------------------------------------------------------
-- Drawing and Raycasting Functions
-------------------------------------------------------------
--- Draws a line between two coordinates (for debugging).
--- @param startCoords vector3 The starting coordinate.
--- @param endCoords vector3 The ending coordinate.
--- @param col vector4 A vector4 specifying color and opacity.
--- @usage
--- ```lua
--- drawLine(vector3(100, 200, 300), vector3(150, 250, 350), vector4(255, 0, 0, 255))
--- ```
function drawLine(startCoords, endCoords, col)
if debugMode then
CreateThread(function()
local count = 1000
while count >= 0 do
DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w)
count -= 10
Wait(0)
end
end)
end
end
--- Draws a sphere at the specified coordinates (for debugging).
--- @param coords vector3 The center of the sphere.
--- @param col vector4 A vector4 specifying color and opacity.
--- @usage
--- ```lua
--- drawSphere(vector3(100, 200, 300), vector4(0, 255, 0, 255))
--- ```
function drawSphere(coords, col)
if debugMode then
CreateThread(function()
local count = 1000
while count >= 0 do
DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w)
count -= 1
Wait(10)
end
end)
end
end
--- Performs a raycast between two coordinates and returns the results.
--- @param startCoords vector3 The starting coordinate.
--- @param endCoords vector3 The ending coordinate.
--- @param entity number|nil An entity to ignore.
--- @param flags number|nil Optional raycast flags (default: 4294967295).
--- @return multiple Multiple values returned by GetShapeTestResultIncludingMaterial.
--- @usage
--- ```lua
--- local hit, hitPos, material = PerformRaycast(startVec, endVec, playerPed, 1)
--- if hit == 1 then
--- print("Hit at position:", hitPos)
--- print("Material:", material)
--- end
--- ```
function PerformRaycast(startCoords, endCoords, entity, flags)
drawLine(startCoords, endCoords, vec4(0,0,255,255))
local val1, val2, val3, val4, val5, val6 = GetShapeTestResult(
StartExpensiveSynchronousShapeTestLosProbe(
startCoords.x, startCoords.y, startCoords.z,
endCoords.x, endCoords.y, endCoords.z,
flags or 4294967295, entity, 0
)
)
return val1, val2, val3, val4, val5, val6
end
--- Adjusts the Z-coordinate of a position to the ground level.
--- @param coords vector4 A vector3 or vector4 with x, y, z (and optional w).
--- @return vector3|vector4 vector The coordinates adjusted for ground level.
--- @usage
--- ```lua
--- local groundCoords = adjustForGround(playerCoords)
--- print("Ground Position:", groundCoords)
--- ```
function adjustForGround(coords)
local foundGround, zPos = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0)
if foundGround then
if coords.w then
return vec4(coords.x, coords.y, zPos, coords.w)
else
return vec3(coords.x, coords.y, zPos)
end
else
return coords
end
end
--- Ensures a network vehicle exists from its network ID.
--- @param vehNetID number The network ID.
--- @return number number The vehicle entity, or 0 if not found.
--- @usage
--- ```lua
--- local vehicle = ensureNetToVeh(netID)
--- if vehicle ~= 0 then
--- print("Vehicle exists:", vehicle)
--- end
--- ```
function ensureNetToVeh(vehNetID)
--debugPrint("^6Bridge^7: ^3ensureNetToVeh^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..vehNetID.."^7)")
local timeout = 100
while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do
timeout -= 1
Wait(10)
end
if not NetworkDoesNetworkIdExist(vehNetID) then return 0 end
timeout = 100
local vehicle = NetToVeh(vehNetID)
while not DoesEntityExist(vehicle) and vehicle ~= 0 and timeout > 0 do
timeout -= 1
Wait(10)
end
if not DoesEntityExist(vehicle) then return 0 end
return vehicle
end
--- Ensures a network entity exists from its network ID.
--- @param entNetID number The network ID.
--- @return number number The entity, or 0 if not found.
--- @usage
--- ```lua
--- local entity = ensureNetToEnt(netID)
--- if entity ~= 0 then
--- print("Entity exists:", entity)
--- end
--- ```
function ensureNetToEnt(entNetID)
--debugPrint("^6Bridge^7: ^3ensureNetToEnt^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..entNetID.."^7)")
local timeout = 100
while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do
timeout -= 1
Wait(10)
end
if not NetworkDoesNetworkIdExist(entNetID) then return 0 end
timeout = 100
local entity = NetworkGetEntityFromNetworkId(entNetID)
while not DoesEntityExist(entity) and entity ~= 0 and timeout > 0 do
timeout -= 1
Wait(10)
end
if not DoesEntityExist(entity) then return 0 end
return entity
end
function sendLog(text)
local Player = getPlayer()
local coords = GetEntityCoords(PlayerPedId())
local _, _, _, hour, min, sec = GetLocalTime()
local data = {
script = debug.getinfo(2, "nSl"),
coords = coords,
localTime = { hour = hour, min = min, sec = sec },
firstname = Player.firstname,
lastname = Player.lastname,
source = Player.source,
id = Player.citizenId,
text = text,
}
debugPrint("^5Log Message^7: "..getScript().." - "..Player.firstname.." "..Player.lastname.."("..Player.source..") ["..Player.citizenId.."]", text)
TriggerServerEvent(getScript()..":server:sendlog", data)
end
function sendServerLog(data)
local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
data.serverTime = { house = hour, min = min, sec = sec }
--jsonPrint(data)
debugPrint("^5Log Message^7: "..getScript().." - "..data.firstname.." "..data.lastname.."("..data.source..") ["..data.id.."]", data.text)
-- Add your logger here
end
RegisterNetEvent(getScript()..":server:sendlog", sendServerLog)
-- This function was created to help create random numbers
-- When you create a table of items with a random hunger value for example
-- `hunger = math.random(10, 20)` this will be set at script start and never change
-- using `hunger = {10, 20}` and then calling this function will make it generate a random number every time
-- It also fallsback if it simply recieved a number instead of a table
-- For example:
-- local hunger = {10, 20}
-- local hungerAmount = GetRandomTiming(hunger)
-- print(hungerAmount) -- number between 10, 20
function GetRandomTiming(tbl)
if type(tbl) == "table" then
return math.random(tbl[1], tbl[2])
else
return tbl
end
end
-------------------------------------------------------------
-- Material and Prop Functions
-------------------------------------------------------------
--- A table mapping material names to their corresponding hash values.
---
--- Used for identifying materials based on hash codes.
local materials = {
none = -1,
Unknown = -1775485061,
concrete = 1187676648,
concrete_pothole = 359120722,
concrete_dusty = -1084640111,
tarmac = 282940568,
tarmac_painted = -1301352528,
tarmac_pothole = 1886546517,
rumble_strip = -250168275,
breeze_block = -954112554,
rock = -840216541,
rock_mossy = -124769592,
stone = 765206029,
cobblestone = 576169331,
brick = 1639053622,
marble = 1945073303,
paving_slab = 1907048430,
sandstone_solid = 592446772,
sandstone_brittle = 1913209870,
sand_loose = -1595148316,
sand_compact = 510490462,
sand_wet = 909950165,
sand_track = -1907520769,
sand_underwater = -1136057692,
sand_dry_deep = 509508168,
sand_wet_deep = 1288448767,
ice = -786060715,
ice_tarmac = -1931024423,
snow_loose = -1937569590,
snow_compact = -878560889,
snow_deep = 1619704960,
snow_tarmac = 1550304810,
gravel_small = 951832588,
gravel_large = 2128369009,
gravel_deep = -356706482,
gravel_train_track = 1925605558,
dirt_track = -1885547121,
mud_hard = -1942898710,
mud_pothole = 312396330,
mud_soft = 1635937914,
mud_underwater = -273490167,
mud_deep = 1109728704,
marsh = 223086562,
marsh_deep = 1584636462,
soil = -700658213,
clay_hard = 1144315879,
clay_soft = 560985072,
grass_long = -461750719,
grass = 1333033863,
grass_short = -1286696947,
hay = -1833527165,
bushes = 581794674,
twigs = -913351839,
leaves = -2041329971,
woodchips = -309121453,
tree_bark = -1915425863,
metal_solid_small = -1447280105,
metal_solid_medium = -365631240,
metal_solid_large = 752131025,
metal_hollow_small = 15972667,
metal_hollow_medium = 1849540536,
metal_hollow_large = -583213831,
metal_chainlink_small = 762193613,
metal_chainlink_large = 125958708,
metal_corrugated_iron = 834144982,
metal_grille = -426118011,
metal_railing = 2100727187,
metal_duct = 1761524221,
metal_garage_door = -231260695,
metal_manhole = -754997699,
wood_solid_small = -399872228,
wood_solid_medium = 555004797,
wood_solid_large = 815762359,
wood_solid_polished = 126470059,
wood_floor_dusty = -749452322,
wood_hollow_small = 1993976879,
wood_hollow_medium = -365476163,
wood_hollow_large = -925419289,
wood_chipboard = 1176309403,
wood_old_creaky = 722686013,
wood_high_density = -1742843392,
wood_lattice = 2011204130,
ceramic = -1186320715,
roof_tile = 1755188853,
roof_felt = -1417164731,
fibreglass = 1354180827,
tarpaulin = -642658848,
plastic = -2073312001,
plastic_hollow = 627123000,
plastic_high_density = -1625995479,
plastic_clear = -1859721013,
plastic_hollow_clear = 772722531,
plastic_high_density_clear = -1338473170,
fibreglass_hollow = -766055098,
rubber = -145735917,
rubber_hollow = -783934672,
linoleum = 289630530,
laminate = 1845676458,
carpet_solid = 669292054,
carpet_solid_dusty = 158576196,
carpet_floorboard = -1396484943,
cloth = 122789469,
plaster_solid = -574122433,
plaster_brittle = -251888898,
cardboard_sheet = 236511221,
cardboard_box = -1409054440,
paper = 474149820,
foam = 808719444,
feather_pillow = 1341866303,
polystyrene = -1756927331,
leather = -570470900,
tvscreen = 1429989756,
slatted_blinds = 673696729,
glass_shoot_through = 937503243,
glass_bulletproof = 244521486,
glass_opaque = 1500272081,
perspex = -1619794068,
car_metal = -93061983,
car_plastic = 2137197282,
car_softtop = -979647862,
car_softtop_clear = 2130571536,
car_glass_weak = 1247281098,
car_glass_medium = 602884284,
car_glass_strong = 1070994698,
car_glass_bulletproof = -1721915930,
car_glass_opaque = 513061559,
water = 435688960,
blood = 5236042,
oil = -634481305,
petrol = -1634184340,
fresh_meat = 868733839,
dried_meat = -1445160429,
emissive_glass = 1501078253,
emissive_plastic = 1059629996,
vfx_metal_electrified = -309134265,
vfx_metal_water_tower = 611561919,
vfx_metal_steam = -691277294,
vfx_metal_flame = 332778253,
phys_no_friction = 1666473731,
phys_golf_ball = -1693813558,
phys_tennis_ball = -256704763,
phys_caster = -235302683,
phys_caster_rusty = 2016463089,
phys_car_void = 1345867677,
phys_ped_capsule = -291631035,
phys_electric_fence = -1170043733,
phys_electric_metal = -2013761145,
phys_barbed_wire = -1543323456,
phys_pooltable_surface = 605776921,
phys_pooltable_cushion = 972939963,
phys_pooltable_ball = -748341562,
buttocks = 483400232,
thigh_left = -460535871,
shin_left = 652772852,
foot_left = 1926285543,
thigh_right = -236981255,
shin_right = -446036155,
foot_right = -1369136684,
spine0 = -1922286884,
spine1 = -1140112869,
spine2 = 1457572381,
spine3 = 32752644,
clavicle_left = -1469616465,
upper_arm_left = -510342358,
lower_arm_left = 1045062756,
hand_left = 113101985,
clavicle_right = -1557288998,
upper_arm_right = 1501153539,
lower_arm_right = 1777921590,
hand_right = 2000961972,
neck = 1718294164,
head = -735392753,
animal_default = 286224918,
car_engine = -1916939624,
puddle = 999829011,
concrete_pavement = 2015599386,
brick_pavement = -1147361576,
phys_dynamic_cover_bound = -2047468855,
vfx_wood_beer_barrel = 998201806,
wood_high_friction = -2140087047,
rock_noinst = 127813971,
bushes_noinst = 1441114862,
metal_solid_road_surface = -729112334,
stunt_ramp_surface = -2088174996,
temp_01 = 746881105,
temp_02 = -1977970111,
temp_03 = 1911121241,
temp_04 = 1923995104,
temp_05 = -1393662448,
temp_06 = 1061250033,
temp_07 = -1765523682,
temp_08 = 1343679702,
temp_09 = 1026054937,
temp_10 = 63305994,
temp_11 = 47470226,
temp_12 = 702596674,
temp_13 = -1637485913,
temp_14 = -645955574,
temp_15 = -1583997931,
temp_16 = -1512735273,
temp_17 = 1011960114,
temp_18 = 1354993138,
temp_19 = -801804446,
temp_20 = -2052880405,
temp_21 = -1037756060,
temp_22 = -620388353,
temp_23 = 465002639,
temp_24 = 1963820161,
temp_25 = 1952288305,
temp_26 = -1116253098,
temp_27 = 889255498,
temp_28 = -1179674098,
temp_29 = 1078418101,
temp_30 = 13626292
}
--- Retrieves the ground material at a given position.
--- @param coords vector3 The coordinate to test.
--- @return number|nil number The material hash if hit, nil otherwise.
--- @return string string The material name.
--- @usage
--- ```lua
--- local matHash, matName = GetGroundMaterialAtPosition(vector3(100,200,300))
--- print("Material:", matName)
--- ```
function GetGroundMaterialAtPosition(coords)
local endX, endY, endZ = coords.x, coords.y, coords.z - 1.0
local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endX, endY, endZ, 1.0, 1, playerPed, 7)
local _, hit, _, _, materialHash, _ = GetShapeTestResultIncludingMaterial(rayHandle)
local materialName = "Unknown"
for k, v in pairs(materials) do
if v == materialHash then
materialName = k
break
end
end
if hit then return materialHash, materialName else return nil, materialName end
end
--- Retrieves the dimensions (width, depth, height) of a prop/model.
--- @param model string The name or hash of the model.
--- @return number number The width of the prop.
--- @return number number The depth of the prop.
--- @return number number The height of the prop.
---
--- @usage
--- ```lua
--- local width, depth, height = GetPropDimensions("prop_barrel_01a")
--- print("Dimensions:", width, depth, height)
--- ```
function GetPropDimensions(model)
loadModel(model)
local minDim, maxDim = GetModelDimensions(model)
local width, depth, height = maxDim.x - minDim.x, maxDim.y - minDim.y, maxDim.z - minDim.z
return width, depth, height
end
--- Retrieves the forward direction vector of an entity based on its heading.
---
--- This function calculates the forward direction vector using the entity's heading angle.
---
--- @param entity number The entity whose forward vector is to be calculated.
--- @return vector3 vector3 The forward direction vector.
---
--- @usage
--- ```lua
--- local forwardVec = GetEntityForwardVector(playerPed)
--- print("Forward Vector:", forwardVec)
--- ```
function GetEntityForwardVector(entity)
local heading = math.rad(GetEntityHeading(entity) + 90)
return vec3(math.cos(heading), math.sin(heading), 0.0)
end

333
shared/input.lua Normal file
View File

@@ -0,0 +1,333 @@
--[[
Input Dialog Module
---------------------
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
types such as radio buttons, numbers, text, and select dropdowns.
]]
--- Creates a simple input dialog using the configured menu system.
---
--- @param title string The title or header of the input dialog.
--- @param opts table A table of input option definitions. Each option should include:
--- - type (string): The input type ("radio", "number", "text", "select").
--- - label (string, optional): A label for the input (used in radio/select for OX).
--- - text (string, optional): The text prompt for the input.
--- - name (string): The identifier for the input.
--- - isRequired (boolean, optional): Whether input is mandatory.
--- - default (any, optional): The default value.
--- - options (table, optional): A table of choices for "radio" and "select" types.
--- - min (number, optional): Minimum value (for "number" and "select").
--- - max (number, optional): Maximum value.
--- - txt (string, optional): Additional description.
---
--- @return table|nil table Returns the user's input as a table if submitted, otherwise nil.
---
---@usage
--- ```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" },
--- }},
--- })
--- ```
function createInput(title, opts)
local dialog = nil
local options = {}
local currentNum = 0
if Config.System.Menu == "ox" then
for i = 1, #opts do
currentNum += 1
if opts[i] == nil then currentNum -= 1 goto skip end
if opts[i].type == "radio" then
-- Convert radio options to select type for OX
for k in pairs(opts[i].options) do
opts[i].options[k].label = opts[i].options[k].text
end
options[currentNum] = {
type = "select",
isRequired = opts[i].isRequired,
label = opts[i].label or opts[i].text,
name = opts[i].name,
default = opts[i].default or opts[i].options[1].value,
options = opts[i].options,
}
end
if opts[i].type == "number" then
options[currentNum] = {
type = opts[i].type,
label = (opts[i].label or opts[i].text)..(opts[i].txt and " - "..opts[i].txt or ""),
isRequired = 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].text..(opts[i].txt and " - "..opts[i].txt or ""),
default = opts[i].default,
isRequired = opts[i].isRequired,
}
end
if opts[i].type == "select" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
isRequired = 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
jsonPrint(opts[i])
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,
isRequired = 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,
isRequired = opts[i].required,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
::skip::
end
dialog = exports[OXLibExport]:inputDialog(title, options)
return dialog
elseif Config.System.Menu == "qb" then
for k, v in pairs(opts) do
currentNum += 1
if opts[k] == nil then
currentNum -= 1
else
options[currentNum] = opts[k]
end
end
dialog = exports['qb-input']:ShowInput(
{ header = title, submitText = "Accept", inputs = options }
)
return dialog
elseif Config.System.Menu == "lation" then
for i in pairs(opts) do
currentNum += 1
if opts[i] == nil then currentNum -= 1 goto skip end
if opts[i].type == "radio" then
for k in pairs(opts[i].options) do
opts[i].options[k].label = opts[i].options[k].text
end
options[currentNum] = {
type = "select",
required = opts[i].isRequired,
label = opts[i].label or opts[i].text,
name = opts[i].name,
default = opts[i].default or opts[i].options[1].value,
options = opts[i].options,
}
end
if opts[i].type == "number" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label or opts[i].text,
description = opts[i].txt and " - "..opts[i].txt or "",
required = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
}
end
if opts[i].type == "text" then
options[currentNum] = {
type = "input",
label = opts[i].label or opts[i].text,
description = (opts[i].txt and " - "..opts[i].txt or ""),
placeholder = opts[i].default,
required = opts[i].isRequired,
}
end
if opts[i].type == "select" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label or opts[i].text,
description = opts[i].txt and " - "..opts[i].txt or "",
required = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
if opts[i].type == "checkbox" then
for k in pairs(opts[i].options) do
if options[currentNum] then currentNum += 1 end
options[currentNum] = {
type = opts[i].type,
label = opts[i].options[k].text..(opts[i].txt and " - "..opts[i].txt or ""),
name = opts[i].options[k].value,
}
end
end
if opts[i].type == "color" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
required = opts[i].isRequired,
format = opts[i].format,
default = opts[i].default,
}
end
if opts[i].type == "slider" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
required = opts[i].required,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
::skip::
end
local dialog = exports.lation_ui:input({
title = title,
submitText = "Accept",
options = options
})
return dialog
elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(opts),
title,
" ",
{
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

247
shared/inventories.lua Normal file
View File

@@ -0,0 +1,247 @@
-------------------------------------------------------------
-- 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

287
shared/isAnimal.lua Normal file
View File

@@ -0,0 +1,287 @@
--[[
Animal Detection Module
-------------------------
This module determines whether a Ped is an animal and categorizes it as a cat, dog,
or other type (e.g., coyote). It uses predefined model hashes stored in the AnimalPeds table.
Global Flags:
- isCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal: Booleans to track the player's
current animal classification.
When running client-side (not on the server), the module checks the player's Ped after they load.
Usage Examples:
-- Check if the player's Ped is an animal:
local animalStatus = isPedAnimal()
-- Check if a given Ped is a cat:
if isCat(somePed) then print("This is a cat!") end
-- Determine if a Ped is a dog and whether it's big or small:
local isDogFlag, isBig = isDog(somePed)
-- Retrieve a flat list of all animal model hashes:
local allAnimalModels = getAnimalModels()
]]
-- Global animal classification flags.
isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false
if not isServer() then
onPlayerLoaded(function()
Wait(1000)
-- Reset classification flags
isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false
-- Check if the player's Ped is an animal.
isPedAnimal()
if isAnimal then
local ped = PlayerPedId()
local pedModel = GetEntityModel(ped)
-- Determine if the Ped is a cat:
-- Also treat 'ft-raccoon' as a cat unless it is 'ft-sphynx'
isPedCat = (isCat(ped) == true or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`)
-- Determine if the Ped is a dog and whether it's big:
isPedDog, isBigDog = isDog(ped)
isSmallDog = not isBigDog
if isPedDog and pedModel == `a_c_coyote` then isPedDog = false end
-- Determine if the Ped is a coyote (special case):
isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`)
-- Special override: if model is 'ft-capmonkey2', treat as a dog.
if pedModel == `ft-capmonkey2` then isPedDog = true end
end
end, true)
-------------------------------------------------------------
-- Animal Classification Functions
-------------------------------------------------------------
--- Determines whether a given Ped is classified as an animal.
---
--- Checks if the Ped's model hash appears in any of the animal categories defined in AnimalPeds.
---
--- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped.
--- @return boolean boolean True if the Ped is an animal, otherwise false.
---
--- @usage
--- ```lua
--- local isPlayerAnimal = isAnimal()
--- local isSpecificPedAnimal = isAnimal(somePedEntity)
--- ```
function isPedAnimal(ped)
local PedModel = GetEntityModel(ped or PlayerPedId())
for _, animalCategory in pairs(AnimalPeds) do
for animalModelHash, _ in pairs(animalCategory) do
if PedModel == animalModelHash then
isAnimal = true
debugPrint("^4isAnimal^7: ^2Ped is ^4Animal")
return true
end
end
end
return false
end
--- Checks if a given Ped is classified as a cat.
---
--- Iterates through the CatPeds table and returns true if the Ped's model matches.
---
--- @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.
---
---@usage
--- ```lua
--- if isCat() then
--- print("Player is a cat!")
--- end
---
--- local anotherPed = GetPedInVehicleSeat(vehicle, -1)
--- if isCat(anotherPed) then
--- print("Driver is a cat!")
--- end
--- ```
function isCat(ped)
local PedModel = GetEntityModel(ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.CatPeds) do
if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Cat")
return true
end
end
return false
end
--- Determines if a given Ped is a dog and identifies its size category.
---
--- Checks the BigDogs and SmallDogs tables to see if the Ped's model matches any dog model.
---
--- @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,
--- `true` and `false` if it's a small dog,
--- or `false` and `nil` if it's not a dog.
---
---@usage
--- ```lua
--- local isPedDog, isBigDog = isDog()
--- if isPedDog then
--- if isBigDog then
--- print("Player is a big dog!")
--- else
--- print("Player is a small dog!")
--- end
--- else
--- print("Player is not a dog.")
--- end
---
--- local somePed = GetPedInVehicleSeat(vehicle, 0)
--- local isPetDog, isLargeDog = isDog(somePed)
--- if isPetDog then
--- if isLargeDog then
--- print("Passenger is a big dog!")
--- else
--- print("Passenger is a small dog!")
--- end
--- end
--- ```
function isDog(ped)
local PedModel = GetEntityModel(ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.BigDogs) do
if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Dog")
return true, true
end
end
for modelHash, _ in pairs(AnimalPeds.SmallDogs) do
if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Small Dog")
return true, false
end
end
return false, nil
end
--- Compiles and returns a flat table of all animal model hashes.
---
--- Iterates through every category in AnimalPeds and collects all model hashes.
---
--- @return table table A table containing all animal model hashes.
---
---@usage
--- ```lua
--- local allAnimalModels = getAnimalModels()
--- for _, modelHash in ipairs(allAnimalModels) do
--- print("Animal Model Hash:", modelHash)
--- end
--- ```
function getAnimalModels()
local animalModels = {}
for _, animalCategory in pairs(AnimalPeds) do
for modelHash, _ in pairs(animalCategory) do
table.insert(animalModels, modelHash)
end
end
return animalModels
end
--- Compiles and returns a table of animal animations for the ped model.
---
--- Iterates through every category in AnimalPeds and collects all anims.
---
--- @return table table A table containing all current model anims.
---
---@usage
--- ```lua
--- local getAnim = getAnimalAnims(ped)
--- playAnim(getAnim.sitDict, getAnim.sitAnim, -1, 1)
--- ```
function getAnimalAnims(ped)
local model = GetEntityModel(ped)
local animalTable = {}
for _, animalCategory in pairs(AnimalPeds) do
for k, v in pairs(animalCategory) do
if k == model then
animalTable = v
break
end
end
end
return animalTable
end
end
-------------------------------------------------------------
-- Animal Models Data
-------------------------------------------------------------
-- Define the animal models and their associated animations.
AnimalPeds = {
BigDogs = {
[`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" },
[`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" },
[`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" },
[`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" },
[`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" },
[`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" },
[`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" },
[`doberman`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", 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" },
[`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" },
[`bernard`] = { 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`] = { 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`] = { 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_malinois`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`abdog`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`dalmatian`] = { 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_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-bs`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
[`chowchow`] = { 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 = {
[`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 = {
[`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 = {
[`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" },
[`a_c_pigeon`] = { deathAnim = "dead_down", deathDict = "creatures@pigeon@move", exitAnim = "nill", exitDict = "creatures@pug@move" },
},
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" },
}
}

861
shared/itemcontrol.lua Normal file
View File

@@ -0,0 +1,861 @@
--[[
Usable Items & Inventory Utilities Module
-------------------------------------------
This module provides functions for:
• Registering items as usable across different inventory systems (ESX, QBcore, QBX).
• Retrieving an item's image as a NUI link.
• Adding and removing items from a player's inventory.
• Toggling items in inventory (with server event for exploit protection).
• Checking for item duplication exploits.
• Handling tool durability mechanics.
• Checking item availability and retrieving inventory.
• Granting random rewards from a reward pool.
• Checking if a player can carry specific items based on weight.
]]
validTokens = {}
-------------------------------------------------------------
-- Registering Usable Items
-------------------------------------------------------------
--- Registers an item as usable for ESX, QBcore, or QBX.
---
--- @param item string The name of the item.
--- @param funct function The function to execute when the item is used.
---
--- @usage
--- ```lua
--- createUseableItem("health_potion", function(source)
--- -- Code to consume the health potion
--- end)
--- ```
function createUseableItem(item, funct)
if doesItemExist(item) then
local itemResource = ""
if isStarted(ESXExport) then
itemResource = ESXExport
while not ESX do Wait(0) end
ESX.RegisterUsableItem(item, funct)
elseif isStarted(QBExport) and not isStarted(QBXExport) then
itemResource = QBExport
Core.Functions.CreateUseableItem(item, funct)
elseif isStarted(QBXExport) then
itemResource = QBXExport
exports[QBXExport]:CreateUseableItem(item, funct)
elseif isStarted(RSGExport) then
itemResource = RSGExport
Core.Functions.CreateUseableItem(item, funct)
end
if itemResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^3UsableItem^2 with ^4"..itemResource.."^7:", item)
else
debugPrint("^4ERROR^7: No supported framework detected for registering usable item: ^3"..item.."^7")
end
else
print("^1ERROR^7: ^1Tried to make item usable but it didn't exist^7: "..item)
end
end
-------------------------------------------------------------
-- Adding and Removing Items
-------------------------------------------------------------
--- Adds an item to a player's inventory.
---
--- Triggers a server event (or local event) to add the specified item.
---
--- @param item string The item name.
--- @param amount number The quantity to add.
--- @param info table|nil Additional metadata for the item.
--- @param src number|nil Optional player source; if nil, defaults to the caller.
---
--- @usage
--- ```lua
--- addItem("health_potion", 2, { quality = "high" })
--- ```
function addItem(item, amount, info, src)
if not Items[item] then
print("^6Bridge^7: ^1Error^7 - ^2Tried to give ^7'^3"..item.."^7'^2 but it doesn't exist")
return
end
if src then
TriggerEvent(getScript()..":server:toggleItem", true, item, amount, src, info)
else
TriggerServerEvent(getScript()..":server:toggleItem", true, item, amount, nil, info, nil, currentToken)
currentToken = nil -- clear client cached token
end
end
--- Removes an item from a player's inventory.
---
--- Triggers a server event (or local event) to remove the specified item.
---
--- @param item string The item name.
--- @param amount number The quantity to remove.
--- @param src number|nil Optional player source.
--- @param slot number|nil Optional inventory slot.
---
--- @usage
--- ```lua
--- removeItem("health_potion", 1)
--- ```
function removeItem(item, amount, src, slot)
if not Items[item] then
print("^6Bridge^7: ^1Error^7 - ^2Tried to remove ^7'^3"..item.."^7'^2 but it doesn't exist")
return
end
if src then
debugPrint(src)
TriggerEvent(getScript()..":server:toggleItem", false, item, amount, src, nil, slot)
else
TriggerServerEvent(getScript()..":server:toggleItem", false, item, amount, nil, nil, slot)
end
end
-------------------------------------------------------------
-- Toggle Items (Server Event)
-------------------------------------------------------------
--- Server event handler to toggle (add or remove) an item from a player's inventory.
---
--- This function validates the item, then calls the appropriate export functions based on the active inventory system.
--- It also includes exploit protection via the dupeWarn function.
---
--- @param give boolean True to add the item, false to remove.
--- @param item string The item name.
--- @param amount number The quantity.
--- @param newsrc number|nil The player source; defaults to event source.
--- @param info table|nil Additional metadata.
--- @param slot number|nil Optional inventory slot.
---
--- @usage
--- ```lua
--- TriggerServerEvent(getScript()..":server:toggleItem", true, "health_potion", 1)
--- ```
RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount, newsrc, info, slot, token)
--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
if not Items[item] then
print("^6Bridge^7: ^1Error^7 - ^2Tried to "..(tostring(give) == "true" and "add" or "remove").." '^3"..item.."^7' but it doesn't exist")
return
end
local src = newsrc or source
if (give == true or give == 1) then
if newsrc == nil then -- this must be coming from client this would be blank
if not checkToken(src, token, "item", item) then
return
end
end
end
local action = (tostring(give) == "true" and "addItem" or "removeItem")
local remamount = amount or 1
if item == nil then return end
-- Grab the current inventory (you can expand usage of 'inv' if needed)
local invName = ""
if give == 0 or give == false then
if not hasItem(item, amount or 1, src) then
dupeWarn(src, item, amount)
else
if isStarted(OXInv) then
invName = OXInv
exports[OXInv]:RemoveItem(src, item, remamount, nil)
elseif isStarted(QSInv) then
invName = QSInv
exports[QSInv]:RemoveItem(src, item, remamount)
elseif isStarted(CoreInv) then
invName = CoreInv
exports[CoreInv]:removeItem(src, item, remamount)
elseif isStarted(OrigenInv) then
invName = OrigenInv
exports[OrigenInv]:removeItem(src, item, remamount)
elseif isStarted(CodeMInv) then
invName = CodeMInv
exports[CodeMInv]:RemoveItem(src, item, remamount)
elseif isStarted(TgiannInv) then
invName = TgiannInv
exports[TgiannInv]:RemoveItem(src, item, remamount)
elseif isStarted(JPRInv) then
invName = JPRInv
while remamount > 0 do
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then
remamount -= 1
else
print("^1Error removing "..item.." Amount left: "..remamount)
break
end
end
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end
elseif isStarted(QBInv) then
invName = QBInv
while remamount > 0 do
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then
remamount -= 1
else
print("^1Error removing "..item.." Amount left: "..remamount)
break
end
end
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end
elseif isStarted(PSInv) then
invName = PSInv
while remamount > 0 do
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then
remamount -= 1
else
print("^1Error removing "..item.." Amount left: "..remamount)
break
end
end
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end
elseif isStarted(RSGInv) then invName = RSGInv
while remamount > 0 do
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1, slot) then
remamount -= 1
else
print("^1Error removing "..item.." Amount left: "..remamount)
break
end
end
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("rsg-inventory:client:ItemBox", src, Items[item], "remove", amount or 1)
end
end
-----
-- Fallback for if no inventory found:
-----
if invName == "" then
if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions
invName = isStarted(QBXExport) and QBXExport or isStarted(QBExport) and QBExport
Core.Functions.GetPlayer(src).Functions.RemoveItem(item, remamount, slot)
elseif ESX and isStarted(ESXExport) then -- if esx then use core functions
invName = ESX
ESX.GetPlayerFromId(src).removeInventoryItem(item, remamount)
end
end
-- Final check for if inventory was found
if invName == "" then
print("^4ERROR^7: No Inventory detected - Check starter.lua")
else
debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..Items[item].label.."("..item..") x"..(amount or 1))
end
end
else
local amountToAdd = amount or 1
if isStarted(OXInv) then invName = OXInv
exports[OXInv]:AddItem(src, item, amountToAdd, info, slot)
elseif isStarted(QSInv) then invName = QSInv
exports[QSInv]:AddItem(src, item, amountToAdd, slot, info)
elseif isStarted(CoreInv) then invName = CoreInv
exports[CoreInv]:addItem(src, item, amountToAdd, info)
elseif isStarted(CodeMInv) then invName = CodeMInv
exports[CodeMInv]:AddItem(src, item, amountToAdd, slot, info)
elseif isStarted(OrigenInv) then invName = OrigenInv
exports[OrigenInv]:addItem(src, item, amountToAdd, info, slot)
elseif isStarted(TgiannInv) then invName = TgiannInv
exports[TgiannInv]:AddItem(src, item, amountToAdd, slot, info)
elseif isStarted(JPRInv) then invName = JPRInv
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("ps-inventory:client:ItemBox", src, Items[item], "add", amountToAdd)
end
end
elseif isStarted(QBInv) then invName = QBInv
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then
TriggerClientEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", src, Items[item], "add", amountToAdd)
end
elseif isStarted(PSInv) then invName = PSInv
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then
if Config.Crafting ~= nil and Config.Crafting.showItemBox then
TriggerClientEvent("ps-inventory:client:ItemBox", src, Items[item], "add", amountToAdd)
end
end
elseif isStarted(RSGInv) then invName = RSGInv
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info) then
TriggerClientEvent("rsg-inventory:client:ItemBox", src, Items[item], "add", amountToAdd)
end
end
if invName == "" then
if isStarted(QBExport) or isStarted(QBXExport) then -- if qbcore or qbxcore, just use core functions
invName = isStarted(QBXExport) and QBXExport or isStarted(QBExport) and QBExport
Core.Functions.GetPlayer(src).Functions.AddItem(item, amountToAdd, nil, info)
elseif ESX and isStarted(ESXExport) then -- if esx then use core functions
invName = ESX
ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd)
end
end
-- Final check for if inventory was found
if invName == "" then
print("^4ERROR^7: No Inventory detected - Check starter.lua")
else
debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..Items[item].label.."("..item..") x"..(amount or 1))
end
end
end)
-------------------------------------------------------------
-- Exploit Protection
-------------------------------------------------------------
--- Warns and kicks a player if they try to remove an item they don't have.
---
--- @param src number The player's source ID.
--- @param item string The item name.
---
--- @usage
--- ```lua
--- dupeWarn(playerId, "health_potion")
--- ```
function dupeWarn(src, item, message)
local name = getPlayer(src).name
print(message or "^5DupeWarn^7: "..name.." (^1"..tostring(src).."^7) ^2Tried to remove item '^3"..item.."^7'^2 but it wasn't there^7")
if not debugMode then
DropPlayer(src, name.."("..tostring(src)..") kicked by exploit protection")
end
print("^5DupeWarn^7: "..name.."(^1"..tostring(src).."^7) ^2Dropped from server - exploit protection^7")
end
-------------------------------------------------------------
-- Tool Durability & Metadata
-------------------------------------------------------------
--- Reduces the durability of a tool by a specified damage amount.
---
--- If durability reaches zero or below, the tool is removed and a break sound is played.
---
--- @param data table Contains:
--- - item (string): The tool's name.
--- - damage (number): The damage % to apply.
---
--- @usage
--- ```lua
--- breakTool({ item = "drill", damage = 10 })
--- ```
function breakTool(data)
local metadata, slot = getItemMetadata(data.item)
metadata = metadata or {}
if not metadata.durability then metadata.durability = 100 end
metadata.durability -= data.damage
if metadata.durability <= 0 then
removeItem(data.item, 1, nil, slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
else
TriggerServerEvent(getScript()..":server:setItemMetaData", { item = data.item, slot = slot, metadata = metadata })
end
end
--- Reduces the uses of a tool by a specified damage amount.
---
--- If uses reaches zero or below, the tool is removed and a break sound is played.
---
--- @param data table Contains:
--- - item (string): The tool's name.
--- - damage (number): The damage % to apply.
---
--- @usage
--- ```lua
--- useToolDegrade({ item = "drill", maxUse = 10 })
--- ```
function useToolDegrade(data) -- WIP
local metadata, slot = getItemMetadata(data.item)
metadata = metadata or {}
if not metadata["Uses Left"] then metadata["Uses Left"] = data.maxUse end
metadata["Uses Left"] -= 1
if metadata["Uses Left"] <= 0 then
removeItem(data.item, 1, nil, slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
else
TriggerServerEvent(getScript()..":server:setItemMetaData", { item = data.item, slot = slot, metadata = metadata })
end
end
function getItemMetadata(item, slot, src)
local lowestSlot = 100
local chosenSlot = slot
local metadata = {}
local itemcheck = getPlayerInv(src)
if chosenSlot then
for k, v in pairs(itemcheck) do
if v.name == item and v.slot == chosenSlot then
lowestSlot = v.slot
metadata = itemcheck[k].info or itemcheck[k].metadata or {}
debugPrint("^6Bridge^7: ^2Found metadata for item ^3"..item.." ^2in slot ^3"..lowestSlot.."^7")
end
end
else
for k, v in pairs(itemcheck) do
if v.name == item then
if v.slot <= lowestSlot then
lowestSlot = v.slot
metadata = itemcheck[k].info or itemcheck[k].metadata or {}
end
end
end
end
return metadata, lowestSlot
end
--- Server event handler to set metadata for an item.
---
--- Updates item metadata (e.g. durability) for the player's inventory based on slot.
---
--- @param data table Contains:
--- - item (string): The item name.
--- - slot (number): The inventory slot.
--- - metadata (table): The metadata to set.
---
--- @usage
--- ```lua
--- TriggerServerEvent("script:server:setItemMetaData", { item = "drill", slot = 5, metadata = { durability = 80 } })
--- ```
RegisterNetEvent(getScript()..":server:setItemMetaData", function(data, src)
local src = src or source
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) or isStarted(JPRInv) then
--debugPrint(src, data.item, 1, data.slot)
local Player = Core.Functions.GetPlayer(src)
Player.PlayerData.items[data.slot].info = data.metadata
if data.metadata.durability then
Player.PlayerData.items[data.slot].description = "HP : "..data.metadata.durability
end
Player.Functions.SetInventory(Player.PlayerData.items)
elseif isStarted(OXInv) then
exports[OXInv]:SetMetadata(src, data.slot, data.metadata)
elseif isStarted(QSInv) then
exports[QSInv]:SetItemMetadata(src, data.slot, data.metadata)
elseif isStarted(CoreInv) then
exports[CoreInv]:setMetadata(src, data.slot, data.metadata)
elseif isStarted(CodeMInv) then
exports[CodeMInv]:SetItemMetadata(src, data.slot, data.metadata)
elseif isStarted(OrigenInv) then
exports[OrigenInv]:setMetadata(src, data.slot, data.metadata)
elseif isStarted(TgiannInv) then
exports[TgiannInv]:UpdateItemMetadata(src, data.item, data.slot, data.metadata)
end
end)
-------------------------------------------------------------
-- Random Reward
-------------------------------------------------------------
--- Grants a random reward from a predefined reward pool if the player is eligible.
---
--- Checks if the item qualifies for a reward, removes the item, then calculates a random reward based on rarity.
---
--- @param itemName string The item name to check.
---
---@usage
--- ```lua
--- getRandomReward("gold_ring")
--- ```
function getRandomReward(itemName)
if Config.Rewards.RewardPool then
local reward = false
if type(Config.Rewards.RewardItem) == "string" then
Config.Rewards.RewardItem = { Config.Rewards.RewardItem }
end
for k, v in pairs(Config.Rewards.RewardItem) do
if v == itemName then
reward = true
break
end
end
if reward then
removeItem(itemName, 1)
local totalRarity = 0
for i = 1, #Config.Rewards.RewardPool do
totalRarity += Config.Rewards.RewardPool[i].rarity
end
debugPrint("^6Bridge^7: ^3getRandomReward^7: Total Rarity '"..totalRarity.."'")
local randomNum = math.random(1, totalRarity)
debugPrint("^6Bridge^7: ^3getRandomReward^7: Random Number '"..randomNum.."'")
local currentRarity = 0
for i = 1, #Config.Rewards.RewardPool do
currentRarity += Config.Rewards.RewardPool[i].rarity
if randomNum <= currentRarity then
debugPrint("^6Bridge^7: ^3getRandomReward^7: ^2Selected toy ^7'^6"..Config.Rewards.RewardPool[i].item.."^7'")
addItem(Config.Rewards.RewardPool[i].item, 1)
return
end
end
end
end
end
-------------------------------------------------------------
-- Carry Capacity Check
-------------------------------------------------------------
--- Checks if a player can carry the specified items based on weight.
---
--- Calculates the current total weight in the player's inventory and determines whether adding the new items would exceed capacity.
---
--- @param itemTable table A table where keys are item names and values are required quantities.
--- @param src number The player's source ID.
--- @return table A table mapping each item to a boolean indicating if it can be carried.
---
--- @usage
--- 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
function canCarry(itemTable, src)
local resultTable = {}
if src then
if isStarted(OXInv) then
for k, v in pairs(itemTable) do
resultTable[k] = exports[OXInv]:CanCarryItem(src, k, v)
end
elseif isStarted(QSInv) then
for k, v in pairs(itemTable) do
resultTable[k] = exports[QSInv]:CanCarryItem(src, k, v)
end
elseif isStarted(CoreInv) then
for k, v in pairs(itemTable) do
resultTable[k] = exports[CoreInv]:canCarry(src, k, v)
end
elseif isStarted(CodeMInv) then --- This really needs updating, their docs are confusing..
local items = getPlayerInv(src)
local totalWeight = 0
if not items then return false end
for _, item in pairs(items) do
totalWeight += (item.weight * item.amount)
end
for k, v in pairs(itemTable) do
local itemInfo = Items[k]
if not itemInfo then
resultTable[k] = true
else
resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight
end
end
elseif isStarted(OrigenInv) then
for k, v in pairs(itemTable) do
resultTable[k] = exports[OrigenInv]:canCarryItem(src, k, v)
end
elseif isStarted(TgiannInv) then
for k, v in pairs(itemTable) do
resultTable[k] = exports[TgiannInv]:CanCarryItem(src, k, v)
end
elseif isStarted(JPRInv) then
local items = getPlayerInv(src)
local totalWeight = 0
if not items then return false end
for _, item in pairs(items) do
totalWeight += (item.weight * item.amount)
end
for k, v in pairs(itemTable) do
local itemInfo = Items[k]
if not itemInfo then
resultTable[k] = true
else
resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight
end
end
elseif isStarted(QBInv) then
if QBInvNew then
for k, v in pairs(itemTable) do
resultTable[k] = exports[QBInv]:CanAddItem(src, k, v)
end
else
local items = getPlayerInv(src)
local totalWeight = 0
if not items then return false end
for _, item in pairs(items) do
totalWeight += (item.weight * item.amount)
end
for k, v in pairs(itemTable) do
local itemInfo = Items[k]
if not itemInfo then
resultTable[k] = true
else
resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight
end
end
end
elseif isStarted(PSInv) then
local items = getPlayerInv(src)
local totalWeight = 0
if not items then return false end
for _, item in pairs(items) do
totalWeight += (item.weight * item.amount)
end
for k, v in pairs(itemTable) do
local itemInfo = Items[k]
if not itemInfo then
resultTable[k] = true
else
resultTable[k] = (totalWeight + (itemInfo.weight * v)) <= InventoryWeight
end
end
end
end
return resultTable
end
-------------------------------------------------------------
-- Server Callback Registration
-------------------------------------------------------------
if isServer() then
createCallback(getScript()..":server:canCarry", function(source, itemTable)
local result = canCarry(itemTable, source)
return result
end)
createCallback(getScript()..":server:getMaxCarryCraft", function(source, data)
local src = source
local item = data.item
local max = data.max or 100
local maxCanCarry = 0
for i = 1, max do
local checkTable = {
[item] = i
}
local result = canCarry(checkTable, src)
if result[item] == true then
maxCanCarry = i
else
break
end
end
return maxCanCarry
end)
end
function getMaxInvWeight()
local weight = 0
if isStarted(QBInv) then
if checkExportExists(QBInv, "GetMaxWeight") then
return exports[QBInv]:GetMaxWeight()
else
return InventoryWeight
end
end
if isStarted(PSInv) then
if checkExportExists(PSInv, "GetMaxWeight") then
return exports[PSInv]:GetMaxWeight()
else
return InventoryWeight
end
end
if isStarted(RSGInv) then
if checkExportExists(RSGInv, "GetMaxWeight") then
return exports[RSGInv]:GetMaxWeight()
else
return InventoryWeight
end
end
if isStarted(JPRInv) then
if checkExportExists(JPRInv, "GetMaxWeight") then
return exports[JPRInv]:GetMaxWeight()
else
return InventoryWeight
end
end
if isStarted(OXInv) then
return exports[OXInv]:GetPlayerMaxWeight()
end
if isStarted(QSInv) then
return InventoryWeight
end
if isStarted(CoreInv) then
return InventoryWeight
end
if isStarted(CodeMInv) then
return InventoryWeight
end
if isStarted(OrigenInv) then
return InventoryWeight
end
if isStarted(TgiannInv) then
return InventoryWeight
end
-- For ESX default inventory (es_extended)
if ESX and isStarted(ESXExport) then
return InventoryWeight
end
return InventoryWeight
end
function getCurrentInvWeight()
local weight = 0
if isStarted(QBInv) or isStarted(PSInv) or isStarted(RSGInv) or isStarted(JPRInv) then
local itemcheck = Core.Functions.GetPlayerData().items
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(OXInv) then
weight = exports[OXInv]:GetPlayerWeight()
end
if isStarted(QSInv) then
local itemcheck = exports[QSInv]:getUserInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(CoreInv) then
local itemcheck = exports[CoreInv]:getInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(CodeMInv) then
local itemcheck = exports[CodeMInv]:GetClientPlayerInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(OrigenInv) then
local itemcheck = exports[OrigenInv]:getPlayerInventory()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
if isStarted(TgiannInv) then
local itemcheck = exports[TgiannInv]:GetPlayerItems()
for _, v in pairs(itemcheck) do
weight += ((v.weight * v.amount) or 0)
end
end
-- For ESX default inventory (es_extended)
if ESX and isStarted(ESXExport) then
local xPlayer = ESX.GetPlayerData() or {}
if xPlayer.inventory then
for _, v in pairs(xPlayer.inventory) do
weight += ((v.weight * v.amount) or 0)
end
end
end
return weight
end
function hasFreeInventorySlots(slots, src)
local inv = getPlayerInv(src)
if (countTable(inv) + slots) <= InventorySlots then
return true
end
return false
end
-------------------------------------------------------------
-- Check Item Existance - return boolean(true,false)
-------------------------------------------------------------
--- checks whether an item exists in your item database
---
--- @param item string The item name.
---
--- @usage
--- ```lua
--- local item = "apple"
--- if doesItemExist(item) then print("item exists") end
--- ```
function doesItemExist(item)
if not item or item == "" then
return false
end
if not Items or not next(Items) then
return item.." (Missing)"
end
if Items[item] ~= nil then
return Items[item]
end
return false
end
function getItemLabel(item)
if not item or item == "" then
return ""
end
if not Items or not next(Items) then
return item.." (Missing)"
end
if Items[item] ~= nil then
return Items[item].label
end
return item.." (Missing)"
end

264
shared/jobfunctions.lua Normal file
View File

@@ -0,0 +1,264 @@
--[[
Duty & Interaction Utilities Module
--------------------------------------
This module provides functions related to:
• Determining boss roles from Jobs and Gangs tables.
• Checking a player's job and duty status.
• Toggling duty state.
• Simulating player interactions such as hand washing, using toilets/urinals,
and teleporting via doors.
]]
-------------------------------------------------------------
-- Global Duty Status
-------------------------------------------------------------
-- This attempts to sync up with the players framework onDuty variable
-- This stops the script getting confused when one is false and one is true
onDuty = false
if not isServer() then
onPlayerLoaded(function()
onDuty = getPlayer().onDuty
end, true)
end
-------------------------------------------------------------
-- Boss Role Detection
-------------------------------------------------------------
--- Scans the 'Jobs' and 'Gangs' tables to identify roles classified as bosses.
---
--- Iterates through the specified role's grades in the Jobs or Gangs table and returns
--- a table mapping the role to the lowest grade number that qualifies as a boss (isboss or bankAuth).
---
--- @param role string The job or gang role to check.
--- @return table table A table with the role mapped to its boss grade number.
---
--- @usage
--- ```lua
--- local bosses = makeBossRoles("police")
--- if bosses["police"] then
--- print("Police role has a boss grade.")
--- end
--- ```
function makeBossRoles(role)
local boss = {}
local data = (Jobs and Jobs[role]) or (Gangs and Gangs[role])
if data then
for grade, info in pairs(data.grades) do
if info.isboss or info.bankAuth or info.isBoss then
boss[role] = boss[role] and math.min(boss[role], tonumber(grade)) or tonumber(grade)
end
end
end
return boss
end
-------------------------------------------------------------
-- Job & Duty Checks
-------------------------------------------------------------
--- Checks if the player has a specific job (or gang) and is on duty.
---
--- Verifies whether the player possesses the specified role. If the role is defined in the Jobs table,
--- it also checks that the player is clocked in (onDuty). If the check fails, a notification is sent.
---
--- @param job string The job or gang to check.
--- @return boolean Returns true if the player meets the criteria; false otherwise.
---
--- @usage
--- ```lua
--- if jobCheck("mechanic") then
--- -- Allow mechanic features.
--- else
--- -- Deny access.
--- end
--- ```
function jobCheck(job)
local canDo = true
if Jobs[job] then
if not hasJob(job) or not getPlayer().onDuty then
triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"])
canDo = false
end
end
if Gangs[job] then
if not hasJob(job) then
canDo = false
end
end
return canDo
end
--- Toggles the player's duty status.
---
--- Switches the player's duty state between on-duty and off-duty. If using QBcore,
--- it triggers the appropriate server event. Otherwise, it manually toggles the onDuty variable and notifies the player.
---
--- @usage
--- ```lua
--- toggleDuty() -- Player receives a notification of their new duty status.
--- ```
function toggleDuty()
if isStarted(QBExport) or isStarted(QBXExport) then
TriggerServerEvent("QBCore:ToggleDuty")
Wait(100)
onDuty = getPlayer().onDuty
elseif isStarted(RSGExport) then
TriggerServerEvent("RSGCore:ToggleDuty")
Wait(100)
onDuty = getPlayer().onDuty
elseif isStarted(ESXExport) then
local tempJob = ESX.GetPlayerData().job
tempJob.onDuty = not onDuty
ESX.SetPlayerData("job", tempJob)
onDuty = getPlayer().onDuty
if onDuty then
triggerNotify(nil, "Now on duty", "success")
else
triggerNotify(nil, "Now off duty", "success")
end
else
onDuty = not onDuty
if onDuty then
triggerNotify(nil, "Now on duty", "success")
else
triggerNotify(nil, "Now off duty", "success")
end
end
end
-- Framework specific functions to keep duty status synced
RegisterNetEvent("QBCore:Client:OnJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
RegisterNetEvent("QBCore:Client:SetDuty", function(duty) onDuty = duty end)
RegisterNetEvent("qbx_core:client:onJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
RegisterNetEvent("RSGCore:Client:OnJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
RegisterNetEvent("RSGCore:Client:SetDuty", function(duty) onDuty = duty end)
-------------------------------------------------------------
-- Interaction Functions
-------------------------------------------------------------
--- Initiates the hand-washing action for the player.
---
--- Triggers an animation and a progress bar to simulate hand washing at the specified coordinates.
--- On success, it notifies the player; if cancelled, it sends an error notification.
---
--- @param data table A table containing:
--- - coords (vector3): The location where the hand-washing action occurs.
---
--- @usage
--- ```lua
--- washHands({ coords = vector3(200.0, 300.0, 40.0) })
--- ```
function washHands(data)
local ped = PlayerPedId()
lookEnt(data.coords)
local cam = createTempCam(ped, data.coords)
if progressBar({
label = Loc[Config.Lan].progressbar["progress_washing"],
time = 5000,
cancel = true,
dict = "mp_arresting",
anim = "a_uncuff",
flag = 32,
icon = "fas fa-hand-holding-droplet",
cam = cam
}) then
triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
else
triggerNotify(nil, Loc[Config.Lan].error["cancel"], "error")
end
ClearPedTasks(ped)
end
--- Handles the player's interaction with a toilet or urinal.
---
--- Manages animations and progress bars for using a urinal or a toilet. If the action is successful,
--- it triggers the appropriate server event (urinal usage) or notifies the player if cancelled.
---
--- @param data table A table containing:
--- - urinal (boolean): `true if using a urinal; false for a toilet.`
--- - sitcoords (vector4): `Coordinates and heading for seating when using a toilet.`
---
--- @usage
--- ```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
--- ```
function useToilet(data)
if data.urinal then
if progressBar({
label = "Using Urinal",
time = 5000,
cancel = true,
dict = "misscarsteal2peeing",
anim = "peeing_loop",
flag = 32
}) then
TriggerServerEvent(getScript().."server:Urinal")
else
lockInv(false)
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

81
shared/make/cameras.lua Normal file
View File

@@ -0,0 +1,81 @@
--- 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.
-- 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.
-- 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.
--
---@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`).
--
---@usage
-- ```lua
-- local cam = createTempCam(entity, targetCoords)
-- ```
function createTempCam(ent, coords)
local cam = nil
if Config.Crafting.craftCam then
if debugMode then
triggerNotify(nil, "ModCam Created", "success")
end
local camCoords = nil
local pointCoords = nil
if type(ent) ~= "vector3" then
camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
else
camCoords = ent
end
cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
if type(coords) == "number" then
SetCamCoord(cam, GetCamCoord(cam) + vec3(0, 0, 1.0))
PointCamAtEntity(cam, coords)
else
PointCamAtCoord(cam, coords)
end
end
return cam
end
--- Activates and starts rendering the temporary camera.
--
-- This function sets the specified camera as active and begins rendering it with a smooth transition.
-- The camera is only activated if `Config.Crafting.craftCam` is enabled in the configuration.
--
---@param cam camID The handle of the camera to activate and render.
--
---@usage
-- ```lua
-- startTempCam(cam)
-- ```
function startTempCam(cam)
if Config.Crafting.craftCam then
SetCamActive(cam, true)
RenderScriptCams(true, true, 1000, true, true)
end
end
--- Deactivates the temporary camera and stops rendering.
--
-- This function waits for one second, then stops rendering script cameras and destroys all cameras.
-- The delay allows for any transitions or animations to complete.
--
-- The camera is only deactivated if `Config.Crafting.craftCam` is enabled in the configuration.
--
---@usage
-- ```lua
-- stopTempCam()
-- ```
function stopTempCam()
if Config.Crafting.craftCam then
CreateThread(function()
Wait(1000)
RenderScriptCams(false, true, 500, true, true)
DestroyAllCams()
end)
end
end

262
shared/make/loaders.lua Normal file
View File

@@ -0,0 +1,262 @@
local time = 500
--- Loads a specified model into memory.
---
--- 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.
---
---@param model string|number The name or hash of the model to load.
---
---@usage
--- ```lua
--- loadModel('prop_chair_01a')
--- ```
function loadModel(model)
if not IsModelValid(model) then print("^6Bridge^7: ^1ERROR^7: ^2Model^7 - '^6"..model.."^7' ^2does not exist in server") return
else
if not HasModelLoaded(model) then
debugPrint("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'")
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
end
time = 500
end
end
--- Unloads a model from 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.
---
---@usage
--- ```lua
--- unloadModel('prop_chair_01a')
--- ```
function unloadModel(model)
debugPrint("^6Bridge^7: ^2Removing Model from memory cache^7: '^6"..model.."^7'")
SetModelAsNoLongerNeeded(model)
end
--- Loads an animation dictionary into memory.
---
--- This function checks if the animation dictionary exists and requests it.
--- It waits until the animation dictionary is loaded before proceeding.
---
---@param animDict string The name of the animation dictionary to load.
---
---@usage
--- ```lua
--- loadAnimDict('amb@world_human_hang_out_street@male_c@base')
--- ```
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
debugPrint("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..animDict.."^7'")
while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end
end
end
--- Unloads an animation dictionary from memory.
---
--- This function removes the animation dictionary from the game's memory cache.
---
---@param animDict string The name of the animation dictionary to unload.
---
---@usage
---@
--- ```lua
--- unloadAnimDict('amb@world_human_hang_out_street@male_c@base')
--- ```
function unloadAnimDict(animDict)
debugPrint("^6Bridge^7: ^2Removing Anim Dictionary from memory cache^7: '^6"..animDict.."^7'")
RemoveAnimDict(animDict)
end
--- Loads a particle effects (ptfx) dictionary into memory.
---
--- 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.
---
---@usage
--- ```lua
--- loadPtfxDict('core')
--- ```
function loadPtfxDict(ptFxName)
if not HasNamedPtfxAssetLoaded(ptFxName) then
debugPrint("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..ptFxName.."^7'")
while not HasNamedPtfxAssetLoaded(ptFxName) do RequestNamedPtfxAsset(ptFxName) Wait(5) end
end
end
--- Unloads a particle effects (ptfx) dictionary from memory.
---
--- 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.
---
---@usage
--- ```lua
--- unloadPtfxDict('core')
--- ```
function unloadPtfxDict(dict)
debugPrint("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'")
RemoveNamedPtfxAsset(dict)
end
--- Loads a texture dictionary into memory.
---
--- This function requests the streamed texture dictionary and waits until it's loaded.
---
---@param dict string The name of the texture dictionary to load.
---
---@usage
--- ```lua
--- loadTextureDict('commonmenu')
--- ```
function loadTextureDict(dict)
if not HasStreamedTextureDictLoaded(dict) then
debugPrint("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'")
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
end
end
--- Loads a script audio bank into memory.
---
--- 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.
---
---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
---
---@usage
--- ```lua
--- local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS')
--- ```
function loadScriptBank(bank)
local timeout = 2000
debugPrint("^6Bridge^7: ^2Loading ^3Script ^2AudioBank^7...")
while not RequestScriptAudioBank(bank, false) do Wait(10) timeout -= 10 if timeout <= 0 then break end end
local success = RequestScriptAudioBank(bank, false)
debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
return success
end
--- Loads an ambient audio bank into memory.
---
--- 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.
---
---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
---
---@usage
--- ```lua
--- local success = loadAmbientBank('AMB_REVERB_GENERIC')
--- ```
function loadAmbientBank(bank)
local timeout = 2000
debugPrint("^6Bridge^7: ^2Loading ^3Ambient ^2AudioBank^7...")
while not RequestAmbientAudioBank(bank, 0) do
Wait(10)
timeout -= 10
if timeout <= 0 then break end
end
local success = RequestAmbientAudioBank(bank, 0)
debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
return success
end
--- Plays an animation on a specified ped.
---
--- This function loads the animation dictionary and instructs the ped to play the animation.
---
---@param animDict string The name of the animation 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 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 speed number (optional) The speed multiplier for the animation. Default is `8.0`.
---
---@usage
--- ```lua
--- playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0)
--- ```
function playAnim(animDict, animName, duration, flag, ped, speed)
loadAnimDict(animDict)
debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName, flag)
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
end
--- Stops a specified animation on a ped.
---
--- This function stops the animation and unloads the animation dictionary from memory.
---
---@param animDict string The name of the animation 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.
---
---@usage
--- ```lua
--- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
--- ```
function stopAnim(animDict, animName, ped)
debugPrint("^6Bridge^7: ^3stopAnim^7() ^2Triggered^7: ", animDict, animName)
StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
unloadAnimDict(animDict)
end
--- 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.
---
---@param bank string The name of the audio bank containing the sound.
---@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 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`.
---
---@usage
--- ```lua
--- playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
--- ```
function playGameSound(audioBank, soundSet, soundRef, coords, synced, range)
debugPrint("^6Bridge^7: ^2Attempting to play: ^3"..soundRef.." ^7('^4"..audioBank.."^7')")
loadScriptBank(audioBank)
local range = range or 10.0
local soundId = GetSoundId()
while not soundId do Wait(10) end
if type(coords) == "vector3" or type(coords) == "vector4" then
debugPrint("^6Bridge^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz))
PlaySoundFromCoord(soundId, soundRef, coords.x, coords.y, coords.z, soundSet, synced, range, 0)
else
debugPrint("^6Bridge^7: ^2Playing sound from Entity^7: ^4"..coords.."^7")
PlaySoundFromEntity(soundId, soundRef, coords, soundSet, synced, 1.0)
end
ReleaseScriptAudioBank(audioBank)
end
-- Experimental, add fade in for spawned entities
function fadeInEnt(ent, duration)
if not DoesEntityExist(ent) then return end
duration = duration or 500 -- in ms
local fadeSteps = 20
local stepTime = duration / fadeSteps
SetEntityAlpha(ent, 0, false)
SetEntityVisible(ent, true, false)
SetEntityLocallyInvisible(ent) -- sometimes helps prevent "pop-in" in early frames
for i = 1, fadeSteps do
Wait(stepTime)
local newAlpha = math.floor((i / fadeSteps) * 255)
SetEntityAlpha(ent, newAlpha, false)
end
-- Restore full visibility
ResetEntityAlpha(ent)
end

168
shared/make/makeBlip.lua Normal file
View File

@@ -0,0 +1,168 @@
local blipTable = {}
--- Creates a blip at specified coordinates with given properties.
--
-- 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.
--
---@param data A table containing blip data and properties.
-- - **coords**: A `vector3` containing x, y, z coordinates where the blip will be placed.
-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
-- - **col** (optional): The color ID of the blip. Default is `5`.
-- - **scale** (optional): The scale of the blip. Default is `0.7`.
-- - **disp** (optional): The display option of the blip. Default is `6`.
-- - **category** (optional): The category ID for the blip.
-- - **name**: The name of the blip, used for display on the map.
-- - **preview** (optional): A URL or image path for a preview image to display with the blip.
--
---@return blip blipID The handle of the created blip.
--
---@usage
-- ```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)
-- ```
function makeBlip(data)
local blip = nil
if gameName == "rdr3" then
blip = BlipAddForCoords(1664425300, data.coords.x, data.coords.y, data.coords.z)
SetBlipSprite(blip, data.sprite or `blip_shop_market_stall`)
SetBlipScale(blip, data.scale or 0.2)
SetBlipName(blip, data.name)
--BlipSetStyle(blip, data.col or `BLIP_STYLE_CREATOR_DEFAULT`)
else
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
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("jim-blipcontroller") then
if data.preview then
local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
if data.preview:find("http") or data.preview:find("nui") then
createDui(txname, data.preview, vec2(512, 256), scriptTxd)
else
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
end
exports["jim-blipcontroller"]:ShowBlipInfo(blip, {
title = data.name,
dict = getScript()..'scriptTxd',
tex = txname,
})
end
end
end
blipTable[blip] = blip
if DoesBlipExist(blip) then
debugPrint("^6Bridge^7: ^6Blip ^2created^7: '^6"..data.name.."^7' - '"..formatCoord(data.coords).."'")
else
print("Error making blip")
end
return blip
end
--- Creates a blip attached to a specified entity with given properties.
--
-- 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.
--
---@param data table A table containing blip data and properties.
-- - **entity**: The entity to which the blip will be attached.
-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
-- - **col** (optional): The color ID of the blip. Default is `5`.
-- - **scale** (optional): The scale of the blip. Default is `0.7`.
-- - **disp** (optional): The display option of the blip. Default is `6`.
-- - **category** (optional): The category ID for the blip.
-- - **name**: The name of the blip, used for display on the map.
-- - **preview** (optional): A URL or image path for a preview image to display with the blip.
--
--
---@return number blipID The handle of the created blip.
---@usage
-- ```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)
-- ```
function makeEntityBlip(data)
local blip = nil
if gameName == "rdr3" then
blip = BlipAddForEntity(1664425300, data.entity)
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("jim-blipcontroller") then
if data.preview then
local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
if data.preview:find("http") or data.preview:find("nui") then
createDui(txname, data.preview, vec2(512, 256), scriptTxd)
else
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
end
exports["jim-blipcontroller"]:ShowBlipInfo(blip, {
title = data.name,
dict = getScript()..'scriptTxd',
tex = txname,
})
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

252
shared/make/makePed.lua Normal file
View File

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

100
shared/make/makeProp.lua Normal file
View File

@@ -0,0 +1,100 @@
local Props = {}
--- 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.
---
---@param data table A table containing prop data.
--- - **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).
---@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`.
---
---@return number entityID The handle of the created prop object.
---
---@usage
--- ```lua
--- local propData = {
--- prop = 'prop_chair_01a',
--- coords = vector4(123.4, 567.8, 90.1, 180.0)
--- }
--- local prop = makeProp(propData, true, false)
--- ```
function makeProp(data, freeze, synced, fade)
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)
SetEntityAlpha(veh, 0, false)
SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
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))
SetModelAsNoLongerNeeded(data.prop)
Props[keyGen()..keyGen()] = prop
if fade ~= false then
CreateThread(function()
fadeInEnt(prop)
end)
end
return prop
end
--- 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.
--- When the player exits the area, the prop is destroyed.
---
---@param data table A table containing prop data.
--- - **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).
---@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`.
---
---@usage
--- ```lua
--- local propData = {
--- prop = 'prop_chair_01a',
--- coords = vector4(123.4, 567.8, 90.1, 180.0)
--- }
--- makeDistProp(propData, true, false)
--- ```
function makeDistProp(data, freeze, synced, range)
local name = keyGen()..keyGen()
createCirclePoly({
name = name,
coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
radius = range or 50.0,
onEnter = function()
Props[name] = makeProp(data, freeze, synced)
end,
onExit = function()
destroyProp(Props[name])
end,
debug = debugMode,
})
end
--- Destroys a prop, detaching it if attached to the player beforehand.
---
---@param entity number The handle of the prop entity to destroy.
---
---@usage
--- ```lua
--- destroyProp(prop)
--- ```
function destroyProp(entity)
if entity then
debugPrint("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'")
if IsEntityAttachedToEntity(entity, PlayerPedId()) then
SetEntityAsMissionEntity(entity)
DetachEntity(entity, true, true)
end
DeleteObject(entity)
end
end
--- Cleans up all created props when the resource stops.
onResourceStop(function()
for k in pairs(Props) do
destroyProp(Props[k])
end
end, true)

113
shared/make/makeVeh.lua Normal file
View File

@@ -0,0 +1,113 @@
local Vehicles = {}
--- 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.
---
---@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).
---
---@return number entityID The handle of the created vehicle.
---
---@usage
--- ```lua
--- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0))
--- ```
function makeVeh(model, coords, synced, fade)
loadModel(model)
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, synced ~= false, false)
SetVehicleHasBeenOwnedByPlayer(veh, true)
if gameName ~= "rdr3" then
SetEntityAlpha(veh, 0, false)
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
Wait(100)
SetVehicleNeedsToBeHotwired(veh, false)
SetVehRadioStation(veh, 'OFF')
SetVehicleFuelLevel(veh, 100.0)
SetVehicleModKit(veh, 0)
end
SetVehicleOnGroundProperly(veh)
debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords))
unloadModel(model)
Vehicles[#Vehicles + 1] = veh
if fade ~= false then
CreateThread(function()
fadeInEnt(veh)
end)
end
return veh
end
local distanceVehicles = {}
--- Creates a vehicle that spawns when the player enters a designated polyzone area.
---
--- This function sets up a circular polyzone; when the player enters the zone, the vehicle is spawned,
--- and when the player exits, the vehicle is deleted.
---
---@param data table A table containing vehicle data.
--- - **vehicle** `string`: The model name or hash of the vehicle to spawn.
--- - **coords** `vector4`: The coordinates where the vehicle will be placed. Should include x, y, z, and w (heading).
---@param freeze boolean (optional) Whether to freeze the vehicle in place. Defaults to `false`.
---@param synced boolean (optional) Whether the vehicle should be synced across clients. Defaults to `false`.
function makeDistVehicle(data, radius, onEnter, onExit)
local vehicle = nil
local zoneId = keyGen() .. keyGen()
local zone = createCirclePoly({
name = zoneId,
coords = vec3(data.coords.x, data.coords.y, data.coords.z),
radius = radius,
onEnter = function()
vehicle = makeVeh(data.model, data.coords, false)
if onEnter then
debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onEnter^7() ^2running^7")
onEnter(vehicle)
end
end,
onExit = function()
deleteVehicle(vehicle)
if onExit then
debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onExit^7() ^2running^7")
onExit(vehicle)
end
end,
debug = debugMode,
})
distanceVehicles[zoneId] = { zone = zone, vehicle = vehicle }
return zoneId
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)

View File

@@ -0,0 +1,301 @@
local inProgress = false
local storedPID = nil
--- 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.
---
---@param data table A table containing the progress bar configuration.
--- - **label** (`string`): The text label to display on the progress bar.
--- - **time** (`number`): The duration of the progress bar in milliseconds.
--- - **dict** (`string`, optional): The animation dictionary to use.
--- - **anim** (`string`, optional): The animation name to play.
--- - **task** (`string`, optional): The task scenario to perform.
--- - **flag** (`number`, optional): The animation flag.
--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
--- - **icon** (`string`, optional): The icon to display (for qb progress bar).
--- - **cam** (`number`, optional): The camera handle to use.
--- - **shared** (`table`, optional): Data for shared progress bars.
--- - **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.
---
---@usage
--- ```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,
--- })
--- ```
function progressBar(data)
local ped = PlayerPedId()
if data.shared then
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
storedPID = data.shared.pid
TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
end
local result = nil
if data.cam then startTempCam(data.cam) end
if Config.System.ProgressBar == "ox" then
local options = {
duration = debugMode and 1000 or data.time,
label = data.label,
position = data.position or "bottom",
useWhileDead = data.dead or false,
canCancel = data.cancel or true,
anim = {
dict = data.dict,
clip = data.anim,
flag = (data.flag == 8 and 32 or data.flag) or nil,
scenario = data.task
},
disable = {
combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
mouse = data.mouse or false
},
}
if data.prop and data.prop.model then
options.prop = {
model = data.prop.model,
pos = data.prop.pos or vec3(0, 0, 0),
rot = data.prop.rot or vec3(0, 0, 0),
bone = data.prop.bone or 0
}
end
if data.propTwo and data.propTwo.model then
options.propTwo = {
model = data.propTwo.model,
pos = data.propTwo.pos or vec3(0, 0, 0),
rot = data.propTwo.rot or vec3(0, 0, 0),
bone = data.propTwo.bone or 0
}
end
if data.progressType == "circle" then
if exports[OXLibExport]:progressCircle(options) then
result = true
else
result = false
end
end
if not data.progressType or data.progressType == "bar" then
if exports[OXLibExport]:progressBar(options) then
result = true
else
result = false
end
end
elseif Config.System.ProgressBar == "qb" then
Core.Functions.Progressbar("progbar",
data.label,
debugMode and 1000 or data.time,
data.dead or false,
data.cancel or true,
{
disableMovement = data.disableMovement or false,
disableCarMovement = data.disableMovement or false,
disableMouse = data.disableMouse or false,
disableCombat = data.disableCombat or true,
},
{
animDict = data.dict,
anim = data.anim,
flags = data.flag or 32,
task = data.task
},
{}, {},
function()
result = true
end, function()
result = false
end, data.icon)
elseif Config.System.ProgressBar == "qs" then -- documentation left intact based on qs interface
local qs_success = exports['qs-interface']:ProgressBar({
duration = debugMode and 1000 or data.time, -- Duration of the progress bar (in milliseconds)
label = data.label, -- Text that will appear on the progress bar
position = 'bottom', -- Position of the progress bar on the screen (e.g., 'top', 'bottom', 'center')
useWhileDead = data.dead or false, -- Whether the progress bar shows when the player is dead (true/false)
canCancel = data.cancel or true, -- Whether the player can cancel the progress (true/false)
disable = data.disableMovement or false, -- Whether to disable player controls during the progress (true/false)
anim = { -- Animation to be played while the progress is happening
dict = data.dict,
clip = data.anim,
flag = data.flag or 32 -- Optional animation flags
},
prop = nil -- Optional prop to show alongside the progress bar (can be nil)
})
if qs_success then
result = true
else
result = false
end
elseif Config.System.ProgressBar == "esx" then
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
FreezePlayer = true,
animation = {
type = data.anim,
dict = data.dict,
scenario = data.task,
},
onFinish = function()
result = true
end,
onCancel = function()
result = false
end
})
elseif Config.System.ProgressBar == "lation" then
if exports.lation_ui:progressBar({
label = data.label,
description = nil,
duration = debugMode and 1000 or data.time,
icon = data.icon,
useWhileDead = data.dead or false,
disable = {
combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
},
anim = {
dict = data.dict,
clip = data.anim,
flag = data.flag
},
prop = {
model = data.prop and data.prop.model,
pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
bone = data.prop and (data.prop.bone or 0)
}
}) then
result = true
else
result = false
end
elseif Config.System.ProgressBar == "red" then
-- Currently only uses jim-redui if you choose this option
if exports["jim_bridge"]:redProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
result = true
else
result = false
end
elseif Config.System.ProgressBar == "gta" then
if exports["jim_bridge"]:gtaProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
result = true
else
result = false
end
end
while result == nil do
Wait(10)
end
-- Cleanup
FreezeEntityPosition(ped, false)
lockInv(false)
if data.cam then
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 = getPlayerMetadata(player, "myKey")
--- ```
function getPlayerMetadata(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] = getPlayerMetadata(player, k)
end
return Metadata
elseif type(key) == "string" then
return getPlayerMetadata(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
--- setPlayerMetadata(player, "myKey", "newValue")
--- ```
function setPlayerMetadata(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:setPlayerMetadata", 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]]
setPlayerMetadata(player, key, value)
print("Metadata set successfully.", key)
return true
end)

153
shared/notify.lua Normal file
View File

@@ -0,0 +1,153 @@
--[[
Notifications Module
----------------------
This module provides a unified interface for displaying notifications using various
notification systems. The active system is determined by the Config.System.Notify setting.
Supported systems include:
• okok
• qb
• ox
• gta (default)
• esx
]]
--- Displays notifications to the player using the configured notification system.
---
--- Supports multiple notification systems based on Config.System.Notify. Can be triggered from both
--- client and server contexts.
---
--- @param title string|nil The notification title (optional for some systems).
--- @param message string The main message content.
--- @param type string The notification type ("success", "error", "info").
--- @param src number|nil Optional server ID; if provided, the notification is sent to that player.
---
--- @usage
--- ```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", playerId)
--- ```
function triggerNotify(title, message, type, src)
if not Config.System or not Config.System.Notify then debugPrint("Notify triggered but not set up") return end
if Config.System.Notify == "okok" then
if not src then
TriggerEvent('okokNotify:Alert', title, message, 6000, type)
else
TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type)
end
elseif Config.System.Notify == "qb" then
if not src then
TriggerEvent("QBCore:Notify", message, type)
else
TriggerClientEvent("QBCore:Notify", src, message, type)
end
elseif Config.System.Notify == "qs" then
if not src then
exports['qs-interface']:AddNotify(message, title, 6000)
else
TriggerClientEvent('interface:notification', src, message, title, 6000)
end
elseif Config.System.Notify == "ox" then
if not src then
TriggerEvent('ox_lib:notify', { title = title, description = message, type = type or "success" })
else
TriggerClientEvent('ox_lib:notify', src, { title = title, description = message, type = type or "success" })
end
elseif Config.System.Notify == "gta" then
if not src then
exports.jim_bridge:Notify(title, message, type)
else
TriggerClientEvent("jim-bridge:Notify", src, title, message, type)
end
elseif Config.System.Notify == "esx" then
if not src then
exports["esx_notify"]:Notify(type, 4000, message)
else
TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message)
end
elseif Config.System.Notify == "lation" then
if not src then
exports.lation_ui:notify({
title = title,
message = message,
type = type or "success",
})
else
TriggerClientEvent("lation_ui:notify", src, {
title = title,
message = message,
type = type or "success",
})
end
elseif Config.System.Notify == "red" then
if isStarted("jim-redui") then
if not src then
TriggerEvent("jim-redui:Notify", title, message, type)
else
TriggerClientEvent("jim-redui:Notify", src, title, message, type)
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)

277
shared/phones.lua Normal file
View File

@@ -0,0 +1,277 @@
--[[
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 = "gksphone",
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)

837
shared/playerfunctions.lua Normal file
View File

@@ -0,0 +1,837 @@
--[[
Player Utility & Server Event Handlers Module
------------------------------------------------
This module provides utility functions for:
• Locking/unlocking the player's inventory.
• Instantly turning or gradually turning the player to face a target.
• Handling player needs (thirst and hunger) via server events.
• Charging/funding players (money removal/addition).
• Processing item consumption and applying effects.
• Checking player job/gang roles and retrieving player information.
• Getting active players near a coordinate.
]]
-------------------------------------------------------------
-- Player Movement
-------------------------------------------------------------
--- Instantly turns an entity to face a target (entity or coordinates) without animation.
---
--- @param ent number|nil The Ped to turn (defaults to player's Ped if nil).
--- @param ent2 number|vector3|nil The target entity or coordinates to face.
---
--- @usage
--- ```lua
--- instantLookEnt(nil, vector3(200.0, 300.0, 40.0))
--- instantLookEnt(ped1, ped2)
--- ```
function instantLookEnt(ent, ent2)
local ped = ent or PlayerPedId()
local p1 = GetEntityCoords(ped, true)
local p2 = type(ent2) == "vector3" and ent2 or GetEntityCoords(ent2, true)
local dx = p2.x - p1.x
local dy = p2.y - p1.y
local heading = GetHeadingFromVector_2d(dx, dy)
debugPrint("^6Bridge^7: ^1Forced ^2Turning Player to^7: '^6"..formatCoord(p2).."^7'")
SetEntityHeading(ped, heading)
end
--- Makes the player look towards a specific target with an animated turn.
---
--- If the player is not already facing the target (entity or coordinates), a turning animation is triggered.
---
--- @param entity number|vector3|vector4|nil The target to look at.
---
--- @usage
--- ```lua
--- lookEnt(vector3(200.0, 300.0, 40.0))
--- lookEnt(pedEntity)
--- ```
function lookEnt(entity)
local ped = PlayerPedId()
if entity then
if type(entity) == "vector3" or type(entity) == "vector4" then
if not IsPedHeadingTowardsPosition(ped, entity.xyz, 30.0) then
TaskTurnPedToFaceCoord(ped, entity.xyz, 1500)
debugPrint("^6Bridge^7: ^2Turning Player to^7: '^6"..formatCoord(entity).."^7'")
Wait(1500)
end
else
if DoesEntityExist(entity) then
local entCoords = GetEntityCoords(entity)
if not IsPedHeadingTowardsPosition(ped, entCoords, 30.0) then
TaskTurnPedToFaceCoord(ped, entCoords, 1500)
debugPrint("^6Bridge^7: ^2Turning Player to^7: '^6"..entity.."^7' - '"..formatCoord(entCoords).."^7'")
Wait(1500)
end
end
end
end
end
-------------------------------------------------------------
-- Server Event Handlers for Needs
-------------------------------------------------------------
--- Server event handler for urinal usage.
--- Decreases player's thirst by a random amount.
RegisterNetEvent(getScript()..":server:Urinal", function()
local src = source
local Player = getPlayer(src)
local thirstamt = math.random(10, 30)
local thirst = Player.thirst - thirstamt
setThirst(src, getPlayer(src).thirst - thirst)
end)
--- Server event handler for setting player needs (thirst or hunger).
---
--- @event
--- @param type string "thirst" or "hunger".
--- @param amount number New value to set.
RegisterNetEvent(getScript()..":server:setNeed", function(needType, amount)
local src = source
if needType == "thirst" then
setThirst(src, amount)
elseif needType == "hunger" then
setHunger(src, amount)
end
end)
--- Sets the player's thirst level.
---
--- @param src number The player's server ID.
--- @param thirst number The new thirst level.
---
--- @usage
--- ```lua
--- setThirst(playerId, 80)
--- ```
function setThirst(src, thirst)
debugPrint("^4Debug^7: ^2Adding thirst^7: ^3"..thirst.." ^2to player^7: ^3"..src.."^7")
if isStarted(ESXExport) then
TriggerClientEvent('esx_status:add', src, 'thirst', thirst)
elseif isStarted(QBExport) or isStarted(QBXExport) or isStarted(RSGExport) then
local Player = Core.Functions.GetPlayer(src)
Player.Functions.SetMetaData('thirst', thirst)
TriggerClientEvent("hud:client:UpdateNeeds", src, Player.PlayerData.metadata.hunger, thirst)
end
end
--- Sets the player's hunger level.
---
--- @param src number The player's server ID.
--- @param hunger number The new hunger level.
---
--- @usage
--- ```lua
--- setHunger(playerId, 60)
--- ```
function setHunger(src, hunger)
debugPrint("^4Debug^7: ^2Adding hunger^7: ^3"..hunger.." ^2to player^7: ^3"..src.."^7")
if isStarted(ESXExport) then
TriggerClientEvent('esx_status:add', src, 'hunger', hunger)
elseif isStarted(QBExport) or isStarted(QBXExport) or isStarted(RSGExport) then
local Player = Core.Functions.GetPlayer(src)
Player.Functions.SetMetaData('hunger', hunger)
TriggerClientEvent("hud:client:UpdateNeeds", src, hunger, Player.PlayerData.metadata.thirst)
end
end
-------------------------------------------------------------
-- Economy Event Handlers
-------------------------------------------------------------
--- BillPlayer
function billPlayer(data)
if not Config.System.Billing or Config.System.Billing == "jim" then
TriggerEvent("jim-payments:client:Charge", {
job = data.job,
gang = data.gang,
coords = data.coords.xyz,
img = data.img
})
elseif Config.System.Billing == "okok" then
-- TriggerServerEvent("okokBilling:CreateCustomInvoice", target, price, reason, invoiceSource, society, societyName)
TriggerEvent("okokBilling:ToggleCreateInvoice")
end
end
--- Charges a player by removing money from their account.
---
--- @param cost number The amount to charge.
--- @param type string "cash" or "bank".
--- @param newsrc number|nil Optional player ID; defaults to event source.
---
--- @usage
--- ```lua
--- chargePlayer(100, "cash", playerId)
--- ```
function chargePlayer(cost, moneyType, newsrc)
local is_success = false
local src = newsrc or source
local fundResource = ""
if cost < 0 then
debugPrint("^1Error^7: ^7SRC: ^3"..src.." ^2Tried to charge a minus value^7", cost)
return
end
if moneyType == "cash" then
if isStarted(OXInv) then fundResource = OXInv
is_success = exports[OXInv]:RemoveItem(src, "money", cost)
elseif isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
elseif isStarted(RSGExport) then
fundResource = RSGExport
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("cash", cost)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).removeMoney(cost, "")
is_success = true
end
elseif moneyType == "bank" then
if isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
elseif isStarted(RSGExport) then
fundResource = RSGExport
is_success = Core.Functions.GetPlayer(src).Functions.RemoveMoney("bank", cost)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).removeMoney(cost, "")
is_success = true
end
end
if fundResource == "" then
print("Cannot charge player - check starter.lua")
else
debugPrint("^6Bridge^7: ^2Charging ^2Player^7: '^6"..cost.."^7'", moneyType, fundResource)
end
return is_success
end
RegisterNetEvent(getScript()..":server:ChargePlayer", function(cost, moneyType, newsrc)
debugPrint(GetInvokingResource())
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
chargePlayer(cost, moneyType, newsrc)
end)
--- Funds a player by adding money to their account.
---
--- @param fund number The amount to add.
--- @param type string "cash" or "bank".
--- @param newsrc number|nil Optional player ID; defaults to event source.
---
--- @usage
--- ```lua
--- fundPlayer(150, "cash", playerId)
--- ```
function fundPlayer(fund, moneyType, newsrc)
local is_success = false
local src = newsrc or source
local fundResource = ""
if moneyType == "cash" then
if isStarted(OXInv) then
fundResource = OXInv
is_success = exports[OXInv]:AddItem(src, "money", fund)
elseif isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
elseif isStarted(RSGExport) then
fundResource = RSGExport
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("cash", fund)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).addMoney(fund, "")
is_success = true
end
elseif moneyType == "bank" then
if isStarted(QBExport) or isStarted(QBXExport) then
fundResource = QBExport
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
elseif isStarted(RSGExport) then
fundResource = RSGExport
is_success = Core.Functions.GetPlayer(src).Functions.AddMoney("bank", fund)
elseif isStarted(ESXExport) then
fundResource = ESXExport
ESX.GetPlayerFromId(src).addMoney(fund, "")
is_success = true
end
end
if fundResource == "" then
print("Cannot fund player - check starter.lua")
else
debugPrint("^6Bridge^7: ^2Funding Player: '^2"..fund.."^7'", moneyType, fundResource)
end
return is_success
end
-------------------------------------------------------------
-- Item Consumption & Effects
-------------------------------------------------------------
--- Handles successful consumption of an item.
---
--- Plays a consumption animation, removes the item, updates player needs, handles alcohol effects,
--- and checks for random rewards.
---
--- @param itemName string The name of the consumed item.
--- @param type string The category of the item (e.g., "alcohol").
--- @param data table Additional data (e.g., hunger and thirst values).
---
--- @usage
--- ```lua
--- -- Player consumes a health pack
--- ConsumeSuccess("health_pack", "health")
---
--- -- Player consumes an alcohol drink
--- ConsumeSuccess("beer", "alcohol")
--- ```
function ConsumeSuccess(itemName, type, data)
local hunger = data and data.hunger or Items[itemName].hunger
local thirst = data and data.thirst or Items[itemName].thirst
ExecuteCommand("e c")
removeItem(itemName, 1)
if isStarted(ESXExport) then
if hunger then
TriggerServerEvent(getScript()..":server:setNeed", "hunger", tonumber(hunger) * 10000)
end
if thirst then
TriggerServerEvent(getScript()..":server:setNeed", "thirst", tonumber(thirst) * 10000)
end
else
if hunger then
TriggerServerEvent(getScript()..":server:setNeed", "hunger", Core.Functions.GetPlayerData().metadata["hunger"] + tonumber(hunger))
end
if thirst then
TriggerServerEvent(getScript()..":server:setNeed", "thirst", Core.Functions.GetPlayerData().metadata["thirst"] + tonumber(thirst))
end
end
if type == "alcohol" then
alcoholCount = (alcoholCount or 0) + 1
if alcoholCount > 1 and alcoholCount < 4 then
TriggerEvent("evidence:client:SetStatus", "alcohol", 200)
elseif alcoholCount >= 4 then
TriggerEvent("evidence:client:SetStatus", "heavyalcohol", 200)
AlienEffect()
end
end
if Config.Reward then
getRandomReward(itemName)
end
end
-------------------------------------------------------------
-- Player Job & Information Utilities
-------------------------------------------------------------
--- Checks if a player has a specific job or gang (and optionally meets a minimum grade).
---
--- @param job string The job or gang name to check.
--- @param source number|nil Optional player source; if nil, checks current player.
--- @param grade number|nil Optional minimum grade level.
--- @return boolean, boolean boolean Returns true and duty status if the check passes; false otherwise.
---
--- @usage
--- ```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
--- ```
function hasJob(job, source, grade)
local hasJobFlag, duty = false, true
if source then
local src = tonumber(source)
if not src then print(tostring(source).." is not a valid player source") end
if isStarted(ESXExport) then
local info = ESX.GetPlayerFromId(src).job
while not info do
info = ESX.GetPlayerData(src).job
Wait(100)
end
if info.name == job then hasJobFlag = true end
elseif isStarted(OXCoreExport) then
local chunk = assert(load(LoadResourceFile('ox_core', ('imports/%s.lua'):format('server')), ('@@ox_core/%s'):format(file)))
chunk()
local player = Ox.GetPlayer(src)
for k, v in pairs(player.getGroups()) do
if k == job then hasJobFlag = true end
end
elseif isStarted(QBXExport) then
local jobinfo = exports[QBXExport]:GetPlayer(src).PlayerData.job
if jobinfo.name == job then
hasJobFlag = true
duty = exports[QBXExport]:GetPlayer(src).PlayerData.job.onduty
if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end
end
local ganginfo = exports[QBXExport]:GetPlayer(src).PlayerData.gang
if ganginfo.name == job then
hasJobFlag = true
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end
elseif isStarted(QBExport) and not isStarted(QBXExport) then
if Core.Functions.GetPlayer then
local player = Core.Functions.GetPlayer(src)
if not player then goto skip end
local jobinfo = player.PlayerData.job
if jobinfo.name == job then
hasJobFlag = true
duty = player.PlayerData.job.onduty
if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end
end
local ganginfo = player.PlayerData.gang
if ganginfo.name == job then
hasJobFlag = true
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end
else
local jobinfo = exports[QBExport]:GetPlayer(src).PlayerData.job
if jobinfo.name == job then
hasJobFlag = true
duty = exports[QBExport]:GetPlayer(src).PlayerData.job.onduty
if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end
end
local ganginfo = exports[QBExport]:GetPlayer(src).PlayerData.gang
if ganginfo.name == job then
hasJobFlag = true
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end
end
elseif isStarted(RSGExport) then
if Core.Functions.GetPlayer then
local player = Core.Functions.GetPlayer(src)
if not player then goto skip end
local jobinfo = player.PlayerData.job
if jobinfo.name == job then
hasJobFlag = true
duty = player.PlayerData.job.onduty
if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end
end
local ganginfo = player.PlayerData.gang
if ganginfo.name == job then
hasJobFlag = true
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end
else
local jobinfo = exports[RSGExport]:GetPlayer(src).PlayerData.job
if jobinfo.name == job then
hasJobFlag = true
duty = exports[RSGExport]:GetPlayer(src).PlayerData.job.onduty
if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end
end
local ganginfo = exports[RSGExport]:GetPlayer(src).PlayerData.gang
if ganginfo.name == job then
hasJobFlag = true
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end
end
else
print("^4ERROR^7: ^2No Core detected for hasJob ^7- ^2Check ^3starter^1.^2lua^7")
end
else
-- Client-side check.
if isStarted(ESXExport) and ESX ~= nil then
local info = ESX.GetPlayerData().job
while not info do
info = ESX.GetPlayerData().job
Wait(100)
end
if info.name == job then hasJobFlag = true end
elseif isStarted(OXCoreExport) then
local info = OxPlayer.getGroups()
for k, v in pairs(info) do
if k == job then hasJobFlag = true break end
end
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayerData()
if info.job.name == job then
hasJobFlag = true
duty = info.job.onduty
if grade and not (grade <= info.job.grade.level) then hasJobFlag = false end
end
if info.gang.name == job then
hasJobFlag = true
if grade and not (grade <= info.gang.grade.level) then hasJobFlag = false end
end
elseif isStarted(QBExport) and not isStarted(QBXExport) then
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
local jobinfo = info.job
if jobinfo.name == job then
hasJobFlag = true
duty = jobinfo.onduty
if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end
end
local ganginfo = info.gang
if ganginfo.name == job then
hasJobFlag = true
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end
elseif isStarted(RSGExport) then
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
local jobinfo = info.job
if jobinfo.name == job then
hasJobFlag = true
duty = jobinfo.onduty
if grade and not (grade <= jobinfo.grade.level) then hasJobFlag = false end
end
local ganginfo = info.gang
if ganginfo.name == job then
hasJobFlag = true
if grade and not (grade <= ganginfo.grade.level) then hasJobFlag = false end
end
else
print("^4ERROR^7: ^2No Core detected for hasJob() ^7- ^2Check ^3starter^1.^2lua^7")
end
end
::skip::
return hasJobFlag, duty
end
--- 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).
---
--- @param source number|nil Optional player server ID.
--- @return table A table containing player details.
---
--- @usage
--- ```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)
--- ```
function getPlayer(source)
local Player = {}
debugPrint("^6Bridge^7: ^2Getting ^3Player^2 info^7")
if source then
local src = tonumber(source)
if isStarted(ESXExport) then
local info = ESX.GetPlayerFromId(src)
if not info then return {} end
Player = {
name = info.getName(),
cash = info.getMoney(),
bank = info.getAccount("bank").money,
firstname = info.variables.firstName,
lastname = info.variables.lastName,
source = info.source,
job = info.job.name,
--jobBoss = info.job.isboss,
--gang = info.gang.name,
--gangBoss = info.gang.isboss,
onDuty = info.job.onDuty,
--account = info.charinfo.account,
citizenId = info.identifier,
}
elseif isStarted(OXCoreExport) then
local file = ('imports/%s.lua'):format('server')
local import = LoadResourceFile('ox_core', file)
local chunk = assert(load(import, ('@@ox_core/%s'):format(file)))
chunk()
local player = Ox.GetPlayer(src)
if not player then return {} end
Player = {
firstname = player.firstName,
lastname = player.lastName,
name = ('%s %s'):format(player.firstName, player.lastName),
cash = exports[OXInv]:Search(src, 'count', "money"),
bank = 0,
source = src,
--job = OxPlayer.getGroups(),
--jobBoss = info.job.isboss,
--gang = OxPlayer.getGroups(),
--gangBoss = info.gang.isboss,
--onDuty = info.job.onduty,
--account = info.charinfo.account,
citizenId = player.stateId,
}
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayer(src)
if not info then return {} end
Player = {
firstname = info.PlayerData.charinfo.firstname,
lastname = info.PlayerData.charinfo.lastname,
name = info.PlayerData.charinfo.firstname.." "..info.PlayerData.charinfo.lastname,
cash = exports[OXInv]:Search(src, 'count', "money"),
bank = info.Functions.GetMoney("bank"),
source = info.PlayerData.source,
job = info.PlayerData.job.name,
jobBoss = info.PlayerData.job.isboss,
jobInfo = info.PlayerData.job,
gang = info.PlayerData.gang.name,
gangInfo = info.PlayerData.gang,
gangBoss = info.PlayerData.gang.isboss,
onDuty = info.PlayerData.job.onduty,
account = info.PlayerData.charinfo.account,
citizenId = info.PlayerData.citizenid,
isDead = info.PlayerData.metadata["isdead"],
isDown = info.PlayerData.metadata["inlaststand"],
charInfo = info.charinfo,
}
elseif isStarted(QBExport) and not isStarted(QBXExport) then
if Core.Functions.GetPlayer(src) then
local info = Core.Functions.GetPlayer(src).PlayerData
if not info then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
name = info.charinfo.firstname.." "..info.charinfo.lastname,
cash = info.money["cash"],
bank = info.money["bank"],
source = info.source,
job = info.job.name,
jobBoss = info.job.isboss,
jobInfo = info.job,
gang = info.gang.name,
gangBoss = info.gang.isboss,
gangInfo = info.gang,
onDuty = info.job.onduty,
account = info.charinfo.account,
citizenId = info.citizenid,
isDead = info.metadata["isdead"],
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
end
elseif isStarted(RSGExport) then
if Core.Functions.GetPlayer(src) then
local info = Core.Functions.GetPlayer(src).PlayerData
if not info then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
name = info.charinfo.firstname.." "..info.charinfo.lastname,
cash = info.money["cash"],
bank = info.money["bank"],
source = info.source,
job = info.job.name,
jobBoss = info.job.isboss,
jobInfo = info.job,
gang = info.gang.name,
gangBoss = info.gang.isboss,
gangInfo = info.gang,
onDuty = info.job.onduty,
account = info.charinfo.account,
citizenId = info.citizenid,
isDead = info.metadata["isdead"],
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
end
else
print("^4ERROR^7: ^2No Core detected for getPlayer() - Check starter.lua")
end
else
-- Client-side: Get current player info.
if isStarted(ESXExport) and ESX ~= nil then
local info = ESX.GetPlayerData()
if not info.firstName then return {} end
local cash, bank = 0, 0
for k, v in pairs(info.accounts) do
if v.name == "money" then cash = v.money end
if v.name == "bank" then bank = v.money end
end
Player = {
firstname = info.firstName,
lastname = info.lastName,
name = info.firstName.." "..info.lastName,
cash = cash,
bank = bank,
source = GetPlayerServerId(PlayerId()),
job = info.job.name,
--jobBoss = info.job.isboss,
--gang = info.gang.name,
--gangBoss = info.gang.isboss,
onDuty = info.job.onDuty,
--account = info.charinfo.account,
citizenId = info.identifier,
isDead = IsEntityDead(PlayerPedId()),
isDown = IsPedDeadOrDying(PlayerPedId(), true)
}
elseif isStarted(OXCoreExport) then
if not OxPlayer.userId then return {} end
Player = {
firstname = OxPlayer.get("firstName"),
lastname = OxPlayer.get("lastName"),
name = OxPlayer.get("firstName").." "..OxPlayer.get("lastName"),
cash = exports[OXInv]:Search('count', "money"),
bank = 0,
source = GetPlayerServerId(PlayerId()),
job = OxPlayer.getGroups(),
--jobBoss = info.job.isboss,
gang = OxPlayer.getGroups(),
--gangBoss = info.gang.isboss,
--onDuty = info.job.onduty,
--account = info.charinfo.account,
citizenId = OxPlayer.userId,
isDead = IsEntityDead(PlayerPedId()),
isDown = IsPedDeadOrDying(PlayerPedId(), true)
}
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayerData()
if not info.charinfo then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
name = info.charinfo.firstname.." "..info.charinfo.lastname,
cash = info.money["cash"],
bank = info.money["bank"],
source = info.source,
job = info.job.name,
jobBoss = info.job.isboss,
jobInfo = info.job,
gang = info.gang.name,
gangBoss = info.gang.isboss,
gangInfo = info.gang,
onDuty = info.job.onduty,
account = info.charinfo.account,
citizenId = info.citizenid,
isDead = info.metadata["isdead"],
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
elseif isStarted(QBExport) and not isStarted(QBXExport) then
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
if not info.charinfo then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
name = info.charinfo.firstname.." "..info.charinfo.lastname,
cash = info.money["cash"],
bank = info.money["bank"],
source = info.source,
job = info.job.name,
jobBoss = info.job.isboss,
jobInfo = info.job,
gang = info.gang.name,
gangBoss = info.gang.isboss,
gangInfo = info.gang,
onDuty = info.job.onduty,
account = info.charinfo.account,
citizenId = info.citizenid,
isDead = info.metadata["isdead"],
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
elseif isStarted(RSGExport) then
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
if not info.charinfo then return {} end
Player = {
firstname = info.charinfo.firstname,
lastname = info.charinfo.lastname,
name = info.charinfo.firstname.." "..info.charinfo.lastname,
cash = info.money["cash"],
bank = info.money["bank"],
source = info.source,
job = info.job.name,
jobBoss = info.job.isboss,
jobInfo = info.job,
gang = info.gang.name,
gangBoss = info.gang.isboss,
gangInfo = info.gang,
onDuty = info.job.onduty,
account = info.charinfo.account,
citizenId = info.citizenid,
isDead = info.metadata["isdead"],
isDown = info.metadata["inlaststand"],
charInfo = info.charinfo,
}
else
print("^4ERROR^7: ^2No Core detected for hasJob ^7- ^2Check ^3starter^1.^2lua^7")
end
end
return Player
end
--- Retrieves all active players within a given radius from the specified coordinates.
---
--- @param coords vector3 The reference coordinates.
--- @param radius number The radius within which to find players.
--- @return table table An array of player IDs.
---
--- @usage
--- ```lua
--- local nearbyPlayers = GetPlayersFromCoords(vector3(100, 200, 30), 20)
--- ```
function GetPlayersFromCoords(coords, radius)
local players = {}
for _, playerId in ipairs(GetActivePlayers()) do
local ped = GetPlayerPed(playerId)
if ped and DoesEntityExist(ped) then
local playerCoords = GetEntityCoords(ped)
if #(coords - playerCoords) <= radius then
players[#players + 1] = playerId
end
end
end
return players
end

155
shared/polyZone.lua Normal file
View File

@@ -0,0 +1,155 @@
--[[
PolyZone Management Module
----------------------------
This module automatically detects the available polyzone library (ox_lib or PolyZone)
and creates polygonal and circular zones accordingly. It also provides a function to remove
previously created zones.
Functions:
• createPoly(data) - Creates a polygonal zone.
• createCirclePoly(data) - Creates a circular zone.
• removePolyZone(Location) - Removes a created zone.
]]
-------------------------------------------------------------
-- Polygonal Zone Creation
-------------------------------------------------------------
--- 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.
---
--- @param data table Zone configuration table with the following keys:
--- - name (string): The zone's identifier.
--- - 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.
--- - onExit (function): Callback when a player exits the zone.
---
--- @return table|nil table Returns the created zone object or nil if creation failed.
---
---@usage
---```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,
---})
---```
function createPoly(data)
local Location = nil
if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name)
data.minZ = data.minZ or 12.0
data.maxZ = data.maxZ or 1000.0
data.thickness = ((data.maxZ / 2) - (data.minZ / 2)) * 2
local mid = data.maxZ - ((data.maxZ / 2) - (data.minZ / 2))
for i = 1, #data.points do
data.points[i] = vec3(data.points[i].x, data.points[i].y, mid)
end
Location = lib.zones.poly(data)
elseif isStarted("PolyZone") then
debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4PolyZone^7': "..data.name)
Location = PolyZone:Create(data.points, {
name = data.name,
minZ = data.minZ or nil,
maxZ = data.maxZ or nil,
debugPoly = data.debug
})
Location:onPlayerInOut(function(isPointInside)
if isPointInside then
data.onEnter()
else
data.onExit()
end
end)
else
print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7")
end
return Location
end
-------------------------------------------------------------
-- Circular Zone Creation
-------------------------------------------------------------
--- Creates a circular zone using the detected polyzone library (ox_lib or PolyZone).
---
--- When using ox_lib, it creates a sphere zone. For PolyZone, it creates a CircleZone and attaches
--- onEnter and onExit callbacks.
---
--- @param data table Zone configuration with the following keys:
--- - name (string): The zone's identifier.
--- - coords (vector3): The center of the circle.
--- - radius (number): The radius of the circle.
--- - onEnter (function): Callback when a player enters the zone.
--- - onExit (function): Callback when a player exits the zone.
---
--- @return table|nil table Returns the created circular zone object or nil if creation failed.
---
--- @usage
--- ```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,
--- })
--- ```
function createCirclePoly(data)
local Location = nil
if isStarted(OXLibExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name)
Location = lib.zones.sphere(data)
elseif isStarted("PolyZone") then
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7PolyZone ".. data.name)
Location = CircleZone:Create(data.coords, data.radius, { name = data.name, debugPoly = debugMode })
Location:onPlayerInOut(function(isPointInside)
if isPointInside then
data.onEnter()
else
data.onExit()
end
end)
else
print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3starter^1.^2lua^7")
end
debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
return Location
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

77
shared/scaleEntity.lua Normal file
View File

@@ -0,0 +1,77 @@
local cacheOrigScale = {}
local initialOffset = {}
--- Sets the scale of an entity.
---
--- 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.
---
---@param entity number The entity ID to scale.
---@param scale number The scale factor to apply to the entity.
---
---@usage
--- ```lua
--- -- Scale an entity to twice its original size
--- SetEntityScale(entityId, 2.0)
--- ```
function SetEntityScale(entity, scale)
local forward, right, up = GetEntityMatrix(entity)
if not cacheOrigScale[entity] then
cacheOrigScale[entity] = {
forward = forward,
right = right,
up = up
}
end
local minDim, maxDim = GetModelDimensions(GetEntityModel(entity))
local originalHeight = maxDim.z - minDim.z
local newHeight = originalHeight * scale
initialOffset[entity] = (newHeight - originalHeight) / 3
local forwardTemp = cacheOrigScale[entity].forward * scale
local rightTemp = cacheOrigScale[entity].right * scale
local upTemp = cacheOrigScale[entity].up * scale
-- Apply the initial offset to the current position
local currentPosition = GetEntityCoords(entity)
local newPosition = vector3(currentPosition.x, currentPosition.y, currentPosition.z + initialOffset[entity])
SetEntityMatrix(entity, forwardTemp, rightTemp, upTemp, currentPosition)
end
--- Resets the scale of an entity to its original values.
---
--- This function restores an entity's original forward, right, and up vectors,
--- effectively undoing any scaling applied by `SetEntityScale`.
---
---@param entity number The entity ID to reset.
---
---@usage
--- ```lua
--- -- Reset the scale of an entity
--- resetScale(entityId)
--- ```
function resetScale(entity)
if cacheOrigScale[entity] then
SetEntityMatrix(entity, cacheOrigScale[entity].forward, cacheOrigScale[entity].right, cacheOrigScale[entity].up, GetEntityCoords(entity))
cacheOrigScale[entity] = nil
end
end
--[[
CreateThread(function()
-- Example usage:
-- 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)
-- SetEntityCollision(prop, false, true)
SetEntityScale(prop, 12)
--[[CreateThread(function()
while true do
Wait(1000)
resetScale(prop)
Wait(1000)
end
end)
end)
]]

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
-------------------------------------------------------------
-- Debug Text Display Functionality
-------------------------------------------------------------
--- Draws debug text on the screen if debugMode is enabled.
---
--- Calculates a background rectangle based on the number of text lines and renders each line on-screen.
---
--- @param textTable table An array of strings to display.
--- @param loc vector2 (Optional) Top-left coordinate for the text box (default: vec2(0.05, 0.65)).
---
--- @usage
--- ```lua
---CreateThread(function()
--- while true do
--- debugScaleForm({
--- "Line 1: Debug info",
--- "Line 2: More info"
--- })
--- Wait(0)
--- end
---end)
--- ```
function debugScaleForm(textTable, loc)
if debugMode then
loc = loc or vec2(0.05, 0.65)
local lineHeight = 0.025 -- Height per line.
local totalHeight = #textTable * lineHeight
local boxPadding = 0.01 -- Padding around the text.
local size = vec2(0.18, totalHeight + boxPadding * 2)
-- Draw background rectangle.
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 200)
-- Render each line of text.
for i = 1, #textTable do
SetTextScale(0.30, 0.30)
BeginTextCommandDisplayText("STRING")
AddTextComponentSubstringKeyboardDisplay(textTable[i])
EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
end
end
end

View File

@@ -0,0 +1,110 @@
-------------------------------------------------------------
-- Instructional Buttons Functionality
-------------------------------------------------------------
--- Loads and draws instructional buttons on-screen using a scaleform movie.
---
--- Requests the "instructional_buttons" scaleform, clears previous data, sets clear space,
--- creates data slots for each button option provided in `info`, and then draws the scaleform fullscreen.
---
--- @param info table An array of tables, where each table represents a button option:
--- - keys (table): An array of key codes (e.g., {38, 29}) to display.
--- - text (string): The label for the button.
---
--- @usage
--- ```lua
---CreateThread(function()
--- while true do
--- makeInstructionalButtons({
--- { keys = {38, 29}, text = "Open Menu" },
--- { keys = {45}, text = "Close Menu" }
--- })
--- Wait(0)
--- end
---end)
--- ```
function makeInstructionalButtons(info)
local build = RequestScaleformMovie("instructional_buttons")
while not HasScaleformMovieLoaded(build) do Wait(0) end
-- Draw the scaleform fullscreen (initial draw).
DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
-- Clear previous instructions.
BeginScaleformMovieMethod(build, "CLEAR_ALL")
EndScaleformMovieMethod()
-- Set clear spacing between buttons.
BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
ScaleformMovieMethodAddParamInt(200)
EndScaleformMovieMethod()
-- Add each button option to the scaleform.
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
-- 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

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

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

707
shared/stashcontrol.lua Normal file
View File

@@ -0,0 +1,707 @@
--[[
Stash Management Module
-------------------------
This module handles stash-related operations including:
• Retrieving stash items (from server or local cache).
• Checking for required items in stashes.
• Opening stashes using different inventory systems.
• Removing items from stashes.
• Checking if a stash has specific items.
]]
-- Global variable to hold the current stash (used in callbacks).
local stash
-- If running on the server, create a callback to retrieve stash items.
if isServer() then
createCallback(getScript()..':server:GetStashItems', function(source, stashName)
if stashName == nil or stashName == "" then
return {}
end
stash = getStash(stashName)
return stash
end)
end
-- Local cache for stashes.
local stashCache = {}
--- Retrieves (or updates) a local stash cache entry with a timeout.
--- When the cache is empty or expired, it triggers a server callback to update the items.
---
--- @param stashName string The name of the stash.
--- @param stop boolean (Optional) If true, clears the entire stash cache.
--- @return boolean True if items exist in cache (and recheck is skipped), false otherwise.
---
--- @usage
--- ```lua
--- local cached = GetStashTimeout("playerStash")
--- ```
function GetStashTimeout(stashName, stop)
if stop or (stashName == nil or stashName == "") then
stashCache = {}
return false
end
-- Retrieve cache for this stash, or initialize if not present.
stash = stashCache[stashName]
if not stash then
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 }
stash = stashCache[stashName]
else
debugPrint("^6Bridge^7: ^2Local Stash for ^7'^3"..stashName.."^7'^2 cache found^7")
end
-- If there are already items in cache, skip recheck.
if countTable(stashCache[stashName].items) > 0 then
debugPrint("^6Bridge^7: '^3"..stashName.."^7' ^2Items found in local cache, skipping server recheck")
return true
end
-- If timeout has expired, update the stash items from the server.
if stashCache[stashName].timeout <= 0 then
stashCache[stashName].items = triggerCallback(getScript()..':server:GetStashItems', stashName)
stashCache[stashName].timeout = 10000 -- Timeout in milliseconds.
CreateThread(function()
while stashCache[stashName] and stashCache[stashName].timeout > 0 do
stashCache[stashName].timeout -= 1000
Wait(1000)
end
debugPrint("^6Bridge^7: ^2Local Stash ^7'^3"..stashName.."^7'^2 cache timed out^7, ^3Clearing^7")
stashCache[stashName] = nil
end)
end
return false
end
--- Checks if the specified stashes have the required items.
---
--- If multiple stashes are provided (as a table), it iterates over each until all required items are found.
---
--- @param stashes string|table Either a single stash name or a table of stash names.
--- @param itemTable table A table where keys are item names and values are the required amounts.
--- @return boolean, string|nil `boolean, string` true and the stash name if found, otherwise false and nil.
---
--- @usage
--- ```lua
--- local found, stashName = checkStashItem({"playerStash", "storageStash"}, { iron = 2, wood = 5 })
--- ```
function checkStashItem(stashes, itemTable)
if not stashes or stashes == "" then
return hasItem(itemTable), nil
end
if type(stashes) == "table" then
debugPrint("^6Bridge^7: ^2Checking multiple stashes for ingredients^7")
-- Iterate over each provided stash name.
for _, name in pairs(stashes) do
GetStashTimeout(name)
if stashhasItem(stashCache[name].items, itemTable, nil) then
return true, name
end
end
else
debugPrint("^6Bridge^7: ^2Checking "..(stashes and " '^3"..stashes.."^7'" or "").." ingredients")
GetStashTimeout(stashes)
return stashhasItem(stashCache[stashes].items, itemTable), stashes
end
return false, nil
end
-------------------------------------------------------------
-- Stash Opening Functions
-------------------------------------------------------------
--- Opens a stash using the active inventory system.
---
--- Checks for job or gang restrictions before opening the stash.
---
--- @param data table A table containing stash data:
--- - stash (string): The stash identifier.
--- - label (string): Display label.
--- - maxWeight (number|nil): Maximum weight (default 600000).
--- - slots (number|nil): Number of slots (default 40).
--- - stashOptions (table|nil): Additional options for the stash.
--- - job/gang (string|nil): Restriction for access.
--- - coords (vector3): Coordinates to "look" at.
---
--- @usage
--- ```lua
--- openStash({ stash = "playerStash", label = "Player Stash", coords = vector3(100, 200, 30) })
--- ```
function openStash(data)
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
if isStarted(OXInv) then
exports[OXInv]:openInventory('stash', data.stash)
elseif isStarted(CoreInv) then
TriggerServerEvent('core_inventory:server:openInventory', data.stash, 'stash')
elseif isStarted(CodeMInv) then
TriggerServerEvent('codem-inventory:server:openstash', data.stash, data.slots, data.maxWeight, data.label)
elseif isStarted(OrigenInv) then
exports[OrigenInv]:openInventory('stash', data.stash, { label = data.label })
elseif isStarted(TgiannInv) then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
elseif isStarted(JPRInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(QBInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(PSInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
TriggerEvent("ps-inventory:client:SetCurrentStash", data.stash)
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(RSGInv) then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
--Fallback to these commands
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
lookEnt(data.coords)
end
-- Wrapper function for opening stash from the server.
-- Messy but not much else I can do about it.
RegisterNetEvent(getScript()..":server:openServerStash", function(data)
local src = source
if isStarted(TgiannInv) then
exports[TgiannInv]:OpenInventory(source, 'stash', data.stashName, data)
end
if isStarted(JPRInv) then
exports[JPRInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(QBInv) then
exports[QBInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(PSInv) then
exports[PSInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(RSGInv) then
exports[RSGInv]:OpenInventory(source, data.stashName, data)
end
end)
function clearStash(stashId)
if isStarted(JPRInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..JPRInv.."^2 Stash^7", stashId)
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashId,
['items'] = json.encode({})
})
elseif isStarted(QBInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..QBInv.."^2 Stash^7", stashId)
if QBInvNew then
exports[QBInv]:ClearStash(stashId)
else
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashId,
['items'] = json.encode({})
})
end
elseif isStarted(OXInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..OXInv.."^2 Stash^7", stashId)
exports[OXInv]:ClearInventory(stashId)
elseif isStarted(PSInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..PSInv.."^2 Stash^7", stashId)
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashId,
['items'] = json.encode({})
})
elseif isStarted(QSInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..QSInv.."^2 Stash^7", stashId)
exports[QSInv]:ClearOtherInventory('stash', stashId)
elseif isStarted(CoreInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..CoreInv.."^2 Stash^7", stashId)
exports[CoreInv]:clearInventory("stash-"..stashId)
elseif isStarted(CodeMInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..CodeMInv.."^2 Stash^7", stashId)
exports[CodeMInv]:ClearInventory(stashId)
elseif isStarted(OrigenInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..OrigenInv.."^2 Stash^7", stashId)
exports[OrigenInv]:ClearInventory(stashId)
elseif isStarted(TgiannInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..TgiannInv.."^2 Stash^7", stashId)
exports["tgiann-inventory"]:DeleteInventory("stash", stashId)
elseif isStarted(RSGInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..RSGInv.."^2 Stash^7", stashId)
exports[RSGInv]:ClearStash(stashId)
end
end
-------------------------------------------------------------
-- Stash Retrieval Function
-------------------------------------------------------------
--- 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 stashName == "" or 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 stashName == "" or stashName == nil then
print("^1ERROR^7: ^1stashRemoveItem triggered but stashName was empty^7")
return
end
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[1].."^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)

532
shared/targets.lua Normal file
View File

@@ -0,0 +1,532 @@
--[[
Experimental GTA In-World Text Prompts Targets Module
-------------------------------------------------------
This module handles the creation, removal, and management of in-world text targets
for interacting with entities and zones using GTA text prompts. It supports multiple
targeting systems: OX Target, QB Target, or a fallback using DrawText3D.
Available functionalities:
• createEntityTarget - Creates a target for a specific entity.
• createBoxTarget - Creates a box-shaped zone target.
• createCircleTarget - Creates a circular zone target.
• createModelTarget - Creates a target for specified models.
• removeEntityTarget - Removes a target from an entity.
• removeZoneTarget - Removes a zone target.
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.
]]
-------------------------------------------------------------
-- Utility Data & Tables
-------------------------------------------------------------
-- Tables for storing created targets for the fallback system and zone management.
local TextTargets = {} -- For fallback DrawText3D targets.
local targetEntities = {} -- For entity targets.
local boxTargets = {} -- For box-shaped zone targets.
local circleTargets = {} -- For circular zone targets.
-------------------------------------------------------------
-- Entity Target Creation
-------------------------------------------------------------
--- Creates a target for an entity with specified options and interaction distance.
--- Supports different targeting systems (OX Target, QB Target, or custom DrawText3D)
--- based on the server configuration.
---
--- @param entity number The entity ID for which the target is created.
--- @param opts table Array of option tables. Each option should include:
--- - icon (string): The icon to display.
--- - label (string): The text label for the option.
--- - item (string|nil): (Optional) An associated item.
--- - job (string|nil): (Optional) The job required to interact.
--- - gang (string|nil): (Optional) The gang required to interact.
--- - action (function|nil): (Optional) The function executed on selection.
--- @param dist number The interaction distance for the target.
---
--- @usage
--- ```lua
---createEntityTarget(entityId, {
--- {
--- action = function()
--- openStorage()
--- end,
--- icon = "fas fa-box",
--- job = "police",
--- label = "Open Storage",
--- },
---}, 2.0)
--- ```
function createEntityTarget(entity, opts, dist)
-- Store the target entity for later cleanup.
targetEntities[#targetEntities + 1] = entity
-- Fallback: Use DrawText3D if targeting systems are disabled or unavailable.
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
exports.jim_bridge:createEntityTarget(entity, opts, dist)
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addLocalEntity(entity, options)
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..QBTargetExport.." ^2for entity ^7"..entity)
local options = { options = opts, distance = dist }
exports[QBTargetExport]:AddTargetEntity(entity, options)
end
end
-------------------------------------------------------------
-- Box Zone Target Creation
-------------------------------------------------------------
--- Creates a box-shaped target zone with specified options and interaction distance.
--- Supports different targeting systems based on the server configuration.
---@param data table A table containing the box zone configuration.
--- - name (`string`): The name identifier for the zone.
--- - coords (`vector3`): The center coordinates of the box.
--- - width (`number`): The width of the box.
--- - height (`number`): The height of the box.
--- - options (`table`): A table with additional options:
--- - heading (`number`): The rotation angle of the box.
--- - debugPoly (`boolean`): Whether to enable debug mode for the zone.
---
---@param opts table A table of option configurations for the target.
--- - 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.
--- - job (`string|nil`): (Optional) The job required to interact with the option.
--- - 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 dist number The interaction distance for the target.
---
---@return string|table name identifier or target object of the created zone.
---
---@usage
---```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)
---```
function createBoxTarget(data, opts, dist)
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^2 for zone ^7"..data[1])
return exports.jim_bridge:createZoneTarget(data, opts, dist)
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].groups or opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
data[5].maxZ = data[5].maxZ or (data[2].z + 0.80)
data[5].minZ = data[5].minZ or data[2].z - 1.05
local thickness = ((data[5].maxZ / 2) - (data[5].minZ / 2)) * 2
local mid = data[5].maxZ - ((data[5].maxZ / 2) - (data[5].minZ / 2))
--if not data[5].useZ then
-- local z = data[2].z + math.abs(data[5].maxZ - data[5].minZ) / 2
data[2] = vec3(data[2].x, data[2].y, mid) -- force the coord to middle of the minZ and maxZ
--end
local target = exports[OXTargetExport]:addBoxZone({
coords = data[2],
size = vec3(data[4], data[3], thickness), -- size uses the math to determine how high it needs to be
rotation = data[5].heading,
debug = data[5].debugPoly,
options = options
})
boxTargets[#boxTargets + 1] = target
return target
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^2for zone ^7"..data[1])
local options = { options = opts, distance = dist }
local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
boxTargets[#boxTargets + 1] = target
return data[1]
end
end
-------------------------------------------------------------
-- Circle Zone Target Creation
-------------------------------------------------------------
--- Creates a circular target zone with specified options and interaction distance.
--- Supports different targeting systems based on server configuration.
---
---@param data table A table containing the circle zone configuration.
--- - name (`string`): The name identifier for the zone.
--- - coords (`vector3`): The center coordinates of the circle.
--- - radius (`number`): The radius of the circle.
--- - options (`table`): A table with additional options:
--- - debugPoly (`boolean`): Whether to enable debug mode for the zone.
---
---@param opts table A table of option configurations for the target.
--- - 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.
--- - job (`string|nil`): (Optional) The job required to interact with the option.
--- - 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 dist number The interaction distance for the target.
---
---@return string|table name identifier or target object of the created zone.
---
---@usage
--- ```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)
--- ```
function createCircleTarget(data, opts, dist)
if Config.System.DontUseTarget then
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6DrawText ^2for zone ^7"..data[1])
return exports.jim_bridge:createZoneTarget(data, opts, dist)
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
local target = exports[OXTargetExport]:addSphereZone({
coords = data[2],
radius = data[3],
debug = data[4].debugPoly,
options = options
})
circleTargets[#circleTargets + 1] = target
return target
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..QBTargetExport.." ^2for zone ^7"..data[1])
local options = { options = opts, distance = dist }
local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options)
circleTargets[#circleTargets + 1] = target
return data[1]
end
end
-------------------------------------------------------------
-- Model Target Creation
-------------------------------------------------------------
--- Creates a target for models with specified options and interaction distance.
--- Supports different targeting systems (OX Target, QB Target) based on server configuration.
---
--- @param models table Array of model identifiers.
--- @param opts table Array of option tables (same structure as in createEntityTarget).
--- @param dist number The interaction distance for the target.
---
--- @usage
--- ```lua
---createModelTarget({ model1, model2 },
---{
--- {
--- action = function()
--- openStorage()
--- end,
--- icon = "fas fa-box",
--- job = "police",
--- label = "Open Storage",
--- },
---}, 2.0)
---```
function createModelTarget(models, opts, dist)
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
return exports.jim_bridge:createModelTarget(models, opts, dist)
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..OXTargetExport)
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addModel(models, options)
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..QBTargetExport)
local options = { options = opts, distance = dist }
exports[QBTargetExport]:AddTargetModel(models, options)
end
end
-------------------------------------------------------------
-- Target Removal Functions
-------------------------------------------------------------
--- Removes a previously created entity target.
---
--- @param entity number The entity ID whose target should be removed.
---
--- @usage
--- ```lua
--- removeEntityTarget(entityId)
--- ```
function removeEntityTarget(entity)
if isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveTargetEntity(entity)
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeLocalEntity(entity, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeEntityTarget(entity)
end
end
--- Removes a previously created zone target.
---
--- @param target string|table The name identifier or target object of the zone to remove.
---
--- @usage
--- ```lua
--- removeZoneTarget('centralPark')
--- removeZoneTarget(targetObject)
--- ```
function removeZoneTarget(target)
if isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveZone(target)
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeZone(target, true)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeZoneTarget(target)
end
end
--- Removes a previously created model target.
---
--- @param model table The model ID whose target should be removed.
---
--- @usage
--- ```lua
--- removeModelTarget(model)
--- ```
function removeModelTarget(model)
if isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveTargetModel(model, "Test")
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeModel(model, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeZoneTarget(target)
end
end
-------------------------------------------------------------
-- Fallback: DrawText3D Targets (Experimental)
-------------------------------------------------------------
-- If no targeting system is detected and this is a client script, use DrawText3D for targets.
if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) and not isServer() then
CreateThread(function()
while true do
local ped = PlayerPedId()
local pedCoords = GetEntityCoords(ped)
local camCoords = GetGameplayCamCoord()
local camRot = GetGameplayCamRot(2)
local camForward = RotationToDirection(camRot)
local closestTarget = nil
local closestDist = math.huge
local targetEntity = nil
-- Shallow copy for safety
local targetsCopy = {}
for k, v in pairs(TextTargets) do
targetsCopy[k] = v
end
-- Detect models and update coords
for _, target in pairs(targetsCopy) do
if target.models then
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)

302
shared/vehicles.lua Normal file
View File

@@ -0,0 +1,302 @@
--[[
Vehicle Info & Properties Module
----------------------------------
This module provides utilities for:
- Retrieving vehicle information from a Vehicles table.
- Getting and setting vehicle properties using the active framework.
- Comparing vehicle property differences.
- Synchronizing vehicle properties across clients.
- Managing network control of vehicles.
- Finding the closest vehicle to a given position.
]]
-- Cached vehicle info to avoid unnecessary re-searches.
local lastCar, carInfo = nil, {}
--- 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.
---
--- @param vehicle number The entity ID of the vehicle to search for.
--- @return table|nil table A table containing the vehicle's details or nil if the vehicle is invalid.
---
--- @usage
--- ```lua
--- local info = searchCar(vehicleEntity)
--- print(info.name, info.price, info.class.name, info.class.index)
--- ```
function searchCar(vehicle)
if lastCar ~= vehicle then -- If same car, use previous info
lastCar = vehicle
carInfo = {}
local model = GetEntityModel(vehicle)
local classlist = {
"Compacts", --1
"Sedans", --2
"SUVs", --3
"Coupes", --4
"Muscle", --5
"Sports Classics", --6
"Sports", --7
"Super", --8
"Motorcycles", --9
"Off-road", --10
"Industrial", --11
"Utility", --12
"Vans", --13
"Cycles", --14
"Boats", --15
"Helicopters", --16
"Planes", --17
"Service", --18
"Emergency", --19
"Military", --20
"Commercial", --21
"Trains", --22
"Open Wheel", --23
}
if Vehicles then
for k, v in pairs(Vehicles) do
if tonumber(v.hash) == model or GetHashKey(v.hash) == model or GetHashKey(v.model) == model 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)")
carInfo.name = Vehicles[k].name.." "..Vehicles[k].brand
carInfo.price = Vehicles[k].price
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
break
end
end
if not carInfo.name then
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.price = 0
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
end
return carInfo
else
if not carInfo.name then
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.price = 0
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
end
end
else
return carInfo
end
end
-------------------------------------------------------------
-- Vehicle Properties Functions
-------------------------------------------------------------
--- Retrieves the properties of a given vehicle using the active framework.
---
--- @param vehicle number The entity ID of the vehicle.
--- @return table|nil table A table containing the vehicle's properties or nil if invalid.
---
--- @usage
--- ```lua
--- local props = getVehicleProperties(vehicleEntity)
--- if props then
--- -- Use vehicle properties
--- end
--- ```
function getVehicleProperties(vehicle)
if not vehicle then return nil end
local properties = {}
if isStarted(QBExport) and not isStarted(QBXExport) then
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]")
elseif isStarted(OXLibExport) then
properties = lib.getVehicleProperties(vehicle)
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
return properties
end
--- 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.
---
--- @param vehicle number The entity ID of the vehicle.
--- @param props table The new properties to apply.
---
--- @usage
--- ```lua
--- setVehicleProperties(vehicleEntity, newProperties)
--- ```
function setVehicleProperties(vehicle, props)
if checkDifferences(vehicle, props) then
if not DoesEntityExist(vehicle) then
print("Unable to set vehicle properties for '"..vehicle.."' (^1entity does not exist^7)")
end
if isStarted(QBExport) and not isStarted(QBXExport) then
Core.Functions.SetVehicleProperties(vehicle, props)
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]")
elseif isStarted(OXLibExport) then
lib.setVehicleProperties(vehicle, props, false)
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
else
debugPrint("^6Bridge^7: ^2No Changes Found ^7 [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
end
end
--- Checks for differences between the current and new vehicle properties.
--- Compares properties using JSON encoding for deep comparison and logs differences.
---
--- @param vehicle number The entity ID of the vehicle.
--- @param newProps table The new properties to compare.
--- @return boolean `true` if differences are found; `false` otherwise.
---
--- @usage
--- ```lua
--- if checkDifferences(vehicleEntity, newProperties) then
--- setVehicleProperties(vehicleEntity, newProperties)
--- end
--- ```
function checkDifferences(vehicle, newProps)
local oldProps = getVehicleProperties(vehicle)
debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7")
local differencesFound = true
for k in pairs(oldProps) do
if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then
differencesFound = true
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 }))
end
end
return differencesFound
end
-------------------------------------------------------------
-- Vehicle Properties Synchronization
-------------------------------------------------------------
--- Event handler for setting vehicle properties received from the server.
--- Listens for the `ox:setVehicleProperties` event and applies the properties.
---
--- @event `getScript()..ox:setVehicleProperties`
--- @param netId number The network ID of the vehicle.
--- @param props table The new vehicle properties.
RegisterNetEvent(getScript()..":ox:setVehicleProperties", function(netId, props)
local vehicle = NetworkGetEntityFromNetworkId(netId)
local value = props
Entity(vehicle).state[getScript()..':setVehicleProperties'] = value
end)
--- Handles state bag changes for updating vehicle properties.
--- When the state bag changes, the new properties are applied to the vehicle.
---
--- @param bagName string The state bag's name.
--- @param key string The key that changed.
--- @param value table The new state value.
AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagName, _, value)
if not value or not GetEntityFromStateBagName then return end
local entity = GetEntityFromStateBagName(bagName)
local networked = not bagName:find('localEntity')
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..entity.."^7] - [^3"..GetEntityModel(entity).."^7] - [^3"..value.plate.."^7]")
if networked then return end
if lib.setVehicleProperties(entity, value) then
Entity(entity).state:set('setVehicleProperties', nil, true)
end
end)
-------------------------------------------------------------
-- Vehicle Control Functions
-------------------------------------------------------------
--- 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.
---
---@param entity number The entity ID of the vehicle to push.
---
---@usage
--- ```lua
--- pushVehicle(vehicleEntity)
--- ```
function pushVehicle(entity)
SetVehicleModKit(entity, 0)
if entity ~= 0 and DoesEntityExist(entity) then
-- Request network control if not already controlled.
if not NetworkHasControlOfEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.")
NetworkRequestControlOfEntity(entity)
local timeout = 2000
while timeout > 0 and not NetworkHasControlOfEntity(entity) do
Wait(100)
timeout = timeout - 100
end
if NetworkHasControlOfEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network now has control of the entity^7.")
end
end
-- Set as mission entity if not already set.
if not IsEntityAMissionEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.")
SetEntityAsMissionEntity(entity, true, true)
local timeout = 2000
while timeout > 0 and not IsEntityAMissionEntity(entity) do
Wait(100)
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

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

@@ -0,0 +1,49 @@
--- Registers a command with the active command system.
--- This function supports multiple command systems (OXLib, qb-core, ESX Legacy).
---
--- @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
--- 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)
local commandResource = ""
if isStarted(OXLibExport) then
commandResource = OXLibExport
lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4])
elseif isStarted(QBExport) and not isStarted(QBXExport) then
commandResource = QBExport
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
elseif isStarted(RSGExport) then
commandResource = RSGExport
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
elseif isStarted(ESXExport) then
commandResource = ESXExport
ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError)
options[4](xPlayer.source, args, showError)
end, false, { help = options[1] })
end
if commandResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^4"..commandResource.."^7:", "^7/"..command)
else
print("^1ERROR^7: ^1Couldn't find supported framework to register command^7:", "/"..command)
end
end

236
starter.lua Normal file
View File

@@ -0,0 +1,236 @@
gameName = not IsDuplicityVersion() and GetCurrentGameName()
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"
}
-- Required variables
debugMode = Config.System.Debug
QBInvNew = true
InventoryWeight = 120000
-- [[ Server.Cfg Convar Check ]]--
-- Check server convars for hard set defaults, otherwise it uses per script configurations
if Config and Config.System then
if Config.System.Debug then
if GetConvar("jim_DisableDebug", "false") == "true" then
debugMode = false
end
if GetConvar("jim_DisableEventDebug", "false") == "true" then
Config.System.EventDebug = false
end
end
Config.System.Menu = GetConvar("jim_menuScript", Config.System.Menu)
Config.System.Notify = GetConvar("jim_notifyScript", Config.System.Notify or "gta")
Config.System.ProgressBar = GetConvar("jim_progressBarScript", Config.System.ProgressBar or "gta")
Config.System.drawText = GetConvar("jim_drawTextScript", Config.System.drawText or "gta")
Config.System.skillCheck = GetConvar("jim_skillCheckScript", Config.System.skillCheck or "gta")
if GetConvar("jim_dontUseTarget", "false") == "true" then
Config.System.DontUseTarget = true
end
end
-- 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
local filesToLoad = {
Shared = {
[Exports.OXLibExport] = { "init.lua" },
[Exports.OXCoreExport] = { "lib/init.lua" },
[Exports.ESXExport] = { "imports.lua" },
[Exports.QBXExport] = { "modules/playerdata.lua" },
},
Client = {
["PolyZone"] = { "client.lua", "BoxZone.lua", "EntityZone.lua", "CircleZone.lua", "ComboZone.lua" },
["warmenu"] = { "warmenu.lua", },
},
}
-- Shared framework file loader
for Type, ScriptTable in pairs(filesToLoad) do
if Type == "Client" and IsDuplicityVersion() then
goto continue
else
for scriptName, files in pairs(ScriptTable) do
local state = GetResourceState(scriptName)
-- if the resource is started, load the file
if state == "started" then
if scriptName == Exports.OXLibExport and GetResourceState(Exports.OXCoreExport):find("start") then
if debugMode then print("OX_Core found, skipping OX_Lib loading") end
goto skip
end
-- Force items into a table if they are not
if type(files) == "string" then
files = { files }
end
for _, file in pairs(files) do
--print("^5CoreLoader^7: '"..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

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

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)
drawGTASuccessText(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,15 @@
1.0.1
2.0.22
- Support for qs-interface
- Fix polyzone minZ + maxZ handling
- Fix ox_target zone location to match qb-target
- Fix "Waiting for script start" loop bug
- Add condition for fading in entities
- Fix typo breaking scripts on gta skillbar fail
- Fix typo for gksphone invoice event
- Fix latio_ui TextUI placement
- Remove registerShop debug message
- Fix typo in print when removing codem stash item
- Fix function typo from origen inv documenation
https://github.com/jimathy/jim_bridge

File diff suppressed because it is too large Load Diff