Compare commits

...

371 Commits

Author SHA1 Message Date
Jim Shield
45f390c177 Update playerfunctions.lua 2025-10-04 22:10:39 +01:00
Jim Shield
803c612cbb Change stress to take away instead of increase 2025-10-04 19:12:50 +01:00
Jim Shield
63bcb86ac1 ESX Fallback functions are very basic.
There aren't many native functions for what the script can do, if anyone has any better ideas

- go to `itemcontrol.lua`
- search `ESX` and tell me what could be done better/right
2025-10-04 01:31:14 +01:00
Jim Shield
0bc3c0f9b4 Update itemcontrol.lua 2025-10-03 17:44:29 +01:00
Jim Shield
672dd7684b Fix ESX authtoken callback saying possible exploit 2025-10-03 13:51:15 +01:00
Jim Shield
c040390d1f Start working on inventory fallback functions
NEEDS TESTING

A lot of ESX stuff not done yet
2025-10-03 12:22:05 +01:00
Jim Shield
26b1f36029 Version Bump
`2.1.04` - `2.1.05`
2025-10-02 12:24:27 +01:00
Jim Shield
331dc94e2f Revert Crafting
I realised in trying to increase readability and optimize, I broke crafting recipes that supported different multiple recipes in the same list

eg. Mining has 3 recipes in the smelter for `goldingot`

This reverts the system back to the previous recipe style but keeps all the fixes/features from the latest updates
2025-10-02 12:20:58 +01:00
Jim Shield
d1dab48acc Fix singleprogress multiplying crafting recipes
Adds a `cloneTable()` function to make a "deep copy" of the crafting recipes when calculating the multiple amounts to be taken when multicrafting
2025-10-02 00:14:13 +01:00
Jim Shield
7e595df143 Remove gta progressbar print from testing 2025-10-01 17:04:59 +01:00
Jim Shield
d791a2e09f quick fix converting old crafting style 2025-10-01 11:42:47 +01:00
Jim Shield
7738bb4fca Version Bump
`2.1.03` - `2.1.04`
2025-09-30 23:39:55 +01:00
Jim Shield
3ad65810c3 Rework crafting.lua
Changes:
- Crafting prop not spawning
- Crafting "sound" not playing
- `SingleProgress` is now "one progressbar"

I'm working on refactoring crafting table's layout style too

I'm updating my scripts crafting recipes to a new format, this commit basically makes use of it and makes it slightly easier to edit later if needed

It *should* increase readability and users ability to edit these recipes

# "old style" crafting recipes should still be compatible thanks to a wrapper I added so you shouldn't notice a difference.
2025-09-28 14:03:23 +01:00
Jim Shield
45eeaf8f0d update a couple debug prints 2025-09-28 13:48:17 +01:00
Jim Shield
37f7eb1116 add testing code for new crafting table style 2025-09-28 03:22:12 +01:00
Jim Shield
145e3aa156 add new module ropeControl.lua
**Needs work** but adds the ability to easily create ropes/wires from one coord to another
2025-09-27 13:01:09 +01:00
Jim Shield
bb176addbf Create setStress function
This allows per framework stress functions to be set, including ESX

For ESX the math is done in this function as their values are insanely high instead of 0 - 100
2025-09-27 12:35:56 +01:00
Jim Shield
178040348e Rework SingleProgress slightly
Realised it wasn't doing the math to take the correct amount of required items, so this fixes it

also if `SingleProgress` is enabled, it won't show the "Ingredient Progress bars" any more
2025-09-27 12:34:46 +01:00
Jim Shield
ee15f3b076 Version Bump
`2.1.02` - `2.1.03`
2025-09-24 23:12:34 +01:00
Jim Shield
6919f038ee Release Notifier Push 2025-09-24 18:20:02 +01:00
Jim Shield
47029da6ed Revert "Version Bump"
This reverts commit f08638812c.
2025-09-24 14:23:43 +01:00
Jim Shield
e1d292d1bf Remove testing debug print 2025-09-24 14:09:19 +01:00
Jim Shield
f08638812c Version Bump
`2.1.02` - `2.1.03`
2025-09-24 13:36:14 +01:00
Jim Shield
de5424c506 Fix getPlayerInv() for CodeM Inventory 2025-09-24 12:30:08 +01:00
Jim Shield
b46d734442 Testing a rework _loaders.lua
Uses more specific functions per framework now for player loading in and out and optimizes use of them
2025-09-24 12:29:48 +01:00
Jim Shield
92ad96e435 Redo math for ox_inv get player current inv weight 2025-09-24 11:59:44 +01:00
Jim Shield
6166314cbf fix ox_inv current inventory weight check 2025-09-23 15:05:46 +01:00
Jim Shield
9fa8f58cfb Make ox_lib progressbars actually return false
I think this was causing progressbars to return true instead of `nil/false` somehow
this *should* make it more certain
2025-09-23 14:59:16 +01:00
Jim Shield
ed997ad8a2 fix typo breaking qb-core /commands 2025-09-23 12:00:40 +01:00
Jim Shield
20cea95783 Set previous active cam inactive
shouldn't be needed but just in case
2025-09-22 21:10:01 +01:00
Jim Shield
0dc5a45b5d Support for esx_society
`esx_society` requires societies to be registered manually before opening. this forces it

also adds full support for charging, funding and retrieving balances in `esx_society`
2025-09-22 21:08:31 +01:00
Jim Shield
54b1280bef Remove debug print 2025-09-21 12:09:12 +01:00
Jim Shield
3ac65d4630 Add "pre support" for jim-shops exploit security 2025-09-21 12:00:05 +01:00
Jim Shield
ed49876c69 Fix cleanup of targets for qb-target
Realised it was trying to remove `nil` when players were unloading/the scripts were ensured
2025-09-21 10:35:22 +01:00
Jim Shield
69d0f07bb6 Potential fix for playerInfo is nil 2025-09-20 17:56:47 +01:00
Jim Shield
b975f318a0 Fix hunger and thirst events
Missed out the math part when adding hunger and thirst to a player when eating items when I reworked the functions

should fix for all frameworks
2025-09-19 23:06:11 +01:00
Jim Shield
ce1df2c205 Version Bump 2025-09-19 17:54:45 +01:00
Jim Shield
75d8146e24 Fix nil print + use .xy for distance check 2025-09-19 17:22:40 +01:00
Jim Shield
268c95f29a Hotfix for shops and stashes erroring 2025-09-15 22:14:18 +01:00
Jim Shield
91dd069e5a Use the correct illenium appearance trigger 2025-09-15 17:23:30 +01:00
Jim Shield
f96d96f26e adjust stash and store registration
This should fix the `jim_bridge` side of the reports of stores erroring when scripts starts
2025-09-15 16:19:52 +01:00
Jim Shield
ae231b6101 Version Bump
`2.0.23` - `2.1.0`
2025-09-14 21:32:12 +01:00
Jim Shield
c709159ea4 Deprecate ConsumeSuccess()
This function and others were too open to exploiters, my scripts not no longer use these functions and handle it all server side.
2025-09-14 17:40:12 +01:00
Jim Shield
c8089109c6 Add "await" for callbacks
This hopefully should help callbacks get their requested data

Basically gives them a timeout for requests, so they don't accidently receive `nil`
2025-09-14 15:18:36 +01:00
Jim Shield
807cfe1a2a Fix stash dist exploit checker 2025-09-13 02:50:31 +01:00
Jim Shield
420e035e2b fix cam wrapperFunctions 2025-09-13 02:47:17 +01:00
Jim Shield
a0377df713 Bit of tidying 2025-09-10 01:26:02 +01:00
Jim Shield
301b069312 add openClothin() function
Attempts to open clothing scripts menus

