Compare commits

...

95 Commits

Author SHA1 Message Date
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
51 changed files with 12416 additions and 7795 deletions

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

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

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

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

1804
README.md Normal file

File diff suppressed because it is too large Load Diff

54
_versioncheck.lua Normal file
View File

@@ -0,0 +1,54 @@
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/jim_bridge/master/version.txt', function(err, newestVersionRaw, headers)
if not newestVersionRaw then
print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)")
return
end
newestVersionRaw = newestVersionRaw:match("[^\r\n]+")
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
-- Made the check a bit more obvious
print("^1----------------------------------------------------------------------^7")
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function()
-- Do a naughty repeat message every 20 minutes until the the script is updated
CheckBridgeVersion()
end)
else
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
end
end)
end)
end
end
CheckBridgeVersion()

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,88 @@
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 -- equal
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
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..script..'/master/version.txt', function(err, fallbackVersionRaw, headers)
if not fallbackVersionRaw then
print("^1Currently unable to run a version check for ^7'^3"..script.."^7' (^3"..currentVersionRaw.."^7)")
return
end
fallbackVersionRaw = fallbackVersionRaw:match("[^\r\n]+"):gsub("v", "")
local compareResult = compareVersions(currentVersionRaw, fallbackVersionRaw)
if compareResult == 0 then
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersionRaw.."^7)")
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function()
-- Do a naughty repeat message every 20 minutes until the the script is updated
CheckVersion()
end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersionRaw.."^7)")
end
end)
else
newestVersionRaw = newestVersionRaw:match("[^\r\n]+"):gsub("v", "")
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
if compareResult == 0 then
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function()
-- Do a naughty repeat message every 20 minutes until the the script is updated
CheckVersion()
end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
end
end
end)
end)
end
end
CheckVersion()

View File

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

View File

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

View File