Still testing this one
2025-09-09 18:49:26 +01:00
Jim Shield
6c09e55fd4 Optimzie hasJob() function
Now makes use of `GetPlayer()` to grab player data on client or server
2025-09-08 21:00:49 +01:00
Jim Shield
d8467f0b02 allow GetPlayer() function to work client side 2025-09-08 20:20:50 +01:00
Jim Shield
a9832da7d7 Update notify-discord.yml
test 2
2025-09-08 18:35:03 +01:00
Jim Shield
2942907963 Update notify-discord.yml
Testing if the discord bot can understand { ' } ( )
2025-09-08 18:26:57 +01:00
Jim Shield
02ca13bcbd Put distance exploit checks in place
This is a continuation from an earlier commit

- If something is attempted to be opened without correctly registered coordinates, (most likely being called outside the script), refuse it
- If the player isn't close enough to the registered coords, refuse it

Basically, built-in distance based exploit protection for shops/stashes/sellShops
2025-09-08 18:08:17 +01:00
Jim Shield
833bc675ce Tidy up currentInvWeight and add function info 2025-09-06 22:58:58 +01:00
Jim Shield
ac9a7df57b Fix stopTempCam() wrapper function 2025-09-06 22:32:02 +01:00
Jim Shield
657bc3935c Add cooldown for onPlayerLoaded()
Add a cooldown to workaround certain multicharacter scripts triggering events like `QBCore:Client:OnPlayerLoaded` more than once and duplicating targets when a player connected
2025-09-06 14:54:36 +01:00
Jim Shield
10085a3c23 Update fx and warn users about possible errors 2025-09-06 14:08:33 +01:00
Jim Shield
0d3888565c Move getCurrentInvWeight to func table
Moves the getCurrentInvWeight functions to each inventory section of the func table to keep that organised
2025-09-06 12:36:08 +01:00
Jim Shield
8533bbff90 add early end to vehicle ownership check 2025-09-06 12:32:39 +01:00
Jim Shield
42630cc3c4 More enhancements to cameras.lua
Tidy up `createTempCam()`
Add `loadLocation()` and `clearLoadLocation()` functions
Add `renderTime` variable for `stopTempCam()`

Add wrapper functions for each one (for my own sanity):
`createCam()` = `createTempCam()`
`startCam()` = `startTempCam()`
`stopTempCam()` = `stopTempCam()`
2025-09-06 12:22:27 +01:00
Jim Shield
188fb81bdc Merge branch 'main' of https://github.com/jimathy/jim_bridge 2025-09-06 12:03:00 +01:00
Jim Shield
cbc3a52643 Remove references to qs scripts 2025-09-06 12:02:55 +01:00
Jim Shield
bda18260b3 Merge pull request #57 from oosayeroo/main 2025-09-06 11:57:48 +01:00
oosayeroo
32dd938eef Update cameras.lua 2025-09-05 23:38:37 +01:00
Jim Shield
2826bc95c7 Enhance built in camera function file
- Allows filters to be applied to custom cameras when running `startTempCam()`
- Automatically interpolates between cameras created in the same script
- Toggle to attempt to render the location if camera is far away from the ped
- Clears all created cameras when `stopTempCam()` is run
2025-09-05 16:34:45 +01:00
Jim Shield
b35817740b add return when using jim-shops through jim_bridge 2025-09-05 14:53:18 +01:00
Jim Shield
fffef0c228 Quick tidy and optimizations of a few functions 2025-09-05 14:37:39 +01:00
Jim Shield
0caeca4824 Merge branch 'main' of https://github.com/jimathy/jim_bridge 2025-09-04 17:40:35 +01:00
Jim Shield
b5536dbb6b Merge pull request #56 from oosayeroo/main 2025-09-04 17:31:01 +01:00
Jim Shield
26f8140ee8 Merge pull request #55 from desypher/patch-1 2025-09-04 17:30:01 +01:00
oosayeroo
6e4d9bc1d7 Update vehicles.lua 2025-09-04 17:22:36 +01:00
Jim Shield
610f492762 Update itemcontrol.lua 2025-09-04 17:01:30 +01:00
oosayeroo
f88cdb4f9d Merge branch 'main' of https://github.com/oosayeroo/jim_bridge 2025-09-03 11:18:57 +01:00
desypher
463358b53a Changed name to phone on roadphone functions 2025-09-02 14:43:39 +02:00
desypher
3b038fb700 Changed name to phone in phoneFunc array 2025-09-02 14:38:17 +02:00
Jim Shield
8efec21408 Create release.yml
Automate release zips and make them generate the folder name correctly

This helps users download a correctly named script so they dont end up with a script with a version number in the title
2025-09-01 10:25:11 +01:00
Jim Shield
39691a2254 enhance built in ui target code
Now properly tracks scripts that added a new target, if that is restarted it created ones properly

If an overriding target is sent, use that and remove old one
2025-08-30 13:19:01 +01:00
Jim Shield
2924c611f7 Optimize built in progressbar math
Instead of rendering segments and trying to work out how much they should fill, add simple lines to segment it instead, this should make it run a bit smoother as there is way less going on
2025-08-30 12:50:10 +01:00
Jim Shield
be5449b100 Add promise:new() for qb/esx progressbars
This stops them instantly returning `false` and actually waits for the results

This fixes issues with players being unable to complete tasks determined by progressbars eg. installing items on a vehicle and it fails instantly
2025-08-30 12:15:09 +01:00
Jim Shield
1e5d4800fc debug print progressbar results 2025-08-29 20:39:30 +01:00
Jim Shield
cb49fa6949 Update fxmanifest.lua 2025-08-27 19:57:56 +01:00
Jim Shield
315634c173 add supress_updates variable to config
This new `fxmanifest.lua` variable (when true) will stop checking for script updates

People REALLY dont like being told when somethings fixed.

This will stop the update notifications for both `jim_bridge` and the script that loads it.
2025-08-27 19:40:11 +01:00
Jim Shield
eafa881cf8 Adjust frameworkCache.lua to be table based
Reworked `frameworkCache.lua` to the new layout

Fix possibility of invisible props/ peds/vehicles on spawn

Start adding very basic and unfinished support for RedM VorpCore
2025-08-26 13:24:36 +01:00
Jim Shield
a851f8823c Fix sell shops showing nil for items 2025-08-25 16:34:37 +01:00
Jim Shield
354002312e Make use of getItemLabel() in jim_bridge more 2025-08-23 23:36:42 +01:00
Jim Shield
392e0601a8 Update wrapperfunctions.lua
This simply adds names for each "option" it receives, just to make it easier to work with.
2025-08-23 23:36:13 +01:00
Jim Shield
035ef26486 Update contextMenus.lua to use new format 2025-08-23 23:34:15 +01:00
Jim Shield
db9e7fea0a fix stashName variable for stashRemoveItem() 2025-08-22 21:18:51 +01:00
oosayeroo
7077b7d831 Merge branch 'jimathy:main' into main 2025-08-22 10:43:04 +01:00
Jim Shield
bd94b14dad Start working on coordinate based exploit checks
These currently work as intended, but can't put in place yet because my scripts don't fully support it yet. But the base is there for it

Also slip in `bannedItems` table so players can't the abuse sell items function to spawn cash
2025-08-17 02:20:13 +01:00
Jim Shield
8b0e4d803f Fix typos breaking fundPlayer function 2025-08-17 02:10:38 +01:00
Jim Shield
5f81bfa899 Add distExploitCheck function (will be used soon)
I'm adding distance based exploit protection to stashes, shops and selling shops to help fight exploiters

Soon all these created through jim_bridge will refuse opening if its not "registered" correctly

Basically, all my scripts will get updates to support this
2025-08-17 02:10:21 +01:00
Jim Shield
85626d3356 Fix qbx getplayer call 2025-08-15 18:35:21 +01:00
Jim Shield
ceeb9021d9 Raise version check messages to hourly 2025-08-15 17:26:18 +01:00
Jim Shield
09887f3a9d Create compatibility tables for playerFunctions.lua 2025-08-14 21:08:32 +01:00
Jim Shield
26c341c6ec Create bossmenu compatibility table 2025-08-14 20:20:42 +01:00
Jim Shield
bfca698457 More + optimization 2025-08-14 20:08:09 +01:00
oosayeroo
c13d39ca05 Commit[wrapperfunctions.lua]: Fix oxlib command argument
line 27 - changd the function option in oxlib from options[4] to options[3]
2025-08-14 16:33:02 +01:00
Jim Shield
0bcf1b7ed4 Prefer export checks over QBInvNew
Small changes for JPRInv too
2025-08-14 13:34:55 +01:00
Jim Shield
d7622f5143 fix canCarry and add hasItem functions 2025-08-14 13:07:15 +01:00
Jim Shield
d1d992f807 Fix destroy poly function trying to do all options 2025-08-13 22:40:54 +01:00
Jim Shield
16f77f442c Follow same format for targets.lua
God I hope this works as intended
2025-08-13 19:12:56 +01:00
Jim Shield
3b959b8567 Follow same format for societybank.lua
Hopefully becomes easier for devs to add their banking functions to this
2025-08-13 18:41:07 +01:00
Jim Shield
345a81aaf7 Follow same format for polyZone.lua
Just better handling of polyZone scripts with less if statements
2025-08-13 18:27:07 +01:00
Jim Shield
06ef5cb53b Tidy up of helpers, and use joaat() instead 2025-08-13 18:13:58 +01:00
Jim Shield
748e5e2201 Follow same format for notify.lua and drawText.lua
place functions into a table to better handle compatibility of each scripts function
2025-08-13 18:13:08 +01:00
Jim Shield
981563df6a Compile inventory functions into one file
This removes redundant inventory files and compiles all of the functions into one

This allows easier editing and control per inventory and hopefully better optimization, readability and easier addition of support for new inventories
2025-08-13 18:11:39 +01:00
Jim Shield
b4df8deac6 Fix vehicles spawning invisible if not fading in 2025-08-13 02:40:11 +01:00
Jim Shield
e3da31970e lower oxlibs min default polyzone to -20 2025-08-10 01:18:25 +01:00
Jim Shield
6fb06da0e4 Update playerfunctions.lua 2025-08-10 01:18:01 +01:00
Jim Shield
945cd52787 Add support for tgainninv's new item cache 2025-08-10 01:17:14 +01:00
Jim Shield
a880c2dadb Change qb-inv item removal to do in one go 2025-08-05 12:44:21 +01:00
Jim Shield
76855d3246 Add cleanup of targets when switching players 2025-08-05 12:40:36 +01:00
Jim Shield
d6c4619717 Enhance Distance based props and cleanup 2025-08-05 12:38:39 +01:00
Jim Shield
f799e6b2d6 move delete ped loop out of another loop 2025-08-05 12:37:28 +01:00
Jim Shield
2acd506504 Improve cleanup of distance based peds
When changing character, this *should* remove the zone to allow the script to make a new one when switching
2025-08-05 12:34:25 +01:00
Jim Shield
6cfb776eb4 Add function to makeDistPed that runs after creation
Kept forgetting to do this

When using `makeDistPed()` (making a npc that only appears when nearby) you can now send a function to apply custom modifiers to it after the npc is created

eg.
```lua
makeDistPed(v.model[i], b, true, false, v.scenario, nil, nil, function(ped)
	if v["killable"] then
		SetEntityInvincible(ped, false)
		SetBlockingOfNonTemporaryEvents(ped, false)
		FreezeEntityPosition(ped, false)
	end
end)
```
2025-07-29 12:25:31 +01:00
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
89 changed files with 17709 additions and 7800 deletions

12
.gitattributes vendored Normal file
View File

@@ -0,0 +1,12 @@
# VCS / CI noise
.gitattributes export-ignore
.gitignore export-ignore
.github/** export-ignore
.gitmodules export-ignore
# Dev tooling/config
.vscode/** export-ignore
*.code-workspace export-ignore
.editorconfig export-ignore
.eslintrc* export-ignore
.prettier* export-ignore

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/']

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

@@ -0,0 +1,20 @@
name: Discord Commit Notifier
on:
push:
branches: ['*']
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Validate event JSON (sanity check)
run: jq -e . "$GITHUB_EVENT_PATH" >/dev/null
- name: Send commit payload to Discord Bot
env:
URL: http://${{ secrets.DISCORDBOT }}:3000/github-commits
shell: bash
run: |
curl -sS --fail-with-body -X POST "$URL" \
-H 'Content-Type: application/json' \
--data-binary "@$GITHUB_EVENT_PATH"

71
.github/workflows/notify-release.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
name: Discord Release Notifier
on:
# Manual or UI-published releases
release:
types: [published]
# Releases created by your tag-driven workflow
workflow_run:
workflows: ["Package & Release"] # must match the 'name:' in release.yml
types: [completed]
permissions:
contents: read
jobs:
# 1) Handle manual / UI / non-workflow-created releases
notify-from-release:
if: github.event_name == 'release' && github.event.action == 'published'
runs-on: ubuntu-latest
steps:
- name: Send release payload to Discord Bot
env:
URL: http://${{ secrets.DISCORDBOT }}:3000/github-releases
run: |
curl -sS -X POST "$URL" \
-H 'Content-Type: application/json' \
--data-binary "@$GITHUB_EVENT_PATH"
# 2) Handle releases created by your 'Package & Release' workflow
# (which wont trigger `on: release` when using GITHUB_TOKEN)
notify-from-workflow-run:
if: >
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Fetch release JSON by tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
OWNER="${GITHUB_REPOSITORY%/*}"
REPO="${GITHUB_REPOSITORY#*/}"
TAG="${{ github.event.workflow_run.head_branch }}"
# head_branch is the tag name for a tag push workflow_run
curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$OWNER/$REPO/releases/tags/$TAG" \
> release.json
- name: Wrap as 'release' event payload & notify bot
env:
URL: http://${{ secrets.DISCORDBOT }}:3000/github-releases
run: |
set -euo pipefail
jq -n --slurpfile rel release.json \
--arg repo "$GITHUB_REPOSITORY" \
--arg repo_url "https://github.com/$GITHUB_REPOSITORY" \
--arg action "published" '
{ action: $action,
repository: { full_name: $repo, html_url: $repo_url },
release: $rel[0],
sender: { login: "github-actions[bot]" } }' > payload.json
curl -sS -X POST "$URL" \
-H 'Content-Type: application/json' \
--data-binary "@payload.json"

88
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Package & Release
on:
push:
tags:
- "v*"
- "*.*.*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Derive vars
id: vars
shell: bash
run: |
echo "name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT # e.g. jim_bridge
echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT # strip leading v
- name: Build zip with folder prefix
shell: bash
run: |
NAME='${{ steps.vars.outputs.name }}'
VER='${{ steps.vars.outputs.version }}'
git archive --format=zip --prefix="${NAME}/" -o "${NAME}-${VER}.zip" "$GITHUB_REF_NAME"
ls -lh "${NAME}-${VER}.zip"
# --- Patch notes generation (commit titles + links) -------------------
- name: Find previous tag (SemVer aware)
id: prev
shell: bash
run: |
CUR="${GITHUB_REF_NAME}"
# Sort tags by version desc, drop the current, take the next newest
PREV=$(git tag --sort=-v:refname | grep -v -x "$CUR" | head -n 1 || true)
echo "prev=$PREV" >> $GITHUB_OUTPUT
echo "Previous tag: ${PREV:-<none>}"
- name: Generate RELEASE_NOTES.md from commits
shell: bash
run: |
REPO="${{ github.repository }}"
CUR="${GITHUB_REF_NAME}"
PREV='${{ steps.prev.outputs.prev }}'
if [ -n "$PREV" ]; then
RANGE="$PREV..$CUR"
HEADER="## Changes in $CUR"
else
# First release: include all commits
ROOT=$(git rev-list --max-parents=0 HEAD | tail -n 1)
RANGE="$ROOT..$CUR"
HEADER="## Changes"
fi
{
echo "$HEADER"
echo
# Oldest → newest, subject only, skip merge commits
# Escape '[' and ']' so markdown doesn't break on rare titles
git log --reverse --no-merges --pretty=format:'- ['%h'] - %s' "$RANGE" \
| perl -pe 's/([\[\]])/\\$1/g'
} > RELEASE_NOTES.md
echo "--- RELEASE_NOTES.md ---"
cat RELEASE_NOTES.md
echo "------------------------"
- name: Create / Update GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
files: |
*.zip
body_path: RELEASE_NOTES.md # attach our generated notes
# If you wanted GitHub's auto notes instead, set:
# generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

88
README.md Normal file
View File

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

78
_versioncheck.lua Normal file
View File

@@ -0,0 +1,78 @@
local function readBoolMeta(key, default)
local val = GetResourceMetadata(GetCurrentResourceName(), key, 0)
if not val then return default end
val = tostring(val):lower()
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
end
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
if not SUPPRESS_UPDATES then
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(3600000, function()
CheckBridgeVersion()
end)
else
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7) (^1Expect Errors^7)")
end
end)
end)
end
end
CheckBridgeVersion()
end

32
clientFrameworkCache.lua Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

556
frameworkCache.lua Normal file
View File

@@ -0,0 +1,556 @@
--[[
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",
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",
VorpExport = "vorp_core",
VorpInv = "vorp_inventory",
VorpMenu = "vorp_menu",
}
-- Prevent reloading if cache is already initialized
local cache = { Items = {}, Vehicles = {}, Jobs = {}, Gangs = {}, }
local cacheReady = false
-- Timer info, for debugging more then anything
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 functions --
local function checkExists(resourceName)
local state = GetResourceState(resourceName)
return state and (state:find("start") or state:find("stopped"))
end
local function waitStarted(resourceName)
while GetResourceState(resourceName) ~= "started" do Wait(100) end
end
local function waitStartedOrStopped(resourceName)
local state = GetResourceState(resourceName)
while state ~= "started" and state ~= "stopped" do
Wait(100)
state = GetResourceState(resourceName)
end
end
local function dupLowercaseWeapons(items)
for k, v in pairs(items or {}) do
if type(k) == "string" then
if k:find("WEAPON") then
items[k:lower()] = v
end
else
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
print("^1Possible Issue found^7:")
print(json.encode(items[k], { indent = true }))
end
end
end
-- Forceload libs --
-- Ensure oxmysql resource is loaded for jim_bridge internally
-- For some core's it needs to get data from the database
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) or checkExists(Exports.VorpExport) 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
waitStartedOrStopped(Exports.OXCoreExport)
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
waitStarted(Exports.ESXExport)
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 -----
---------------------
local itemFunc = {
{ script = Exports.OXInv,
cacheItem = function()
local success, result = pcall(function()
return exports[Exports.OXInv]:Items()
end)
if success and result then
cache.Items = result
end
end,
},
{ script = Exports.TgiannInv,
cacheItem = function()
local success, result = pcall(function()
return exports[Exports.TgiannInv]:Items()
end)
if success and result then
cache.Items = result
end
end,
},
{ script = Exports.QBXExport,
cacheItem = function()
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.OrigenInv):find("start") then
itemResource = Exports.OrigenInv
cache.Items = exports[Exports.OrigenInv]:Items()
elseif GetResourceState(Exports.CodeMInv):find("start") then
itemResource = Exports.CodeMInv
cache.Items = exports[Exports.CodeMInv]:GetItemList()
elseif GetResourceState(Exports.CoreInv):find("start") then
itemResource = Exports.CoreInv
cache.Items = exports[Exports.CoreInv]:getItemsList()
elseif GetResourceState(Exports.TgiannInv):find("start") then
itemResource = Exports.TgiannInv
cache.Items = exports[Exports.TgiannInv]:Items()
end
end
end,
},
{ script = Exports.QBExport,
cacheItem = function()
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
end,
},
{ script = Exports.ESXExport,
cacheItem = function()
cache.Items = ESX.GetItems()
while not next(cache.Items) do
cache.Items = ESX.GetItems()
Wait(1000)
end
end,
},
{ script = Exports.RSGExport,
cacheItem = function()
cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
end,
},
{ script = Exports.VorpInv,
cacheItem = function()
local dbItems = MySQL.query.await('SELECT * FROM `items`')
local tempItems = {}
for i = 1, #dbItems do
local v = dbItems[i]
tempItems[v.item] = {
name = v.item,
label = v.label,
weight = v.weight,
info = v.metadata,
usable = v.usable,
type = v.type,
description = v.desc,
}
end
cache.Items = tempItems
end,
},
}
for i = 1, #itemFunc do
local data = itemFunc[i]
if checkExists(data.script) then
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheItem() -- run tablized function for core/inv
dupLowercaseWeapons(cache.Items) -- make usable weapon item names for jim_bridge
itemResource = data.script -- Grab script name to announce later
endTimer("Items") -- end timer
break -- break loop so it doesn't keep checking
end
end
---------------------
--- Load Vehicles ---
---------------------
---
local vehicleFunc = {
{ script = Exports.QBXExport,
cacheVehicle = function()
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.QBExport,
cacheVehicle = function()
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.OXCoreExport,
cacheVehicle = function()
cache.Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
cache.Vehicles[k] = {
model = k, hash = joaat(k),
price = v.price,
name = v.name,
brand = v.make
}
end
end,
},
{ script = Exports.ESXExport,
cacheVehicle = function()
while not MySQL do Wait(100) end
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
cache.Vehicles[v.model] = {
model = v.model,
hash = joaat(v.model),
price = v.price,
name = v.name,
}
end
end,
},
{ script = Exports.RSGExport,
cacheVehicle = function()
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.VorpExport,
cacheVehicle = function()
cache.Vehicles = { ["unkown"] = {} }
end,
},
}
for i = 1, #vehicleFunc do
local data = vehicleFunc[i]
if checkExists(data.script) then
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheVehicle() -- run tablized function for core
vehResource = data.script -- Grab script name to announce later
endTimer("Vehicles") -- end timer
break -- break loop so it doesn't keep checking
end
end
---------------------
----- Load Jobs -----
---------------------
local jobFunc = {
{ script = Exports.QBXExport,
cacheJob = function()
cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
end,
},
{ script = Exports.QBExport,
cacheJob = function()
Core = exports[Exports.QBExport]:GetCoreObject()
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end,
},
{ script = Exports.OXCoreExport,
cacheJob = function()
while not MySQL do Wait(100) 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
end,
},
{ script = Exports.ESXExport,
cacheJob = function()
ESX = ESX or 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
end,
},
{ script = Exports.RSGExport,
cacheJob = function()
Core = exports[Exports.RSGExport]:GetCoreObject()
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end,
},
{ script = Exports.VorpExport,
cacheJob = function()
cache.Jobs = { ["unkown"] = {} }
cache.Gangs = cache.Jobs
end,
},
}
for i = 1, #jobFunc do
local data = jobFunc[i]
if checkExists(data.script) then
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheJob() -- run tablized function for core
jobResource = data.script -- Grab script name to announce later
endTimer("Jobs") -- end timer
break -- break loop so it doesn't keep checking
end
end
-- 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)
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.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
waitStartedOrStopped(script)
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,14 +1,28 @@
name "Jim_Bridge" name "Jim_Bridge"
author "Jimathy" author "Jimathy"
version "2.0" version "2.1.05"
description "Framework Bridge By Jimathy" description "Framework Bridge By Jimathy"
fx_version "cerulean" fx_version "cerulean"
game "gta5" rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
lua54 'yes' games { 'gta5', 'rdr3' }
lua54 'yes'
files {
'starter.lua', files {
'shared/*.lua', 'starter.lua',
'shared/make/*.lua', 'shared/*.lua',
'shared/scaleforms/*.lua', 'shared/make/*.lua',
} 'shared/scaleforms/*.lua',
}
-- Version checker
server_scripts {
'frameworkCache.lua',
'_versioncheck.lua',
}
client_scripts {
'clientFrameworkCache.lua',
'ui_modules/*.lua',
}
suppress_updates 'false' -- set to 'true' to disable update pings

157
shared/_authToken.lua Normal file
View File

@@ -0,0 +1,157 @@
-------------------------------------------------------------
-- Exploit Auth System
-------------------------------------------------------------
forceDisableExplotProtection = false -- dangerous, this allows exploits
AuthEvent = nil
currentToken = nil
if isServer() then
local excludeRes = {
[Exports.QBExport] = true,
[Exports.ESXExport] = true,
[Exports.VorpExport] = true,
}
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
local invokingRes = GetInvokingResource()
debugPrint(invokingRes)
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] 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
local invokingRes = GetInvokingResource()
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] 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 forceDisableExplotProtection == true then return true end
if token == nil then
debugPrint("^1Auth^7: ^1No token recieved^7")
if genType == "stash" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1without an auth token^7")
elseif genType == "item" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1without an auth token^7")
end
return false
else
debugPrint("^1Auth^7: ^2Auth token received^7, ^2checking against server cache^7..")
if token ~= validTokens[src] then
debugPrint("^1Auth^7: ^1Tokens don't match! ^7", token, validTokens[src])
if genType == "stash" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1with an incorrect auth token^7")
elseif genType == "item" then
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1with an incorrect auth token^7")
end
return false
else
debugPrint("^1Auth^7: ^2Client and Server Auth tokens match^7!", token, validTokens[src])
validTokens[src] = nil
return true
end
end
end
else
onPlayerLoaded(function()
debugPrint("^1Auth^7: ^2Requesting ^3Auth Event^7")
AuthEvent = triggerCallback(getScript()..":callback:GetAuthEvent")
end, true)
onPlayerUnload(function()
debugPrint("^1Auth^7: ^2Clearing Auth Event^7")
TriggerServerEvent(getScript()..":clearAuthEventRequest")
end, true)
end
function distExploitCheck(table, src)
if forceDisableExplotProtection == true then return true end
if not table then
print("^1Error^7: ^1This wasn^7'^1t reigstered correctly or this is an exploit attempt^1")
return false
end
local ped = src and GetPlayerPed(src) or PlayerPedId()
local srcCoords = GetEntityCoords(ped)
local allow = false
for i = 1, #table do
if #(table[i].xy - srcCoords.xy) <= 10 then
return true
else
allow = false
end
end
if not allow then
print(src and ("^1Src ^3"..src.." ") or "", "^1Tried to open a registered shop/stash from over the distance limit^7")
return false
end
end

View File

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

View File