@@ -1,170 +1,284 @@
-- Create empty Variables --
Items, Vehicles, Jobs, Gangs, Core, ESX = {}, nil, nil, nil, nil, nil
-- Correct QB inventory export (if needed) from 'qb-inventory' to 'ps-inventory' or 'lj-inventory' --
Exports.QBInv = (isStarted("ps-inventory") and "ps-inventory") or (isStarted("lj-inventory") and "lj-inventory") or Exports.QBInv
-- Create simple variables based on the corresponding framework exports --
OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport = Exports.OXLibExport or "", Exports.QBXExport or "", Exports.QBExport or "", Exports.ESXExport or "", Exports.OXCoreExport or ""
-- Create simple variables based on the corresponding inventory names --
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 ""
-- QB-Menu export name grabbed from exports.lua --
QBMenuExport = Exports.QBMenuExport or ""
-- Target exports based on what is loaded --
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
-- If Debug mode is on in the loading script, print the list of found exports --
-- Some may "lie", 'ox_target' attempts to use 'qb-target' exports and this print will say its loaded (which is technically true) --
for _, v in pairs(Exports) do
if isStarted(v) then debugPrint("^6Bridge^7: '^3"..v.."^7' ^2export found ^7") end
end
local itemResource, jobResource, vehResource = "", "", ""
-- Load item lists --
-- Complies the items from ox_inventory, qb-core or esx into 'Items' and loads them in a layout similar to qb-core's Shared items.lua --
-- 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
Items = exports[OXInv]:Items()
for k, v in pairs(Items) do
if v.client and v.client.image then
Items[k].image = (v.client.image):gsub("nui://"..OXInv.."/web/images/", "")
else
Items[k].image = k..".png"
end
Items[k].hunger = v.client and v.client.hunger or nil
Items[k].thirst = v.client and v.client.thirst or nil
end
elseif isStarted(QBExport) then itemResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = Core or exports[QBExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
end)
end
elseif isStarted(ESXExport) then itemResource = ESXExport
ESX = exports[ESXExport]:getSharedObject()
Items = ESX and ESX.Items or nil
end
-- If it fails to load items, then it will print the error below --
-- If it loads them and debug is on, print how many items and where from --
if not Items then
print("^4ERROR^7: ^2No Core Items detected ^7- ^2Check ^3exports^1.^2lua^7")
else
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7" .. itemResource)
end
-- Load Vehicles --
-- Complies the vehicles from the detected frameworks into a table in the style of qb-cores shared vehicles.lua --
-- For example, instead of using QBCore.Shared.Vehicles[vehicle] you can load 'Vehicles[vehicle]' in the script --
if isStarted(QBXExport) or isStarted(QBExport) then
Core = Core or exports[QBExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = Core or exports[QBExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
end)
end
vehResource = QBExport
elseif isStarted(OXCoreExport) then
Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
Vehicles[k] = { model = k, price = v.price, name = v.name, brand = v.make }
end
vehResource = OXCoreExport
elseif isStarted(ESXExport) then
-- print("^6Bridge^7: ^2Loading ^3Vehicles^2 from ^7"..ESXExport)
-- print("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..ESXExport)
CreateThread(function()
if isServer() then
createCallback(getScript()..":getVehiclesPrices", function(source)
Vehicles = MySQL.query.await('SELECT model, price, name FROM vehicles')
vehResource = ESXExport
return Vehicles
end)
end
if not isServer() then
local TempVehicles = triggerCallback(getScript()..":getVehiclesPrices")
for _, v in pairs(TempVehicles) do
Vehicles = Vehicles or {}
Vehicles[v.model] = { model = v.model, price = v.price, name = v.name, brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper) }
end
end
end)
end
if vehResource == nil then
print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3exports^1.^2lua^7")
else
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..vehResource)
end
-- Load Jobs --
-- Attempts to load the details of jobs and gangs and compile into tables --
-- For example, instead of using QBCore.Shared.Jobs[job] you can load 'Jobs[job]' in the script --
if isStarted(QBXExport) then jobResource = QBXExport
Core = Core or exports[QBExport]:GetCoreObject()
Jobs, Gangs = exports[QBXExport]:GetJobs(), exports[QBXExport]:GetGangs()
elseif isStarted(OXCoreExport) then jobResource = OXExport
CreateThread(function()
if isServer() then
createCallback(getScript()..":getOxGroups", function(source)
Jobs = MySQL.query.await('SELECT * FROM `ox_groups`') return Jobs
end)
else
local TempJobs = triggerCallback(getScript()..":getOxGroups")
Jobs = TempJobs or {}
for k, v in pairs(TempJobs) do
local grades = {}
for i = 1, #v.grades do grades[i] = { name = v.grades[i], isboss = (i == #v.grades)} end
Jobs[v.name] = { label = v.label, grades = grades }
end
Gangs = Jobs
end
end)
elseif isStarted(QBExport) then jobResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = exports[QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end)
end
elseif isStarted(ESXExport) then
--print("^6Bridge^7: ^2Loading ^3Jobs^7/^3Gangs^2 from ^7"..ESXExport)
ESX = exports[ESXExport]:getSharedObject()
if isServer() then
Jobs = ESX.GetJobs()
for k, v in pairs(Jobs) do
local count = countTable(Jobs[k].grades)-1
Jobs[k].grades[tostring(count)].isBoss = true
end
Gangs = Jobs
end
CreateThread(function()
while not ESX do Wait(0) end
if isServer() then
createCallback(getScript()..":getJobs", function(source)
return Jobs
end)
end
if not isServer() then
Jobs = triggerCallback(getScript()..":getJobs")
Gangs = Jobs
end
end)
end
if not isStarted(ESXExport) and Jobs then
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Jobs).." ^3Jobs^2 from ^7"..jobResource, "^6Bridge^7: ^2Loading ^6"..countTable(Gangs).." ^3Gangs^2 from ^7"..jobResource)
--[[
Resource Initialization Module
--------------------------------
This module initializes and loads shared data (Items, Vehicles, Jobs, Gangs) from the
various frameworks/inventory systems (OX, QB, ESX, etc.). It also corrects export names,
caches framework exports into simple variables, and prints debug information if enabled.
]]
-------------------------------------------------------------
-- Global Variable Initialization
-------------------------------------------------------------
Items, Vehicles, Jobs, Gangs, Core = {}, nil, nil, nil, nil
-------------------------------------------------------------
-- Correct QB Inventory Export
-------------------------------------------------------------
-- Correct ps-invetory to ls-inventory if somehow you still have that
Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv
-------------------------------------------------------------
-- Framework Exports and Inventory Identifiers
-------------------------------------------------------------
OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
Exports.OXLibExport or "",
Exports.QBXExport or "",
Exports.QBExport or "",
Exports.ESXExport or "",
Exports.OXCoreExport or ""
OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv =
Exports.OXInv or "",
Exports.QBInv or "",
Exports.PSInv or "",
Exports.QSInv or "",
Exports.CoreInv or "",
Exports.CodeMInv or "",
Exports.OrigenInv or "",
Exports.TgiannInv or ""
RSGExport, RSGInv =
Exports.RSGExport or "",
Exports.RSGInv or ""
QBMenuExport = Exports.QBMenuExport or ""
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
-------------------------------------------------------------
-- Debug: Print Found Exports
-------------------------------------------------------------
-- Print a list of all exports that are currently started (if debugMode is enabled).
for _, v in pairs(Exports) do
if isStarted(v) then
debugPrint("^6Bridge^7: '^3"..v.."^7' export found")
end
end
OxPlayer = nil
if isStarted(OXCoreExport) then
if not isServer() then
OxPlayer = Ox.GetPlayer()
end
end
-------------------------------------------------------------
-- Resource Variables for Items, Jobs, and Vehicles
-------------------------------------------------------------
local itemResource, jobResource, vehResource = "", "", ""
-------------------------------------------------------------
-- Loading Items
-------------------------------------------------------------
-- Load and compile shared items from the detected inventory system.
if isStarted(OXInv) then
itemResource = OXInv
Items = exports[OXInv]:Items()
for k, v in pairs(Items) do
if v.client and v.client.image then
Items[k].image = (v.client.image):gsub("nui://"..OXInv.."/web/images/", "")
else
Items[k].image = k..".png"
end
Items[k].hunger = v.client and v.client.hunger or nil
Items[k].thirst = v.client and v.client.thirst or nil
end
elseif isStarted(QBExport) then
itemResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = Core or exports[QBExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
end)
end
elseif isStarted(ESXExport) then
itemResource = ESXExport
CreateThread(function()
if isServer() then
Items = ESX.GetItems()
while not createCallback do Wait(100) end
createCallback(getScript()..":getItems", function(source)
return Items
end)
end
if not isServer() then
Items = triggerCallback(getScript()..":getItems")
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7"..itemResource)
end
end)
elseif isStarted(RSGExport) then
itemResource = RSGExport
Core = Core or exports[RSGExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
RegisterNetEvent('RSGCore:Client:UpdateObject', function()
Core = Core or exports[RSGExport]:GetCoreObject()
Items = Core and Core.Shared.Items or nil
end)
end
if itemResource == nil then
print("^4ERROR^7: ^2No Item info detected ^7- ^2Check ^3starter^1.^2lua^7")
else
while not Items do Wait(100) end
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Items).." ^3Items^2 from ^7"..itemResource)
end
-------------------------------------------------------------
-- Loading Vehicles
-------------------------------------------------------------
-- Compile vehicles from the detected frameworks into a unified table.
if isStarted(QBXExport) or isStarted(QBExport) then
vehResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = Core or exports[QBExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
end)
end
elseif isStarted(OXCoreExport) then
vehResource = OXCoreExport
Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
Vehicles[k] = { model = k, hash = GetHashKey(k), price = v.price, name = v.name, brand = v.make }
end
elseif isStarted(ESXExport) then
vehResource = ESXExport
CreateThread(function()
if isServer() then
createCallback(getScript()..":getVehiclesPrices", function(source)
return Vehicles
end)
while not MySQL do Wait(2000) print("^1Waiting for MySQL to exist") end
Vehicles = MySQL.query.await('SELECT model, price, name FROM vehicles')
end
if not isServer() then
--while not triggerCallback do print("waiting") Wait(100) end
local TempVehicles = triggerCallback(getScript()..":getVehiclesPrices")
for _, v in pairs(TempVehicles) do
Vehicles = Vehicles or {}
Vehicles[v.model] = {
model = v.model,
hash = GetHashKey(v.model),
price = v.price,
name = v.name,
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
}
end
end
end)
elseif isStarted(RSGExport) then
vehResource = RSGExport
Core = Core or exports[RSGExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
RegisterNetEvent('RSGCore:Client:UpdateObject', function()
Core = Core or exports[RSGExport]:GetCoreObject()
Vehicles = Core and Core.Shared.Vehicles
end)
end
if vehResource == nil then
print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3starter^1.^2lua^7")
else
CreateThread(function()
while not Vehicles do Wait(1000) end
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Vehicles).." ^3Vehicles^2 from ^7"..vehResource)
end)
end
-------------------------------------------------------------
-- Loading Jobs and Gangs
-------------------------------------------------------------
-- Compile jobs and gangs from the detected framework.
if isStarted(QBXExport) then
jobResource = QBXExport
Core = Core or exports[QBExport]:GetCoreObject()
Jobs, Gangs = exports[QBXExport]:GetJobs(), exports[QBXExport]:GetGangs()
elseif isStarted(OXCoreExport) then
jobResource = OXExport
CreateThread(function()
if isServer() then
createCallback(getScript()..":getOxGroups", function(source)
Jobs = MySQL.query.await('SELECT * FROM `ox_groups`')
return Jobs
end)
else
local TempJobs = triggerCallback(getScript()..":getOxGroups")
Jobs = {}
for k, v in pairs(TempJobs) do
local grades = {}
--for i = 1, #v.grades do
-- grades[i] = { name = v.grades[i], isboss = (i == #v.grades) }
--end
Jobs[v.name] = { label = v.label, grades = grades }
end
Gangs = Jobs
end
end)
elseif isStarted(QBExport) then
jobResource = QBExport
Core = Core or exports[QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
if isStarted(QBExport) and not isStarted(QBXExport) then
RegisterNetEvent('QBCore:Client:UpdateObject', function()
Core = exports[QBExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end)
end
elseif isStarted(ESXExport) then
jobResource = ESXExport
if isServer() then
-- If server, create callback to get jobs
createCallback(getScript()..":getESXJobs", function(source)
return Jobs
end)
-- Populate jobs table with ESX.GetJobs()
Jobs = ESX.GetJobs()
--If retreived jobs is empty, wait for ESX to load
while countTable(Jobs) == 0 do
Jobs = ESX.GetJobs()
Wait(100)
end
-- Organise into a table the script can use
for k, v in pairs(Jobs) do
local count = countTable(Jobs[k].grades) - 1
Jobs[k].grades[tostring(count)].isBoss = true
end
-- ESX Default doesn't have gangs, so copy jobs to gangs
Gangs = Jobs
end
-- If client side, trigger callback to get jobs
if not isServer() then
Jobs = triggerCallback(getScript()..":getESXJobs")
Gangs = Jobs
end
elseif isStarted(RSGExport) then
jobResource = RSGExport
Core = Core or exports[RSGExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
RegisterNetEvent('RSGCore:Client:UpdateObject', function()
Core = exports[RSGExport]:GetCoreObject()
Jobs, Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end)
end
if jobResource == nil then
print("^4ERROR^7: ^2No Vehicle info detected ^7- ^2Check ^3starter^1.^2lua^7")
else
while not Jobs do Wait(1000) end
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Jobs).." ^3Jobs^2 from ^7"..jobResource)
debugPrint("^6Bridge^7: ^2Loading ^6"..countTable(Gangs).." ^3Gangs^2 from ^7"..jobResource)
end

View File

@@ -1,481 +1,494 @@
local CraftLock = false
--- Opens a crafting menu based on the provided data.
---
--- This function checks job requirements, prepares the menu options, and opens the crafting menu.
--- It handles item availability, crafting recipes, and displays appropriate icons and labels.
---
---@param data table A table containing crafting menu data.
--- - **craftable** (`table`): The crafting options and settings.
--- - **Header** (`string`): The header/title of the crafting menu.
--- - **Recipes** (`table`): A list of crafting recipes.
--- - **coords** (`vector3`): The coordinates where the crafting menu is being opened.
--- - **stashTable** (`string` or `table`, optional): The stash name(s) to check for item availability.
--- - **stashName** (`string` or `table`, optional): Alias for `stashTable`.
--- - **job** (`string` or `table`, optional): Job(s) required to access the crafting menu.
--- - **gang** (`string` or `table`, optional): Gang(s) required to access the crafting menu.
--- - **onBack** (`function`, optional): Function to call when returning from the menu.
---
---@usage
--- ```lua
--- craftingMenu({
--- craftable = {
--- Header = "Weapon Crafting",
--- Recipes = {
--- [1] = {
--- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
--- amount = 1,
--- },
--- -- More recipes...
--- },
--- Anims = {
--- animDict = "amb@prop_human_parking_meter@male@idle_a",
--- anim = "idle_a",
--- },
--- },
--- coords = vector3(100.0, 200.0, 300.0),
--- stashTable = "crafting_stash",
--- job = "mechanic", -- Optional
--- onBack = function() print("Returning to previous menu") end,
--- })
--- ```
function craftingMenu(data)
if CraftLock then return end
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
if Config.System.Menu == "jim" then
triggerNotify(nil, "Thinking", "info")
else
openMenu({ { header = "Thinking...", icon = "fas fa-hourglass-end", isMenuHeader = true } }, { header = "Crafting Menu" } )
end
if data.stashTable then data.stashName = data.stashTable end
local Menu, hasjob = {}, false
local Recipes = data.craftable.Recipes
local tempCarryTable = {}
for i = 1, #Recipes do
for k in pairs(Recipes[i]) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
tempCarryTable[k] = Recipes[i].amount or 1
end
end
end
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
for k, v in pairs(Recipes[i]) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
if Recipes[i].job then
for l, b in pairs(Recipes[i].job) do
hasjob = hasJob(l, nil, b)
if hasjob == true then break end
end
else hasjob = true end
local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or nil)
if hasjob then
local itemTable = {}
local metaTable = {}
for l, b in pairs(Recipes[i][tostring(k)]) do
settext = settext..(settext ~= "" and br or "")..(Items[l] and Items[l].label or "error - "..l)..(b > 1 and " x"..b or "")
metaTable[Items[l] and Items[l].label or "error - "..l] = b
itemTable[l] = b
Wait(0)
end
while not canCarryTable do Wait(0) end
disable = not checkHasItem(data.stashName, itemTable)
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - " .. tostring(k)) .. (Recipes[i]["amount"] > 1 and " x" .. Recipes[i]["amount"] or "")
if not disable then
if not canCarryTable[k] then setheader = setheader .. " 📦"
else setheader = setheader .. " ✔️" end
elseif not canCarryTable[k] then setheader = setheader .. " 📦" end
Menu[#Menu + 1] = {
arrow = not disable and canCarryTable[k],
disable = isStarted(QBMenuExport) and disable and not canCarryTable[k],
icon = invImg((metadata and metadata.image) or tostring(k)),
image = invImg((metadata and metadata.image) or tostring(k)),
header = setheader..((disable or not canCarryTable[k]) and "" or ""),
txt = isStarted(QBMenuExport) and settext or nil,
--metadata = debugMode and Recipes[i]["metadata"] or nil,
metadata = metaTable,
onSelect = ((not disable and canCarryTable[k]) and (function()
local transdata = { item = k, craft = data.craftable.Recipes[i], craftable = data.craftable, coords = data.coords, stashName = data.stashName, onBack = data.onBack, metadata = Recipes[i]["metadata"] }
if Config.Crafting.MultiCraft then multiCraft(transdata) else makeItem(transdata) end
end) or nil),
}
end
end
Wait(0)
end
end
openMenu(Menu, { header = data.craftable.Header, onBack = data.onBack or nil, canClose = true, onExit = function() end, })
lookEnt(data.coords)
end
--- Opens a menu for selecting the quantity to craft.
---
--- This function presents the player with options to craft multiple quantities of an item, based on `Config.Crafting.MultiCraftAmounts`.
---
---@param data table A table containing crafting data.
--- - **item** (`string`): The item to craft.
--- - **craft** (`table`): The crafting recipe for the item.
--- - **craftable** (`table`): The crafting options and settings.
--- - **coords** (`vector3`): The coordinates where the crafting is taking place.
--- - **stashName** (`string` or `table`, optional): The stash name(s) to check for item availability.
--- - **onBack** (`function`, optional): Function to call when returning from the menu.
--- - **metadata** (`table`, optional): Metadata for the crafted item.
---
---@usage
--- ```lua
--- multiCraft({
--- item = "weapon_pistol",
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
--- craftable = craftingOptions,
--- coords = vector3(100.0, 200.0, 300.0),
--- stashName = "crafting_stash",
--- onBack = function() craftingMenu(data) end,
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- })
--- ```
function multiCraft(data)
local Menu = {}
local success = Config.Crafting.MultiCraftAmounts
local metadata = data.metadata or nil
Menu[#Menu+1] = {
isMenuHeader = true,
icon = invImg(metadata and metadata.image or data.item),
header = metadata and metadata.label or Items[data.item].label,
}
for k in pairsByKeys(success) do
local settext = ""
local itemTable = {}
for l, b in pairs(data.craft[data.item]) do
itemTable[l] = (b * k)
settext = settext..(settext ~= "" and br or "")..Items[l].label..(b*k > 1 and "- x"..b*k or "")
Wait(0)
end
local disable, stashname = checkHasItem(data.stashName, itemTable)
Menu[#Menu + 1] = {
isMenuHeader = not disable,
arrow = disable,
header = "Craft - x"..k * data.craft.amount,
txt = settext,
onSelect = function ()
makeItem({item = data.item, craft = data.craft, craftable = data.craftable, amount = k, coords = data.coords, stashName = stashname, stashTable = data.stashName, onBack = data.onBack, metadata = data.metadata })
end,
}
end
openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end, })
end
--- Initiates the crafting process for a specified item.
---
--- This function handles the crafting animation, progress bar, item removal, and item creation.
---
---@param data table A table containing crafting data.
--- - **item** (`string`): The item to craft.
--- - **craft** (`table`): The crafting recipe for the item.
--- - **craftable** (`table`): The crafting options and settings.
--- - **amount** (`number`, optional): The quantity to craft. Default is `1`.
--- - **coords** (`vector3`): The coordinates where the crafting is taking place.
--- - **stashName** (`string` or `table`, optional): The stash name(s) to remove items from.
--- - **stashTable** (`string` or `table`, optional): Alias for `stashName`.
--- - **onBack** (`function`, optional): Function to call when returning from the menu.
--- - **metadata** (`table`, optional): Metadata for the crafted item.
---
---@usage
--- ```lua
--- makeItem({
--- item = "weapon_pistol",
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
--- craftable = craftingOptions,
--- amount = 2,
--- coords = vector3(100.0, 200.0, 300.0),
--- stashName = "crafting_stash",
--- onBack = function() craftingMenu(data) end,
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- })
--- ```
function makeItem(data)
if CraftLock then return end
CraftLock = true
if data.stashTable then data.stashName = data.stashTable end
local bartime = data.craftable.progressBar and data.craftable.progressBar.time or 5000
local bartext = (data.craftable.progressBar and data.craftable.progressBar.label) or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"]) or "Making a"
local animDict = data.craftable.Anims and data.craftable.Anims.animDict or "amb@prop_human_parking_meter@male@idle_a"
local anim = data.craftable.Anims and data.craftable.Anims.anim or "idle_a"
local amount = data.amount and (data.amount ~= 1) and data.amount or 1
local metadata = data.metadata or nil
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
local crafted, crafting = true, true
local cam = createTempCam(PlayerPedId(), data.coords)
startTempCam(cam)
for i = 1, amount do
for k, v in pairs(data.craft) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
if type(v) == "table" then
for l, b in pairs(v) do
if crafting and progressBar({
label = "Using "..b.." "..Items[l].label,
time = 1000,
cancel = true,
dict = 'pickup_object',
anim = "putdown_low",
flag = 48,
icon = l,
}) then
TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "").."inventory:client:ItemBox", Items[l], "use", b) -- Show item box for each item
else
crafted, crafting = false, false
break
end
Wait(200)
end
if crafted then
local craftProp = nil
if prop then
local model, pos, rot, bone = prop.model, prop.pos, prop.rot, prop.bone
craftProp = makeProp({ prop = model, coords = vec4(0, 0, 0, 0), true, true })
AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), bone), pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, true, true, false, true, 1, true)
end
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label),
time = bartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
}) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata)
else
crafting = false
break
end
if craftProp then destroyProp(craftProp) end
end
end
end
end
Wait(500)
end
stopTempCam()
CraftLock = false
lockInv(false)
craftingMenu(data)
ClearPedTasks(PlayerPedId())
end
--- Server event handler for giving the crafted item to the player.
---
--- This event is triggered when the crafting process is completed successfully.
---
--- @param ItemMake string The item being crafted.
--- @param craftable table The crafting recipe and details.
--- @param stashName string|table The stash name(s) to remove items from.
--- @param metadata table (optional) Metadata for the crafted item.
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata)
local src, amount, stashItems = source, craftable and craftable.amount or 1, nil
if stashName then
local itemRemove = {}
if type(stashName) == "table" then
for _, name in pairs(stashName) do
stashItems = getStash(name)
for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do
if k == b.name then itemRemove[k] = v end
end
end
end
else
stashItems = getStash(stashName)
for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do
if k == b.name then itemRemove[k] = v end
end
end
end
stashRemoveItem(stashItems, stashName, itemRemove)
else
if craftable then
for k, v in pairs(craftable[ItemMake] or {}) do
TriggerEvent(getScript()..":server:toggleItem", false, tostring(k), v, src)
end
end
end
TriggerEvent(getScript()..":server:toggleItem", true, ItemMake, amount, src, metadata)
--if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
end)
--- Opens a selling menu based on the provided data.
---
--- This function checks available items to sell, prepares the menu options, and opens the selling menu.
---
---@param data table A table containing selling menu data.
--- - **sellTable** (`table`): The selling options and settings.
--- - **Items** (`table`): A list of items that can be sold with their prices.
--- - **Header** (`string`, optional): The header/title of the selling menu.
--- - **ped** (`number`, optional): The ped entity involved in the selling interaction.
--- - **onBack** (`function`, optional): Function to call when returning from the menu.
---
---@usage
--- ```lua
--- sellMenu({
--- sellTable = {
--- Header = "Sell Items",
--- Items = {
--- ["gold_ring"] = 100,
--- ["diamond"] = 500,
--- },
--- },
--- ped = pedEntity,
--- onBack = function() print("Returning to previous menu") end,
--- })
--- ```
function sellMenu(data)
local origData = data
local Menu = {}
if data.sellTable.Items then
local itemList = {}
for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
local _, hasTable = hasItem(itemList)
for k, v in pairsByKeys(data.sellTable.Items) do
Menu[#Menu +1] = {
isMenuHeader = not hasTable[k].hasItem,
icon = invImg(k),
header = Items[k].label.. (hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
txt = Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"],
onSelect = function()
sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
end,
}
end
else
for k, v in pairsByKeys(data.sellTable) do
if type(v) == "table" then
Menu[#Menu +1] = {
arrow = true,
header = k,
txt = "Amount of items: "..countTable(v.Items),
onSelect = function()
v.onBack = function() sellMenu(origData) end
v.sellTable = data.sellTable[k]
sellMenu(v)
end,
}
end
end
end
openMenu(Menu, { header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items), headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items) or "", canClose = true, onBack = data.onBack })
end
--- Handles the selling animation and item transaction.
---
--- This function plays the selling animation, removes the item from the player's inventory, and gives the player money.
---
---@param data table A table containing selling data.
--- - **item** (`string`): The item to sell.
--- - **price** (`number`): The price per item.
--- - **ped** (`number`, optional): The ped entity involved in the selling interaction.
--- - **onBack** (`function`, optional): Function to call when returning from the menu.
---
---@usage
--- ```lua
--- sellAnim({
--- item = "gold_ring",
--- price = 100,
--- ped = pedEntity,
--- onBack = function() sellMenu(data) end,
--- })
--- ```
function sellAnim(data)
if not hasItem(data.item, 1) then
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error")
return
end
for k, v in pairs(GetGamePool('CObject')) do
for _, model in pairs({`p_cs_clipboard`}) do
if GetEntityModel(v) == model then
if IsEntityAttachedToEntity(data.ped, v) then
DeleteObject(v) DetachEntity(v, 0, 0) SetEntityAsMissionEntity(v, true, true)
Wait(100) DeleteEntity(v)
end
end
end
end
TriggerServerEvent(getScript().."Sellitems", data)
lookEnt(data.ped)
local dict = "mp_common"
playAnim(dict, "givetake2_a", 0.3, 2)
playAnim(dict, "givetake2_b", 0.3, 2, data.ped)
Wait(2000)
StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5)
StopAnimTask(data.ped, dict, "givetake2_b", 0.5)
if data.onBack then data.onBack() end
end
--- Server event handler for processing the item sale.
---
--- This event removes the sold item from the player's inventory and adds money to their account.
---
---@param data table The data containing item and price information.
RegisterNetEvent(getScript().."Sellitems", function(data)
local src = source
local hasItems, hasTable = hasItem(data.item, 1, src)
if hasItems then
TriggerEvent(getScript()..":server:toggleItem", false, data.item, hasTable[data.item].count, src)
TriggerEvent(getScript()..":server:FundPlayer", (hasTable[data.item].count * data.price), "cash", src)
else
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src)
end
end)
--- Opens a shop interface for the player.
---
--- This function checks job requirements and opens the shop using the appropriate inventory system.
---
---@param data table A table containing shop data.
--- - **shop** (`string`): The shop identifier.
--- - **items** (`table`): The items available in the shop.
--- - **coords** (`vector3`): The coordinates where the shop interaction is happening.
--- - **job** (`string` or `table`, optional): Job(s) required to access the shop.
--- - **gang** (`string` or `table`, optional): Gang(s) required to access the shop.
---
---@usage
--- ```lua
--- openShop({
--- shop = "weapon_shop",
--- items = weaponShopItems,
--- coords = vector3(100.0, 200.0, 300.0),
--- job = "police",
--- })
--- ```
function openShop(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('shop', { type = data.shop })
elseif isStarted(QBInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:OpenShopNewQB', data.shop) -- i hate qb-inv
else
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
else
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
lookEnt(data.coords)
end
--- Server event handler for opening a new QB inventory shop.
---
--- This event is triggered when using the new QB inventory system.
---
---@param data table The shop data to open.
RegisterNetEvent(getScript()..':server:OpenShopNewQB', function(data)
exports[QBInv]:OpenShop(source, data)
end)
--- Server-side callback registration for checking if the player can carry items.
if isServer() then
createCallback(getScript()..':server:canCarry', function(source, itemTable) local result = canCarry(itemTable, source) return result end)
end
--[[
Crafting, Selling, and Shop Module
-----------------------------------
This module provides functions for opening crafting menus, handling multi-crafting,
performing the crafting process (with animations and progress bars), selling items,
and opening shop interfaces. It integrates with various inventory and menu systems,
and uses server callbacks to check item carry capacity.
]]
-------------------------------------------------------------
-- Global Variables
-------------------------------------------------------------
CraftLock = false
-- helper filter table for crafting menus
local excludeKeys = {
amount = true, metadata = true, description = true, info = true,
job = true, gang = true, oneUse = true, slot = true,
blueprintRef = true, craftingLevel = true, craftedItems = true,
hasCrafted = true, exp = true, anim = true, time = true,
}
-------------------------------------------------------------
-- Crafting Menu
-------------------------------------------------------------
--- Opens the crafting menu based on provided data.
--- Checks job restrictions, builds the recipe menu, and opens the menu.
---
--- @param data table Crafting menu configuration containing:
--- - craftable (`table`) Table with Header, Recipes, Anims, and (optionally) craftedItems.
--- - coords (`vector3`) The coordinates where the crafting menu is being opened.
--- - stashTable|stashName (`string\table`) Name(s) of the stash for checking item availability.
--- - job|gang (`string`) Job or gang requirements.
--- - onBack (optional): Function to call when returning.
---
--- @usage
--- ```lua
--- craftingMenu({
--- craftable = {
--- Header = "Weapon Crafting",
--- Recipes = {
--- [1] = {
--- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
--- amount = 1,
--- },
--- -- More recipes...
--- },
--- Anims = {
--- animDict = "amb@prop_human_parking_meter@male@idle_a",
--- anim = "idle_a",
--- },
--- },
--- coords = vector3(100.0, 200.0, 300.0),
--- stashTable = "crafting_stash",
--- job = "mechanic",
--- onBack = function() print("Returning to previous menu") end,
--- })
function craftingMenu(data)
if CraftLock then return end
-- Job or gang check; exit if not authorized.
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
-- Display a temporary "thinking" notification.
if Config.System.Menu == "jim" then
triggerNotify(nil, "Thinking", "info")
else
openMenu({ { header = "Thinking...", icon = "fas fa-hourglass-end", isMenuHeader = true } }, { header = "Crafting Menu" } )
end
-- Normalize stash name.
data.stashName = data.stashTable or data.stashName
local Menu = {}
local Recipes = data.craftable.Recipes
local craftedItems = {}
local tempCarryTable = {}
-- Build a table of all required ingredients (default quantity is 1).
for i = 1, #Recipes do
for k in pairs(Recipes[i]) do
if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
tempCarryTable[k] = Recipes[i].amount or 1
end
end
end
-- Check if the player can carry the required items (server callback).
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
-- Process each recipe to create menu entries.
for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
for k, _ in pairs(Recipes[i]) do
if not excludeKeys[k] then
local hasjob = true
if Recipes[i].job then
for l, b in pairs(Recipes[i].job) do
hasjob = hasJob(l, nil, b)
if hasjob then break end
end
end
if hasjob then
local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil)
local itemTable = {}
local metaTable = {}
-- Build ingredient details.
for l, b in pairs(Recipes[i][tostring(k)]) do
settext = settext..(settext ~= "" and br or "")..(Items[l] and Items[l].label or "error - "..l)..(b > 1 and " x"..b or "")
metaTable[Items[l] and Items[l].label or "error - "..l] = b
itemTable[l] = b
--Wait(0)
end
while not canCarryTable do Wait(0) end
disable = not checkStashItem(data.stashName, itemTable)
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k))
..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
local statusEmoji = disable and "" or not canCarryTable[k] and " 📦" or " ✔️"
local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or ""
setheader = isNew .. setheader .. statusEmoji
Menu[#Menu + 1] = {
arrow = not disable and canCarryTable[k],
isMenuHeader = disable or not canCarryTable[k],
icon = invImg((metadata and metadata.image) or tostring(k)),
image = invImg((metadata and metadata.image) or tostring(k)),
header = setheader,
txt = (isStarted(QBMenuExport) or disable) and settext or nil,
metadata = metaTable,
onSelect = (not disable and canCarryTable[k]) and function()
local transdata = {
item = k,
craft = data.craftable.Recipes[i],
craftable = data.craftable,
coords = data.coords,
stashName = data.stashName,
onBack = data.onBack,
metadata = metadata,
}
if Config.Crafting.MultiCraft then
multiCraft(transdata)
else
makeItem(transdata)
end
end or nil,
}
end
end
--Wait(0)
end
end
openMenu(Menu, {
header = data.craftable.Header,
headertxt = data.craftable.Headertxt,
onBack = data.onBack or nil,
canClose = true,
onExit = data.onExit or (function() end),
})
lookEnt(data.coords)
end
-------------------------------------------------------------
-- Multi-Craft Menu
-------------------------------------------------------------
--- Opens a menu for selecting the quantity to craft.
---
--- Presents the player with multiple crafting quantities based on Config.Crafting.MultiCraftAmounts.
---
--- @param data table Crafting configuration containing:
--- - item `string`) The item to craft.
--- - craft (`table`) The crafting recipe.
--- - craftable (`table`) Crafting options.
--- - coords (`vector3`) where crafting occurs.
--- - stashName (`string`) The stash name(s) for item availability.
--- - onBack (`function`) Callback when returning.
--- - metadata (`table`) (optional): Metadata for the crafted item.
---
--- @usage
--- ```lua
--- multiCraft({
--- item = "weapon_pistol",
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
--- craftable = craftingOptions,
--- coords = vector3(100,200,300),
--- stashName = "crafting_stash",
--- onBack = function() craftingMenu(data) end,
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- })
--- ```
function multiCraft(data)
local max = 0
local stashName = nil
local maxCreation = 100
-- Generate item table to check against stash or inventory
-- takes into account the ingredients needed for multiple items
local multiItemTable = {}
for i = 1, maxCreation do
multiItemTable[i] = {}
for l, b in pairs(data.craft[data.item]) do
multiItemTable[i][l] = (b * i)
end
end
for i = 1, maxCreation do
-- if its received a stash name, check if the items are in the stash
if data.stashName then
local hasItems, stashname = checkStashItem(data.stashName, multiItemTable[i])
if hasItems == true then
max += 1
stashName = stashname
else
break
end
else
-- if not check the players inventory for the items
local has, _ = hasItem(multiItemTable[i], nil, nil)
if has then
max += 1
else
break
end
end
end
local carryMax = triggerCallback(getScript()..":server:getMaxCarryCraft", {
item = data.item,
max = max
})
local dialog = createInput(data.craftable.Header..(Config.System.Menu == "qb" and ": "..br.."How many to craft? "..br.."Max: "..carryMax or ""), {
((Config.System.Menu == "ox") and {
type = "slider",
label = "How many to craft? "..br.."Max: "..carryMax,
required = true,
default = 1,
min = 1,
max = carryMax
}) or nil,
((Config.System.Menu == "qb") and {
type = "number",
name = "amount",
isRequired = true,
default = 1,
}) or nil,
})
if dialog then
if Config.System.Menu == "ox" then
end
if Config.System.Menu == "qb" then
dialog["amount"] = tonumber(dialog["amount"])
if dialog["amount"] > carryMax or dialog["amount"] < 1 or dialog["amount"] == nil or dialog["amount"] == "" then
triggerNotify(nil, "Invalid Amount", "error")
craftingMenu(data)
return
end
end
makeItem({
item = data.item,
craft = data.craft,
craftable = data.craftable,
amount = dialog["amount"] or dialog[1],
coords = data.coords,
stashName = stashName or nil,
onBack = data.onBack,
metadata = data.metadata,
})
end
end
-------------------------------------------------------------
-- Crafting Process
-------------------------------------------------------------
--- Initiates the crafting process for a specified item.
---
--- Plays crafting animations, shows progress bars, removes ingredients, and triggers item creation.
---
--- @param data table Crafting configuration containing:
--- - item `string`) The item to craft.
--- - craft (`table`) The crafting recipe.
--- - craftable (`table`) Crafting options.
--- - amount (`number`) (optional): Quantity to craft (default 1).
--- - coords (`vector3`) where crafting occurs.
--- - stashName (`string`) The stash name(s) for item availability.
--- - onBack (`function`) Callback when returning.
--- - metadata (`table`) (optional): Metadata for the crafted item.
---
--- @usage
--- ```lua
--- makeItem({
--- item = "weapon_pistol",
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
--- craftable = craftingOptions,
--- amount = 2,
--- coords = vector3(100,200,300),
--- stashName = "crafting_stash",
--- onBack = function() craftingMenu(data) end,
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
--- })
--- ```
function makeItem(data)
if CraftLock then return end
CraftLock = true
data.stashName = data.stashTable or data.stashName
local bartime = (data.craftable.progressBar and data.craftable.progressBar.time) or 5000
local bartext = (data.craftable.progressBar and data.craftable.progressBar.label)
or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"])
or "Making "
local animDict = (data.craftable.Anims and data.craftable.Anims.animDict) or "amb@prop_human_parking_meter@male@idle_a"
local anim = (data.craftable.Anims and data.craftable.Anims.anim) or "idle_a"
local craftAmount = (data.amount and data.amount ~= 1) and data.amount or 1
local metadata = data.metadata or nil
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
local canReturn = true
local crafted, crafting = true, true
local cam = createTempCam(PlayerPedId(), data.coords)
startTempCam(cam)
for i = 1, craftAmount do
for k, v in pairs(data.craft) do
if not excludeKeys[k] then
if type(v) == "table" then
for l, b in pairs(v) do
if isInventoryOpen() then
print("^1Error^7: ^2Inventory is open, you tried to break things")
crafted, crafting = false, false
return
end
if crafting and progressBar({
label = "Using "..b.." "..Items[l].label,
time = 1000,
cancel = true,
dict = 'pickup_object',
anim = "putdown_low",
flag = 49,
icon = l,
}) then
TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b)
else
crafted, crafting = false, false
break
end
Wait(200)
end
if isInventoryOpen() then
--print("^1Error^7: ^2Inventory is open, you tried to break things")
crafted, crafting, CraftLock = false, false, false
return
end
if crafted then
local craftProp = nil
if prop then
craftProp = makeProp({ prop = prop.model, coords = vec4(0, 0, 0, 0), true, true })
AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true)
end
if data.sound then
local s = data.sound
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end
if isInventoryOpen() then
--print("^1Error^7: ^2Inventory is open, you tried to break things")
crafted, crafting = false, false
crafted, crafting, CraftLock = false, false, false
return
end
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label),
time = bartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
request = true,
}) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil -- clear client cached token
CreateThread(function()
if data.craft["hasCrafted"] ~= nil then
debugPrint("hasCrafted Found, marking '"..data.item.."' as crafted for player")
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems)
end
Wait(100)
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give
jsonPrint(data.craft["exp"])
debugPrint("exp found, giving exp for '"..data.item.."'")
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel)
end
end)
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false
end
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
else
crafting = false
break
end
if craftProp then destroyProp(craftProp) end
end
end
end
end
Wait(500)
end
stopTempCam()
CraftLock = false
if canReturn then craftingMenu(data) end
ClearPedTasks(PlayerPedId())
end
-------------------------------------------------------------
-- Server Event Handler: Crafted Item
-------------------------------------------------------------
--- Server event handler for giving the crafted item to the player.
---
--- Removes required ingredients from the player's inventory or stash,
--- then adds the crafted item to their inventory.
---
--- @param ItemMake string The item being crafted.
--- @param craftable table The crafting recipe and details.
--- @param stashName string|table The stash name(s) to remove ingredients from.
--- @param metadata table (optional) Metadata for the crafted item.
--- @usage
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata, token)
local src = source
debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return
end
if not checkToken(src, token, "item", ItemMake) then
return
end
local hasItems, hasTable = hasItem(ItemMake, 1, src)
if stashName then
local itemRemove = {}
if type(stashName) == "table" then
for _, name in pairs(stashName) do
stashItems = getStash(name)
for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do
if k == b.name then
itemRemove[k] = v
end
end
end
end
else
stashItems = getStash(stashName)
for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do
if k == b.name then
itemRemove[k] = v
end
end
end
end
stashRemoveItem(stashItems, stashName, itemRemove)
else
if craftable then
for k, v in pairs(craftable[ItemMake] or {}) do
removeItem(tostring(k), v, src)
end
end
end
addItem(ItemMake, craftable.amount or 1, metadata, src)
-- Optionally, add experience here:
-- for example:
-- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
end)

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,156 +1,241 @@
-- INPUT --
-- Multiscript input script function to create simple input text boxes --
--- Creates a simple input dialog compatible with multiple menu systems.
---
--- This function generates input dialogs for different frameworks (OX, QB, GTA) based on the configuration.
--- 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.
--- - **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).
--- - **text** (`string`, optional): The text prompt for the input.
--- - **name** (`string`): The identifier name for the input.
--- - **isRequired** (`boolean`, optional): Whether the input is required.
--- - **default** (`any`, optional): The default value for the input.
--- - **options** (`table`, optional): A table of options for "radio" and "select" types.
--- - **min** (`number`, optional): The minimum value (used for "select" type).
--- - **max** (`number`, optional): The maximum value (used for "number" and "select" types).
--- - **txt** (`string`, optional): Additional text or description for the input.
---
---@return table|nil table Returns the user's input as a table if the dialog is submitted, otherwise returns `nil`.
---
---@usage
--- ```lua
--- local userInput = createInput("Enter Details", {
--- { type = "text", text = "Name", name = "playerName", isRequired = true },
--- { type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
--- { type = "radio", label = "Gender", name = "playerGender", options = {
--- { text = "Male", value = "male" },
--- { text = "Female", value = "female" },
--- { text = "Other", value = "other" },
--- }},
--- })
--- ```
function createInput(title, opts)
local dialog = nil
local options = {}
if Config.System.Menu == "ox" then
for i = 1, #opts do
if opts[i].type == "radio" then
-- Convert radio options to select type for OX
for k in pairs(opts[i].options) do
opts[i].options[k].label = opts[i].options[k].text
end
options[i] = {
type = "select",
isRequired = opts[i].isRequired,
label = opts[i].label or opts[i].text,
name = opts[i].name,
default = opts[i].default or opts[i].options[1].value,
options = opts[i].options,
}
end
if opts[i].type == "number" then
options[i] = {
type = "number",
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
isRequired = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
}
end
if opts[i].type == "text" then
options[i] = {
type = "input",
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
default = opts[i].default,
isRequired = opts[i].isRequired,
}
end
if opts[i].type == "select" then
options[i] = {
type = "select",
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
isRequired = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
end
dialog = exports[OXLibExport]:inputDialog(title, options)
return dialog
end
if Config.System.Menu == "qb" then
dialog = exports['qb-input']:ShowInput({ header = title, submitText = "Accept", inputs = opts })
return dialog
end
if 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
end
--[[
Input Dialog Module
---------------------
This module provides a function to create a simple input dialog compatible with
multiple menu systems (OX, QB, GTA/WarMenu, and ESX). It supports various input
types such as radio buttons, numbers, text, and select dropdowns.
]]
--- Creates a simple input dialog using the configured menu system.
---
--- @param title string The title or header of the input dialog.
--- @param opts table A table of input option definitions. Each option should include:
--- - type (string): The input type ("radio", "number", "text", "select").
--- - label (string, optional): A label for the input (used in radio/select for OX).
--- - text (string, optional): The text prompt for the input.
--- - name (string): The identifier for the input.
--- - isRequired (boolean, optional): Whether input is mandatory.
--- - default (any, optional): The default value.
--- - options (table, optional): A table of choices for "radio" and "select" types.
--- - min (number, optional): Minimum value (for "number" and "select").
--- - max (number, optional): Maximum value.
--- - txt (string, optional): Additional description.
---
--- @return table|nil table Returns the user's input as a table if submitted, otherwise nil.
---
---@usage
--- ```lua
--- local userInput = createInput("Enter Details", {
--- { type = "text", text = "Name", name = "playerName", isRequired = true },
--- { type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
--- { type = "radio", label = "Gender", name = "playerGender", options = {
--- { text = "Male", value = "male" },
--- { text = "Female", value = "female" },
--- { text = "Other", value = "other" },
--- }},
--- })
--- ```
function createInput(title, opts)
local dialog = nil
local options = {}
local currentNum = 0
if Config.System.Menu == "ox" then
for i = 1, #opts do
currentNum += 1
if opts[i] == nil then currentNum -= 1 goto skip end
if opts[i].type == "radio" then
-- Convert radio options to select type for OX
for k in pairs(opts[i].options) do
opts[i].options[k].label = opts[i].options[k].text
end
options[currentNum] = {
type = "select",
isRequired = opts[i].isRequired,
label = opts[i].label or opts[i].text,
name = opts[i].name,
default = opts[i].default or opts[i].options[1].value,
options = opts[i].options,
}
end
if opts[i].type == "number" then
options[currentNum] = {
type = opts[i].type,
label = (opts[i].label or opts[i].text)..(opts[i].txt and " - "..opts[i].txt or ""),
isRequired = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
}
end
if opts[i].type == "text" then
options[currentNum] = {
type = "input",
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
default = opts[i].default,
isRequired = opts[i].isRequired,
}
end
if opts[i].type == "select" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
isRequired = opts[i].isRequired,
name = opts[i].name,
options = opts[i].options,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
if opts[i].type == "checkbox" then
jsonPrint(opts[i])
for k in pairs(opts[i].options) do
if options[currentNum] then currentNum += 1 end
options[currentNum] = {
type = opts[i].type,
label = opts[i].options[k].text..(opts[i].txt and " - "..opts[i].txt or ""),
name = opts[i].options[k].value,
}
end
end
if opts[i].type == "color" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
isRequired = opts[i].isRequired,
format = opts[i].format,
default = opts[i].default,
}
end
if opts[i].type == "slider" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
isRequired = opts[i].required,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
::skip::
end
dialog = exports[OXLibExport]:inputDialog(title, options)
return dialog
elseif Config.System.Menu == "qb" then
for k, v in pairs(opts) do
currentNum += 1
if opts[k] == nil then
currentNum -= 1
else
options[currentNum] = opts[k]
end
end
dialog = exports['qb-input']:ShowInput(
{ header = title, submitText = "Accept", inputs = options }
)
return dialog
elseif Config.System.Menu == "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

265
shared/inventories.lua Normal file
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,209 +1,302 @@
local inProgress = false
--- Displays a progress bar using the configured progress bar system.
---
--- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
--- It supports shared progress bars between players, animations, camera effects, and more.
---
---@param data table A table containing the progress bar configuration.
--- - **label** (`string`): The text label to display on the progress bar.
--- - **time** (`number`): The duration of the progress bar in milliseconds.
--- - **dict** (`string`, optional): The animation dictionary to use.
--- - **anim** (`string`, optional): The animation name to play.
--- - **task** (`string`, optional): The task scenario to perform.
--- - **flag** (`number`, optional): The animation flag.
--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
--- - **icon** (`string`, optional): The icon to display (for qb progress bar).
--- - **cam** (`number`, optional): The camera handle to use.
--- - **shared** (`table`, optional): Data for shared progress bars.
--- - **pid** (`number`): The player ID to share the progress bar with.
--- - **label** (`string`): The label to display on the shared progress bar.
---
--- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
---
---@usage
--- ```lua
--- local success = progressBar({
--- label = "Processing...",
--- time = 5000,
--- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
--- anim = "base",
--- flag = 49,
--- cancel = true,
--- })
--- ```
function progressBar(data)
local ped = PlayerPedId()
if data.shared then
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
storedPID = data.shared.pid
TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
end
local result = nil
if data.cam then startTempCam(data.cam) end
if Config.System.ProgressBar == "ox" then
if exports[OXLibExport]:progressBar({
duration = debugMode and 1000 or data.time,
label = data.label,
useWhileDead = data.dead or false,
canCancel = data.cancel and data.cancel or true,
anim = {
dict = data.dict,
clip = data.anim,
flag = (data.flag == 8 and 32 or data.flag) or nil,
scenario = data.task
},
disable = {
combat = true
},
}) then
result = true
else
result = false
end
elseif Config.System.ProgressBar == "qb" then
Core.Functions.Progressbar("progbar",
data.label,
debugMode and 1000 or data.time,
data.dead or false,
data.cancel or true,
{ disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true },
{ animDict = data.dict, anim = data.anim, flags = data.flag or 32, task = data.task }, {}, {},
function()
result = true
end, function()
result = false
end, data.icon)
elseif Config.System.ProgressBar == "esx" then
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
FreezePlayer = true,
animation = {
type = data.anim,
dict = data.dict,
scenario = data.task,
},
onFinish = function()
result = true
end,
onCancel = function()
result = false
end
})
elseif Config.System.ProgressBar == "gta" then
local wait = debugMode and 1000 or data.time
inProgress = true
if not (data.dead or false) then
lockInv(true)
displaySpinner(data.label)
if data.dict then
playAnim(data.dict, data.anim, -1, (data.flag == 8 and 32 or data.flag) or nil)
end
if data.task then
TaskStartScenarioInPlace(ped, data.task, -1, true)
end
while inProgress and wait > 0 do
wait -= 15
local waitTimer = 0
DisablePlayerFiring(ped, true)
DisableControlAction(0, 25, true) -- 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.cam ~= nil then
DisableControlAction(0, 1, true) -- Disable look left/right
DisableControlAction(0, 2, true) -- Disable look up/down
DisableControlAction(0, 106, true) -- Disable vehicle mouse control
end
if data.cancel then
if IsControlJustReleased(0, 202) or IsControlJustReleased(0, 77) then -- Cancel key (Backspace or Delete)
inProgress = false
waitTimer = 1500
displaySpinner(Loc[Config.Lan].error["cancel"])
end
end
Wait(waitTimer)
end
inProgress = false
if data.dict then stopAnim(data.dict, data.anim, ped) end
ClearPedTasks(ped)
end
stopSpinner()
result = (wait <= 0)
end
while result == nil do Wait(10) end
-- Cleanup
FreezeEntityPosition(ped, false)
lockInv(false)
if data.cam then stopTempCam(data.cam) end
if result == false and data.shared then
debugPrint("^6Bridge^7: ^2Sending cancel to ^6"..storedPID.."^7")
TriggerServerEvent(getScript().."server:sharedProg:cancel", storedPID)
end
storedPID = nil
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 stopPropgressBar()
if Config.System.ProgressBar == "ox" then
exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "gta" then
inProgress = false
BusyspinnerOff()
end
end
-- System to handle sending/sharing progress bars between players --
-- For example, healing someone --
local storedPID = nil
--- Server event handler for starting a shared progress bar.
--- This event is triggered when a player wants to start a progress bar on another player.
--- 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")
stopPropgressBar()
end)
--- Cleans up when the resource stops.
--- This event handler ensures that any active spinners or progress bars are stopped when the resource is stopped.
onResourceStop(function() stopSpinner() end, true)
local inProgress = false
--- Displays a progress bar using the configured progress bar system.
---
--- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
--- It supports shared progress bars between players, animations, camera effects, and more.
---
---@param data table A table containing the progress bar configuration.
--- - **label** (`string`): The text label to display on the progress bar.
--- - **time** (`number`): The duration of the progress bar in milliseconds.
--- - **dict** (`string`, optional): The animation dictionary to use.
--- - **anim** (`string`, optional): The animation name to play.
--- - **task** (`string`, optional): The task scenario to perform.
--- - **flag** (`number`, optional): The animation flag.
--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
--- - **icon** (`string`, optional): The icon to display (for qb progress bar).
--- - **cam** (`number`, optional): The camera handle to use.
--- - **shared** (`table`, optional): Data for shared progress bars.
--- - **pid** (`number`): The player ID to share the progress bar with.
--- - **label** (`string`): The label to display on the shared progress bar.
---
--- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
---
---@usage
--- ```lua
--- local success = progressBar({
--- label = "Processing...",
--- time = 5000,
--- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
--- anim = "base",
--- flag = 49,
--- cancel = true,
--- })
--- ```
function progressBar(data)
local ped = PlayerPedId()
if data.shared then
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
storedPID = data.shared.pid
TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
end
local result = nil
if data.cam then startTempCam(data.cam) end
if Config.System.ProgressBar == "ox" then
if exports[OXLibExport]:progressBar({
duration = debugMode and 1000 or data.time,
label = data.label,
useWhileDead = data.dead or false,
canCancel = data.cancel and data.cancel or true,
anim = {
dict = data.dict,
clip = data.anim,
flag = (data.flag == 8 and 32 or data.flag) or nil,
scenario = data.task
},
disable = {
combat = true
},
}) then
result = true
else
result = false
end
elseif Config.System.ProgressBar == "qb" then
Core.Functions.Progressbar("progbar",
data.label,
debugMode and 1000 or data.time,
data.dead or false,
data.cancel or true,
{ disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true },
{ animDict = data.dict, anim = data.anim, flags = data.flag or 32, task = data.task }, {}, {},
function()
result = true
end, function()
result = false
end, data.icon)
elseif Config.System.ProgressBar == "esx" then
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
FreezePlayer = true,
animation = {
type = data.anim,
dict = data.dict,
scenario = data.task,
},
onFinish = function()
result = true
end,
onCancel = function()
result = false
end
})
elseif Config.System.ProgressBar == "red" then
-- Currently only uses jim-redui if you choose this option
if exports["jim-redui"]:progressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
cancel = true,
}) then
result = true
else
result = false
end
elseif Config.System.ProgressBar == "gta" then
loadTextureDict("timerbars")
if inProgress then return false end
inProgress = true
local wait = debugMode and 1000 or data.time
local endTime = GetGameTimer() + wait
local ped = PlayerPedId()
-- Setup Animation/Task if specified
if data.dict then
playAnim(data.dict, data.anim, -1, data.flag or 32)
elseif data.task then
TaskStartScenarioInPlace(ped, data.task, -1, true)
end
-- Progress bar rendering loop
CreateThread(function()
while GetGameTimer() < endTime and inProgress do
Wait(0)
local elapsed = GetGameTimer()
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
-- Convert to segmented progress (assuming 5 segments here)
local segments = 5 -- Number of segments in the bar
local segmentProgress = {}
local progressPerSegment = 100 / segments
for i = 1, segments do
local segmentStart = (i - 1) * progressPerSegment
local segmentEnd = i * progressPerSegment
if percentage >= segmentEnd then
segmentProgress[i] = 100
elseif percentage <= segmentStart then
segmentProgress[i] = 0
else
segmentProgress[i] = ((percentage - segmentStart) / progressPerSegment) * 100
end
end
percentage = percentage >= 100 and 100 or percentage
-- Draw your segmented progress bar
ShowGTAProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress
DisablePlayerFiring(ped, true)
DisableControlAction(0, 25, true) -- Disable aim
DisableControlAction(0, 21, true) -- Disable sprint
DisableControlAction(0, 30, true) -- Disable move left/right
DisableControlAction(0, 31, true) -- Disable move forward/back
DisableControlAction(0, 36, true) -- Disable stealth
if data.cancel and (IsControlJustReleased(0, 202) or IsControlJustReleased(0, 177) or IsControlJustReleased(0, 73)) then
inProgress = false
end
end
end)
-- Wait for completion or cancel
while GetGameTimer() < endTime and inProgress do
Wait(100)
end
-- Cleanup animations/tasks
if data.dict then stopAnim(data.dict, data.anim, ped) end
ClearPedTasks(ped)
result = inProgress
inProgress = false
end
while result == nil do Wait(10) end
-- Cleanup
FreezeEntityPosition(ped, false)
lockInv(false)
if data.cam then
stopTempCam(data.cam)
end
if result == false and data.shared then
debugPrint("^6Bridge^7: ^2Sending cancel to ^6"..storedPID.."^7")
TriggerServerEvent(getScript().."server:sharedProg:cancel", storedPID)
end
storedPID = nil
if result == false then
currentToken = nil
TriggerServerEvent(getScript()..":clearAuthToken")
end
if result == true and data.request then
TriggerServerEvent(getScript()..":clearAuthToken")
currentToken = triggerCallback(AuthEvent)
end
return result
end
function ShowGTAProgressBar(currentProg, title, level)
local loc = vec2(0.37, 0.90)
local size = vec2(0.3, 0.03)
-- Draw background box
DrawSprite("timerbars", "all_black_bg", loc.x +0.028, loc.y-0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255)
DrawSprite("timerbars", "all_black_bg", loc.x +0.170, loc.y-0.01, 0.15, 0.07, 180.0, 255, 255, 255, 255)
SetTextFont(0)
SetTextProportional(1)
SetTextScale(0.35, 0.35)
SetTextColour(255, 255, 255, 255)
SetTextDropshadow(0, 0, 0, 0, 255)
SetTextDropShadow()
SetTextOutline()
SetTextEntry("STRING")
AddTextComponentString(title)
DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034) -- Adjust text position
SetTextFont(0)
SetTextProportional(1)
SetTextScale(0.35, 0.25)
SetTextColour(255, 255, 255, 255)
SetTextEntry("STRING")
AddTextComponentString(level)
DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030) -- Right-aligned additional text
local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each)
local gap = segmentWidth / #currentProg -- Smaller gap between segments
for i = 1, #currentProg do
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
local fillPercentage = currentProg[i]
local progressBarWidth = segmentWidth * (fillPercentage / 100)
-- Semi-transparent background for each segment
DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255)
-- Filling progress for each segment
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 93, 182, 229, 255) -- Blue progress
end
end
end
--- Stops the current progress bar.
---
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
function stopProgressBar()
if Config.System.ProgressBar == "ox" then
exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "gta" then
inProgress = false
end
end
-- System to handle sending/sharing progress bars between players --
-- For example, healing someone --
local storedPID = nil
--- Server event handler for starting a shared progress bar.
--- This event is triggered when a player wants to start a progress bar on another player.
--- It adjusts the data to prevent loops and sends the data to the target client.
RegisterNetEvent(getScript()..":server:sharedProg:Start", function(data)
local pid = data.shared.pid -- Get player ID from the client
data.label = data.shared.label -- Set progress bar label to the shared label
data.cancel = false -- Make it so it can't be canceled
data.dead = true -- Allow progress bar even if player is dead
data.shared = nil -- Remove shared info to prevent loops
data.anim = nil -- Remove animation so players don't share it
debugPrint("^6Bridge^7: ^6"..source.." ^2is sending shared progressBar to player^7, ^6"..pid.."^7")
TriggerClientEvent(getScript()..":client:sharedProg:Start", pid, data)
end)
--- Client event handler for starting a shared progress bar.
--- This event is triggered when the server wants the client to start a shared progress bar.
RegisterNetEvent(getScript()..":client:sharedProg:Start", function(data)
debugPrint("^6Bridge^7: ^2You have been sent a progressBar^7")
progressBar(data)
end)
--- Server event handler for canceling a shared progress bar.
--- This event is triggered when a progress bar is canceled and the server needs to notify the other player.
RegisterNetEvent(getScript()..":server:sharedProg:Cancel", function(pid)
debugPrint("^6Bridge^7: ^2Sending cancel progressBar to ^6"..pid.."^7")
TriggerClientEvent(getScript()..":client:sharedProg:Cancel", pid)
end)
--- Client event handler for canceling a shared progress bar.
--- This event is triggered when the server wants the client to cancel a shared progress bar.
RegisterNetEvent(getScript()..":client:sharedProg:Cancel", function()
debugPrint("^6Bridge^7: ^2Receiving cancel progressBar^7")
stopProgressBar()
end)

157
shared/metaHandlers.lua Normal file
View File

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

View File

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

171
shared/phones.lua Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,41 +1,44 @@
--- Displays debug information on the player's screen.
---
--- 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.
---
--- @param textTable table A table containing strings to display.
--- @param loc vector2|nil Optional. The screen position to display the debug box. Defaults to `vec2(0.05, 0.65)`.
---
--- @usage
--- ```lua
--- debugScaleForm({
--- "Player Position: X=123.45 Y=678.90 Z=12.34",
--- "Current Action: Running",
--- })
--- ```
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
-------------------------------------------------------------
-- Debug Text Display Functionality
-------------------------------------------------------------
--- Draws debug text on the screen if debugMode is enabled.
---
--- Calculates a background rectangle based on the number of text lines and renders each line on-screen.
---
--- @param textTable table An array of strings to display.
--- @param loc vector2 (Optional) Top-left coordinate for the text box (default: vec2(0.05, 0.65)).
---
--- @usage
--- ```lua
---CreateThread(function()
--- while true do
--- debugScaleForm({
--- "Line 1: Debug info",
--- "Line 2: More info"
--- })
--- Wait(0)
--- end
---end)
--- ```
function debugScaleForm(textTable, loc)
if debugMode then
loc = loc or vec2(0.05, 0.65)
local lineHeight = 0.025 -- Height per line.
local totalHeight = #textTable * lineHeight
local boxPadding = 0.01 -- Padding around the text.
local size = vec2(0.18, totalHeight + boxPadding * 2)
-- Draw background rectangle.
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 200)
-- Render each line of text.
for i = 1, #textTable do
SetTextScale(0.30, 0.30)
BeginTextCommandDisplayText("STRING")
AddTextComponentSubstringKeyboardDisplay(textTable[i])
EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
end
end
end

View File

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

View File

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

View File

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

259
shared/shops.lua Normal file
View File

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

156
shared/skillcheck.lua Normal file
View File

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

180
shared/societybank.lua Normal file
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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

View File

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

View File

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

View File

@@ -1 +1 @@
1.2
2.0.10