@@ -1,110 +1,322 @@
--- Executes a function when the player character is loaded into the game. --[[
--- Player & Resource Event Utility Functions
--- This function sets up event listeners for player loading based on the game framework detected (e.g., QB, ESX, OX). -------------------------------------------
--- This module provides functions to:
--- If `onStart` is `true`, it will also attempt to execute the function on resource start after ensuring the player is logged in. (Helpful for debugging) • Execute code when the player character is loaded or unloaded.
--- • Execute code on resource start and stop.
--- @param func function The function to execute when the player is loaded. • Wait for the player to be logged in before proceeding.
--- @param onStart boolean (optional) If `true`, the function will also execute on resource start. Default is `false`. ]]
---
--- @usage local onLoadLast = {} -- keyed by tostring(func)
--- ```lua
--- onPlayerLoaded(function() local function _debouncedRun(func)
--- -- Your code here local key = tostring(func)
--- end, true) local now = GetGameTimer()
--- ``` local last = onLoadLast[key] or -1e12
function onPlayerLoaded(func, onStart)
local onPlayerName = "" if (now - last) < 5000 then
local loaded = false debugPrint(("^6Bridge^7 ^3onPlayerLoaded^7 skipped — cooldown %dms remaining"):format(5000 - (now - last)))
if onStart then return
onResourceStart(function() end
if not LocalPlayer.state.isLoggedIn then
Wait(3000) onLoadLast[key] = now
if not LocalPlayer.state.isLoggedIn then -- If the player is not logged in after waiting, skip execution CreateThread(function()
return Wait(2000) -- keep your small delay
end debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded callback")
end func()
loaded = true -- Mark as already loaded end)
debugPrint("^6Bridge^7: ^2Loading ^3onResourceStart^7() ^2through ^3onPlayerLoaded^7()") end
Wait(2000)
func() local frameworkLoadFunc = {
end, true) { framework = Exports.QBXExport,
end onPlayerLoaded =
if not loaded then function(func)
local tempFunc = function() RegisterNetEvent('QBCore:Client:OnPlayerLoaded', func)
debugPrint("^6Bridge^7: ^2Executing ^3onPlayerLoaded^7()") end,
func() onPlayerUnload =
end function(func)
if isStarted(QBExport) or isStarted(QBXExport) then onPlayerName = QBExport RegisterNetEvent('QBCore:Client:OnPlayerUnload', func)
AddEventHandler('QBCore:Client:OnPlayerLoaded', tempFunc) end,
elseif isStarted(ESXExport) then onPlayerName = ESXExport waitforLogin =
AddEventHandler('esx:playerLoaded', tempFunc) function(timeout)
elseif isStarted(OXCoreExport) then onPlayerName = OXCoreExport local startTime = GetGameTimer()
AddEventHandler('ox:playerLoaded', tempFunc) while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
end Wait(100)
if onPlayerName ~= "" then end
debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^2 with ^7"..onPlayerName) return LocalPlayer.state.isLoggedIn
else end,
print("^4ERROR^7: ^2No Core detected for onPlayerLoaded ^7- ^2Check ^3exports^1.^2lua^7") },
end
end { framework = Exports.QBExport,
end onPlayerLoaded =
function(func)
--- Executes a function when the resource starts. RegisterNetEvent('QBCore:Client:OnPlayerLoaded', func)
--- end,
--- This function wraps the `onResourceStart` event, allowing you to execute code when the resource starts. onPlayerUnload =
--- function(func)
--- @param func function The function to execute on resource start. RegisterNetEvent('QBCore:Client:OnPlayerUnload', func)
--- @param thisScript boolean (optional) If `true`, only runs the function when this resource starts. Default is `true`. end,
--- waitforLogin =
--- @usage function(timeout)
--- ```lua local startTime = GetGameTimer()
--- onResourceStart(function() while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
--- -- Your code here Wait(100)
--- end, true) end
--- ``` return LocalPlayer.state.isLoggedIn
function onResourceStart(func, thisScript) end,
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^2") },
AddEventHandler('onResourceStart', function(resourceName)
if getScript() == resourceName and (thisScript or true) then { framework = Exports.ESXExport,
func() onPlayerLoaded =
end function(func)
end) RegisterNetEvent("esx:playerLoaded", function()
end -- make sure shared has loaded (because sql)
if waitForSharedLoad() then func() end
--- Executes a function when the resource stops. end)
--- end,
--- This function wraps the `onResourceStop` event, allowing you to execute code when the resource stops. onPlayerUnload =
--- function(func)
--- @param func function The function to execute on resource stop. RegisterNetEvent("esx:onPlayerLogout", func)
--- @param thisScript boolean (optional) If `true`, only runs the function when this resource stops. Default is `true`. end,
--- waitforLogin =
--- @usage function(timeout)
--- ```lua local startTime = GetGameTimer()
--- onResourceStop(function() while (GetGameTimer() - startTime) < timeout do
--- -- Cleanup code here local playerData = ESX.GetPlayerData()
--- end, true) if playerData and playerData.job then
--- ``` return true
function onResourceStop(func, thisScript) end
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^2") Wait(100)
AddEventHandler('onResourceStop', function(resourceName) end
if getScript() == resourceName and (thisScript or true) then end,
func() },
end
end) { framework = Exports.OXCoreExport,
end onPlayerLoaded =
function(func)
--- Waits until the player is logged in before continuing execution. RegisterNetEvent('ox:playerLoaded', func)
--- end,
--- This function blocks execution until `LocalPlayer.state.isLoggedIn` is `true`. onPlayerUnload =
--- function(func)
---@usage RegisterNetEvent('ox:playerLogout', func)
--- ```lua end,
--- waitForLogin() waitforLogin =
--- ``` function(timeout)
function waitForLogin() if OxPlayer["stateId"] then
while not LocalPlayer.state.isLoggedIn do return true
debugPrint("Waiting") end
Wait(100) while not OxPlayer["stateId"] do
end Wait(1000)
if OxPlayer.get["stateId"] then
return true
end
end
end,
},
{ framework = Exports.RSGExport,
onPlayerLoaded =
function(func)
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', func)
end,
onPlayerUnload =
function(func)
RegisterNetEvent('RSGCore:Client:OnPlayerUnload', func)
end,
waitforLogin =
function(timeout)
local startTime = GetGameTimer()
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
return LocalPlayer.state.isLoggedIn
end,
},
{ framework = Exports.VorpExport,
onPlayerLoaded =
function(func)
RegisterNetEvent('vorp_core:Client:OnPlayerSpawned', func)
end,
onPlayerUnload =
function(func)
--??
end,
waitforLogin =
function(timeout)
local startTime = GetGameTimer()
while not LocalPlayer.state.IsInSession and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
return LocalPlayer.state.IsInSession
end,
},
}
-------------------------------------------------------------
-- 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)
if onStart then
onResourceStart(function()
if not waitForLogin() then return end
debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 routed through ^3onPlayerLoaded^7()")
_debouncedRun(func)
end, true)
end
local handler = function()
_debouncedRun(func)
end
for i = 1, #frameworkLoadFunc do
local data = frameworkLoadFunc[i]
jsonPrint(data)
if isStarted(data.framework) then
debugPrint("^6Bridge^7: ^2Registering ^3"..data.framework.." ^5onPlayerLoaded^7()")
data.onPlayerLoaded(handler)
return
end
end
print("^1ERROR^7: ^1No supported core detected for onPlayerLoaded - Check starter.lua^7")
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)
for i = 1, #frameworkLoadFunc do
local data = frameworkLoadFunc[i]
if isStarted(data.framework) then
debugPrint("^6Bridge^7: ^2Registering ^3"..data.framework.." ^5onPlayerUnload^7()")
data.onPlayerUnload(func)
return
end
end
print("^1ERROR^7: ^1No supported core detected for onPlayerUnload - Check starter.lua^7")
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()
for i = 1, #frameworkLoadFunc do
local data = frameworkLoadFunc[i]
if isStarted(data.framework) then
debugPrint("^6Bridge^7: ^2Waiting for ^3"..data.framework.."^2 player login^7.")
local result = data.waitforLogin(10000)
if result == true then
debugPrint("^6Bridge^7: ^3"..data.framework.."^2 Player Login Detected^7.")
else
print("^4Error^7: ^2Timeout reached while waiting for player login^7.")
end
return result
end
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 end

View File

@@ -0,0 +1,111 @@
local function readBoolMeta(key, default)
local val = GetResourceMetadata("jim_bridge", key, 0)
if not val then return default end
val = tostring(val):lower()
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
end
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
if not SUPPRESS_UPDATES then
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(3600000, 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()
end

View File

@@ -1,68 +1,150 @@
--- Registers a callback function with the appropriate framework. local CALLBACK_RETRIES = 0
---
--- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly. -- Internal: await a callback with a timeout
--- It adapts the callback function to match the expected signature for the framework. local function awaitWithTimeout(registerFn, timeoutMs)
--- local p = promise.new()
---@param callbackName string The name of the callback to register. local finished = false
---@param funct function The function to be called when the callback is triggered.
--- registerFn(function(result)
---@usage if finished then return end
--- ```lua finished = true
--- createCallback('myCallback', function(source, ...) p:resolve(result)
--- -- Your callback code here end)
--- end)
--- ``` -- timeout watchdog
function createCallback(callbackName, funct) CreateThread(function()
if isStarted(OXLibExport) then Wait(5000)
lib.callback.register(callbackName, funct) if not finished then
else finished = true
local adaptedFunction = function(source, cb, ...) p:reject('timeout')
local result = funct(source, ...) end
cb(result) end)
end
local ok, res = pcall(function() return Citizen.Await(p) end)
if isStarted(QBExport) then if ok then return res, nil end
Core = Core or exports[QBExport]:GetCoreObject() return nil, res
Core.Functions.CreateCallback(callbackName, adaptedFunction) end
elseif isStarted(ESXExport) then
ESX.RegisterServerCallback(callbackName, adaptedFunction) --- Registers a callback function with the appropriate framework.
else ---
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to register callback with", callbackName) --- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
end --- It adapts the callback function to match the expected signature for the framework.
end ---
end ---@param callbackName string The name of the callback to register.
---@param funct function The function to be called when the callback is triggered.
--- Triggers a server callback and returns the result. ---
--- ---@usage
--- This function triggers a server callback using the appropriate framework's method and awaits the result. --- ```lua
--- --- local table = { ["info"] = "HI" }
---@param callbackName string The name of the callback to trigger. --- createCallback('myCallback', function(source, ...)
---@param ... any Additional arguments to pass to the callback. --- return table
--- --- end)
---@return any any The result returned by the callback function. ---
--- --- createCallback("callback:checkVehicleOwned", function(source, plate)
---@usage --- local result = isVehicleOwned(plate)
--- ```lua --- if result then
--- local result = triggerCallback('myCallback', arg1, arg2) --- return true
--- ``` --- else
function triggerCallback(callbackName, ...) --- return false
local result = nil --- end
if isStarted(OXLibExport) then --- end)
result = lib.callback.await(callbackName, false, ...) --- ```
elseif isStarted(QBExport) then function createCallback(callbackName, funct)
local p = promise.new() if isServer() then
Core.Functions.TriggerCallback(callbackName, function(cbResult) if isStarted(OXLibExport) then
p:resolve(cbResult) debugPrint("^6Bridge^7: ^2Registering ^4"..OXLibExport.." ^3Callback^7:", callbackName)
end, ...) lib.callback.register(callbackName, funct)
result = Citizen.Await(p) else
elseif isStarted(ESXExport) then local adaptedFunction = function(source, cb, ...)
local p = promise.new() local result = funct(source, ...)
ESX.TriggerServerCallback(callbackName, function(cbResult) cb(result)
p:resolve(cbResult) end
end, ...)
result = Citizen.Await(p) if isStarted(QBExport) then
else debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with", callbackName) Core = Core or exports[QBExport]:GetCoreObject()
end Core.Functions.CreateCallback(callbackName, adaptedFunction)
return result elseif isStarted(VorpExport) then
end debugPrint("^6Bridge^7: ^2Registering ^4"..VorpExport.." ^3Callback^7:", callbackName)
Core = Core or exports.vorp_core:GetCore()
Core.Callback.Register(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.
---@return string string The error/success message returned by the callback function.
---
---@usage
--- ```lua
--- local result = triggerCallback('myCallback')
--- jsonPrint(result)
---
--- local result = triggerCallback("callback:checkVehicleOwned", plate)
--- print(result)
--- ```
function triggerCallback(callbackName, ...)
debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName)
local args = {...}
if isStarted(OXLibExport) then
local ok, res = pcall(function()
return lib.callback.await(callbackName, false, table.unpack(args))
end)
if ok then return res, "nil" end
return nil, tostring(res)
end
local attempts = 0
local lastErr
repeat
attempts = attempts + 1
if isStarted(QBExport) then
local res, err = awaitWithTimeout(function(cb)
Core.Functions.TriggerCallback(callbackName, cb, table.unpack(args))
end, 5000)
if res ~= nil then return res, "nil" end
lastErr = err
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (QB) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
elseif isStarted(VorpExport) then
local res, err = awaitWithTimeout(function(cb)
Core.Callback.TriggerAwait(callbackName, cb, table.unpack(args))
end, 5000)
if res ~= nil then return res, "nil" end
lastErr = err
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (Vorp) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
elseif isStarted(ESXExport) then
local res, err = awaitWithTimeout(function(cb)
ESX.TriggerServerCallback(callbackName, cb, table.unpack(args))
end, 5000)
if res ~= nil then return res, "nil" end
lastErr = err
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (ESX) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
else
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with^7:", callbackName)
return nil, "no_framework"
end
Wait(10)
until attempts > (1 + CALLBACK_RETRIES)
return nil, lastErr or "timeout"
end

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,75 +1,184 @@
--- Creates a temporary camera at a specified position, pointing towards given coordinates. local camCache = {}
--
-- 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. --- Creates a temporary camera at a specified position, pointing towards given coordinates.
-- The camera is only created if `Config.Crafting.craftCam` is enabled in the configuration. --
-- -- 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.
---@param ent entityId|coords The base position for the camera. Can be an entity handle or a `vector3` position. -- The camera is only created if `Config.Crafting.craftCam` is enabled in the configuration.
-- 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 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`.
---@param coords vector3 The target `vector3` coordinates that the camera will point at. -- If `ent` is a `vector3`, it is used directly as the camera's position.
-- --
---@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`). ---@param coords vector3|entityId The target `vector3` coordinates that the camera will point at.
-- --
---@usage ---@return camID number The handle of the created camera, or `nil` if the camera was not created (e.g., if `Config.Crafting.craftCam` is `false`).
-- ```lua --
-- local cam = createTempCam(entity, targetCoords) ---@usage
-- ``` -- ```lua
function createTempCam(ent, coords) -- local cam = createTempCam(entity, targetCoords)
local cam = nil -- ```
if Config.Crafting.craftCam then function createTempCam(ent, coords)
if debugMode then local camID = nil
triggerNotify(nil, "ModCam Created", "success") if Config.Crafting?.craftCam or Config.System.enableCam then
end
local camCoords = nil -- if not ent or coords are provided, make a basic camera to control later
if type(ent) ~= "vector3" then if not ent and not coords then
camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8) camID = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
else camCache[#camCache+1] = camID
camCoords = ent return camID
end end
-- Create the camera with specified parameters
cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0) -- if received a vector3 or vector4 use those coords for origin point, otherwrise get offset from entity
-- Point the camera at the target coordinates local camCoords = type(ent) ~= "number" and ent or GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
PointCamAtCoord(cam, coords)
end -- Create the camera
return cam camID = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
end camCache[#camCache+1] = camID
--- Activates and starts rendering the temporary camera. debugPrint("^6Bridge^7: ^2Custom Camera Created", camID)
--
-- This function sets the specified camera as active and begins rendering it with a smooth transition. --if type(coords) == "number" then
-- The camera is only activated if `Config.Crafting.craftCam` is enabled in the configuration. -- SetCamCoord(camID, GetCamCoord(camID) + vec3(0, 0, 1.0))
-- --end
---@param cam camID The handle of the camera to activate and render. if coords then
-- camLookAt(camID, coords)
---@usage end
-- ```lua
-- startTempCam(cam) end
-- ``` return camID
function startTempCam(cam) end
if Config.Crafting.craftCam then
SetCamActive(cam, true)
RenderScriptCams(true, true, 1000, true, true) local cacheCameraEffect = {}
end local cachePrevCam = nil
end --- Activates and starts rendering the temporary camera.
--
--- Deactivates the temporary camera and stops rendering. -- 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.
-- 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. ---@param cam number The handle of the camera to activate and render.
-- ---@param renderTime number The handle of the camera to activate and render.
-- The camera is only deactivated if `Config.Crafting.craftCam` is enabled in the configuration. ---@param loadScene boolean The handle of the camera to activate and render.
-- ---@param filter table The filter or postFx to render when starting the camera.
---@usage --
-- ```lua ---@usage
-- stopTempCam() -- ```lua
-- ``` -- startTempCam(camID, 1000, true, { postFx = "HeistCelebEnd" })
function stopTempCam() -- ```
if Config.Crafting.craftCam then function startTempCam(cam, renderTime, loadScene, filter, switchCam)
CreateThread(function() if cam and DoesCamExist(cam) then
Wait(1000) debugPrint("Starting camera")
RenderScriptCams(false, true, 500, true, true) -- if moving from a cached previous cam, or you've sent a camre id for it to switch from, interpolate to it
DestroyAllCams() if switchCam or cachePrevCam then
end) SetCamActiveWithInterp(cam, cachePrevCam, renderTime or 1000, 0, 0)
end SetCamActive(cachePrevCam, false) -- Set previous cam inactive (shouldn't be needed but just in case)
end else
SetCamActive(cam, true)
end
cachePrevCam = cam
-- Clear previous filters
if cacheCameraEffect.postFx then
AnimpostfxStop(cacheCameraEffect.postFx)
cacheCameraEffect.postFx = nil
end
if cacheCameraEffect.timecycle then
ClearTimecycleModifier()
cacheCameraEffect.timecycle = nil
end
-- Apply filters (timecycle) here as requested
if filter and filter.modifier then
SetTimecycleModifier(filter.modifier)
SetTimecycleModifierStrength((filter.strength or 1.0) + 0.0)
cacheCameraEffect.timecycle = true
end
if filter and filter.postFx then
AnimpostfxPlay(filter.postFx, 0, filter.loop and true or false)
cacheCameraEffect.postFx = filter.postFx
end
if loadScene then
loadLocation(cam, nil, 100.0)
end
RenderScriptCams(true, true, renderTime or 1000, true, true)
end
end
-- Follow cam or coords
function camLookAt(cam, entCoords)
if cam and DoesCamExist(cam) and entCoords then
if type(entCoords) ~= "number" then
PointCamAtCoord(cam, entCoords.xyz)
else
PointCamAtEntity(cam, entCoords)
end
end
end
function loadLocation(cam, pos, radius)
local pos = pos or GetCamCoord(cam)
SetFocusPosAndVel(pos.x, pos.y, pos.z, 0.0, 0.0, 0.0)
RequestCollisionAtCoord(pos.x, pos.y, pos.z)
NewLoadSceneStart(pos.x, pos.y, pos.z, 0.0, 0.0, 0.0, radius or 100.0, 0)
local t0 = GetGameTimer()
while not IsNewLoadSceneLoaded() and (GetGameTimer() - t0) < 2000 do
RequestCollisionAtCoord(pos.x, pos.y, pos.z)
Wait(0)
end
NewLoadSceneStop()
end
function clearLoadLocation()
ClearFocus()
NewLoadSceneStop()
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(renderTime)
CreateThread(function()
Wait(1000)
cachePrevCam = nil
-- Clear previous filters
if cacheCameraEffect.postFx then
AnimpostfxStop(cacheCameraEffect.postFx)
cacheCameraEffect.postFx = nil
end
if cacheCameraEffect.timecycle then
ClearTimecycleModifier()
cacheCameraEffect.timecycle = nil
end
RenderScriptCams(false, true, renderTime or 500, true, true)
clearLoadLocation()
Wait(renderTime or 0)
for i = 1, #camCache do
DestroyCam(camCache[i], true)
end
camCache = {}
end)
end
function createCam(...) return createTempCam(...) end
function startCam(...) return startTempCam(...) end
function stopCam(...) return stopTempCam(...) end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

255
shared/make/ropeControl.lua Normal file
View File

@@ -0,0 +1,255 @@
if not isServer() then
local RopeCache = {
nextId = 1,
byId = {},
byHandle = {},
}
function loadRopeTextures()
RopeLoadTextures()
local tries = 0
while not RopeAreTexturesLoaded() and tries < 200 do
Wait(0)
tries += 1
end
return RopeAreTexturesLoaded()
end
function maybeUnloadTextures()
local anyLeft = false
local all = GetAllRopes()
if all and type(all) == "table" then
for _, _ in ipairs(all) do anyLeft = true break end
end
if not anyLeft then
RopeUnloadTextures()
end
end
function registerRope(ropeHandle, startCoords, endCoords, opts)
if not ropeHandle or ropeHandle == 0 then return nil end
local id = RopeCache.nextId
RopeCache.nextId = id + 1
local rec = {
handle = ropeHandle,
startCoords = startCoords,
endCoords = endCoords,
type = opts.ropeType,
rate = opts.lengthChangeRate,
breakable = opts.breakable,
createdAt = GetGameTimer()
}
RopeCache.byId[id] = rec
RopeCache.byHandle[ropeHandle] = id
debugPrint("^5Debug^7: 2Registered Rope^7'^3"..id.."^7' - '^3"..ropeHandle.."^7'")
return id
end
function resolve(id)
if type(id) ~= "number" then return nil, "rope id must be a number" end
local rec = RopeCache.byId[id]
if not rec then return nil, "rope id not found" end
if not DoesRopeExist(rec.handle) then
-- stale; clean mapping
RopeCache.byHandle[rec.handle] = nil
RopeCache.byId[id] = nil
return nil, "rope no longer exists"
end
return rec
end
--- Create a rope pinned between two coordinates.
--- @param startCoords vector3|table start coord
--- @param endCoords vector3|table end coord
--- @param opts table|nil { ropeType=0..7, breakable=false, collision=true, lockFromFront=false, lengthChangeRate=1.0, timeMultiplier=1.0, slack=0.0, preset=nil }
--- @return integer ropeId
function ropeCreateLine(startCoords, endCoords, opts)
startCoords = type(startCoords) == "number" and GetEntityCoords(startCoords) or startCoords
endCoords = type(endCoords) == "number" and GetEntityCoords(endCoords) or endCoords
opts = opts or {}
local length = #(startCoords - endCoords)
local mid = (startCoords + endCoords) * 0.5
local ropeType = opts.ropeType or 1 -- 0..7 (see ropedata.xml types)
local initLength = length + (opts.slack or 0.0)
local maxLength = initLength -- allow droop equal to init
local minLength = 0.0
local lengthChangeRate = opts.lengthChangeRate or 1.0
local collisionOn = (opts.collision ~= false)
local lockFromFront = (opts.lockFromFront == true)
local timeMultiplier = opts.timeMultiplier or 1.0
local breakable = (opts.breakable == true)
local rope = AddRope(mid.x, mid.y, mid.z, 0.0, 0.0, 0.0, maxLength, ropeType, initLength, minLength, lengthChangeRate, false, collisionOn, lockFromFront, timeMultiplier, breakable, 0)
if not rope or rope == 0 or not DoesRopeExist(rope) then
debugPrint("^1AddRope failed")
return nil
end
-- Optional preset (e.g., "ropeFamily3") if provided.
if opts.preset then
-- Best-effort; if invalid it just won't change.
pcall(function() LoadRopeData(rope, tostring(opts.preset)) end)
end
ActivatePhysics(rope)
-- Pin both ends to the given coords.
local count = GetRopeVertexCount(rope)
if count and count >= 2 then
PinRopeVertex(rope, 0, startCoords.x, startCoords.y, startCoords.z)
PinRopeVertex(rope, count - 1, endCoords.x, endCoords.y, endCoords.z)
end
local id = registerRope(rope, startCoords, endCoords, {
ropeType = ropeType,
lengthChangeRate = lengthChangeRate,
breakable = breakable
})
return id
end
--- Delete and unregister a rope.
--- @param id integer
function ropeDelete(id)
local rec, err = resolve(id)
if not rec then return false, err end
local rope = rec.handle
if DoesRopeExist(rope) then
DeleteRope(rope)
end
RopeCache.byHandle[rope] = nil
RopeCache.byId[id] = nil
debugPrint(("deleted rope id=%s"):format(id))
maybeUnloadTextures()
return true
end
--- Force rope to a specific length (instant snap).
--- @param id integer
--- @param newLength number
function ropeSetLength(id, newLength)
local rec, err = resolve(id)
if not rec then return false, err end
RopeForceLength(rec.handle, newLength)
return true
end
--- Smoothly wind the rope (shorten over time).
--- @param id integer
function ropeStartWinding(id)
local rec, err = resolve(id)
if not rec then return false, err end
StartRopeWinding(rec.handle)
return true
end
--- Stop winding (if winding).
function ropeStopWinding(id)
local rec, err = resolve(id)
if not rec then return false, err end
StopRopeWinding(rec.handle)
return true
end
--- Smoothly unwind the rope from the front (lengthen over time).
function ropeStartUnwinding(id)
local rec, err = resolve(id)
if not rec then return false, err end
StartRopeUnwindingFront(rec.handle)
return true
end
--- Stop unwinding (if unwinding).
function ropeStopUnwinding(id)
local rec, err = resolve(id)
if not rec then return false, err end
StopRopeUnwindingFront(rec.handle)
return true
end
--- Change the wind speed (length change rate).
--- @param id integer
--- @param rate number (units per sec; try 0.5..5.0)
function ropeSetRate(id, rate)
local rec, err = resolve(id)
if not rec then return false, err end
SetRopeLengthChangeRate(rec.handle, rate)
rec.rate = rate
return true
end
--- Move end endpoint to a new coordinate (re-pins the vertex).
--- @param id integer
--- @param pos vector3|table
function ropeMoveEnd(id, pos)
local rec, err = resolve(id)
if not rec then return false, err end
pos = type(pos) == "number" and GetEntityCoords(pos) or pos
local rope = rec.handle
local count = GetRopeVertexCount(rope)
if not count or count < 2 then return false, "invalid vertex count" end
PinRopeVertex(rope, count - 1, pos.x, pos.y, pos.z)
rec.endCoord = pos
return true
end
--- Move start endpoint to a new coordinate (re-pins the vertex).
--- @param id integer
--- @param pos vector3|table
function ropeMoveStart(id, pos)
local rec, err = resolve(id)
if not rec then return false, err end
pos = type(pos) == "number" and GetEntityCoords(pos) or pos
local rope = rec.handle
local count = GetRopeVertexCount(rope)
if not count or count < 2 then return false, "invalid vertex count" end
PinRopeVertex(rope, 0, pos.x, pos.y, pos.z)
rec.startCoord = pos
return true
end
--- Re-pin both endpoints in one call.
function ropeSetEnds(ropeId, startCoord, endCoord)
startCoord = type(startCoord) == "number" and GetEntityCoords(startCoord) or startCoord
endCoord = type(endCoord) == "number" and GetEntityCoords(endCoord) or endCoord
local rec, err = resolve(ropeId)
if not rec then return false, err end
local rope = rec.handle
local count = GetRopeVertexCount(rope)
if not count or count < 2 then
return false, "invalid vertex count"
end
PinRopeVertex(rope, 0, startCoord.x, startCoord.y, startCoord.z)
PinRopeVertex(rope, count - 1, endCoord.x, endCoord.y, endCoord.z)
rec.startCoord = startCoord
rec.endCoord = endCoord
return true
end
function ropeExists(id)
local rec = RopeCache.byId[id]
return rec and DoesRopeExist(rec.handle) or false
end
onResourceStart(function()
loadRopeTextures()
end)
onResourceStop(function()
for id, rec in pairs(RopeCache.byId) do
if rec.handle and DoesRopeExist(rec.handle) then
DeleteRope(rec.handle)
end
RopeCache.byHandle[rec.handle] = nil
RopeCache.byId[id] = nil
end
maybeUnloadTextures()
end)
end

219
shared/metaHandlers.lua Normal file
View File

@@ -0,0 +1,219 @@
--[[
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.
]]
local metaDataFunc = {
{ framework = QBXExport,
GetPlayer =
function(src)
if src then
return exports[QBXExport]:GetPlayer(src)
end
return exports[QBXExport]:GetPlayerData()
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.PlayerData.metadata[dataToCheck]
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.Functions.SetMetaData(key, value)
end,
},
{ framework = QBExport,
GetPlayer =
function(src)
if src then
return exports[QBExport]:GetCoreObject().Functions.GetPlayer(src)
end
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
return info
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.PlayerData.metadata[dataToCheck]
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.Functions.SetMetaData(key, value)
end,
},
{ framework = ESXExport,
GetPlayer =
function(src)
if src then
return ESX.GetPlayerFromId(src)
end
return ESX.GetPlayerData()
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.getMeta(dataToCheck)
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.set(key, value)
end,
},
{ framework = OXCoreExport,
GetPlayer =
function(src)
if src then
return exports[OXCoreExport]:GetPlayer(src)
end
return {}
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.get(dataToCheck)
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.set(key, value)
end,
},
{ framework = RSGExport,
GetPlayer =
function(src)
if src then
return exports[RSGExport]:GetCoreObject().Functions.GetPlayer(src)
end
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
return info
end,
GetPlayerMetadata = function(Player, dataToCheck)
return Player.PlayerData.metadata[dataToCheck]
end,
SetPlayerMetadata = function(Player, key, value)
return Player.Functions.SetMetaData(key, value)
end,
},
}
-------------------------------------------------------------
-- 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)
for i = 1, #metaDataFunc do
local framework = metaDataFunc[i]
if isStarted(framework.framework) then
return framework.GetPlayer(source)
end
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)
-- Assume this is client side and callback to server to get the data
if not player then
debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key)
return triggerCallback(getScript()..":server:GetMetadata", key)
else
-- else grab server metadata about the player
for i = 1, #metaDataFunc do
local framework = metaDataFunc[i]
if isStarted(framework.framework) then
debugPrint("^6Bridge^7: ^3GetMetadata^7() ^3"..framework.framework.."^7", key)
return framework.GetPlayerMetadata(player, key)
end
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^7...")
for i = 1, #metaDataFunc do
local framework = metaDataFunc[i]
if isStarted(framework.framework) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() ^3"..framework.framework.."^7", key, value)
return framework.GetPlayerMetadata(player, key)
end
end
debugPrint("^6Bridge^7: ^1Error setting metadata^7, ^1framework not supported^7?")
end
-- Register a server callback for setting metadata.
createCallback(getScript()..":server:setPlayerMetadata", function(source, key, value)
local src = source
debugPrint("SetMetadata callback triggered for source:", src, "key:", key, "value:", value)
local player = GetPlayer(src)
setPlayerMetadata(player, key, value)
print("Metadata set successfully.", key)
return true
end)

View File

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

245
shared/phones.lua Normal file
View File

@@ -0,0 +1,245 @@
--[[
Phone Mails Module
------------------
This module handles sending phone mails using different phone systems.
Supported systems include:
- gksphone
- yflip-phone
- roadphone
- lb-phone
- qb-phone
- jpr-phonesystem
]]
local phoneFunc = {
{ phone = "gksphone",
sendMail = function(mailData)
exports["gksphone"]:SendNewMail(mailData)
end,
sendInvoice =
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,
},
{ phone = "yflip-phone",
sendMail =
function(mailData)
TriggerServerEvent(getScript()..":yflip:SendMail", mailData)
end,
},
{ phone = "roadphone",
sendMail =
function(mailData)
-- Convert HTML line breaks to newlines for roadphone.
mailData.message = mailData.message:gsub("%<br>", "\n")
exports["roadphone"]:sendMail(mailData)
end,
},
{ phone = "lb-phone",
sendMail =
function(mailData)
-- Convert HTML line breaks to newlines for lb-phone.
mailData.message = mailData.message:gsub("%<br>", "\n")
TriggerServerEvent(getScript()..":lbphone:SendMail", mailData)
end,
},
{ phone = "qb-phone",
sendMail =
function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
sendInvoice =
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,
},
{ phone = "npwd_qbx_mail",
sendMail =
function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
{ phone = "jpr-phonesystem",
sendMail =
function(mailData)
TriggerServerEvent(getScript()..":jpr:SendMail", mailData)
end,
},
{ phone = "ef-phone",
sendMail =
function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
}
--- 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(mailData)
-- Check each phone system in order and use the first active one.
for i = 1, #phoneFunc do
local script = phoneFunc[i]
if isStarted(script.phone) and script.sendMail then
debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player")
script.sendMail(mailData)
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
-- Check each phone system in order and use the first active one.
for i = 1, #phoneFunc do
local script = phoneFunc[i]
if isStarted(script.phone) and script.sendInvoice then
debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player^7", data.src)
script.sendInvoice(data)
return true
end
end
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
return false
end
-------------------------------------------------------------
-- Phone System Event Handlers
-------------------------------------------------------------
--- Handles sending mail for lb-phone.
--- Listens for the `lbphone:SendMail` event and sends an email using lb-phone's API.
---
--- @event lbphone:SendMail
--- @param data table The mail data.
--- - subject (string): The email subject.
--- - message (string): The email content.
--- - buttons (table|nil): Optional action buttons (mapped from data.actions if present).
RegisterNetEvent(getScript()..":lbphone:SendMail", function(data)
local src = source
local phoneNumber = exports["lb-phone"]:GetEquippedPhoneNumber(src)
local emailAddress = exports["lb-phone"]:GetEmailAddress(phoneNumber)
-- Map actions to buttons if provided.
data.buttons = data.actions or data.buttons
exports["lb-phone"]:SendMail({
to = emailAddress,
subject = data.subject,
message = data.message,
actions = data.buttons,
})
end)
--- Handles sending mail for yflip-phone.
--- Listens for the `yflip:SendMail` event and sends an email using yflip-phone's API.
---
--- @event yflip:SendMail
--- @param data table The mail data.
--- - subject (string): The email subject.
--- - sender (string): The sender identifier.
--- - message (string): The email content.
--- - buttons (table|nil): Optional action buttons.
RegisterNetEvent(getScript()..":yflip:SendMail", function(data)
local src = source
exports["yflip-phone"]:SendMail({
title = data.subject,
sender = data.sender,
senderDisplayName = data.sender,
content = data.message,
actions = data.buttons,
}, 'source', src)
end)
--- Handles sending mail for jpr-phonesystem.
--- Listens for the `jpr:SendMail` event and sends an email using jpr-phonesystem's API.
---
--- @event jpr:SendMail
--- @param data table The mail data.
--- - subject (string): The email subject.
--- - sender (string): The sender identifier.
--- - message (string): The email content.
--- - buttons (table|nil): Optional action buttons.
RegisterNetEvent(getScript()..":jpr:SendMail", function(data)
local src = source
local Player = Core.Functions.GetPlayer(src)
TriggerEvent('jpr-phonesystem:server:sendEmail', {
Assunto = data.subject, -- Email subject
Conteudo = data.message, -- Email content
Enviado = data.sender, -- Sender information
Destinatario = Player.PlayerData.citizenid, -- Recipient identifier
Event = {}, -- Optional event details
})
end)

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,115 @@
-------------------------------------------------------------
-- 3D Text Rendering
-------------------------------------------------------------
--- Draws 3D text at specified world coordinates.
---
--- Configures text properties, draws the text, and displays a background rectangle behind it.
---
--- @param coord table A vector3 with x, y, and z coordinates.
--- @param text string The text to display.
--- @param highlight boolean (Optional) If true, highlights parts of the text.
---
--- @usage
--- ```lua
--- CreateThread(function()
--- while true do
--- DrawText3D(vector3(100, 200, 300), "Hello World", true)
--- Wait(0)
--- end
--- end)
--- ```
function DrawText3D(coord, text, highlight)
SetTextScale(0.30, 0.30)
SetTextFont(0)
SetTextProportional(1)
SetTextColour(255, 255, 255, 215)
SetTextEntry("STRING")
SetTextCentre(true)
local totalLength = string.len(text)
local textMaxLength = 99 -- max 99
local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text
AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text)
SetDrawOrigin(coord.x, coord.y, coord.z, 0)
DrawText(0.0, 0.0)
local count, length = GetLineCountAndMaxLength(text)
local padding = 0.005
local heightFactor = (count / 43) + padding
local weightFactor = (length / 150) + padding
local height = (heightFactor / 2) - padding / 1
local width = (weightFactor / 2) - padding / 1
DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
ClearDrawOrigin()
end
--- Calculates the number of lines and the maximum line length from the given text.
---
--- @param text string The text to analyze.
--- @return number, number The line count and maximum line length.
---
--- @usage
--- ```lua
--- local count, maxLen = GetLineCountAndMaxLength("Hello World")
--- ```
function GetLineCountAndMaxLength(text)
local lineCount, maxLength = 0, 0
for line in text:gmatch("[^\n]+") do
lineCount += 1
local lineLength = string.len(line)
if lineLength > maxLength then
maxLength = lineLength
end
end
if lineCount == 0 then lineCount = 1 end
return lineCount, maxLength
end
-------------------------------------------------------------
-- Additional UI Helpers
-------------------------------------------------------------
--- Displays a help message on the screen.
---
--- @param text string The message to display.
---
--- @usage
--- ```lua
--- DisplayHelpMsg("Press E to interact")
--- ```
function DisplayHelpMsg(text)
BeginTextCommandDisplayHelp("STRING")
AddTextComponentScaleform(text)
EndTextCommandDisplayHelp(0, true, false, -1)
end
--- Displays a "Saving/Loading" spinner with a custom message.
---
--- @param text string The message to display alongside the spinner.
---
--- @usage
--- ```lua
--- displaySpinner("Saving data...")
--- ```
function displaySpinner(text)
BeginTextCommandBusyspinnerOn('STRING')
AddTextComponentSubstringPlayerName(text)
EndTextCommandBusyspinnerOn(4)
end
--- Stops the "Saving/Loading" spinner.
---
--- This function should only be called client-side.
---
--- @usage
--- ```lua
--- stopSpinner()
--- ```
function stopSpinner()
if not isServer() then
BusyspinnerOff()
end
end

View File

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

73
shared/skillcheck.lua Normal file
View File

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

266
shared/societybank.lua Normal file
View File

@@ -0,0 +1,266 @@
--[[
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
]]
local societyFunc = {
{ bankName = "Renewed-Banking",
getAccount =
function(society)
return exports["Renewed-Banking"]:getAccountMoney(society)
end,
chargeSociety =
function(society, amount)
exports['Renewed-Banking']:removeAccountMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports['Renewed-Banking']:addAccountMoney(society, amount)
end,
},
{ bankName = "fd_banking",
getAccount =
function(society)
return exports["fd_banking"]:GetAccount(society)
end,
chargeSociety =
function(society, amount)
exports["fd_banking"]:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports["fd_banking"]:AddMoney(society, amount)
end,
},
{ bankName = "okokBanking",
getAccount =
function(society)
return exports['okokBanking']:GetAccount(society)
end,
chargeSociety =
function(society, amount)
exports['okokBanking']:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports['okokBanking']:AddMoney(society, amount)
end,
},
{ bankName = "tgiann-bank",
getAccount =
function(society)
if Jobs[society] then
return exports["tgiann-bank"]:GetJobAccountBalance(society)
else
return exports["tgiann-bank"]:GetGangAccountBalance(society)
end
end,
chargeSociety =
function(society, amount)
if Jobs[society] then
exports["tgiann-bank"]:RemoveJobMoney(society, amount)
else
exports["tgiann-bank"]:RemoveGangMoney(society, amount)
end
end,
fundSociety =
function(society, amount)
if Jobs[society] then
exports["tgiann-bank"]:AddJobMoney(society, amount)
else
exports["tgiann-bank"]:AddGangMoney(society, amount)
end
end,
},
{ bankName = "qb-banking",
getAccount =
function(society)
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
return exports["qb-banking"]:GetAccountBalance(society)
end,
chargeSociety =
function(society, amount)
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)
end,
fundSociety =
function(society, amount)
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)
end,
},
{ bankName = "esx_society",
getAccount =
function(society)
local checkExist = exports.esx_society:GetSociety(society)
if checkExist == nil then
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
exports.esx_society:registerSociety(
society,
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
society,
society,
society,
{type = "public"}
)
end
local p = promise.new()
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
p:resolve(account.money)
end)
local newAmount = Citizen.Await(p)
return newAmount
end,
chargeSociety =
function(society, amount)
local checkExist = exports.esx_society:GetSociety(society)
if checkExist == nil then
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
exports.esx_society:registerSociety(
society,
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
society,
society,
society,
{type = "public"}
)
end
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
if amount > 0 and account.money >= amount then
account.removeMoney(amount)
end
end)
end,
fundSociety =
function(society, amount)
local checkExist = exports.esx_society:GetSociety(society)
if checkExist == nil then
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
exports.esx_society:registerSociety(
society,
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
society,
society,
society,
{type = "public"}
)
end
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
account.addMoney(amount)
end)
end,
},
}
--- 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 amount = 0
if society == nil or society == "none" then return amount end
for i = 1, #societyFunc do
local script = societyFunc[i]
if isStarted(script.bankName) then
local amount = script.getAccount(society)
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Retrieved account ^7'^6"..society.."^7' ($"..tostring(amount)..")")
return amount
end
end
print("^1Error^7: ^3GetSocietyAccount^7: ^2No supported banking script found")
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)
for i = 1, #societyFunc do
local script = societyFunc[i]
if isStarted(script.bankName) then
script.chargeSociety(society, amount)
local newAmount = getSocietyAccount(society)
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Removing $"..amount.." from account '^6"..society.."^7' ($"..tostring(newAmount)..")")
return
end
end
print("^1Error^7: ^3ChargeSociety^7: ^2No supported banking script found")
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)
for i = 1, #societyFunc do
local script = societyFunc[i]
if isStarted(script.bankName) then
script.fundSociety(society, amount)
local newAmount = getSocietyAccount(society)
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Adding ^7$"..amount.." ^2to account ^7'^6"..society.."^7' ($"..tostring(newAmount)..")")
return
end
end
print("^1Error^7: ^3FundSociety^7: ^2No supported banking script found")
end

View File

@@ -1,271 +0,0 @@
if isServer() then
createCallback(getScript()..':server:GetStashItems', function(source, stashName) local stash = getStash(stashName) return stash end)
end
local stashCache ={}
function GetStashTimeout(stashName, stop)
if stop then stashCache = {} return end
local stash = stashCache[stashName]
if not stash then
stashCache[stashName] = { items = {}, timeout = 0 }
stash = stashCache[stashName]
end
if #stash.items > 0 then return true end
if stash.timeout <= 0 then
stash.items = triggerCallback(getScript()..':server:GetStashItems', stashName)
stash.timeout = 10000
CreateThread(function()
while stash.timeout > 0 do stash.timeout -= 1000 Wait(1000) end
stashCache[stashName] = nil
end)
end
return false
end
function checkHasItem(stashes, itemTable)
if not stashes then return hasItem(itemTable), nil end
if type(stashes) == "table" then
local succeses = 0
local itemCount = 0
for _, item in pairs(itemTable) do itemCount += 1 end
for _, name in pairs(stashes) do
GetStashTimeout(name)
for item, amount in pairs(itemTable) do
debugPrint("^6Bridge^7: ^2Checking"..(name and " ^7'^6"..name.."^7'" or "").." ^2ingredients^7 - ^6"..item.."^7")
if stashhasItem(stashCache[name].items, item, amount) then
succeses += 1
if succeses == itemCount then return true, name end
end
end
end
else
debugPrint("^6Bridge^7: ^2Checking"..(stashes and " ^7'^6"..stashes.."^7'" or "").." ^2ingredients^7 - ^6"..k.."^7")
GetStashTimeout(stashes)
return stashhasItem(stashCache[stashes].items, itemTable), stashes
end
return false, nil
end
-- Stash Items
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(CodeMInv) then
exports[CodeMInv]:OpenStash(data.stash, StashWeight, 100)
elseif isStarted(QBInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:OpenStashQB', { 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, data.stashOptions)
end
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, data.stashOptions)
end
lookEnt(data.coords)
end
RegisterNetEvent(getScript()..':server:OpenStashQB', function(data)
exports[QBInv]:OpenInventory(source, data.stashName, data)
end)
function getStash(stashName) local stashResource = ""
if type(stashName) ~= "string" then return print("Stash name was not a string %s(%s)", stashName, type(stashName)) end
local stashItems, items = {}, {}
if isStarted(OXInv) then stashResource = OXInv
stashItems = exports[OXInv]:Inventory(stashName).items
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]:GetInventoryItems('Stash', stashName)
elseif isStarted(OrigenInv) then stashResource = OrigenInv
stashItems = exports[OrigenInv]:GetStashItems(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(QBInv) then stashResource = QBInv
local result = MySQL.scalar.await("SELECT items FROM "..(QBInvNew and "inventories" or "stashitem").." WHERE identifier = ?", { stashName })
if result then stashItems = json.decode(result) end
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 -- Added to help recreate missing slot numbers
items[(item.slot and 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 ^7'^6"..stashName.."^7' ^2retrieved^7")
end
return items
end
function stashRemoveItem(stashItems, stashName, items) local amount = amount and amount or 1
-- print("stashItems: "..json.encode(stashItems, { indent = true}))
-- print("stashName: "..json.encode(stashName, { indent = true}))
-- print("items: "..json.encode(items, { indent = true}))
if isStarted(OXInv) then
for k, v in pairs(items) do
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
if type(stashName) == "table" then
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
else
exports[OXInv]:RemoveItem(stashName, k, v)
end
end
elseif isStarted(QSInv) 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"..QBInv, k, v)
exports[QSInv]:RemoveItemIntoStash(stashName, k, v, l)
end
end
end
end
elseif isStarted(CoreInv) then
for k, v in pairs(items) do
exports[CoreInv]:removeItemExact(stashName, 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
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
elseif isStarted(OrigenInv) then
for k, v in pairs(items) do
exports[OrigenInv]:RemoveFromStash(stashName, k, v)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OrigenInv, k, v)
end
elseif isStarted(PSInv) 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"..QBInv, k, v)
stashItems[l].amount -= v
end
end
end
end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['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
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName[1].."^7'")
MySQL.Async.insert('INSERT INTO inventories (identifier, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName[1], ['items'] = json.encode(stashItems) })
else
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
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) })
end
else
print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3exports^1.^2lua^7")
end
end
RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
function stashhasItem(stashItems, items, amount)
local invs = {OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, QBInv, PSInv}
local foundInv = ""
for _, inv in ipairs(invs) do
if isStarted(inv) then
foundInv = inv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
break
end
end
if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
local hasTable = {}
for item, amount 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 >= amount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, amount)
debugPrint(debugMsg)
hasTable[item] = { hasItem = (count >= amount), count = count }
end
for k, v in pairs(hasTable) do if v.hasItem == false then return false, hasTable end end
return true, hasTable
end

View File

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

View File

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

View File

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

694
shared/warmenu.lua Normal file
View File

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

View File

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

View File

@@ -1,89 +1,238 @@
Exports = { gameName = not IsDuplicityVersion() and GetCurrentGameName()
QBExport = "qb-core",
QBXExport = "qbx_core", Exports = {
ESXExport = "es_extended", QBExport = "qb-core",
OXCoreExport = "ox_core", QBXExport = "qbx_core",
ESXExport = "es_extended",
OXInv = "ox_inventory", OXCoreExport = "ox_core",
QBInv = "qb-inventory",
PSInv = "ps-inventory", OXInv = "ox_inventory",
QSInv = "qs-inventory", QBInv = "qb-inventory",
CoreInv = "core_inventory", PSInv = "ps-inventory",
CodeMInv = "codem-inventory", CoreInv = "core_inventory",
OrigenInv = "origen_inventory", CodeMInv = "codem-inventory",
OrigenInv = "origen_inventory",
OXLibExport = "ox_lib", TgiannInv = "tgiann-inventory",
JPRInv = "jpr-inventory",
QBMenuExport = "qb-menu",
OXLibExport = "ox_lib",
QBTargetExport = "qb-target",
OXTargetExport = "ox_target" QBMenuExport = "qb-menu",
}
QBTargetExport = "qb-target",
-- Required variables OXTargetExport = "ox_target",
debugMode = Config.System.Debug
-- REDM
QBInvNew = true RSGExport = "rsg-core",
RSGInv = "rsg-inventory",
InventoryWeight = 120000
VorpExport = "vorp_core",
-- Load files here into the invoking script VorpInv = "vorp_inventory",
for _, v in pairs({ -- This is a specific load order VorpMenu = "vorp_menu",
'helpers.lua', -- needs to be first
'_loaders.lua', }
'_eventDebug.lua', -- Required variables
'coreloader.lua', -- needs to be second to load all core related stuff before everything else debugMode = Config.System.Debug
'callback.lua',
QBInvNew = true
'duifunctions.lua',
InventoryWeight = 120000
-- Native Scaleforms
'scaleforms/bigMessageInstance.lua', -- [[ Server.Cfg Convar Check ]]--
'scaleforms/countDownHandler.lua', -- Check server convars for hard set defaults, otherwise it uses per script configurations
'scaleforms/debugScaleform.lua', if Config and Config.System then
'scaleforms/instructionalButtons.lua', if Config.System.Debug then
'scaleforms/timerBars.lua', if GetConvar("jim_DisableDebug", "false") == "true" then
debugMode = false
-- Required functions end
'make/loaders.lua', if GetConvar("jim_DisableEventDebug", "false") == "true" then
'make/makeBlip.lua', Config.System.EventDebug = false
'make/makePed.lua', end
'make/makeProp.lua', end
'make/makeVeh.lua',
'make/cameras.lua', Config.System.Menu = GetConvar("jim_menuScript", Config.System.Menu)
'make/progressBars.lua', Config.System.Notify = GetConvar("jim_notifyScript", Config.System.Notify or "gta")
Config.System.ProgressBar = GetConvar("jim_progressBarScript", Config.System.ProgressBar or "gta")
'wrapperfunctions.lua', Config.System.drawText = GetConvar("jim_drawTextScript", Config.System.drawText or "gta")
'polyZone.lua', Config.System.skillCheck = GetConvar("jim_skillCheckScript", Config.System.skillCheck or "gta")
'itemcontrol.lua',
'playerfunctions.lua', if GetConvar("jim_dontUseTarget", "false") == "true" then
'jobfunctions.lua', Config.System.DontUseTarget = true
end
-- Interactions end
'targets.lua',
'contextmenus.lua', -- Load the core files here instead of in fxmanifests
'input.lua', -- Forcefully loads the files and quietly, compared to fxmanifest complaining if they don't exist when you don't need them all
'notify.lua', local filesToLoad = {
'drawText.lua', Shared = {
[Exports.OXLibExport] = { "init.lua" },
-- Crafting / Shops / Stashes [Exports.OXCoreExport] = { "lib/init.lua" },
'crafting.lua', [Exports.ESXExport] = { "imports.lua" },
'stashcontrol.lua', [Exports.QBXExport] = { "modules/playerdata.lua" },
},
-- Kind of "other" Client = {
'isAnimal.lua', ["PolyZone"] = { "client.lua", "BoxZone.lua", "EntityZone.lua", "CircleZone.lua", "ComboZone.lua" },
'scaleEntity.lua', ["warmenu"] = { "warmenu.lua", },
'vehicles.lua', },
'effects.lua', }
'versioncheck.lua'
}) do -- Shared framework file loader
if debugMode then for Type, ScriptTable in pairs(filesToLoad) do
print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...") if Type == "Client" and IsDuplicityVersion() then
end goto continue
local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v))) else
fileLoader() for scriptName, files in pairs(ScriptTable) do
if debugMode then local state = GetResourceState(scriptName)
print("^5Success^7: ^2loaded file^7: 'jim_bridge/shared/"..v.."'!") -- if the resource is started, load the file
end 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',
'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',
-- Kind of "other"
'isAnimal.lua',
'scaleEntity.lua',
'vehicles.lua',
'effects.lua',
'make/ropeControl.lua',
--'warmenu.lua',
'_authToken.lua',
-- Do version check last
'_scriptversioncheck.lua'
}) do
if debugMode then
--print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
end
local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v)))
fileLoader()
if debugMode then
print("^5CoreLoader^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7")
end
end
-- Auto Detection of Old/New QBInv/PSInv versions --
if isStarted(QBInv) then
if isServer() then
if not checkExportExists(QBInv, "CreateShop") then
print("^6Bridge^7: ^2Can^7'^2t ^2find new ^4"..QBInv.."^2 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
else
if checkExportExists(QBInv, "GetTrunkSize") then
print("^6Bridge^7: ^2Found old ^4"..QBInv.."^3 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
end
end
if isStarted(PSInv) then
if isServer() then
if not checkExportExists(PSInv, "CreateShop") then
print("^6Bridge^7: ^2Can^7'^2t ^2find new ^4"..PSInv.."^2 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
else
local function versionCompare(v1, v2)
local split = function(v) local t = {} for s in v:gmatch("[^.]+") do t[#t+1] = tonumber(s) end return t end
local a, b = split(v1), split(v2)
for i = 1, math.max(#a, #b) do
local ai, bi = a[i] or 0, b[i] or 0
if ai > bi then return true
elseif ai < bi then return false end
end
return true
end
if versionCompare(GetResourceMetadata(PSInv, "version", 0), "1.0.6") then
print("^6Bridge^7: ^4"..PSInv.."^2 Version above ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1true^7")
QBInvNew = true
else
print("^6Bridge^7: ^4"..PSInv.."^2 Version ^1below ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
QBInvNew = false
end
end
end end

View File

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

345
ui_modules/progressbar.lua Normal file
View File

@@ -0,0 +1,345 @@
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
if percentage < 0 then percentage = 0 end
if percentage > 100 then percentage = 100 end
-- Draw your segmented progress bar
ShowRedProgressBar(percentage, 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(percentage, title, level)
local loc = vec2(0.40, 0.90)
local size = vec2(0.3, 0.03)
local tickCount = 10 -- visually split into 10 segments (9 inner lines)
local barHeight = size.y / 3.4
local barWidth = 0.21 -- tuned to your existing layout
local barLeft = (loc.x - size.x / 4) + 0.075
local barCenter = barLeft + barWidth / 2
local lineW = 0.001
local lineH = barHeight + 0.00
-- Background plate
DrawSprite("generic_textures", "inkroller_1a", loc.x + 0.1, loc.y - 0.01, 0.25, 0.07, 180.0, 0, 0, 0, 200)
-- Title (left)
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)
-- Percentage (right)
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)
-- Track (bg)
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
-- Fill
local fillWidth = barWidth * (percentage / 100.0)
if fillWidth > 0.0 then
local fillCenter = barLeft + (fillWidth / 2.0)
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 255, 0, 0, 200) -- red fill
end
-- Tick lines (9 inner lines for 10 segments)
for i = 1, (tickCount - 1) do
local x = barLeft + (barWidth * (i / tickCount))
DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
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
if percentage < 0 then percentage = 0 end
if percentage > 100 then percentage = 100 end
-- Draw your segmented progress bar
ShowGTAProgressBar(percentage, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress
DisablePlayerFiring(PlayerId(), 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
result = inProgress
end)
-- Wait for completion or cancel
while result == nil do Wait(10) end
inProgress = false
-- Cleanup animations/tasks
if data.dict then
stopAnim(data.dict, data.anim, ped)
end
if data.task then
ClearPedTasks(ped)
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(percentage, title, level)
local loc = vec2(0.37, 0.90)
local size = vec2(0.3, 0.03)
local tickCount = 10
local barHeight = size.y / 3.4
local barWidth = 0.19
local barLeft = (loc.x - size.x / 4) + 0.075
local barCenter = barLeft + barWidth / 2
local lineW = 0.001
local lineH = barHeight + 0.00
-- Background plates
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)
-- Title (left)
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)
-- Percentage (right)
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)
-- Track (bg)
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
-- Fill
local fillWidth = barWidth * (percentage / 100.0)
if fillWidth > 0.0 then
local fillCenter = barLeft + (fillWidth / 2.0)
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 93, 182, 229, 255) -- GTA blue
end
-- Tick lines
for i = 1, (tickCount - 1) do
local x = barLeft + (barWidth * (i / tickCount))
DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
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)

373
ui_modules/target.lua Normal file
View File

@@ -0,0 +1,373 @@
-- 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] = "."
}
-- ===== Ownership + indexes =====
-- TextTargets: key -> target data (coords/entity/models/options/etc.)
local TextTargets = {}
-- targetEntities kept for parity (not strictly required)
local targetEntities = {}
-- registry: key -> owner, owner -> set(keys)
local TargetRegistry = { byKey = {}, byResource = {} }
local function getOwnerResource()
return GetInvokingResource() or GetCurrentResourceName() or "unknown"
end
local function registerTarget(owner, key)
-- move key across owners if replacing
local prev = TargetRegistry.byKey[key]
if prev and TargetRegistry.byResource[prev] then
TargetRegistry.byResource[prev][key] = nil
end
TargetRegistry.byKey[key] = owner
TargetRegistry.byResource[owner] = TargetRegistry.byResource[owner] or {}
TargetRegistry.byResource[owner][key] = true
end
local function removeTargetKey(key, reason)
local owner = TargetRegistry.byKey[key]
if TextTargets[key] then
TextTargets[key] = nil
-- print("^6Bridge^7:^5 Target^7: ^2Removed target '%s'%s", key, reason and (" ("..reason..")") or "")
end
if owner then
if TargetRegistry.byResource[owner] then
TargetRegistry.byResource[owner][key] = nil
end
TargetRegistry.byKey[key] = nil
end
end
AddEventHandler("onResourceStop", function(res)
local owned = TargetRegistry.byResource[res]
if not owned then return end
local cnt = 0
for key in pairs(owned) do
removeTargetKey(key, "resource stopped: "..res)
cnt = cnt + 1
end
TargetRegistry.byResource[res] = nil
-- print("^6Bridge^7:^5 Target^7: ^2Cleared "..cnt.." target(s) from '"..res.."'")
end)
-- ===== Helpers =====
local function vecKey(v)
-- stable rounded coord string for entity dedupe when name isn't provided
return ("%.3f,%.3f,%.3f"):format(v.x, v.y, v.z)
end
local function bakeButtons(opts)
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] =
(" ~b~[~w~%s~b~] ~w~%s"):format(Keys[opts[i].key] or ("K"..opts[i].key), opts[i].label or ("Option "..i))
end
return tempText
end
-- Update cached text blob
local function updateCachedText(target)
target.text = table.concat(target.buttontext, "\n")
end
-- ===== Public API: Create targets =====
-- ENTITY: createEntityTarget(entity, opts, dist, nameOpt?)
function createEntityTarget(entity, opts, dist, name)
startTargetLoop()
if not entity or entity == 0 then return end
targetEntities[#targetEntities + 1] = entity
local owner = getOwnerResource()
local coords = GetEntityCoords(entity)
local key = tostring(name) or ("entity@" .. vecKey(coords))
-- Always overwrite on same key
local buttontext = bakeButtons(opts)
TextTargets[key] = {
_key = key,
_type = "entity",
_owner = owner,
entity = entity,
coords = vec3(coords.x, coords.y, coords.z),
buttontext = buttontext,
options = opts,
dist = dist,
}
updateCachedText(TextTargets[key])
registerTarget(owner, key)
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ENTITY target '"..key.."' by '"..owner.."' @ "..formatCoord(coords))
return key
end
-- ZONE: createZoneTarget(data, opts, dist)
-- Expect data[1] = id/name, data[2] = vec3 coords (as in your original)
function createZoneTarget(data, opts, dist)
startTargetLoop()
local owner = getOwnerResource()
local zname = tostring(data[1] or ("zone@"..vecKey(data[2] or vec3(0,0,0))))
local coords = data[2]
local buttontext = bakeButtons(opts)
TextTargets[zname] = {
_key = zname,
_type = "zone",
_owner = owner,
coords = coords,
buttontext = buttontext,
options = opts,
dist = dist,
}
updateCachedText(TextTargets[zname])
registerTarget(owner, zname)
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ZONE target '"..zname.."' by '"..owner.."' @ "..formatCoord(coords))
return zname
end
-- MODEL: createModelTarget(models, opts, dist, nameOpt?)
function createModelTarget(models, opts, dist, name)
startTargetLoop()
local owner = getOwnerResource()
if type(models) ~= "table" then models = { models } end
local key
if name then
key = tostring(name)
else
local parts = {}
for i, m in ipairs(models) do parts[i] = tostring(m) end
key = "model_" .. table.concat(parts, "_")
end
local buttontext = bakeButtons(opts)
TextTargets[key] = {
_key = key,
_type = "model",
_owner = owner,
models = models,
buttontext = buttontext,
options = opts,
dist = dist,
coords = vec3(0, 0, 0), -- will be updated by the refresher
}
updateCachedText(TextTargets[key])
registerTarget(owner, key)
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated MODEL target '"..key.."' by '"..owner.."' (models: "..table.concat(models, ",")..")")
return key
end
-- ===== Public API: Remove targets =====
-- Entity removal accepts entity handle or key string.
function removeEntityTarget(entityOrKey)
local key = nil
if type(entityOrKey) == "string" then
key = entityOrKey
elseif type(entityOrKey) == "number" then
for k, t in pairs(TextTargets) do
if t._type == "entity" and t.entity == entityOrKey then key = k break end
end
if not key then
-- Fallback: try coord-key match
local c = GetEntityCoords(entityOrKey)
local guess = "entity@"..vecKey(c)
if TextTargets[guess] then key = guess end
end
end
if key then removeTargetKey(key, "removeEntityTarget") end
end
function removeZoneTarget(key)
if not key then return end
removeTargetKey(key, "removeZoneTarget")
end
-- For models, pass the returned key from createModelTarget (recommended).
function removeModelTarget(key)
if not key then return end
removeTargetKey(key, "removeModelTarget")
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
started = true
-- lazy include (unchanged from your original)
Config = { System = {} }
local fileLoader = assert(load(LoadResourceFile("jim_bridge", ('starter.lua')), ('@@jim_bridge/starter.lua')))
fileLoader()
-- Model Entity Refresher (kept)
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 (unchanged logic, just uses new TextTargets entries)
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)
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
local txt = totalLength > textMaxLength and text:sub(1, textMaxLength) or text
AddTextComponentString(highlight and txt:gsub("%~w~", "~y~") or txt)
SetDrawOrigin(coord.x, coord.y, coord.z, 0)
DrawText(0.0, 0.0)
local count, length = GetLineCountAndMaxLength(txt)
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
function GetLineCountAndMaxLength(text)
local lineCount, maxLength = 0, 0
for line in text:gmatch("[^\n]+") do
lineCount = 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

View File

@@ -1 +1,7 @@
1.2 2.1.05
- Fix "true/false" print from built-in gta progressbars
- Revert Crafting system to previous style (I was too quick to do this)
- Fix singleprogress multiplying crafting recipes
https://github.com/jimathy/jim_bridge/releases/latest