Compare commits

...

114 Commits

Author SHA1 Message Date
Jim Shield
ae231b6101 Version Bump
`2.0.23` - `2.1.0`
2025-09-14 21:32:12 +01:00
Jim Shield
c709159ea4 Deprecate ConsumeSuccess()
This function and others were too open to exploiters, my scripts not no longer use these functions and handle it all server side.
2025-09-14 17:40:12 +01:00
Jim Shield
c8089109c6 Add "await" for callbacks
This hopefully should help callbacks get their requested data

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

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

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

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

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

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

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

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

People REALLY dont like being told when somethings fixed.

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

Fix possibility of invisible props/ peds/vehicles on spawn

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

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

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

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

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

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

eg.
```lua
makeDistPed(v.model[i], b, true, false, v.scenario, nil, nil, function(ped)
	if v["killable"] then
		SetEntityInvincible(ped, false)
		SetBlockingOfNonTemporaryEvents(ped, false)
		FreezeEntityPosition(ped, false)
	end
end)
```
2025-07-29 12:25:31 +01:00
Jim Shield
f09e34b1b5 Version Bump
`2.0.21` - `2.0.22``

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

12
.gitattributes vendored Normal file
View File

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

View File

@@ -1,17 +1,20 @@
name: Discord Commit Notifier name: Discord Commit Notifier
on: on:
push: push:
branches: branches: ['*']
- '*' # triggers on all branches
jobs: jobs:
notify: notify:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Discord Commits - name: Validate event JSON (sanity check)
uses: Sniddl/discord-commits@v1.6 run: jq -e . "$GITHUB_EVENT_PATH" >/dev/null
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }} - name: Send commit payload to Discord Bot
template: "avatar-with-link" env:
include-extras: true URL: http://${{ secrets.DISCORDBOT }}:3000/github-commits
shell: bash
run: |
curl -sS --fail-with-body -X POST "$URL" \
-H 'Content-Type: application/json' \
--data-binary "@$GITHUB_EVENT_PATH"

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

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

View File

@@ -1,67 +1,78 @@
local function parseVersion(version) local function readBoolMeta(key, default)
local parts = {} local val = GetResourceMetadata(GetCurrentResourceName(), key, 0)
for num in version:gmatch("%d+") do if not val then return default end
table.insert(parts, tonumber(num)) val = tostring(val):lower()
end return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
return parts
end end
local function compareVersions(current, newest) local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
local currentParts = parseVersion(current)
local newestParts = parseVersion(newest) if not SUPPRESS_UPDATES then
for i = 1, math.max(#currentParts, #newestParts) do local function parseVersion(version)
local c = currentParts[i] or 0 local parts = {}
local n = newestParts[i] or 0 for num in version:gmatch("%d+") do
if c < n then return -1 table.insert(parts, tonumber(num))
elseif c > n then return 1 end end
return parts
end end
return 0 -- equal
end
function CheckBridgeVersion() local function compareVersions(current, newest)
if IsDuplicityVersion() then local currentParts = parseVersion(current)
CreateThread(function() local newestParts = parseVersion(newest)
Wait(4000) for i = 1, math.max(#currentParts, #newestParts) do
local currentVersionRaw = GetResourceMetadata("jim_bridge", 'version') local c = currentParts[i] or 0
--PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/test.txt', function(err, body, headers) local n = newestParts[i] or 0
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, body, headers) if c < n then return -1
if not body then elseif c > n then return 1 end
print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)") end
return return 0 -- equal
end end
local lines = {} function CheckBridgeVersion()
for line in body:gmatch("[^\r\n]+") do if IsDuplicityVersion() then
table.insert(lines, line) CreateThread(function()
end Wait(4000)
local currentVersionRaw = GetResourceMetadata("jim_bridge", 'version')
local newestVersionRaw = lines[1] or "0.0.0" --PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/test.txt', function(err, body, headers)
local changelog = {} PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, body, headers)
for i = 2, #lines do if not body then
table.insert(changelog, lines[i]) print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)")
end return
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
if compareResult == 0 then
print("^7'^3jim_bridge^7' - ^2You are running the latest version^7. ^7(^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line)
end end
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function() local lines = {}
CheckBridgeVersion() for line in body:gmatch("[^\r\n]+") do
end) table.insert(lines, line)
else end
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
end local newestVersionRaw = lines[1] or "0.0.0"
local changelog = {}
for i = 2, #lines do
table.insert(changelog, lines[i])
end
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
if compareResult == 0 then
print("^7'^3jim_bridge^7' - ^2You are running the latest version^7. ^7(^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line)
end
print("^1----------------------------------------------------------------------^7")
SetTimeout(3600000, function()
CheckBridgeVersion()
end)
else
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7) (^1Expect Errors^7)")
end
end)
end) end)
end) end
end end
CheckBridgeVersion()
end end
CheckBridgeVersion()

View File

@@ -21,12 +21,12 @@ This module provides access and control over player metadata, which is useful fo
print("Player stress level:", stress) print("Player stress level:", stress)
``` ```
- **SetMetadata(player, key, value)** - **setPlayerMetadata(player, key, value)**
⚠️ Server side only ⚠️ Server side only
- Updates or assigns a value to a specific metadata key for a player. - Updates or assigns a value to a specific metadata key for a player.
- The function updates the player's metadata using the active core export. - The function updates the player's metadata using the active core export.
- **Example:** - **Example:**
```lua ```lua
SetMetadata(player, "stress", 0) setPlayerMetadata(player, "stress", 0)
``` ```

View File

@@ -1,6 +1,6 @@
### wrapperfunctions.lua ### wrapperfunctions.lua
Provides wrapper compatibility functions for command and inventory stash systems across different frameworks (OX, QB, ESX, QS, etc). Provides wrapper compatibility functions for command and inventory stash systems across different frameworks (OX, QB, ESX, etc).
- **registerCommand(command, options)** - **registerCommand(command, options)**
@@ -23,7 +23,7 @@ Provides wrapper compatibility functions for command and inventory stash systems
- **registerStash(name, label, slots?, weight?, owner?, coords?)** - **registerStash(name, label, slots?, weight?, owner?, coords?)**
⚠️ Server Side Only ⚠️ Server Side Only
- Registers a stash using OX, QS, or Origen inventory systems. - Registers a stash using OX, or Origen inventory systems.
- **Example:** - **Example:**
```lua ```lua
registerStash( registerStash(

View File

@@ -15,7 +15,6 @@ local Exports = {
OXInv = "ox_inventory", OXInv = "ox_inventory",
QBInv = "qb-inventory", QBInv = "qb-inventory",
PSInv = "ps-inventory", PSInv = "ps-inventory",
QSInv = "qs-inventory",
CoreInv = "core_inventory", CoreInv = "core_inventory",
CodeMInv = "codem-inventory", CodeMInv = "codem-inventory",
OrigenInv = "origen_inventory", OrigenInv = "origen_inventory",
@@ -31,17 +30,18 @@ local Exports = {
-- REDM -- REDM
RSGExport = "rsg-core", RSGExport = "rsg-core",
RSGInv = "rsg-inventory" RSGInv = "rsg-inventory",
VorpExport = "vorp_core",
VorpInv = "vorp_inventory",
VorpMenu = "vorp_menu",
} }
-- Prevent reloading if cache is already initialized -- Prevent reloading if cache is already initialized
local cache = { local cache = { Items = {}, Vehicles = {}, Jobs = {}, Gangs = {}, }
Items = {},
Vehicles = {},
Jobs = {},
Gangs = {},
}
local cacheReady = false local cacheReady = false
-- Timer info, for debugging more then anything
local timers = {} local timers = {}
local function startTimer(label) local function startTimer(label)
timers[label] = GetGameTimer() timers[label] = GetGameTimer()
@@ -49,27 +49,60 @@ end
local function endTimer(label) local function endTimer(label)
timers[label] = GetGameTimer() - (timers[label] or GetGameTimer()) timers[label] = GetGameTimer() - (timers[label] or GetGameTimer())
timers[label] = timers[label] / 1000 timers[label] = "("..(timers[label] / 1000).."s)"
end end
startTimer("Cache") startTimer("Cache") startTimer("Items") startTimer("Vehicles") startTimer("Jobs") startTimer("InvWeight") startTimer("InvSlots")
-- Helper function to check if resource exists in server (instead of if it is already started)
-- Helper functions --
local function checkExists(resourceName) local function checkExists(resourceName)
return GetResourceState(resourceName):find("start") or GetResourceState(resourceName):find("stopped") local state = GetResourceState(resourceName)
return state and (state:find("start") or state:find("stopped"))
end end
-- Ensure oxmysql resource is loaded local function waitStarted(resourceName)
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua'))) while GetResourceState(resourceName) ~= "started" do Wait(100) end
fileLoader() end
local function waitStartedOrStopped(resourceName)
local state = GetResourceState(resourceName)
while state ~= "started" and state ~= "stopped" do
Wait(100)
state = GetResourceState(resourceName)
end
end
local function dupLowercaseWeapons(items)
for k, v in pairs(items or {}) do
if type(k) == "string" then
if k:find("WEAPON") then
items[k:lower()] = v
end
else
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
print("^1Possible Issue found^7:")
print(json.encode(items[k], { indent = true }))
end
end
end
-- Forceload libs --
-- Ensure oxmysql resource is loaded for jim_bridge internally
-- For some core's it needs to get data from the database
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) or checkExists(Exports.VorpExport) then
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua')))
fileLoader()
end
if checkExists(Exports.OXCoreExport) then if checkExists(Exports.OXCoreExport) then
-- Detected OX_Core in server, wait for it to be started if needed -- Detected OX_Core in server, wait for it to be started if needed
while GetResourceState(Exports.OXCoreExport) ~= "started" do Wait(100) end waitStartedOrStopped(Exports.OXCoreExport)
local fileLoader = assert(load(LoadResourceFile(Exports.OXCoreExport, ('lib/init.lua')), ('@@'..Exports.OXCoreExport..'/lib/init.lua'))) local fileLoader = assert(load(LoadResourceFile(Exports.OXCoreExport, ('lib/init.lua')), ('@@'..Exports.OXCoreExport..'/lib/init.lua')))
fileLoader() fileLoader()
end end
if checkExists(Exports.ESXExport) then if checkExists(Exports.ESXExport) then
-- Detected ESX in server, wait for it to be started if needed -- Detected ESX in server, wait for it to be started if needed
while GetResourceState(Exports.ESXExport) ~= "started" do Wait(100) end waitStarted(Exports.ESXExport)
local fileLoader = assert(load(LoadResourceFile(Exports.ESXExport, ('/imports.lua')), ('@@'..Exports.ESXExport..'/imports.lua'))) local fileLoader = assert(load(LoadResourceFile(Exports.ESXExport, ('/imports.lua')), ('@@'..Exports.ESXExport..'/imports.lua')))
fileLoader() fileLoader()
end end
@@ -87,202 +120,267 @@ end
--------------------- ---------------------
---- Load Items ----- ---- Load Items -----
--------------------- ---------------------
-- Items initialization based on detected inventory system
startTimer("Items")
if checkExists(Exports.OXInv) then
-- Wait for OX Inventory to start if it's not already started
while GetResourceState(Exports.OXInv) ~= "started" do Wait(100) end
itemResource = Exports.OXInv
local success, result = pcall(function() local itemFunc = {
return exports[Exports.OXInv]:Items() { script = Exports.OXInv,
end) cacheItem = function()
if success and result then local success, result = pcall(function()
cache.Items = result return exports[Exports.OXInv]:Items()
end end)
if success and result then
-- Get Weapon info and duplicate them if they are uppercase cache.Items = result
-- (duplicate incase anything checks for the uppercase version)
for k, v in pairs(cache.Items) do
if type(k) == "string" then
if k:find("WEAPON") then
cache.Items[k:lower()] = cache.Items[k]
end end
else end,
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?") },
print("^1Possible Issue found^7:") { script = Exports.TgiannInv,
print(json.encode(Items[k], {indent = true})) cacheItem = function()
end local success, result = pcall(function()
end return exports[Exports.TgiannInv]:Items()
end)
if success and result then
cache.Items = result
end
end,
},
{ script = Exports.QBXExport,
cacheItem = function()
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
-- If this is nil, they need to update to qbx_core 1.23.0+
if not cache.Items then
-- if their inventory doesn't allow that (they refuse to update their butchered core replacement):
elseif checkExists(Exports.QBXExport) then if GetResourceState(Exports.OrigenInv):find("start") then
while GetResourceState(Exports.QBXExport) ~= "started" do Wait(100) end itemResource = Exports.OrigenInv
itemResource = Exports.QBXExport cache.Items = exports[Exports.OrigenInv]:Items()
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
-- If this is nil, they need to update to qbx_core 1.23.0+
if not cache.Items then
-- if their inventory doesn't allow that (they refuse to update their butchered core replacement):
if GetResourceState(Exports.QSInv):find("start") then
itemResource = Exports.QSInv
cache.Items = exports[Exports.QSInv]:GetItemList()
elseif GetResourceState(Exports.OrigenInv):find("start") then elseif GetResourceState(Exports.CodeMInv):find("start") then
itemResource = Exports.OrigenInv itemResource = Exports.CodeMInv
cache.Items = exports[Exports.OrigenInv]:Items() cache.Items = exports[Exports.CodeMInv]:GetItemList()
elseif GetResourceState(Exports.CodeMInv):find("start") then elseif GetResourceState(Exports.CoreInv):find("start") then
itemResource = Exports.CodeMInv itemResource = Exports.CoreInv
cache.Items = exports[Exports.CodeMInv]:GetItemList() cache.Items = exports[Exports.CoreInv]:getItemsList()
elseif GetResourceState(Exports.CoreInv):find("start") then elseif GetResourceState(Exports.TgiannInv):find("start") then
itemResource = Exports.CoreInv itemResource = Exports.TgiannInv
cache.Items = exports[Exports.CoreInv]:getItemsList() cache.Items = exports[Exports.TgiannInv]:Items()
end
elseif GetResourceState(Exports.TgiannInv):find("start") then end
itemResource = Exports.TgiannInv end,
cache.Items = exports[Exports.TgiannInv]:Items() },
end { script = Exports.QBExport,
end cacheItem = function()
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
elseif checkExists(Exports.QBExport) then end,
while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end },
itemResource = Exports.QBExport { script = Exports.ESXExport,
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items cacheItem = function()
elseif checkExists(Exports.ESXExport) then
itemResource = Exports.ESXExport
if GetResourceState(Exports.QSInv):find("start") then
cache.Items = exports[Exports.QSInv]:GetItemList()
else
cache.Items = ESX.GetItems()
while not next(cache.Items) do
cache.Items = ESX.GetItems() cache.Items = ESX.GetItems()
Wait(1000) while not next(cache.Items) do
end cache.Items = ESX.GetItems()
end Wait(1000)
end
end,
},
{ script = Exports.RSGExport,
cacheItem = function()
cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
end,
},
{ script = Exports.VorpInv,
cacheItem = function()
local dbItems = MySQL.query.await('SELECT * FROM `items`')
local tempItems = {}
for i = 1, #dbItems do
local v = dbItems[i]
tempItems[v.item] = {
name = v.item,
label = v.label,
weight = v.weight,
info = v.metadata,
usable = v.usable,
type = v.type,
description = v.desc,
}
end
cache.Items = tempItems
end,
},
}
elseif checkExists(Exports.RSGExport) then for i = 1, #itemFunc do
while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end local data = itemFunc[i]
itemResource = Exports.RSGExport if checkExists(data.script) then
cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items waitStarted(data.script) -- Wait for detected script to start fully
data.cacheItem() -- run tablized function for core/inv
dupLowercaseWeapons(cache.Items) -- make usable weapon item names for jim_bridge
itemResource = data.script -- Grab script name to announce later
endTimer("Items") -- end timer
break -- break loop so it doesn't keep checking
end
end end
endTimer("Items")
--------------------- ---------------------
--- Load Vehicles --- --- Load Vehicles ---
--------------------- ---------------------
startTimer("Vehicles") ---
-- Vehicle loading depending on framework local vehicleFunc = {
if checkExists(Exports.QBXExport) then
vehResource = Exports.QBXExport
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
elseif checkExists(Exports.QBExport)then { script = Exports.QBXExport,
vehResource = Exports.QBExport cacheVehicle = function()
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.QBExport,
cacheVehicle = function()
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.OXCoreExport,
cacheVehicle = function()
cache.Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
cache.Vehicles[k] = {
model = k, hash = joaat(k),
price = v.price,
name = v.name,
brand = v.make
}
end
end,
},
{ script = Exports.ESXExport,
cacheVehicle = function()
while not MySQL do Wait(100) end
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
cache.Vehicles[v.model] = {
model = v.model,
hash = joaat(v.model),
price = v.price,
name = v.name,
}
end
end,
},
{ script = Exports.RSGExport,
cacheVehicle = function()
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.VorpExport,
cacheVehicle = function()
cache.Vehicles = { ["unkown"] = {} }
end,
},
}
elseif checkExists(Exports.OXCoreExport) then for i = 1, #vehicleFunc do
vehResource = Exports.OXCoreExport local data = vehicleFunc[i]
cache.Vehicles = {} if checkExists(data.script) then
for k, v in pairs(Ox.GetVehicleData()) do waitStarted(data.script) -- Wait for detected script to start fully
cache.Vehicles[k] = { data.cacheVehicle() -- run tablized function for core
model = k, hash = GetHashKey(k), vehResource = data.script -- Grab script name to announce later
price = v.price, endTimer("Vehicles") -- end timer
name = v.name, break -- break loop so it doesn't keep checking
brand = v.make
}
end end
elseif checkExists(Exports.ESXExport) then
vehResource = Exports.ESXExport
while not MySQL do Wait(1000) end
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
cache.Vehicles[v.model] = {
model = v.model,
hash = GetHashKey(v.model),
price = v.price,
name = v.name,
}
end
elseif checkExists(Exports.RSGExport) then
vehResource = Exports.RSGExport
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
end end
endTimer("Vehicles")
--------------------- ---------------------
----- Load Jobs ----- ----- Load Jobs -----
--------------------- ---------------------
startTimer("Jobs")
-- Jobs loading based on framework
if checkExists(Exports.QBXExport) then
jobResource = Exports.QBXExport
cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
elseif checkExists(Exports.QBExport) then local jobFunc = {
jobResource = Exports.QBExport
cache.Jobs, cache.Gangs = exports[Exports.QBExport]:GetCoreObject().Shared.Jobs, exports[Exports.QBExport]:GetCoreObject().Shared.Gangs
elseif checkExists(Exports.OXCoreExport) then { script = Exports.QBXExport,
jobResource = Exports.OXCoreExport cacheJob = function()
while not MySQL do Wait(1000) end cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`') end,
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`') },
local gradeMap = {} { script = Exports.QBExport,
for _, grade in pairs(tempGrades) do cacheJob = function()
gradeMap[grade.group] = gradeMap[grade.group] or {} Core = exports[Exports.QBExport]:GetCoreObject()
gradeMap[grade.group][grade.grade] = { name = grade.label } cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end end,
for _, job in pairs(tempJobs) do },
cache.Jobs[job.name] = { { script = Exports.OXCoreExport,
label = job.label, cacheJob = function()
grades = gradeMap[job.name] or {} while not MySQL do Wait(100) end
} local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
end local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
cache.Gangs = cache.Jobs local gradeMap = {}
for _, grade in pairs(tempGrades) do
elseif checkExists(Exports.ESXExport) then gradeMap[grade.group] = gradeMap[grade.group] or {}
jobResource = Exports.ESXExport gradeMap[grade.group][grade.grade] = { name = grade.label }
ESX = exports[Exports.ESXExport]:getSharedObject()
cache.Jobs = ESX.GetJobs()
while not next(cache.Jobs) do
Wait(100)
cache.Jobs = ESX.GetJobs()
end
for Role, Grades in pairs(cache.Jobs) do
-- Check for if user has added grades
if Grades.grades == nil or not next(Grades.grades) then
goto continue
end
for grade, info in pairs(Grades.grades) do
if info.label and info.label:find("[Bb]oss") then
cache.Jobs[Role].grades[grade].isBoss = true
goto continue
end end
end for _, job in pairs(tempJobs) do
local highestGrade = nil cache.Jobs[job.name] = {
for k in pairs(Grades.grades) do label = job.label,
local num = tonumber(k) grades = gradeMap[job.name] or {}
if num and (not highestGrade or num > highestGrade) then }
highestGrade = num
end end
end cache.Gangs = cache.Jobs
end,
},
{ script = Exports.ESXExport,
cacheJob = function()
ESX = ESX or exports[Exports.ESXExport]:getSharedObject()
cache.Jobs = ESX.GetJobs()
while not next(cache.Jobs) do
Wait(100)
cache.Jobs = ESX.GetJobs()
end
for Role, Grades in pairs(cache.Jobs) do
-- Check for if user has added grades
if Grades.grades == nil or not next(Grades.grades) then
goto continue
end
for grade, info in pairs(Grades.grades) do
if info.label and info.label:find("[Bb]oss") then
cache.Jobs[Role].grades[grade].isBoss = true
goto continue
end
end
local highestGrade = nil
for k in pairs(Grades.grades) do
local num = tonumber(k)
if num and (not highestGrade or num > highestGrade) then
highestGrade = num
end
end
if highestGrade then if highestGrade then
cache.Jobs[Role].grades[tostring(highestGrade)].isBoss = true cache.Jobs[Role].grades[tostring(highestGrade)].isBoss = true
end end
::continue:: ::continue::
end
cache.Gangs = cache.Jobs
end,
},
{ script = Exports.RSGExport,
cacheJob = function()
Core = exports[Exports.RSGExport]:GetCoreObject()
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
end,
},
{ script = Exports.VorpExport,
cacheJob = function()
cache.Jobs = { ["unkown"] = {} }
cache.Gangs = cache.Jobs
end,
},
}
for i = 1, #jobFunc do
local data = jobFunc[i]
if checkExists(data.script) then
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheJob() -- run tablized function for core
jobResource = data.script -- Grab script name to announce later
endTimer("Jobs") -- end timer
break -- break loop so it doesn't keep checking
end end
cache.Gangs = cache.Jobs
elseif checkExists(Exports.RSGExport) then
jobResource = Exports.RSGExport
cache.Jobs, cache.Gangs = exports[Exports.RSGExport]:GetCoreObject().Shared.Jobs, exports[Exports.RSGExport]:GetCoreObject().Shared.Gangs
end end
endTimer("Jobs")
-- Fallback if nil or empty -- Fallback if nil or empty
if cache.Items == nil or not next(cache.Items) then if cache.Items == nil or not next(cache.Items) then
@@ -308,8 +406,6 @@ end
-- Forcefully load the the specified config file from inventory scripts -- Forcefully load the the specified config file from inventory scripts
-- This allows to get information required for certain functions that need to detect how much space is left in a players inventory -- This allows to get information required for certain functions that need to detect how much space is left in a players inventory
-- This is born from too many tickets of me needing to explain that they need to change "InventoryWeight" to match their inv setting -- This is born from too many tickets of me needing to explain that they need to change "InventoryWeight" to match their inv setting
startTimer("InvWeight")
startTimer("InvSlots")
local function getInventoryConfig(resource, data) local function getInventoryConfig(resource, data)
if data.convars then if data.convars then
return function(path) return function(path)
@@ -323,12 +419,17 @@ local function getInventoryConfig(resource, data)
if not content then return nil, "Failed to load file" end if not content then return nil, "Failed to load file" end
local env = { local env = {
GetConvar = GetConvar, vector3 = vector3, Citizen = Citizen, GetConvar = GetConvar,
GetResourceState = GetResourceState, exports = exports, vector3 = vector3,
DependencyCheck = DependencyCheck or function() return nil end, vector4 = vector4,
Citizen = Citizen,
GetResourceState = GetResourceState,
exports = exports,
DependencyCheck = function() return nil end,
} }
local fn, err = load(content, '@'..data.file, 't', env) local fn, err = load(content, '@'..data.file, 't', env)
if not fn then return nil, "Failed to compile config: " .. err end if not fn then return nil, "Failed to compile config: " .. err end
if not pcall(fn) then return nil, "Error executing config file" end if not pcall(fn) then return nil, "Error executing config file" end
@@ -359,7 +460,6 @@ local invWeightTable = {
}, },
[Exports.JPRInv] = { file = "configs/main_config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } }, [Exports.JPRInv] = { file = "configs/main_config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
[Exports.PSInv] = { file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } }, [Exports.PSInv] = { file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
[Exports.QSInv] = { file = "config/config.lua", path = { "InventoryWeight", "weight" }, slotPath = { "InventoryWeight", "slots" } },
[Exports.TgiannInv] = { file = "configs/config.lua", path = { "slotsMaxWeights", "player", "maxWeight" }, slotPath = { "slotsMaxWeights", "player", "slots" } }, [Exports.TgiannInv] = { file = "configs/config.lua", path = { "slotsMaxWeights", "player", "maxWeight" }, slotPath = { "slotsMaxWeights", "player", "slots" } },
[Exports.CodeMInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } }, [Exports.CodeMInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
[Exports.RSGInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } }, [Exports.RSGInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
@@ -372,6 +472,8 @@ for script, data in pairs(invWeightTable) do
if checkExists(script) then if checkExists(script) then
if script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end if script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end
waitStartedOrStopped(script)
local attempts = data.fallback or { data } local attempts = data.fallback or { data }
local lookup, used, err local lookup, used, err
@@ -419,17 +521,17 @@ CreateThread(function()
if type(v) ~= "number" then for count in pairs(v) do counts[k] += 1 end end if type(v) ~= "number" then for count in pairs(v) do counts[k] += 1 end end
end end
if cache.InventoryWeight then if cache.InventoryWeight then
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventoryWeight^7: ^3"..cache.InventoryWeight.."^7 (^3"..(cache.InventoryWeight / 1000).."kg^7) ^7("..timers["InvWeight"].."s)") print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventoryWeight^7: ^3"..cache.InventoryWeight.."^7 (^3"..(cache.InventoryWeight / 1000).."kg^7) ^7"..timers["InvWeight"])
end end
if cache.InventorySlots then if cache.InventorySlots then
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7("..timers["InvSlots"].."s)") print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7"..timers["InvSlots"])
end end
print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7("..timers["Items"].."s)") print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7"..timers["Items"])
print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7("..timers["Vehicles"].."s)") print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7"..timers["Vehicles"])
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7("..timers["Jobs"].."s)") print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7"..timers["Jobs"])
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7("..timers["Jobs"].."s)") print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7"..timers["Jobs"])
endTimer("Cache") endTimer("Cache")
print("^6FrameworkCache^7: ^2Cache Ready ^7("..timers["Cache"].."s)") print("^6FrameworkCache^7: ^2Cache Ready ^7"..timers["Cache"])
cacheReady = true cacheReady = true
end) end)

View File

@@ -1,13 +1,12 @@
name "Jim_Bridge" name "Jim_Bridge"
author "Jimathy" author "Jimathy"
version "2.0.19" version "2.1.0"
description "Framework Bridge By Jimathy" description "Framework Bridge By Jimathy"
fx_version "cerulean" 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.' 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' } games { 'gta5', 'rdr3' }
lua54 'yes' lua54 'yes'
files { files {
'starter.lua', 'starter.lua',
'shared/*.lua', 'shared/*.lua',
@@ -25,3 +24,5 @@ client_scripts {
'clientFrameworkCache.lua', 'clientFrameworkCache.lua',
'ui_modules/*.lua', 'ui_modules/*.lua',
} }
suppress_updates 'false' -- set to 'true' to disable update pings

View File

@@ -1,6 +1,8 @@
------------------------------------------------------------- -------------------------------------------------------------
-- Exploit Auth System -- Exploit Auth System
------------------------------------------------------------- -------------------------------------------------------------
forceDisableExplotProtection = false -- dangerous, this allows exploits
AuthEvent = nil AuthEvent = nil
currentToken = nil currentToken = nil
if isServer() then if isServer() then
@@ -12,7 +14,7 @@ if isServer() then
local src = source local src = source
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
--debugPrint(GetInvokingResource()) --debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= Exports.QBExport and GetInvokingResource() ~= Exports.VorpExport then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7") debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return "" return ""
end end
@@ -45,21 +47,22 @@ if isServer() then
createCallback(getScript()..":callback:GetAuthEvent", function(source) createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source local src = source
--debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= Exports.QBExport and GetInvokingResource() ~= Exports.VorpExport then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7") debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7")
return "" return ""
end end
if authCooldown[src] then if authCooldown[src] then
debugPrint("^1Auth^7: ^3Cooldown active^7 for Player ^1"..src.."^7, ignoring additional auth request") debugPrint("^1Auth^7: ^3Cooldown active^7 for Player ^1"..src.."^7, ignoring additional auth request")
return "" return AuthEvent
end end
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent) debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent)
authCooldown[src] = true authCooldown[src] = true
SetTimeout(5000, function() -- 5 second cooldown SetTimeout(60000, function() -- 1 minute cooldown
authCooldown[src] = nil authCooldown[src] = nil
end) end)
@@ -80,6 +83,8 @@ if isServer() then
-- Multiuse function to check if the generated client token is valid -- Multiuse function to check if the generated client token is valid
function checkToken(src, token, genType, name) function checkToken(src, token, genType, name)
if forceDisableExplotProtection == true then return true end
if token == nil then if token == nil then
debugPrint("^1Auth^7: ^1No token recieved^7") debugPrint("^1Auth^7: ^1No token recieved^7")
if genType == "stash" then if genType == "stash" then
@@ -116,3 +121,30 @@ else
TriggerServerEvent(getScript()..":clearAuthEventRequest") TriggerServerEvent(getScript()..":clearAuthEventRequest")
end, true) end, true)
end end
function distExploitCheck(table, src)
if forceDisableExplotProtection == true then return true end
if not table then
print("^1Error^7: ^1This wasn^7'^1t reigstered correctly or this is an exploit attempt^1")
return false
end
local ped = src and GetPlayerPed(src) or PlayerPedId()
local srcCoords = GetEntityCoords(ped)
local allow = false
for i = 1, #table do
if #(table[i].xyz - srcCoords) <= 10 then
return true
else
allow = false
end
end
if not allow then
print(src and ("^1Src ^3"..src.." ").."^1Tried to open a registered shop/stash from over the distance limit^7")
return false
end
end

View File

@@ -7,6 +7,26 @@
• Wait for the player to be logged in before proceeding. • Wait for the player to be logged in before proceeding.
]] ]]
local onLoadLast = {} -- keyed by tostring(func)
local function _debouncedRun(func)
local key = tostring(func)
local now = GetGameTimer()
local last = onLoadLast[key] or -1e12
if (now - last) < 5000 then
debugPrint(("^6Bridge^7 ^3onPlayerLoaded^7 skipped — cooldown %dms remaining"):format(5000 - (now - last)))
return
end
onLoadLast[key] = now
CreateThread(function()
Wait(2000) -- keep your small delay
debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded callback")
func()
end)
end
------------------------------------------------------------- -------------------------------------------------------------
-- Player Loaded and Unloaded Events -- Player Loaded and Unloaded Events
------------------------------------------------------------- -------------------------------------------------------------
@@ -30,45 +50,42 @@ function onPlayerLoaded(func, onStart)
if onStart then if onStart then
onResourceStart(function() onResourceStart(function()
if not waitForLogin() then return end if not waitForLogin() then return end
debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 routed through ^3onPlayerLoaded^7()")
loaded = true _debouncedRun(func)
debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 executed through ^3onPlayerLoaded^7()")
Wait(2000)
func()
end, true) end, true)
end end
if not loaded then local handler = function()
local tempFunc = function() _debouncedRun(func)
Wait(2000) end
debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded")
func()
end
if isStarted(QBExport) or isStarted(QBXExport) then if isStarted(QBExport) or isStarted(QBXExport) then
onPlayerFramework = QBExport onPlayerFramework = QBExport
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', tempFunc) RegisterNetEvent('QBCore:Client:OnPlayerLoaded', handler)
elseif isStarted(ESXExport) then
onPlayerFramework = ESXExport
RegisterNetEvent('esx:playerLoaded', function()
if waitForSharedLoad() then
tempFunc()
end
end
)
elseif isStarted(OXCoreExport) then
onPlayerFramework = OXCoreExport
RegisterNetEvent('ox:playerLoaded', tempFunc)
elseif isStarted(RSGExport) then
onPlayerFramework = RSGExport
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
end
if onPlayerFramework ~= "" then elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^7()^2 with ^3" .. onPlayerFramework.."^7") onPlayerFramework = ESXExport
else RegisterNetEvent('esx:playerLoaded', function()
print("^4ERROR^7: No supported core detected for onPlayerLoaded - Check starter.lua") if waitForSharedLoad() then handler() end
end end)
elseif isStarted(OXCoreExport) then
onPlayerFramework = OXCoreExport
RegisterNetEvent('ox:playerLoaded', handler)
elseif isStarted(RSGExport) then
onPlayerFramework = RSGExport
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', handler)
elseif isStarted(VorpExport) then
onPlayerFramework = VorpExport
RegisterNetEvent('vorp_core:Client:OnPlayerSpawned', handler)
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 end
@@ -117,8 +134,7 @@ function onResourceStart(func, thisScript)
debugPrint("^6Bridge^7: ^2Shared Load Detected^7.") debugPrint("^6Bridge^7: ^2Shared Load Detected^7.")
hasPrinted = true hasPrinted = true
end end
if isStarted(ESXExport) then Wait(10000) end func(resourceName)
func()
end end
end end
end) end)
@@ -137,7 +153,7 @@ function onResourceStop(func, thisScript)
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^7()") debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^7()")
AddEventHandler('onResourceStop', function(resourceName) AddEventHandler('onResourceStop', function(resourceName)
if getScript() == resourceName and (thisScript or true) then if getScript() == resourceName and (thisScript or true) then
func() func(resourceName)
end end
end) end)
end end
@@ -175,6 +191,12 @@ function waitForLogin()
break break
end end
end end
elseif isStarted(VorpExport) then
-- For other frameworks, use LocalPlayer.state.isLoggedIn.
while not LocalPlayer.state.IsInSession and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
loggedIn = LocalPlayer.state.IsInSession
else else
-- For other frameworks, use LocalPlayer.state.isLoggedIn. -- For other frameworks, use LocalPlayer.state.isLoggedIn.
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
@@ -194,14 +216,13 @@ end
local messageShown = false local messageShown = false
function waitForSharedLoad() function waitForSharedLoad()
local timeout = 100000 -- 10 seconds in milliseconds local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
local startTime = GetGameTimer()
local loaded = true
local loop = 0 local loop = 0
while ((not Jobs or not next(Jobs)) or while ((not Jobs or not next(Jobs)) or
(not Items or not next(Items)) or (not Items or not next(Items)) or
(not Vehicles or not next(Vehicles)) (not Vehicles or not next(Vehicles))
) and ((GetGameTimer() - startTime) < timeout) do ) and (timeout and GetGameTimer() < timeout) do
if loop >= 3 and not messageShown then if loop >= 3 and not messageShown then
if (not Jobs or not next(Jobs)) then if (not Jobs or not next(Jobs)) then
print("^4Debug^7: ^2Waiting for ^7Jobs^2 to be loaded") print("^4Debug^7: ^2Waiting for ^7Jobs^2 to be loaded")
@@ -214,19 +235,16 @@ function waitForSharedLoad()
end end
messageShown = true messageShown = true
end end
--print((GetGameTimer() - startTime) < timeout)
Wait(1000) Wait(1000)
if Jobs and Items and Vehicles then
--print("^6Bridge^7: ^2Jobs, Items, and Vehicles Loaded^7.")
loaded = true
break
end
loop += 1 loop += 1
end end
if not loaded then
if Jobs and Items and Vehicles then
debugPrint("^6Bridge^7: ^2Jobs, Items, and Vehicles Loaded^7.")
return true
else
print("^4Error^7: ^1Timeout reached while waiting for shared load^7.") print("^4Error^7: ^1Timeout reached while waiting for shared load^7.")
return false return false
else
return true
end end
end end

View File

@@ -1,100 +1,111 @@
function parseVersion(version) local function readBoolMeta(key, default)
local parts = {} local val = GetResourceMetadata("jim_bridge", key, 0)
for num in version:gmatch("%d+") do if not val then return default end
table.insert(parts, tonumber(num)) val = tostring(val):lower()
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
end
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
if not SUPPRESS_UPDATES then
function parseVersion(version)
local parts = {}
for num in version:gmatch("%d+") do
table.insert(parts, tonumber(num))
end
return parts
end end
return parts
end
function compareVersions(current, newest) function compareVersions(current, newest)
local currentParts = parseVersion(current) local currentParts = parseVersion(current)
local newestParts = parseVersion(newest) local newestParts = parseVersion(newest)
for i = 1, math.max(#currentParts, #newestParts) do for i = 1, math.max(#currentParts, #newestParts) do
local c = currentParts[i] or 0 local c = currentParts[i] or 0
local n = newestParts[i] or 0 local n = newestParts[i] or 0
if c < n then return -1 if c < n then return -1
elseif c > n then return 1 end elseif c > n then return 1 end
end
return 0
end end
return 0
end
function capitalize(str) function capitalize(str)
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end)) return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
end end
local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or "" local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or "" local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or "" local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or "" local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7") print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
function CheckVersion() function CheckVersion()
if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then
CreateThread(function() CreateThread(function()
Wait(4000) Wait(4000)
local script = getScript() local script = getScript()
local currentVersionRaw = GetResourceMetadata(script, 'version') local currentVersionRaw = GetResourceMetadata(script, 'version')
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..script..'.txt', function(err, newestVersionRaw, headers) PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..script..'.txt', function(err, newestVersionRaw, headers)
if not newestVersionRaw then if not newestVersionRaw then
-- fallback -- fallback
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..script..'/master/version.txt', function(err, fallbackVersionRaw, headers) PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..script..'/master/version.txt', function(err, fallbackVersionRaw, headers)
if not fallbackVersionRaw then if not fallbackVersionRaw then
print("^1Currently unable to run a version check for ^7'^3"..script.."^7' (^3"..currentVersionRaw.."^7)") print("^1Currently unable to run a version check for ^7'^3"..script.."^7' (^3"..currentVersionRaw.."^7)")
return return
end end
local lines = {}
for line in fallbackVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
local fallbackVersion = (lines[1] or "0.0.0"):gsub("v", "")
local changelog = {}
for i = 2, #lines do table.insert(changelog, lines[i]) end
local compareResult = compareVersions(currentVersionRaw, fallbackVersion)
if compareResult == 0 then
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersion.."^7)")
if #changelog > 0 then
for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line)
end
end
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function() CheckVersion() end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersion.."^7)")
end
end)
else
local lines = {} local lines = {}
for line in fallbackVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end for line in newestVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
local fallbackVersion = (lines[1] or "0.0.0"):gsub("v", "") local newestVersion = (lines[1] or "0.0.0"):gsub("v", "")
local changelog = {} local changelog = {}
for i = 2, #lines do table.insert(changelog, lines[i]) end for i = 2, #lines do table.insert(changelog, lines[i]) end
local compareResult = compareVersions(currentVersionRaw, fallbackVersion) local compareResult = compareVersions(currentVersionRaw, newestVersion)
if compareResult == 0 then if compareResult == 0 then
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)") print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7") print("^1----------------------------------------------------------------------^7")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersion.."^7)") print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersion.."^7)")
if #changelog > 0 then if #changelog > 0 then
for _, line in ipairs(changelog) do for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line) print((line:find("http") and "^7" or "^5")..line)
end end
end end
print("^1----------------------------------------------------------------------^7") print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function() CheckVersion() end) SetTimeout(3600000, function() CheckVersion() end)
else else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersion.."^7)") print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
end end
end)
else
local lines = {}
for line in newestVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
local newestVersion = (lines[1] or "0.0.0"):gsub("v", "")
local changelog = {}
for i = 2, #lines do table.insert(changelog, lines[i]) end
local compareResult = compareVersions(currentVersionRaw, newestVersion)
if compareResult == 0 then
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
elseif compareResult < 0 then
print("^1----------------------------------------------------------------------^7")
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersion.."^7)")
if #changelog > 0 then
for _, line in ipairs(changelog) do
print((line:find("http") and "^7" or "^5")..line)
end
end
print("^1----------------------------------------------------------------------^7")
SetTimeout(1200000, function() CheckVersion() end)
else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
end end
end end)
end) end)
end) end
end end
end
CheckVersion() CheckVersion()
end

View File

@@ -1,3 +1,30 @@
local CALLBACK_RETRIES = 0
-- Internal: await a callback with a timeout
local function awaitWithTimeout(registerFn, timeoutMs)
local p = promise.new()
local finished = false
registerFn(function(result)
if finished then return end
finished = true
p:resolve(result)
end)
-- timeout watchdog
CreateThread(function()
Wait(5000)
if not finished then
finished = true
p:reject('timeout')
end
end)
local ok, res = pcall(function() return Citizen.Await(p) end)
if ok then return res, nil end
return nil, res
end
--- Registers a callback function with the appropriate framework. --- Registers a callback function with the appropriate framework.
--- ---
--- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly. --- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
@@ -37,6 +64,10 @@ function createCallback(callbackName, funct)
debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName) debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
Core = Core or exports[QBExport]:GetCoreObject() Core = Core or exports[QBExport]:GetCoreObject()
Core.Functions.CreateCallback(callbackName, adaptedFunction) Core.Functions.CreateCallback(callbackName, adaptedFunction)
elseif isStarted(VorpExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..VorpExport.." ^3Callback^7:", callbackName)
Core = Core or exports.vorp_core:GetCore()
Core.Callback.Register(callbackName, adaptedFunction)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName) debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
ESX.RegisterServerCallback(callbackName, adaptedFunction) ESX.RegisterServerCallback(callbackName, adaptedFunction)
@@ -55,6 +86,7 @@ end
---@param ... any Additional arguments to pass to the callback. ---@param ... any Additional arguments to pass to the callback.
--- ---
---@return any any The result returned by the callback function. ---@return any any The result returned by the callback function.
---@return string string The error/success message returned by the callback function.
--- ---
---@usage ---@usage
--- ```lua --- ```lua
@@ -65,25 +97,54 @@ end
--- print(result) --- print(result)
--- ``` --- ```
function triggerCallback(callbackName, ...) function triggerCallback(callbackName, ...)
local result = nil
debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName) debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName)
local args = {...}
if isStarted(OXLibExport) then if isStarted(OXLibExport) then
result = lib.callback.await(callbackName, false, ...) local ok, res = pcall(function()
elseif isStarted(QBExport) then return lib.callback.await(callbackName, false, table.unpack(args))
local p = promise.new() end)
Core.Functions.TriggerCallback(callbackName, function(cbResult) if ok then return res, "nil" end
p:resolve(cbResult) return nil, tostring(res)
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 end
return result
local attempts = 0
local lastErr
repeat
attempts = attempts + 1
if isStarted(QBExport) then
local res, err = awaitWithTimeout(function(cb)
Core.Functions.TriggerCallback(callbackName, cb, table.unpack(args))
end, 5000)
if res ~= nil then return res, "nil" end
lastErr = err
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (QB) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
elseif isStarted(VorpExport) then
local res, err = awaitWithTimeout(function(cb)
Core.Callback.TriggerAwait(callbackName, cb, table.unpack(args))
end, 5000)
if res ~= nil then return res, "nil" end
lastErr = err
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (Vorp) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
elseif isStarted(ESXExport) then
local res, err = awaitWithTimeout(function(cb)
ESX.TriggerServerCallback(callbackName, cb, table.unpack(args))
end, 5000)
if res ~= nil then return res, "nil" end
lastErr = err
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (ESX) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
else
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with^7:", callbackName)
return nil, "no_framework"
end
Wait(10)
until attempts > (1 + CALLBACK_RETRIES)
return nil, lastErr or "timeout"
end end

View File

@@ -10,6 +10,263 @@
• esx (using ESX.UI.Menu) • esx (using ESX.UI.Menu)
]] ]]
local contextFunc = {
["ox"] =
function(Menu, data)
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
end,
["qb"] =
function(Menu, data)
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)
end,
["gta"] =
function(Menu, data)
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
titleColor = { 222, 255, 255 },
maxOptionCountOnScreen = 15,
width = 0.25,
x = 0.7,
})
if WarMenu.IsAnyMenuOpened() then return end
WarMenu.OpenMenu(tostring(Menu))
CreateThread(function()
local close = true
while true do
if WarMenu.Begin(tostring(Menu)) then
if data.onBack then
if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
WarMenu.CloseMenu()
Wait(10)
data.onBack()
end
end
for k in pairs(Menu) do
local pressed = WarMenu.Button(Menu[k].header)
if not Menu[k].header then
Menu[k].header = Menu[k].txt
Menu[k].txt = nil
end
if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
if Menu[k].disabled or Menu[k].isMenuHeader then
WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
else
WarMenu.ToolTip(
(Menu[k].blip and "~BLIP_".."8".."~ " or "")..
Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
true)
end
end
if pressed and not Menu[k].isMenuHeader then
WarMenu.CloseMenu()
close = false
Wait(10)
Menu[k].onSelect()
end
end
WarMenu.End()
else
return
end
if not WarMenu.IsAnyMenuOpened() and close then
stopTempCam(cam)
if data.onExit then data.onExit() end
end
Wait(0)
end
end)
end,
["esx"] =
function(Menu, data)
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,
["lation"] =
function(Menu, data)
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
onSelect = data.onBack,
header = "Return",
})
end
for k in pairs(Menu) do
if data.onSelected and Menu[k].arrow then
Menu[k].icon = "fas fa-angle-right"
end
-- If no title, use header or txt as title/label.
if not Menu[k].title then
if Menu[k].header ~= nil and Menu[k].header ~= "" then
Menu[k].title = Menu[k].header
Menu[k].label = Menu[k].header
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
else
Menu[k].title = Menu[k].txt
Menu[k].label = Menu[k].txt
end
end
-- Copy parameters from 'params' if available.
if Menu[k].params then
Menu[k].event = Menu[k].params.event
Menu[k].args = Menu[k].params.args or {}
end
if Menu[k].isMenuHeader then
Menu[k].readOnly = true
end
end
exports.lation_ui:registerMenu({
id = 'menu',
title = data.header,
onExit = data.onExit and data.onExit or nil,
subtitle = (data.headertxt and data.headertxt or ""),
options = Menu,
})
-- Show menu
exports.lation_ui:showMenu('menu')
end,
}
--- Opens a menu using the configured menu system. --- 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`. --- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
@@ -47,255 +304,22 @@
--- }) --- })
--- ``` --- ```
function openMenu(Menu, data) function openMenu(Menu, data)
if Config.System.Menu == "ox" then contextFunc[Config.System.Menu](Menu, data)
local index = nil
if data.onBack and not data.onSelected then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
title = "Return",
onSelect = data.onBack,
label = "Return",
})
end
for k in pairs(Menu) do
if data.onSelected and Menu[k].arrow then
Menu[k].icon = "fas fa-angle-right"
end
-- If no title, use header or txt as title/label.
if not Menu[k].title then
if Menu[k].header ~= nil and Menu[k].header ~= "" then
Menu[k].title = Menu[k].header
Menu[k].label = Menu[k].header
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
else
Menu[k].title = Menu[k].txt
Menu[k].label = Menu[k].txt
end
end
-- Copy parameters from 'params' if available.
if Menu[k].params then
Menu[k].event = Menu[k].params.event
Menu[k].args = Menu[k].params.args or {}
end
if Menu[k].isMenuHeader then
Menu[k].readOnly = true
end
end
local menuID = 'Menu'
(data.onSelected and lib.registerMenu or lib.registerContext)({
id = menuID,
title = data.header..br..br..(data.headertxt and data.headertxt or ""),
position = 'top-right',
options = Menu,
canClose = data.canClose and data.canClose or nil,
onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
onExit = data.onExit and data.onExit or nil,
onSelected = data.onSelected and (function(selected) index = selected end) or nil,
}, data.onSelected and (function(x, y, args)
if Menu[x].refresh then
if Menu[x].onSelect then
Menu[x].onSelect()
end
lib.showMenu(menuID, index)
else
if Menu[x].onSelect then
Menu[x].onSelect()
else
lib.showMenu(menuID, index)
end
end
end) or nil)
if data.onSelected then
lib.showMenu(menuID, 1)
else
lib.showContext(menuID)
end
elseif Config.System.Menu == "qb" then
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
header = " ",
txt = "Return",
params = {
isAction = true,
event = data.onBack,
},
})
elseif data.canClose then
table.insert(Menu, 1, {
icon = "fas fa-circle-xmark",
header = " ",
txt = "Close",
params = {
isAction = true,
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
},
})
end
if data.header ~= nil then
local tempMenu = {}
for k, v in pairs(Menu) do tempMenu[k + 1] = v end
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
Menu = tempMenu
end
for k in pairs(Menu) do
if not Menu[k].params or not Menu[k].params.event then
Menu[k].params = {
isAction = true,
event = Menu[k].onSelect or function() end,
}
end
if not Menu[k].header then Menu[k].header = " " end
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
end
exports[QBMenuExport]:openMenu(Menu)
elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
titleColor = { 222, 255, 255 },
maxOptionCountOnScreen = 15,
width = 0.25,
x = 0.7,
})
if WarMenu.IsAnyMenuOpened() then return end
WarMenu.OpenMenu(tostring(Menu))
CreateThread(function()
local close = true
while true do
if WarMenu.Begin(tostring(Menu)) then
if data.onBack then
if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
WarMenu.CloseMenu()
Wait(10)
data.onBack()
end
end
for k in pairs(Menu) do
local pressed = WarMenu.Button(Menu[k].header)
if not Menu[k].header then
Menu[k].header = Menu[k].txt
Menu[k].txt = nil
end
if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
if Menu[k].disabled or Menu[k].isMenuHeader then
WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
else
WarMenu.ToolTip(
(Menu[k].blip and "~BLIP_".."8".."~ " or "")..
Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
true)
end
end
if pressed and not Menu[k].isMenuHeader then
WarMenu.CloseMenu()
close = false
Wait(10)
Menu[k].onSelect()
end
end
WarMenu.End()
else
return
end
if not WarMenu.IsAnyMenuOpened() and close then
stopTempCam(cam)
if data.onExit then data.onExit() end
end
Wait(0)
end
end)
elseif Config.System.Menu == "esx" then
for k in pairs(Menu) do
Menu[k].label = Menu[k].header
Menu[k].name = "button"..k
end
if data.canClose then
table.insert(Menu, 1, {
icon = "fas fa-circle-xmark",
label = "Close",
name = "close",
onSelect = data.onExit,
})
end
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
label = "Return",
name = "return",
onSelect = data.onBack,
})
end
ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
title = data.header,
align = 'top-right',
elements = Menu,
},
function(menuData, menu)
for k in pairs(Menu) do
if menuData.current.name == Menu[k].name then
menu.close()
Wait(10)
Menu[k].onSelect()
end
end
end,
function(data, menu)
menu.close()
end)
elseif Config.System.Menu == "lation" then
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
onSelect = data.onBack,
header = "Return",
})
end
for k in pairs(Menu) do
if data.onSelected and Menu[k].arrow then
Menu[k].icon = "fas fa-angle-right"
end
-- If no title, use header or txt as title/label.
if not Menu[k].title then
if Menu[k].header ~= nil and Menu[k].header ~= "" then
Menu[k].title = Menu[k].header
Menu[k].label = Menu[k].header
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
else
Menu[k].title = Menu[k].txt
Menu[k].label = Menu[k].txt
end
end
-- Copy parameters from 'params' if available.
if Menu[k].params then
Menu[k].event = Menu[k].params.event
Menu[k].args = Menu[k].params.args or {}
end
if Menu[k].isMenuHeader then
Menu[k].readOnly = true
end
end
exports.lation_ui:registerMenu({
id = 'menu',
title = data.header,
onExit = data.onExit and data.onExit or nil,
subtitle = (data.headertxt and data.headertxt or ""),
options = Menu,
})
-- Show menu
exports.lation_ui:showMenu('menu')
end
end end
-- Line break conversion table, basically, to automate the linebreak in functions `br`
-- This is declared and automatically sets it for what menu you are using (depends if one is uding MD formatting or HTML formatting)
local lineBreakConversion = {
["ox"] = "\n",
["gta"] = "\n",
["lation"] = "\n",
["qb"] = "<br>",
["esx"] = "<br>",
}
--- A line break constant used for menu header formatting. --- A line break constant used for menu header formatting.
br = (Config.System.Menu == "ox" or Config.System.Menu == "gta" or Config.System.Menu == "lation") and "\n" or "<br>" br = lineBreakConversion[Config.System.Menu]
--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes. --- 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. --- @return boolean boolean True if using ox or gta menus, otherwise false.

View File

@@ -10,11 +10,10 @@ OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
Exports.ESXExport or "", Exports.ESXExport or "",
Exports.OXCoreExport or "" Exports.OXCoreExport or ""
OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv = OXInv, QBInv, PSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
Exports.OXInv or "", Exports.OXInv or "",
Exports.QBInv or "", Exports.QBInv or "",
Exports.PSInv or "", Exports.PSInv or "",
Exports.QSInv or "",
Exports.CoreInv or "", Exports.CoreInv or "",
Exports.CodeMInv or "", Exports.CodeMInv or "",
Exports.OrigenInv or "", Exports.OrigenInv or "",
@@ -22,6 +21,9 @@ OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
Exports.JPRInv or "" Exports.JPRInv or ""
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or "" RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
VorpExport, VorpInv = Exports.VorpExport or "", Exports.VorpInv or ""
QBMenuExport = Exports.QBMenuExport or "" QBMenuExport = Exports.QBMenuExport or ""
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or "" QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
@@ -29,15 +31,17 @@ if isStarted(QBXExport) or isStarted(QBExport) then
Core = Core or exports[QBExport]:GetCoreObject() Core = Core or exports[QBExport]:GetCoreObject()
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
Core = Core or exports[RSGExport]:GetCoreObject() Core = Core or exports[RSGExport]:GetCoreObject()
elseif isStarted(VorpExport) then
Core = Core or exports[VorpExport]:GetCore()
end end
if IsDuplicityVersion() then if IsDuplicityVersion() then
local cache = nil local cache = nil
local timeout = GetGameTimer() + 120000 -- 2 minutes max wait (had to up this from 5 seconds because of slow servers) local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
-- Wait until jim_bridge is started and export is available -- Wait until jim_bridge is started and export is available
while not cache and GetGameTimer() < timeout do while not cache and (timeout and GetGameTimer() < timeout) do
if GetResourceState("jim_bridge"):find("start") then if GetResourceState("jim_bridge"):find("start") then
local success, result = pcall(function() local success, result = pcall(function()
return exports["jim_bridge"]:GetSharedData() return exports["jim_bridge"]:GetSharedData()
@@ -49,7 +53,7 @@ if IsDuplicityVersion() then
Wait(100) Wait(100)
end end
if not cache then if timeout and not cache then
print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.") print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
return return
end end

View File

@@ -89,7 +89,7 @@ function craftingMenu(data)
-- Check if the player can carry the required items (server callback). -- Check if the player can carry the required items (server callback).
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable) local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
local usingStash = data.stashName ~= nil local usingStash = data.stashName ~= nil and data.stashName ~= ""
Menu[#Menu+1] = { Menu[#Menu+1] = {
icon = usingStash and "fas fa-boxes-stacked" or "fas fa-person", icon = usingStash and "fas fa-boxes-stacked" or "fas fa-person",
@@ -115,19 +115,21 @@ function craftingMenu(data)
local metaTable = {} local metaTable = {}
-- Build ingredient details. -- Build ingredient details.
for l, b in pairs(Recipes[i][tostring(k)]) do for l, b in pairs(Recipes[i][tostring(k)]) do
local label = Items[l] and Items[l].label or "error - "..l local label = getItemLabel(l)
local hasItem = checkStashItem(data.stashName, { [l] = b }) local hasItem = checkStashItem(data.stashName, { [l] = b })
local missingMark = not hasItem and "" or " " local missingMark = not hasItem and "" or " "
settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark
metaTable[Items[l] and Items[l].label or "error - "..l] = b metaTable[label] = b
itemTable[l] = b itemTable[l] = b
end end
while not canCarryTable do Wait(0) end while not canCarryTable do
Wait(10)
end
disable = not checkStashItem(data.stashName, itemTable) disable = not checkStashItem(data.stashName, itemTable)
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k)) setheader = ((metadata and metadata.label) or getItemLabel(k))
..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "") ..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️" local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️"
local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or "" local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or ""
@@ -320,8 +322,6 @@ end
--- }) --- })
--- ``` --- ```
Config.Crafting.SingleProgress = true -- Set false to use individual progress bars per craft
function makeItem(data) function makeItem(data)
if CraftLock then return end if CraftLock then return end
CraftLock = true CraftLock = true
@@ -359,7 +359,7 @@ function makeItem(data)
return return
end end
if crafting and progressBar({ if crafting and progressBar({
label = "Using "..b.." "..Items[l].label, label = "Using "..b.." "..getItemLabel(l),
time = 1000, time = 1000,
cancel = true, cancel = true,
dict = 'pickup_object', dict = 'pickup_object',
@@ -398,7 +398,7 @@ function makeItem(data)
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0) PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end end
if crafting and progressBar({ if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label).." x"..craftAmount, label = bartext..((metadata and metadata.label) or getItemLabel(data.item)).." x"..craftAmount,
time = totalBartime, time = totalBartime,
cancel = true, cancel = true,
dict = animDict, dict = animDict,
@@ -413,11 +413,11 @@ function makeItem(data)
-- handle metadata and experience in a single go -- handle metadata and experience in a single go
if data.craft["hasCrafted"] ~= nil then if data.craft["hasCrafted"] ~= nil then
data.craftable.craftedItems[data.item] = true data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems) triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end end
if data.craft["exp"] ~= nil then if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give * craftAmount craftingLevel += data.craft["exp"].give * craftAmount
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel) triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end end
if data.craftable.Recipes[1].oneUse == true then if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot) removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
@@ -437,7 +437,7 @@ function makeItem(data)
-- Run the original loop for multiple progress bars -- Run the original loop for multiple progress bars
for i = 1, craftAmount do for i = 1, craftAmount do
if crafting and progressBar({ if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label), label = bartext..((metadata and metadata.label) or getItemLabel(data.item)),
time = bartime, time = bartime,
cancel = true, cancel = true,
dict = animDict, dict = animDict,
@@ -450,11 +450,11 @@ function makeItem(data)
currentToken = nil currentToken = nil
if data.craft["hasCrafted"] ~= nil then if data.craft["hasCrafted"] ~= nil then
data.craftable.craftedItems[data.item] = true data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems) triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end end
if data.craft["exp"] ~= nil then if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give craftingLevel += data.craft["exp"].give
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel) triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end end
if data.craftable.Recipes[1].oneUse == true then if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot) removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)

View File

@@ -5,6 +5,114 @@
various frameworks: QB, OX, GTA, and ESX. various frameworks: QB, OX, GTA, and ESX.
]] ]]
-- Text system handlers
local textHandlers = {
qb = {
show =
function(text, image)
if image then
text = '<img src="'..(radarTable[image] or "")..'" style="width:12px;height:12px">'..text
end
exports[QBExport]:DrawText(text, 'left')
end,
hide =
function()
exports[QBExport]:HideText()
end,
},
ox = {
show =
function(input, image, oxStyleTable)
local inputCount = #input
for i = 1, inputCount do
input[i] = input[i] .. (i ~= inputCount and " \n" or "")
end
lib.showTextUI(table.concat(input), {
icon = (image and radarTable[image] or image) or nil,
position = 'left-center',
style = oxStyleTable
})
end,
hide =
function()
lib.hideTextUI()
end,
},
lation = {
show =
function(input, image)
local inputCount = #input
for i = 1, inputCount do
input[i] = input[i] .. (i ~= inputCount and " \n" or "")
end
exports.lation_ui:showText({
description = table.concat(input),
keybind = nil,
icon = (image and radarTable[image] or image) or nil,
iconColor = '#3B82F6',
position = 'left-center'
})
end,
hide =
function()
exports.lation_ui:hideText()
end,
},
esx = {
show =
function(text, image)
if image then
text = '<img src="'..(radarTable[image] or "")..'" style="width:12px;height:12px">'..text
end
ESX.TextUI(text, nil)
end,
hide =
function()
ESX.HideUI()
end,
},
gta = {
show =
function(input, image, style)
local text = ""
for i = 1, #input do
if input[i] ~= "" then
text = text .. input[i] .. "\n~s~"
end
end
if image then
text = "~BLIP_" .. image .. "~ " .. text
end
DisplayHelpMsg(text:gsub("%:", ":~" .. (style or "g") .. "~"))
end,
hide =
function()
ClearAllHelpMessages()
end,
},
red = {
show =
function(input)
local text = ""
for i = 1, #input do
if input[i] ~= "" then
text = text .. input[i] .. "\n~q~"
end
end
TriggerEvent("jim-redui:DrawText", text)
end,
hide =
function()
TriggerEvent("jim-redui:HideText")
end,
}
}
--- Displays text on the screen using the configured draw text system. --- Displays text on the screen using the configured draw text system.
--- ---
--- Depending on Config.System.drawText, this function will use different methods to --- Depending on Config.System.drawText, this function will use different methods to
@@ -20,73 +128,33 @@
--- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~") --- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
--- ``` --- ```
function drawText(image, input, style, oxStyleTable) function drawText(image, input, style, oxStyleTable)
local text = ""
if not radarTable then radarTable = {} end if not radarTable then radarTable = {} end
if Config.System.drawText == "qb" then
-- Concatenate lines for QB system with HTML line breaks. local systemType = Config.System.drawText
local handler = textHandlers[systemType]
if not handler then return end
if systemType == "qb" or systemType == "esx" then
-- Concatenate lines for QB/ESX system with HTML line breaks.
local text = ""
for i = 1, #input do for i = 1, #input do
text = text..input[i].."</span>"..(input[i + 1] and "<br>" or "") text = text .. input[i] .. "</span>" .. (input[i + 1] and "<br>" or "")
end end
text = text:gsub("%:", ":<span style='color:yellow'>") text = text:gsub("%:", ":<span style='color:yellow'>")
if image then handler.show(text, image)
text = '<img src="'..(radarTable[image] or "")..'" style="width:12px;height:12px">'..text
end
exports[QBExport]:DrawText(text, 'left')
elseif Config.System.drawText == "ox" then elseif systemType == "ox" then
-- Append newline spacing to each input line. handler.show(input, image, oxStyleTable)
local inputnum = countTable(input)
for k, v in pairs(input) do
input[k] = v..(inputnum ~= k and " \n" or "")
end
lib.showTextUI(table.concat(input), { icon = (image and radarTable[image] or image) or nil, position = 'left-center', style = oxStyleTable })
elseif Config.System.drawText == "lation" then elseif systemType == "lation" then
local inputnum = countTable(input) handler.show(input, image)
for k, v in pairs(input) do
input[k] = v..(inputnum ~= k and " \n" or "")
end
exports.lation_ui:showText({
--title = " ",
description = table.concat(input),
keybind = nil,
icon = (image and radarTable[image] or image) or nil,
iconColor = '#3B82F6',
position = 'center-left'
})
elseif Config.System.drawText == "gta" then elseif systemType == "gta" then
-- Concatenate input lines and apply GTA style formatting. handler.show(input, image, style)
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)
elseif systemType == "red" then
handler.show(input)
end end
end end
@@ -99,17 +167,8 @@ end
--- hideText() --- hideText()
--- ``` --- ```
function hideText() function hideText()
if Config.System.drawText == "qb" then local handler = textHandlers[Config.System.drawText]
exports[QBExport]:HideText() if handler and handler.hide then
elseif Config.System.drawText == "ox" then handler.hide()
lib.hideTextUI()
elseif Config.System.drawText == "lation" then
exports.lation_ui:hideText()
elseif Config.System.drawText == "gta" then
ClearAllHelpMessages()
elseif Config.System.drawText == "esx" then
ESX.HideUI()
elseif Config.System.drawText == "red" then
TriggerEvent("jim-redui:HideText")
end end
end end

View File

@@ -56,6 +56,14 @@ function isServer()
return IsDuplicityVersion() return IsDuplicityVersion()
end end
--- Allows the user to check if an export exists before trying to call it
--- @return boolean boolean True if it exists
---@usage
--- ```lua
--- if checkExportExists("qb-inventory", "OpenInventory") then
--- exports["qb-inventory"]:OpenInventory()
--- end
--- ```
function checkExportExists(resource, export) function checkExportExists(resource, export)
if not resource or not export then return false end if not resource or not export then return false end
@@ -73,8 +81,6 @@ function checkExportExists(resource, export)
end end
end end
------------------------------------------------------------- -------------------------------------------------------------
-- Debugging and JSON Utilities -- Debugging and JSON Utilities
------------------------------------------------------------- -------------------------------------------------------------
@@ -182,6 +188,7 @@ function GetPrintTime()
local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S') local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")" return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
else else
if gameName == "rdr3" then return "" end
local _, _, _, hour, min, sec = GetLocalTime() local _, _, _, hour, min, sec = GetLocalTime()
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")" return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
end end
@@ -226,9 +233,9 @@ end
function cv(amount) function cv(amount)
local formatted = tostring(amount or "0") local formatted = tostring(amount or "0")
while true do while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') local newFormatted, count = formatted:gsub("^(-?%d+)(%d%d%d)", '%1,%2')
if k == 0 then break end if count == 0 then break end
Wait(0) formatted = newFormatted
end end
return formatted return formatted
end end
@@ -243,13 +250,14 @@ end
--- ``` --- ```
function formatCoord(coord) function formatCoord(coord)
local vecType = type(coord):gsub("tor", "") local vecType = type(coord):gsub("tor", "")
local components = { local parts = {}
[1] = coord.x and ("^6"..string.format("%.1f", coord.x)) or "",
[2] = coord.y and ("^7, ^6"..string.format("%.1f", coord.y)) or "", if coord.x then parts[#parts + 1] = string.format("^6%.1f", coord.x) end
[3] = coord.z and ("^7, ^6"..string.format("%.1f", coord.z)) or "", if coord.y then parts[#parts + 1] = string.format("^6%.1f", coord.y) end
[4] = coord.w and ("^7, ^6"..string.format("%.1f", coord.w)) or "", if coord.z then parts[#parts + 1] = string.format("^6%.1f", coord.z) end
} if coord.w then parts[#parts + 1] = string.format("^6%.1f", coord.w) end
return "^5"..vecType.."^7("..components[1]..components[2]..components[3]..components[4].."^7)"
return string.format("^5%s^7(%s^7)", vecType, table.concat(parts, "^7, "))
end end
--- Calculates the center point of a list of coordinates. --- Calculates the center point of a list of coordinates.
@@ -261,13 +269,17 @@ end
--- print("Center of Zones:", center) --- print("Center of Zones:", center)
--- ``` --- ```
function getCenterOfZones(tbl) function getCenterOfZones(tbl)
local count = #tbl
if count == 0 then return vector3(0, 0, 0) end
local totalX, totalY, totalZ = 0, 0, 0 local totalX, totalY, totalZ = 0, 0, 0
for _, coord in ipairs(tbl) do for i = 1, count do
local coord = tbl[i]
totalX = totalX + coord.x totalX = totalX + coord.x
totalY = totalY + coord.y totalY = totalY + coord.y
totalZ = totalZ + coord.z totalZ = totalZ + coord.z
end end
local count = #tbl
return vector3(totalX / count, totalY / count, totalZ / count) return vector3(totalX / count, totalY / count, totalZ / count)
end end
@@ -280,9 +292,9 @@ end
--- print("Number of keys:", count) --- print("Number of keys:", count)
--- ``` --- ```
function countTable(tbl) function countTable(tbl)
local i = 0 local count = 0
for _ in pairs(tbl) do i += 1 end for _ in pairs(tbl) do count = count + 1 end
return i return count
end end
--- Returns an iterator over a table's keys in sorted order. --- Returns an iterator over a table's keys in sorted order.
@@ -299,7 +311,19 @@ function pairsByKeys(t)
print("^1Error^7: ^3Nil ^2table recieved for ^3pairsByKeys^7(), ^2setting to ^7{} ^2to prevent break^7") print("^1Error^7: ^3Nil ^2table recieved for ^3pairsByKeys^7(), ^2setting to ^7{} ^2to prevent break^7")
t = {} t = {}
end end
local a = {} for n in pairs(t) do a[#a+1] = n end table.sort(a) local i = 0 local iter = function() i += 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter
local keys = {}
for key in pairs(t) do
keys[#keys + 1] = key
end
table.sort(keys)
local index = 0
return function()
index = index + 1
local key = keys[index]
return key, t[key]
end
end end
--- Creates a new table with consecutive numerical indices sorted by the 'id' field. --- Creates a new table with consecutive numerical indices sorted by the 'id' field.
@@ -313,15 +337,18 @@ end
--- end --- end
--- ``` --- ```
function createConsecutiveTable(originalTable) function createConsecutiveTable(originalTable)
local sortedEntries = {} local entries = {}
for _, entry in pairs(originalTable) do table.insert(sortedEntries, entry) end for _, entry in pairs(originalTable) do
table.sort(sortedEntries, function(a, b) return a.id < b.id end) entries[#entries + 1] = entry
local newTable = {}
for newIndex, entry in ipairs(sortedEntries) do
entry.id = newIndex
newTable[newIndex] = entry
end end
return newTable
table.sort(entries, function(a, b) return a.id < b.id end)
for index, entry in ipairs(entries) do
entry.id = index
end
return entries
end end
--- Concatenates a table of strings into a single string separated by newlines. --- Concatenates a table of strings into a single string separated by newlines.
@@ -333,11 +360,7 @@ end
--- print(combinedText) --- print(combinedText)
--- ``` --- ```
function concatenateText(tbl) function concatenateText(tbl)
local result = "" return table.concat(tbl, "\n")
for i = 1, #tbl do
result = result..tbl[i]..(i < #tbl and "\n" or "")
end
return result
end end
--- Converts a rotation (degrees) to a direction vector. --- Converts a rotation (degrees) to a direction vector.
@@ -349,11 +372,14 @@ end
--- print(direction) --- print(direction)
--- ``` --- ```
function RotationToDirection(rot) function RotationToDirection(rot)
local adjust = math.pi / 180 local radianConvert = math.pi / 180
local rotX, rotZ = radianConvert * rot.x, radianConvert * rot.z
local cosX = math.abs(math.cos(rotX))
return vec3( return vec3(
-math.sin(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)), -math.sin(rotZ) * cosX,
math.cos(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)), math.cos(rotZ) * cosX,
math.sin(adjust * rot.x) math.sin(rotX)
) )
end end
@@ -366,11 +392,9 @@ end
--- print(bar) --- print(bar)
--- ``` --- ```
function basicBar(percentage) function basicBar(percentage)
local perc = math.ceil(percentage)
local total = 10 local total = 10
local filled = math.floor((perc / 100) * total) local filled = math.floor(math.min(math.max(percentage, 0), 100) / 100 * total)
local empty = total - filled return string.rep("", filled) .. string.rep("", total - filled)
return string.rep("", filled)..string.rep("", empty)
end end
--- Normalizes a 3D vector. --- Normalizes a 3D vector.
@@ -382,12 +406,8 @@ end
--- print(normalizedVec) --- print(normalizedVec)
--- ``` --- ```
function normalizeVector(vec) function normalizeVector(vec)
local len = math.sqrt(vec.x^2 + vec.y^2 + vec.z^2) local length = math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z)
if len ~= 0 then return length > 0 and vec3(vec.x / length, vec.y / length, vec.z / length) or vec3(0, 0, 0)
return vec3(vec.x / len, vec.y / len, vec.z / len)
else
return vec3(0, 0, 0)
end
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -403,16 +423,15 @@ end
--- drawLine(vector3(100, 200, 300), vector3(150, 250, 350), vector4(255, 0, 0, 255)) --- drawLine(vector3(100, 200, 300), vector3(150, 250, 350), vector4(255, 0, 0, 255))
--- ``` --- ```
function drawLine(startCoords, endCoords, col) function drawLine(startCoords, endCoords, col)
if debugMode then if not debugMode then return end
CreateThread(function()
local count = 1000 local col = col or vec4(255, 255, 255, 150)
while count >= 0 do CreateThread(function()
DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w) for i = 100, 0, -1 do
count -= 10 DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w)
Wait(0) Wait(0)
end end
end) end)
end
end end
--- Draws a sphere at the specified coordinates (for debugging). --- Draws a sphere at the specified coordinates (for debugging).
@@ -423,16 +442,15 @@ end
--- drawSphere(vector3(100, 200, 300), vector4(0, 255, 0, 255)) --- drawSphere(vector3(100, 200, 300), vector4(0, 255, 0, 255))
--- ``` --- ```
function drawSphere(coords, col) function drawSphere(coords, col)
if debugMode then if not debugMode then return end
CreateThread(function()
local count = 1000 local col = col or vec4(255, 255, 255, 150)
while count >= 0 do CreateThread(function()
DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w) for i = 100, 0, -1 do
count -= 1 DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w)
Wait(10) Wait(10)
end end
end) end)
end
end end
--- Performs a raycast between two coordinates and returns the results. --- Performs a raycast between two coordinates and returns the results.
@@ -450,15 +468,15 @@ end
--- end --- end
--- ``` --- ```
function PerformRaycast(startCoords, endCoords, entity, flags) function PerformRaycast(startCoords, endCoords, entity, flags)
drawLine(startCoords, endCoords, vec4(0,0,255,255)) drawLine(startCoords, endCoords, vec4(0, 0, 255, 255))
local val1, val2, val3, val4, val5, val6 = GetShapeTestResult(
StartExpensiveSynchronousShapeTestLosProbe( local shapeTest = StartExpensiveSynchronousShapeTestLosProbe(
startCoords.x, startCoords.y, startCoords.z, startCoords.x, startCoords.y, startCoords.z,
endCoords.x, endCoords.y, endCoords.z, endCoords.x, endCoords.y, endCoords.z,
flags or 4294967295, entity, 0 flags or 4294967295, entity, 4
)
) )
return val1, val2, val3, val4, val5, val6
return GetShapeTestResult(shapeTest)
end end
--- Adjusts the Z-coordinate of a position to the ground level. --- Adjusts the Z-coordinate of a position to the ground level.
@@ -470,16 +488,34 @@ end
--- print("Ground Position:", groundCoords) --- print("Ground Position:", groundCoords)
--- ``` --- ```
function adjustForGround(coords) function adjustForGround(coords)
local foundGround, zPos = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0) local foundGround, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0)
if foundGround then if foundGround then
if coords.w then return coords.w and vec4(coords.x, coords.y, groundZ, coords.w) or vec3(coords.x, coords.y, groundZ)
return vec4(coords.x, coords.y, zPos, coords.w)
else
return vec3(coords.x, coords.y, zPos)
end
else
return coords
end end
return coords
end
local function waitForNetworkEntity(netId, converter, timeout)
timeout = timeout or 100
while not NetworkDoesNetworkIdExist(netId) and timeout > 0 do
timeout = timeout - 1
Wait(10)
end
if not NetworkDoesNetworkIdExist(netId) then return 0 end
local entity = converter(netId)
timeout = 100
while not DoesEntityExist(entity) and entity ~= 0 and timeout > 0 do
timeout = timeout - 1
Wait(10)
end
return DoesEntityExist(entity) and entity or 0
end end
--- Ensures a network vehicle exists from its network ID. --- Ensures a network vehicle exists from its network ID.
@@ -493,21 +529,7 @@ end
--- end --- end
--- ``` --- ```
function ensureNetToVeh(vehNetID) function ensureNetToVeh(vehNetID)
--debugPrint("^6Bridge^7: ^3ensureNetToVeh^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..vehNetID.."^7)") return waitForNetworkEntity(vehNetID, NetToVeh)
local timeout = 100
while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do
timeout -= 1
Wait(10)
end
if not NetworkDoesNetworkIdExist(vehNetID) then return 0 end
timeout = 100
local vehicle = NetToVeh(vehNetID)
while not DoesEntityExist(vehicle) and vehicle ~= 0 and timeout > 0 do
timeout -= 1
Wait(10)
end
if not DoesEntityExist(vehicle) then return 0 end
return vehicle
end end
--- Ensures a network entity exists from its network ID. --- Ensures a network entity exists from its network ID.
@@ -521,21 +543,7 @@ end
--- end --- end
--- ``` --- ```
function ensureNetToEnt(entNetID) function ensureNetToEnt(entNetID)
--debugPrint("^6Bridge^7: ^3ensureNetToEnt^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..entNetID.."^7)") return waitForNetworkEntity(entNetID, NetworkGetEntityFromNetworkId)
local timeout = 100
while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do
timeout -= 1
Wait(10)
end
if not NetworkDoesNetworkIdExist(entNetID) then return 0 end
timeout = 100
local entity = NetworkGetEntityFromNetworkId(entNetID)
while not DoesEntityExist(entity) and entity ~= 0 and timeout > 0 do
timeout -= 1
Wait(10)
end
if not DoesEntityExist(entity) then return 0 end
return entity
end end
function sendLog(text) function sendLog(text)
@@ -580,13 +588,70 @@ RegisterNetEvent(getScript()..":server:sendlog", sendServerLog)
-- local hungerAmount = GetRandomTiming(hunger) -- local hungerAmount = GetRandomTiming(hunger)
-- print(hungerAmount) -- number between 10, 20 -- print(hungerAmount) -- number between 10, 20
function GetRandomTiming(tbl) function GetRandomTiming(tbl)
if type(tbl) == "table" then return type(tbl) == "table" and math.random(tbl[1], tbl[2]) or tbl
return math.random(tbl[1], tbl[2])
else
return tbl
end
end end
-------------------------------------------------------------
-- Player Movement
-------------------------------------------------------------
--- Instantly turns an entity to face a target (entity or coordinates) without animation.
---
--- @param ent number|nil The Ped to turn (defaults to player's Ped if nil).
--- @param ent2 number|vector3|nil The target entity or coordinates to face.
---
--- @usage
--- ```lua
--- instantLookEnt(nil, vector3(200.0, 300.0, 40.0))
--- instantLookEnt(ped1, ped2)
--- ```
function instantLookEnt(ent, ent2)
local ped = ent or PlayerPedId()
local p1 = GetEntityCoords(ped, true)
local p2 = type(ent2) == "vector3" and ent2 or GetEntityCoords(ent2, true)
local dx = p2.x - p1.x
local dy = p2.y - p1.y
local heading = GetHeadingFromVector_2d(dx, dy)
debugPrint("^6Bridge^7: ^1Forced ^2Turning Player to^7: '^6"..formatCoord(p2).."^7'")
SetEntityHeading(ped, heading)
end
--- Makes the player look towards a specific target with an animated turn.
---
--- If the player is not already facing the target (entity or coordinates), a turning animation is triggered.
---
--- @param entity number|vector3|vector4|nil The target to look at.
---
--- @usage
--- ```lua
--- lookEnt(vector3(200.0, 300.0, 40.0))
--- lookEnt(pedEntity)
--- ```
function lookEnt(entity)
local ped = PlayerPedId()
if entity then
if type(entity) == "vector3" or type(entity) == "vector4" then
if not IsPedHeadingTowardsPosition(ped, entity.xyz, 30.0) then
TaskTurnPedToFaceCoord(ped, entity.xyz, 1500)
debugPrint("^6Bridge^7: ^2Turning Player to^7: '^6"..formatCoord(entity).."^7'")
Wait(1500)
end
else
if DoesEntityExist(entity) then
local entCoords = GetEntityCoords(entity)
if not IsPedHeadingTowardsPosition(ped, entCoords, 30.0) then
TaskTurnPedToFaceCoord(ped, entCoords, 1500)
debugPrint("^6Bridge^7: ^2Turning Player to^7: '^6"..entity.."^7' - '"..formatCoord(entCoords).."^7'")
Wait(1500)
end
end
end
end
end
------------------------------------------------------------- -------------------------------------------------------------
-- Material and Prop Functions -- Material and Prop Functions
------------------------------------------------------------- -------------------------------------------------------------
@@ -820,9 +885,9 @@ local materials = {
--- local matHash, matName = GetGroundMaterialAtPosition(vector3(100,200,300)) --- local matHash, matName = GetGroundMaterialAtPosition(vector3(100,200,300))
--- print("Material:", matName) --- print("Material:", matName)
--- ``` --- ```
function GetGroundMaterialAtPosition(coords) function GetGroundMaterialAtPosition(coords, Ped)
local endX, endY, endZ = coords.x, coords.y, coords.z - 1.0 local endCoords = vec3(coords.x, coords.y, coords.z - 1.1)
local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endX, endY, endZ, 1.0, 1, playerPed, 7) local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endCoords.x, endCoords.y, endCoords.z, 1.0, 1, Ped or PlayerPedId(), 7)
local _, hit, _, _, materialHash, _ = GetShapeTestResultIncludingMaterial(rayHandle) local _, hit, _, _, materialHash, _ = GetShapeTestResultIncludingMaterial(rayHandle)
local materialName = "Unknown" local materialName = "Unknown"
for k, v in pairs(materials) do for k, v in pairs(materials) do

View File

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

View File

@@ -73,8 +73,8 @@ if not isServer() then
--- local isPlayerAnimal = isAnimal() --- local isPlayerAnimal = isAnimal()
--- local isSpecificPedAnimal = isAnimal(somePedEntity) --- local isSpecificPedAnimal = isAnimal(somePedEntity)
--- ``` --- ```
function isPedAnimal(ped) function isPedAnimal(Ped)
local PedModel = GetEntityModel(ped or PlayerPedId()) local PedModel = GetEntityModel(Ped or PlayerPedId())
for _, animalCategory in pairs(AnimalPeds) do for _, animalCategory in pairs(AnimalPeds) do
for animalModelHash, _ in pairs(animalCategory) do for animalModelHash, _ in pairs(animalCategory) do
if PedModel == animalModelHash then if PedModel == animalModelHash then
@@ -105,8 +105,8 @@ if not isServer() then
--- print("Driver is a cat!") --- print("Driver is a cat!")
--- end --- end
--- ``` --- ```
function isCat(ped) function isCat(Ped)
local PedModel = GetEntityModel(ped or PlayerPedId()) local PedModel = GetEntityModel(Ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.CatPeds) do for modelHash, _ in pairs(AnimalPeds.CatPeds) do
if PedModel == modelHash then if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Cat") debugPrint("^4isAnimal^7: ^2Ped is ^4Cat")
@@ -148,8 +148,8 @@ if not isServer() then
--- end --- end
--- end --- end
--- ``` --- ```
function isDog(ped) function isDog(Ped)
local PedModel = GetEntityModel(ped or PlayerPedId()) local PedModel = GetEntityModel(Ped or PlayerPedId())
for modelHash, _ in pairs(AnimalPeds.BigDogs) do for modelHash, _ in pairs(AnimalPeds.BigDogs) do
if PedModel == modelHash then if PedModel == modelHash then
debugPrint("^4isAnimal^7: ^2Ped is ^4Dog") debugPrint("^4isAnimal^7: ^2Ped is ^4Dog")
@@ -199,8 +199,8 @@ if not isServer() then
--- local getAnim = getAnimalAnims(ped) --- local getAnim = getAnimalAnims(ped)
--- playAnim(getAnim.sitDict, getAnim.sitAnim, -1, 1) --- playAnim(getAnim.sitDict, getAnim.sitAnim, -1, 1)
--- ``` --- ```
function getAnimalAnims(ped) function getAnimalAnims(Ped)
local model = GetEntityModel(ped) local model = GetEntityModel(Ped)
local animalTable = {} local animalTable = {}
for _, animalCategory in pairs(AnimalPeds) do for _, animalCategory in pairs(AnimalPeds) do
for k, v in pairs(animalCategory) do for k, v in pairs(animalCategory) do

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,58 @@
and teleporting via doors. and teleporting via doors.
]] ]]
local bossMenuFunc = {
{ name = "qb-management",
openBossMenu = function(isGang, group)
if isGang then
TriggerEvent("qb-gangmenu:client:OpenMenu")
else
TriggerEvent("qb-bossmenu:client:OpenMenu")
end
end,
},
{ name = "qbx_management",
openBossMenu = function(isGang, group)
if isGang then
exports["qbx_management"]:OpenBossMenu("gang")
else
exports["qbx_management"]:OpenBossMenu("job")
end
end,
},
{ name = "esx_society",
openBossMenu = function(isGang, group)
TriggerEvent('esx_society:openBossMenu', group, function() end, { wash = false })
end,
},
{ name = "tss-bossmenu",
openBossMenu = function(isGang, group)
TriggerEvent("tss-bossmenu:client:OpenMenu")
end,
},
{ name = "okokBossMenu",
openBossMenu = function(isGang, group)
ExecuteCommand('openbossmenu')
end,
},
}
function openBossMenu(isGang, group)
for i = 1, #bossMenuFunc do
local bossmenu = bossMenuFunc[i]
if isStarted(bossmenu.name) then
bossmenu.openBossMenu(isGang, group)
return
end
end
end
------------------------------------------------------------- -------------------------------------------------------------
-- Global Duty Status -- Global Duty Status
------------------------------------------------------------- -------------------------------------------------------------
@@ -100,21 +152,58 @@ end
--- toggleDuty() -- Player receives a notification of their new duty status. --- toggleDuty() -- Player receives a notification of their new duty status.
--- ``` --- ```
function toggleDuty() function toggleDuty()
if isStarted(QBExport) or isStarted(QBXExport) then local dutyFunc = {
TriggerServerEvent("QBCore:ToggleDuty") { framework = QBExport,
Wait(100) func = function()
onDuty = getPlayer().onDuty TriggerServerEvent("QBCore:ToggleDuty")
elseif isStarted(RSGExport) then Wait(100)
TriggerServerEvent("RSGCore:ToggleDuty") onDuty = getPlayer().onDuty
Wait(100) end
onDuty = getPlayer().onDuty },
else { framework = QBXExport,
if onDuty then func = function()
triggerNotify(nil, "Now on duty", "success") TriggerServerEvent("QBCore:ToggleDuty")
else Wait(100)
triggerNotify(nil, "Now off duty", "success") onDuty = getPlayer().onDuty
end
},
{ framework = RSGExport,
func = function()
TriggerServerEvent("RSGCore:ToggleDuty")
Wait(100)
onDuty = getPlayer().onDuty
end
},
{ framework = ESXExport,
func = function()
local tempJob = ESX.GetPlayerData().job
tempJob.onDuty = not onDuty
ESX.SetPlayerData("job", tempJob)
onDuty = getPlayer().onDuty
if onDuty then
triggerNotify(nil, "Now on duty", "success")
else
triggerNotify(nil, "Now off duty", "success")
end
end
},
}
for i = 1, #dutyFunc do
local framework = dutyFunc[i]
if isStarted(framework.framework) then
framework.func()
return
end end
end end
-- fallback
onDuty = not onDuty
if onDuty then
triggerNotify(nil, "Now on duty", "success")
else
triggerNotify(nil, "Now off duty", "success")
end
end end
-- Framework specific functions to keep duty status synced -- Framework specific functions to keep duty status synced
@@ -231,19 +320,3 @@ function useDoor(data)
DoScreenFadeIn(1000) DoScreenFadeIn(1000)
Wait(100) Wait(100)
end end
function openBossMenu(isGang, group)
if isStarted("qb-management") then
if isGang then
TriggerEvent("qb-gangmenu:client:OpenMenu")
else
TriggerEvent("qb-bossmenu:client:OpenMenu")
end
elseif isStarted("qbx_management") then
exports["qbx_management"]:OpenBossMenu(isGang and "gang" or "job")
elseif isStarted("esx_society") then
TriggerEvent('esx_society:openBossMenu', group, function() end, { wash = false })
end
end

View File

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

View File

@@ -187,7 +187,7 @@ end
--- ``` --- ```
function playAnim(animDict, animName, duration, flag, ped, speed) function playAnim(animDict, animName, duration, flag, ped, speed)
loadAnimDict(animDict) loadAnimDict(animDict)
debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName) debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName, flag)
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false) TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
end end
@@ -239,3 +239,24 @@ function playGameSound(audioBank, soundSet, soundRef, coords, synced, range)
end end
ReleaseScriptAudioBank(audioBank) ReleaseScriptAudioBank(audioBank)
end end
-- Experimental, add fade in for spawned entities
function fadeInEnt(ent, duration)
if not DoesEntityExist(ent) then return end
duration = duration or 500 -- in ms
local fadeSteps = 20
local stepTime = duration / fadeSteps
SetEntityAlpha(ent, 0, false)
SetEntityVisible(ent, true, false)
SetEntityLocallyInvisible(ent) -- sometimes helps prevent "pop-in" in early frames
for i = 1, fadeSteps do
Wait(stepTime)
local newAlpha = math.floor((i / fadeSteps) * 255)
SetEntityAlpha(ent, newAlpha, false)
end
-- Restore full visibility
ResetEntityAlpha(ent)
end

View File

@@ -133,16 +133,19 @@ function makeEntityBlip(data)
AddTextComponentString(tostring(data.name)) AddTextComponentString(tostring(data.name))
EndTextCommandSetBlipName(blip) EndTextCommandSetBlipName(blip)
-- Handle preview image if certain resources are running -- Handle preview image if certain resources are running
if isStarted("fs_smallresources") or isStarted("blip-info") or isStarted("blipinfo") then if isStarted("jim-blipcontroller") then
if data.preview then if data.preview then
local txname = string.gsub(tostring(data.name..'preview'), "[ ()~]", "") local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
if data.preview:find("http") or data.preview:find("nui") then if data.preview:find("http") or data.preview:find("nui") then
createDui(txname, data.preview, vec2(512, 256), scriptTxd) createDui(txname, data.preview, vec2(512, 256), scriptTxd)
else else
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview) CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
end end
exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'previewTxd', txname) exports["jim-blipcontroller"]:ShowBlipInfo(blip, {
exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false) title = data.name,
dict = getScript()..'scriptTxd',
tex = txname,
})
end end
end end
end end

View File

@@ -1,5 +1,6 @@
--- A table to keep track of all created Peds. --- A table to keep track of all created Peds.
local Peds = {} local Peds = {}
local distPeds = {}
--- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area. --- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area.
-- --
@@ -18,18 +19,20 @@ local Peds = {}
-- ```lua -- ```lua
-- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true) -- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true)
-- ``` -- ```
function makeDistPed(data, coords, freeze, collision, scenario, anim, synced) function makeDistPed(data, coords, freeze, collision, scenario, anim, synced, func)
local zoneCoords = type(data) == "table" and data.coords or coords local zoneCoords = type(data) == "table" and data.coords or coords
local randName = keyGen()..keyGen() local randName = keyGen()..keyGen()
createCirclePoly({ distPeds[#distPeds+1] = createCirclePoly({
name = randName, name = randName,
coords = vec3(zoneCoords.x, zoneCoords.y, zoneCoords.z - 1.03), coords = vec3(zoneCoords.x, zoneCoords.y, zoneCoords.z - 1.03),
radius = 50.0, radius = 50.0,
onEnter = function() onEnter = function()
Peds[randName] = makePed(data, coords, freeze, collision, scenario, anim, synced) Peds[randName] = makePed(data, coords, freeze, collision, scenario, anim, synced)
if func then func(Peds[randName]) end
end, end,
onExit = function() onExit = function()
DeletePed(Peds[randName]) DeletePed(Peds[randName])
Peds[randName] = nil
end, end,
debug = debugMode, debug = debugMode,
}) })
@@ -55,14 +58,13 @@ end
-- ```lua -- ```lua
-- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true) -- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true)
-- ``` -- ```
function makePed(data, coords, freeze, collision, scenario, anim, synced) function makePed(data, coords, freeze, collision, scenario, anim, synced, fade)
local ped = nil local ped = nil
local model = nil local model = nil
if type(data) == "table" then if type(data) == "table" then
model = data.model model = data.model
loadModel(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) ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
-- Inheritance -- 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) 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)
@@ -116,6 +118,7 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced)
loadModel(model) loadModel(model)
if gameName == "rdr3" then if gameName == "rdr3" then
ped = CreatePed(model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false) ped = CreatePed(model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
SetEntityAlpha(ped, 0, false)
SetEntityVisible(ped, 1) -- SetEntityVisible SetEntityVisible(ped, 1) -- SetEntityVisible
SetEntityAlpha(ped, 255, false) -- SetEntityAlpha SetEntityAlpha(ped, 255, false) -- SetEntityAlpha
SetRandomOutfitVariation(ped, true) -- Invisible without SetRandomOutfitVariation(ped, true) -- Invisible without
@@ -141,6 +144,12 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced)
end end
unloadModel(model) unloadModel(model)
Peds[keyGen()..keyGen()] = ped Peds[keyGen()..keyGen()] = ped
if fade ~= false and gameName ~= "rdr3" then
SetEntityAlpha(ped, 0, false)
CreateThread(function()
fadeInEnt(ped)
end)
end
return ped return ped
end end
@@ -238,6 +247,16 @@ function GenerateRandomPedData(data)
return newTable return newTable
end end
onPlayerUnload(function()
for k in pairs(Peds) do
DeletePed(Peds[k])
end
for i = 1, #distPeds do
removeZoneTarget(distPeds[i])
end
distPeds = {}
end)
--- Cleans up all created Peds when the resource stops. --- Cleans up all created Peds when the resource stops.
onResourceStop(function() onResourceStop(function()
for k in pairs(Peds) do for k in pairs(Peds) do

View File

@@ -1,4 +1,5 @@
local Props = {} local Props = {}
local distProps = {}
--- Creates a prop (object) in the game world at specified coordinates. --- Creates a prop (object) in the game world at specified coordinates.
--- ---
@@ -20,7 +21,7 @@ local Props = {}
--- } --- }
--- local prop = makeProp(propData, true, false) --- local prop = makeProp(propData, true, false)
--- ``` --- ```
function makeProp(data, freeze, synced) function makeProp(data, freeze, synced, fade)
loadModel(data.prop) loadModel(data.prop)
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false) local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
SetEntityHeading(prop, (data.coords.w or 0) + 180.0) SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
@@ -29,6 +30,12 @@ function makeProp(data, freeze, synced)
debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords)) debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords))
SetModelAsNoLongerNeeded(data.prop) SetModelAsNoLongerNeeded(data.prop)
Props[keyGen()..keyGen()] = prop Props[keyGen()..keyGen()] = prop
if fade ~= false and gameName ~= "rdr3" then
SetEntityAlpha(prop, 0, false)
CreateThread(function()
fadeInEnt(prop)
end)
end
return prop return prop
end end
@@ -51,17 +58,21 @@ end
--- } --- }
--- makeDistProp(propData, true, false) --- makeDistProp(propData, true, false)
--- ``` --- ```
function makeDistProp(data, freeze, synced, range) function makeDistProp(data, freeze, synced, range, func)
local name = keyGen()..keyGen() local name = keyGen()..keyGen()
createCirclePoly({ distProps[#distProps + 1] = createCirclePoly({
name = name, name = name,
coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03), coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
radius = range or 50.0, radius = range or 50.0,
onEnter = function() onEnter = function()
Props[name] = makeProp(data, freeze, synced) Props[name] = makeProp(data, freeze, synced)
if func then
func(Props[name])
end
end, end,
onExit = function() onExit = function()
destroyProp(Props[name]) destroyProp(Props[name])
Props[name] = nil
end, end,
debug = debugMode, debug = debugMode,
}) })
@@ -86,6 +97,16 @@ function destroyProp(entity)
end end
end end
onPlayerUnload(function()
for k in pairs(Props) do
DeleteObject(Props[k])
end
for i = 1, #distProps do
removeZoneTarget(distProps[i])
end
distProps = {}
end)
--- Cleans up all created props when the resource stops. --- Cleans up all created props when the resource stops.
onResourceStop(function() onResourceStop(function()
for k in pairs(Props) do for k in pairs(Props) do

View File

@@ -13,23 +13,29 @@ local Vehicles = {}
--- ```lua --- ```lua
--- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0)) --- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0))
--- ``` --- ```
function makeVeh(model, coords) function makeVeh(model, coords, synced, fade)
loadModel(model) loadModel(model)
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, synced ~= false, false)
SetVehicleHasBeenOwnedByPlayer(veh, true) SetVehicleHasBeenOwnedByPlayer(veh, true)
if gameName ~= "rdr3" then if gameName ~= "rdr3" then
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true) SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
Wait(100) Wait(100)
SetVehicleNeedsToBeHotwired(veh, false) SetVehicleNeedsToBeHotwired(veh, false)
SetVehRadioStation(veh, 'OFF') SetVehRadioStation(veh, 'OFF')
SetVehicleFuelLevel(veh, 100.0) SetVehicleFuelLevel(veh, 100.0)
SetVehicleModKit(veh, 0) SetVehicleModKit(veh, 0)
end end
SetVehicleOnGroundProperly(veh) SetVehicleOnGroundProperly(veh)
debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords)) debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords))
unloadModel(model) unloadModel(model)
Vehicles[#Vehicles + 1] = veh Vehicles[#Vehicles + 1] = veh
if fade ~= false and gameName ~= "rdr3" then
SetEntityAlpha(veh, 0, false)
CreateThread(function()
fadeInEnt(veh)
end)
end
return veh return veh
end end
@@ -52,7 +58,7 @@ function makeDistVehicle(data, radius, onEnter, onExit)
coords = vec3(data.coords.x, data.coords.y, data.coords.z), coords = vec3(data.coords.x, data.coords.y, data.coords.z),
radius = radius, radius = radius,
onEnter = function() onEnter = function()
vehicle = makeVeh(data.model, data.coords) vehicle = makeVeh(data.model, data.coords, false)
if onEnter then if onEnter then
debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onEnter^7() ^2running^7") debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onEnter^7() ^2running^7")
onEnter(vehicle) onEnter(vehicle)

View File

@@ -1,6 +1,213 @@
local inProgress = false local inProgress = false
local storedPID = nil local storedPID = nil
local progressFunc = {
ox = {
start =
function(data)
local options = {
duration = debugMode and 1000 or data.time,
label = data.label,
position = data.position or "bottom",
useWhileDead = data.dead or false,
canCancel = data.cancel or true,
anim = {
dict = data.dict,
clip = data.anim,
flag = (data.flag == 8 and 32 or data.flag) or nil,
scenario = data.task
},
disable = {
combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
mouse = data.mouse or false
},
}
if data.prop and data.prop.model then
options.prop = {
model = data.prop.model,
pos = data.prop.pos or vec3(0, 0, 0),
rot = data.prop.rot or vec3(0, 0, 0),
bone = data.prop.bone or 0
}
end
if data.propTwo and data.propTwo.model then
options.propTwo = {
model = data.propTwo.model,
pos = data.propTwo.pos or vec3(0, 0, 0),
rot = data.propTwo.rot or vec3(0, 0, 0),
bone = data.propTwo.bone or 0
}
end
if data.progressType == "circle" then
if exports[OXLibExport]:progressCircle(options) then
return true
else
return false
end
end
if not data.progressType or data.progressType == "bar" then
if exports[OXLibExport]:progressBar(options) then
return true
else
print("^1progressBar was not successful")
end
end
end,
stop =
function()
exports[OXLibExport]:cancelProgress()
end,
},
qb = {
start =
function(data)
local p = promise.new()
Core.Functions.Progressbar("progbar",
data.label,
debugMode and 1000 or data.time,
data.dead or false,
data.cancel or true,
{
disableMovement = data.disableMovement or false,
disableCarMovement = data.disableMovement or false,
disableMouse = data.disableMouse or false,
disableCombat = data.disableCombat or true,
},
{
animDict = data.dict,
anim = data.anim,
flags = data.flag or 32,
task = data.task
},
{}, {},
function() p:resolve(true) end,
function() p:resolve(false) end,
data.icon)
return Citizen.Await(p)
end,
stop =
function()
TriggerEvent("progressbar:client:cancel")
end,
},
esx = {
start =
function(data)
local p = promise.new()
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
FreezePlayer = true,
animation = {
type = data.anim,
dict = data.dict,
scenario = data.task,
},
onFinish = function()
p:resolve(true)
end,
onCancel = function()
p:resolve(false)
return false
end
})
return Citizen.Await(p)
end,
stop =
function()
ESX.CancelProgressbar()
end,
},
lation = {
start =
function(data)
if exports.lation_ui:progressBar({
label = data.label,
description = nil,
duration = debugMode and 1000 or data.time,
icon = data.icon,
useWhileDead = data.dead or false,
disable = {
combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
},
anim = {
dict = data.dict,
clip = data.anim,
flag = data.flag
},
prop = {
model = data.prop and data.prop.model,
pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
bone = data.prop and (data.prop.bone or 0)
}
}) then
return true
else
return false
end
end,
stop =
function()
exports.lation_ui:cancelProgress()
end,
},
red = {
start =
function(data)
if exports.jim_bridge:redProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
return true
else
return false
end
end,
stop =
function()
exports.jim_bridge:stopProgressBar()
end,
},
gta = {
start =
function(data)
if exports.jim_bridge:gtaProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
return true
else
return false
end
end,
stop =
function()
exports.jim_bridge:stopProgressBar()
end,
},
}
--- Displays a progress bar using the configured progress bar system. --- Displays a progress bar using the configured progress bar system.
--- ---
--- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta). --- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
@@ -34,7 +241,11 @@ local storedPID = nil
--- cancel = true, --- cancel = true,
--- }) --- })
--- ``` --- ```
local progresssBarActive = false
function progressBar(data) function progressBar(data)
if progresssBarActive then return else progresssBarActive = true end
local ped = PlayerPedId() local ped = PlayerPedId()
if data.shared then if data.shared then
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7") debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
@@ -43,164 +254,17 @@ function progressBar(data)
end end
local result = nil local result = nil
if data.cam then startTempCam(data.cam) end if data.cam then startTempCam(data.cam) end
if Config.System.ProgressBar == "ox" then
local options = {
duration = debugMode and 1000 or data.time,
label = data.label,
position = data.position or "bottom",
useWhileDead = data.dead or false,
canCancel = data.cancel or true,
anim = {
dict = data.dict,
clip = data.anim,
flag = (data.flag == 8 and 32 or data.flag) or nil,
scenario = data.task
},
disable = {
combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
mouse = data.mouse or false
},
}
if data.prop and data.prop.model then
options.prop = {
model = data.prop.model,
pos = data.prop.pos or vec3(0, 0, 0),
rot = data.prop.rot or vec3(0, 0, 0),
bone = data.prop.bone or 0
}
end
if data.propTwo and data.propTwo.model then
options.propTwo = {
model = data.propTwo.model,
pos = data.propTwo.pos or vec3(0, 0, 0),
rot = data.propTwo.rot or vec3(0, 0, 0),
bone = data.propTwo.bone or 0
}
end
if data.progressType == "circle" then
if exports[OXLibExport]:progressCircle(options) then
result = true
else
result = false
end
end
if not data.progressType or data.progressType == "bar" then
if exports[OXLibExport]:progressBar(options) then
result = true
else
result = false
end
end
elseif Config.System.ProgressBar == "qb" then
Core.Functions.Progressbar("progbar",
data.label,
debugMode and 1000 or data.time,
data.dead or false,
data.cancel or true,
{
disableMovement = data.disableMovement or false,
disableCarMovement = data.disableMovement or false,
disableMouse = data.disableMouse or false,
disableCombat = data.disableCombat or true,
},
{
animDict = data.dict,
anim = data.anim,
flags = data.flag or 32,
task = data.task
},
{}, {},
function()
result = true
end, function()
result = false
end, data.icon)
elseif Config.System.ProgressBar == "esx" then
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
FreezePlayer = true,
animation = {
type = data.anim,
dict = data.dict,
scenario = data.task,
},
onFinish = function()
result = true
end,
onCancel = function()
result = false
end
})
elseif Config.System.ProgressBar == "lation" then
if exports.lation_ui:progressBar({
label = data.label,
description = nil,
duration = debugMode and 1000 or data.time,
icon = data.icon,
useWhileDead = data.dead or false,
disable = {
combat = data.combat or true,
move = data.disableMovement or false,
car = data.disableMovement or false,
},
anim = {
dict = data.dict,
clip = data.anim,
},
prop = {
model = data.prop and data.prop.model,
pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
bone = data.prop and (data.prop.bone or 0)
}
}) then
result = true
else
result = true
end
elseif Config.System.ProgressBar == "red" then
-- Currently only uses jim-redui if you choose this option
if exports["jim_bridge"]:redProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
result = true
else
result = false
end
elseif Config.System.ProgressBar == "gta" then
if exports["jim_bridge"]:gtaProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
result = true
else
result = false
end
if progressFunc[Config.System.ProgressBar].start(data) then
result = true
else
result = false
end end
while result == nil do while result == nil do
Wait(10) Wait(10)
end end
debugPrint("^5Debug^7: ^2ProgressBar result^7: ^3"..tostring(result).."^7")
-- Cleanup -- Cleanup
FreezeEntityPosition(ped, false) FreezeEntityPosition(ped, false)
@@ -221,6 +285,7 @@ function progressBar(data)
TriggerServerEvent(getScript()..":clearAuthToken") TriggerServerEvent(getScript()..":clearAuthToken")
currentToken = triggerCallback(AuthEvent) currentToken = triggerCallback(AuthEvent)
end end
progresssBarActive = false
return result return result
end end
@@ -228,15 +293,8 @@ end
--- ---
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup. --- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
function stopProgressBar() function stopProgressBar()
if Config.System.ProgressBar == "ox" then progressFunc[Config.System.ProgressBar].stop()
exports[OXLibExport]:cancelProgress() progresssBarActive = false
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "lation" then
exports.lation_ui:cancelProgress()
elseif Config.System.ProgressBar == "gta" or Config.System.ProgressBar == "red" then
exports["jim_bridge"]:stopProgressBar()
end
end end
-- System to handle sending/sharing progress bars between players -- -- System to handle sending/sharing progress bars between players --

View File

@@ -6,6 +6,102 @@
for getting and setting metadata. for getting and setting metadata.
]] ]]
local metaDataFunc = {
{ framework = QBXExport,
GetPlayer =
function(src)
if src then
return exports[QBXExport]:GetPlayer(src)
end
return exports[QBXExport]:GetPlayerData()
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.PlayerData.metadata[dataToCheck]
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.Functions.SetMetaData(key, value)
end,
},
{ framework = QBExport,
GetPlayer =
function(src)
if src then
return exports[QBExport]:GetCoreObject().Functions.GetPlayer(src)
end
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
return info
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.PlayerData.metadata[dataToCheck]
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.Functions.SetMetaData(key, value)
end,
},
{ framework = ESXExport,
GetPlayer =
function(src)
if src then
return ESX.GetPlayerFromId(src)
end
return ESX.GetPlayerData()
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.getMeta(dataToCheck)
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.set(key, value)
end,
},
{ framework = OXCoreExport,
GetPlayer =
function(src)
if src then
return exports[OXCoreExport]:GetPlayer(src)
end
return {}
end,
GetPlayerMetadata =
function(Player, dataToCheck)
return Player.get(dataToCheck)
end,
SetPlayerMetadata =
function(Player, key, value)
return Player.set(key, value)
end,
},
{ framework = RSGExport,
GetPlayer =
function(src)
if src then
return exports[RSGExport]:GetCoreObject().Functions.GetPlayer(src)
end
local info = nil
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
return info
end,
GetPlayerMetadata = function(Player, dataToCheck)
return Player.PlayerData.metadata[dataToCheck]
end,
SetPlayerMetadata = function(Player, key, value)
return Player.Functions.SetMetaData(key, value)
end,
},
}
------------------------------------------------------------- -------------------------------------------------------------
-- Player Retrieval -- Player Retrieval
------------------------------------------------------------- -------------------------------------------------------------
@@ -20,26 +116,11 @@
--- local player = GetPlayer(playerId) --- local player = GetPlayer(playerId)
--- ``` --- ```
function GetPlayer(source) function GetPlayer(source)
if isStarted(QBExport) then for i = 1, #metaDataFunc do
debugPrint("^6Bridge^7: ^3GetPlayer^7() QBExport") local framework = metaDataFunc[i]
return exports[QBExport]:GetCoreObject().Functions.GetPlayer(source) if isStarted(framework.framework) then
return framework.GetPlayer(source)
elseif isStarted(QBXExport) then end
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 end
return nil return nil
end end
@@ -58,29 +139,22 @@ end
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- local myMeta = GetMetadata(player, "myKey") --- local myMeta = getPlayerMetadata(player, "myKey")
--- ``` --- ```
function GetMetadata(player, key) function getPlayerMetadata(player, key)
-- Assume this is client side and callback to server to get the data
if not player then if not player then
debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key) debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key)
return triggerCallback(getScript()..":server:GetMetadata", key) return triggerCallback(getScript()..":server:GetMetadata", key)
else else
if isStarted(QBExport) or isStarted(QBXExport) then -- else grab server metadata about the player
debugPrint("^6Bridge^7: ^3GetMetadata^7() QBExport/QBXExport", key) for i = 1, #metaDataFunc do
return player.PlayerData.metadata[key] local framework = metaDataFunc[i]
if isStarted(framework.framework) then
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]
debugPrint("^6Bridge^7: ^3GetMetadata^7() ^3"..framework.framework.."^7", key)
return framework.GetPlayerMetadata(player, key)
end
end end
end end
return nil return nil
@@ -98,11 +172,11 @@ createCallback(getScript()..":server:GetMetadata", function(source, key)
if type(key) == "table" then if type(key) == "table" then
local Metadata = {} local Metadata = {}
for _, k in ipairs(key) do for _, k in ipairs(key) do
Metadata[k] = GetMetadata(player, k) Metadata[k] = getPlayerMetadata(player, k)
end end
return Metadata return Metadata
elseif type(key) == "string" then elseif type(key) == "string" then
return GetMetadata(player, key) return getPlayerMetadata(player, key)
end end
end) end)
@@ -120,38 +194,26 @@ end)
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- SetMetadata(player, "myKey", "newValue") --- setPlayerMetadata(player, "myKey", "newValue")
--- ``` --- ```
function SetMetadata(player, key, value) function setPlayerMetadata(player, key, value)
debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata for key: "..key) debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata^7...")
if isStarted(QBExport) or isStarted(QBXExport) then for i = 1, #metaDataFunc do
debugPrint("^6Bridge^7: ^3SetMetadata^7() using QBExport/QBXExport") local framework = metaDataFunc[i]
player.Functions.SetMetaData(key, value) if isStarted(framework.framework) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() ^3"..framework.framework.."^7", key, value)
elseif isStarted(ESXExport) then return framework.GetPlayerMetadata(player, key)
debugPrint("^6Bridge^7: ^3SetMetadata^7() using ESXExport") end
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
debugPrint("^6Bridge^7: ^1Error setting metadata^7, ^1framework not supported^7?")
end end
-- Register a server callback for setting metadata. -- Register a server callback for setting metadata.
createCallback(getScript()..":server:SetMetadata", function(source, key, value) createCallback(getScript()..":server:setPlayerMetadata", function(source, key, value)
debugPrint("SetMetadata callback triggered for source:", source, "key:", key, "value:", value) local src = source
local player = GetPlayer(source) debugPrint("SetMetadata callback triggered for source:", src, "key:", key, "value:", value)
--[[if not player then local player = GetPlayer(src)
print("Error setting metadata: player not found for source "..tostring(source)) setPlayerMetadata(player, key, value)
return false
end]]
SetMetadata(player, key, value)
print("Metadata set successfully.", key) print("Metadata set successfully.", key)
return true return true
end) end)

View File

@@ -8,10 +8,90 @@
• okok • okok
• qb • qb
• ox • ox
• red (default)
• gta (default) • gta (default)
• lation
• esx • esx
]] ]]
local notifyFunc = {
okok = {
client =
function(title, message, type)
TriggerEvent('okokNotify:Alert', title, message, 6000, type)
end,
server =
function(title, message, type, src)
TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type)
end,
},
qb = {
client =
function(title, message, type)
TriggerEvent("QBCore:Notify", message, type)
end,
server =
function(title, message, type, src)
TriggerClientEvent("QBCore:Notify", src, message, type)
end,
},
ox = {
client =
function(title, message, type)
exports.ox_lib:notify({ title = title, description = message, type = type or "success" })
end,
server =
function(title, message, type, src)
TriggerClientEvent('ox_lib:notify', src, { title = title, description = message, type = type or "success" })
end,
},
esx = {
client =
function(title, message, type)
exports["esx_notify"]:Notify(type, 4000, message)
end,
server =
function(title, message, type, src)
TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message)
end,
},
lation = {
client =
function(title, message, type)
exports.lation_ui:notify({ title = title, message = message, type = type or "success", })
end,
server =
function(title, message, type, src)
TriggerClientEvent("lation_ui:notify", src, { title = title, message = message, type = type or "success", })
end,
},
gta = {
client =
function(title, message, type)
exports.jim_bridge:Notify(title, message, type)
end,
server =
function(title, message, type, src)
TriggerClientEvent("jim-bridge:Notify", src, title, message, type)
end,
},
red = {
client =
function(title, message, type)
TriggerEvent("jim-redui:Notify", title, message, type)
end,
server =
function(title, message, type, src)
TriggerClientEvent("jim-redui:Notify", src, title, message, type)
end,
},
}
--- Displays notifications to the player using the configured notification system. --- Displays notifications to the player using the configured notification system.
--- ---
--- Supports multiple notification systems based on Config.System.Notify. Can be triggered from both --- Supports multiple notification systems based on Config.System.Notify. Can be triggered from both
@@ -31,60 +111,12 @@
--- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId) --- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId)
--- ``` --- ```
function triggerNotify(title, message, type, src) function triggerNotify(title, message, type, src)
if not Config.System or not Config.System.Notify then debugPrint("Notify triggered but not set up") return end if not Config.System?.Notify then debugPrint("Notify triggered but not set up") return end
if Config.System.Notify == "okok" then
if not src then
TriggerEvent('okokNotify:Alert', title, message, 6000, type)
else
TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type)
end
elseif Config.System.Notify == "qb" then
if not src then
TriggerEvent("QBCore:Notify", message, type)
else
TriggerClientEvent("QBCore:Notify", src, message, type)
end
elseif Config.System.Notify == "ox" then
if not src then
TriggerEvent('ox_lib:notify', { title = title, description = message, type = type or "success" })
else
TriggerClientEvent('ox_lib:notify', src, { title = title, description = message, type = type or "success" })
end
elseif Config.System.Notify == "gta" then
if not src then
exports.jim_bridge:Notify(title, message, type)
else
TriggerClientEvent("jim-bridge:Notify", src, title, message, type)
end
elseif Config.System.Notify == "esx" then
if not src then
exports["esx_notify"]:Notify(type, 4000, message)
else
TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message)
end
elseif Config.System.Notify == "lation" then
if not src then
exports.lation_ui:notify({
title = title,
message = message,
type = type or "success",
})
else
TriggerClientEvent("lation_ui:notify", src, {
title = title,
message = message,
type = type or "success",
})
end
elseif Config.System.Notify == "red" then if src then
if isStarted("jim-redui") then notifyFunc[Config.System.Notify].server(title, message, type, src)
if not src then else
TriggerEvent("jim-redui:Notify", title, message, type) notifyFunc[Config.System.Notify].client(title, message, type)
else
TriggerClientEvent("jim-redui:Notify", src, title, message, type)
end
end
end end
end end
@@ -97,7 +129,6 @@ end
--- Listens for DisplayESXNotify events and triggers the ESX notification on the client. --- Listens for DisplayESXNotify events and triggers the ESX notification on the client.
--- ---
--- @param type string The notification type. --- @param type string The notification type.
--- @param title string The notification title.
--- @param text string The notification message. --- @param text string The notification message.
--- ---
--- @usage --- @usage

View File

@@ -5,110 +5,74 @@
Supported systems include: Supported systems include:
- gksphone - gksphone
- yflip-phone - yflip-phone
- qs-smartphone
- qs-smartphone-pro
- roadphone - roadphone
- lb-phone - lb-phone
- qb-phone - qb-phone
- jpr-phonesystem - jpr-phonesystem
]] ]]
--- Sends a phone mail using the detected phone system. local phoneFunc = {
--- The function iterates through a prioritized list of supported phone systems. { phone = "gksphone",
--- Once an active system is found (via `isStarted`), the corresponding mail function is executed. sendMail = function(mailData)
--- exports["gksphone"]:SendNewMail(mailData)
--- @param data table A table containing the mail data. end,
--- - subject (string): The email subject. sendInvoice =
--- - sender (string): The sender identifier. function(mailData)
--- - message (string): The email body content. -- Defensive check for required fields
--- - actions (table|nil): Optional action buttons for the email. local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "label" }
--- @usage for _, field in ipairs(required) do
--- sendPhoneMail({ if mailData[field] == nil then
--- subject = "Welcome!", print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
--- sender = "Admin", return
--- message = "Thank you for joining our server.", end
--- actions = { end
--- { label = "Reply", action = replyFunction }
--- } MySQL.Async.execute(
--- }) 'INSERT INTO gksphone_invoices (citizenid, amount, society, sender, sendercitizenid, label) VALUES (@citizenid, @amount, @society, @sender, @sendercitizenid, @label)',
function sendPhoneMail(data) {
-- Define each supported phone system and its corresponding mail-sending function. ['@citizenid'] = mailData.billedCitizenid,
local phoneSystems = { ['@amount'] = mailData.amount,
{ name = "gksphone", ['@society'] = mailData.job,
send = function(mailData) ['@sender'] = mailData.name,
exports["gksphone"]:SendNewMail(mailData) ['@sendercitizenid'] = mailData.billerCitizenid,
['@label'] = mailData.label
}
)
end, end,
}, },
{ name = "yflip-phone",
send = function(mailData) { phone = "yflip-phone",
sendMail =
function(mailData)
TriggerServerEvent(getScript()..":yflip:SendMail", mailData) TriggerServerEvent(getScript()..":yflip:SendMail", mailData)
end, end,
}, },
{ name = "qs-smartphone",
send = function(mailData) { phone = "roadphone",
TriggerServerEvent('qs-smartphone:server:sendNewMail', mailData) sendMail =
end, function(mailData)
},
{ 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. -- Convert HTML line breaks to newlines for roadphone.
mailData.message = mailData.message:gsub("%<br>", "\n") mailData.message = mailData.message:gsub("%<br>", "\n")
exports["roadphone"]:sendMail(mailData) exports["roadphone"]:sendMail(mailData)
end, end,
}, },
{ name = "lb-phone",
send = function(mailData) { phone = "lb-phone",
sendMail =
function(mailData)
-- Convert HTML line breaks to newlines for lb-phone. -- Convert HTML line breaks to newlines for lb-phone.
mailData.message = mailData.message:gsub("%<br>", "\n") mailData.message = mailData.message:gsub("%<br>", "\n")
TriggerServerEvent(getScript()..":lbphone:SendMail", mailData) TriggerServerEvent(getScript()..":lbphone:SendMail", mailData)
end, end,
}, },
{ name = "qb-phone",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
{ name = "npwd_qbx_mail",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
{ name = "jpr-phonesystem",
send = function(mailData)
TriggerServerEvent(getScript()..":jpr:SendMail", mailData)
end,
},
{ name = "ef-phone",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
}
-- Check each phone system in order and use the first active one. { phone = "qb-phone",
for _, phone in ipairs(phoneSystems) do sendMail =
if isStarted(phone.name) then function(mailData)
debugPrint("^6Bridge^7[^3"..phone.name.."^7]: ^2Sending mail to player") TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
phone.send(data) end,
return true sendInvoice =
end function(mailData)
end
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
return false
end
function sendPhoneInvoice(data)
if not data.src then return end
-- Define each supported phone system and its corresponding mail-sending function.
local phoneSystems = {
{
name = "qb-phone",
send = function(mailData)
-- Defensive check for required fields -- Defensive check for required fields
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "src" } local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "src" }
for _, field in ipairs(required) do for _, field in ipairs(required) do
@@ -134,75 +98,76 @@ function sendPhoneInvoice(data)
end end
end end
) )
TriggerClientEvent('qb-phone:RefreshPhone', mailData.src) TriggerClientEvent('qb-phone:RefreshPhone', mailData.src)
end end,
}, },
{
name = "codem-phone",
send = function(mailData)
-- Defensive check for required fields
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "src" }
for _, field in ipairs(required) do
if mailData[field] == nil then
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
return
end
end
-- Safe insert with all parameters present { phone = "npwd_qbx_mail",
MySQL.Async.insert( sendMail =
'INSERT INTO phone_invoices (citizenid, amount, society, sender, sendercitizenid) VALUES (?, ?, ?, ?, ?)', function(mailData)
{ TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
mailData.billedCitizenid, end,
mailData.amount, },
mailData.job,
mailData.name, { phone = "jpr-phonesystem",
mailData.billerCitizenid sendMail =
}, function(mailData)
function(id) TriggerServerEvent(getScript()..":jpr:SendMail", mailData)
-- if id then end,
-- TriggerClientEvent('qb-phone:client:AcceptorDenyInvoice', mailData.src, id, mailData.name, mailData.job, mailData.billerCitizenid, mailData.amount, GetInvokingResource()) },
-- end
end { phone = "ef-phone",
) sendMail =
function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
}
end --- Sends a phone mail using the detected phone system.
}, --- The function iterates through a prioritized list of supported phone systems.
{ --- Once an active system is found (via `isStarted`), the corresponding mail function is executed.
name = "gks-phone", ---
send = function(mailData) --- @param data table A table containing the mail data.
-- Defensive check for required fields --- - subject (string): The email subject.
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "label" } --- - sender (string): The sender identifier.
for _, field in ipairs(required) do --- - message (string): The email body content.
if mailData[field] == nil then --- - actions (table|nil): Optional action buttons for the email.
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData") --- @usage
return --- sendPhoneMail({
end --- subject = "Welcome!",
end --- sender = "Admin",
--- message = "Thank you for joining our server.",
MySQL.Async.execute( --- actions = {
'INSERT INTO gksphone_invoices (citizenid, amount, society, sender, sendercitizenid, label) VALUES (@citizenid, @amount, @society, @sender, @sendercitizenid, @label)', --- { label = "Reply", action = replyFunction }
{ --- }
['@citizenid'] = mailData.billedCitizenid, --- })
['@amount'] = mailData.amount, function sendPhoneMail(mailData)
['@society'] = mailData.job, -- Check each phone system in order and use the first active one.
['@sender'] = mailData.name, for i = 1, #phoneFunc do
['@sendercitizenid'] = mailData.billerCitizenid, local script = phoneFunc[i]
['@label'] = mailData.label if isStarted(script.phone) and script.sendMail then
} debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player")
) script.sendMail(mailData)
end return true
} end
} end
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
return false
end
function sendPhoneInvoice(data)
if not data.src then return end
-- Check each phone system in order and use the first active one. -- Check each phone system in order and use the first active one.
for _, phone in ipairs(phoneSystems) do
if isStarted(phone.name) then for i = 1, #phoneFunc do
debugPrint("^6Bridge^7[^3"..phone.name.."^7]: ^2Sending mail to player^7", data.src) local script = phoneFunc[i]
phone.send(data) if isStarted(script.phone) and script.sendInvoice then
debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player^7", data.src)
script.sendInvoice(data)
return true return true
end end
end end
@@ -275,4 +240,6 @@ RegisterNetEvent(getScript()..":jpr:SendMail", function(data)
Destinatario = Player.PlayerData.citizenid, -- Recipient identifier Destinatario = Player.PlayerData.citizenid, -- Recipient identifier
Event = {}, -- Optional event details Event = {}, -- Optional event details
}) })
end) end)

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,68 @@
• removePolyZone(Location) - Removes a created zone. • removePolyZone(Location) - Removes a created zone.
]] ]]
local polyCreation = {
{
polyZoneScript = OXLibExport,
createPoly = function(data)
data.minZ = data.minZ or -20.0
data.maxZ = data.maxZ or 1000.0
data.thickness = ((data.maxZ / 2) - (data.minZ / 2)) * 2
local mid = data.maxZ - ((data.maxZ / 2) - (data.minZ / 2))
for i = 1, #data.points do
data.points[i] = vec3(data.points[i].x, data.points[i].y, mid)
end
return lib.zones.poly(data)
end,
createCircle = function(data)
return lib.zones.sphere(data)
end,
removeZone = function(Location)
Location:remove()
end,
},
{
polyZoneScript = "PolyZone",
createPoly = function(data)
local zone = PolyZone:Create(data.points, {
name = data.name,
minZ = data.minZ or nil,
maxZ = data.maxZ or nil,
debugPoly = data.debug
})
zone:onPlayerInOut(function(isPointInside)
if isPointInside then
data.onEnter()
else
data.onExit()
end
end)
return zone
end,
createCircle = function(data)
local zone = CircleZone:Create(data.coords, data.radius, {
name = data.name,
debugPoly = data.debug
})
zone:onPlayerInOut(function(isPointInside)
if isPointInside then
data.onEnter()
else
data.onExit()
end
end)
return zone
end,
removeZone = function(Location)
Location:destroy()
end,
}
}
------------------------------------------------------------- -------------------------------------------------------------
-- Polygonal Zone Creation -- Polygonal Zone Creation
------------------------------------------------------------- -------------------------------------------------------------
@@ -41,26 +103,16 @@
---}) ---})
---``` ---```
function createPoly(data) function createPoly(data)
local Location = nil for i = 1, #polyCreation do
if isStarted(OXLibExport) then local script = polyCreation[i]
debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name) if isStarted(script.polyZoneScript) then
-- Convert 2D points to 3D with a fixed Z coordinate (e.g., 12.0) debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..script.polyZoneScript.."^7': "..data.name)
for i = 1, #data.points do return script.createPoly(data)
data.points[i] = vec3(data.points[i].x, data.points[i].y, 12.0)
end 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 end
return Location
print("^4ERROR^7: ^2No PolyZone creation script detected ^7")
return nil
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -92,25 +144,17 @@ end
--- }) --- })
--- ``` --- ```
function createCirclePoly(data) function createCirclePoly(data)
local Location = nil for i = 1, #polyCreation do
if isStarted(OXLibExport) then local script = polyCreation[i]
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name) if isStarted(script.polyZoneScript) then
Location = lib.zones.sphere(data) debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..script.polyZoneScript.." "..data.name)
elseif isStarted("PolyZone") then debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7PolyZone ".. data.name) return script.createCircle(data)
Location = CircleZone:Create(data.coords, data.radius, { name = data.name, debugPoly = debugMode }) end
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 end
debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
return Location print("^4ERROR^7: ^2No PolyZone creation script detected ^7")
return nil
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -130,11 +174,12 @@ end
--- removePolyZone(zone) --- removePolyZone(zone)
--- ``` --- ```
function removePolyZone(Location) function removePolyZone(Location)
if isStarted(OXLibExport) then for i = 1, #polyCreation do
debugPrint("^6Bridge^7: ^2Removing ^2poly with ^7"..OXLibExport) local script = polyCreation[i]
Location:remove() if isStarted(script.polyZoneScript) then
elseif isStarted("PolyZone") then debugPrint("^6Bridge^7: ^2Removing ^3"..script.polyZoneScript.." ^2Zone^7")
debugPrint("^6Bridge^7: ^2poly with ^7PolyZone") script.removeZone(Location)
Location:destroy() break
end
end end
end end

View File

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

View File

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

View File

@@ -11,6 +11,127 @@
• Tgiann-bank • Tgiann-bank
]] ]]
local societyFunc = {
{ bankName = "qb-banking",
getAccount =
function(society)
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
return exports["qb-banking"]:GetAccountBalance(society)
end,
chargeSociety =
function(society, amount)
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null"
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null"
end
end
exports["qb-banking"]:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
exports["qb-banking"]:AddMoney(society, amount)
end,
},
{ bankName = "Renewed-Banking",
getAccount =
function(society)
return exports["Renewed-Banking"]:getAccountMoney(society)
end,
chargeSociety =
function(society, amount)
exports['Renewed-Banking']:removeAccountMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports['Renewed-Banking']:addAccountMoney(society, amount)
end,
},
{ bankName = "fd_banking",
getAccount =
function(society)
return exports["fd_banking"]:GetAccount(society)
end,
chargeSociety =
function(society, amount)
exports["fd_banking"]:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports["fd_banking"]:AddMoney(society, amount)
end,
},
{ bankName = "okokBanking",
getAccount =
function(society)
return exports['okokBanking']:GetAccount(society)
end,
chargeSociety =
function(society, amount)
exports['okokBanking']:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports['okokBanking']:AddMoney(society, amount)
end,
},
{ bankName = "tgiann-bank",
getAccount =
function(society)
if Jobs[society] then
return exports["tgiann-bank"]:GetJobAccountBalance(society)
else
return exports["tgiann-bank"]:GetGangAccountBalance(society)
end
end,
chargeSociety =
function(society, amount)
if Jobs[society] then
exports["tgiann-bank"]:RemoveJobMoney(society, amount)
else
exports["tgiann-bank"]:RemoveGangMoney(society, amount)
end
end,
fundSociety =
function(society, amount)
if Jobs[society] then
exports["tgiann-bank"]:AddJobMoney(society, amount)
else
exports["tgiann-bank"]:AddGangMoney(society, amount)
end
end,
},
}
--- Retrieves the current balance of a society's bank account. --- Retrieves the current balance of a society's bank account.
--- @param society string The identifier of the society. --- @param society string The identifier of the society.
--- @return number number The current account balance. --- @return number number The current account balance.
@@ -20,56 +141,19 @@
--- print("Police account balance: $"..balance) --- print("Police account balance: $"..balance)
--- ``` --- ```
function getSocietyAccount(society) function getSocietyAccount(society)
local bankScript, amount = "", 0 local amount = 0
if society == nil or society == "none" then return amount end if society == nil or society == "none" then return amount end
if isStarted("qb-banking") then for i = 1, #societyFunc do
bankScript = "qb-banking" local script = societyFunc[i]
if not exports["qb-banking"]:GetAccount(society) then if isStarted(script.bankName) then
if Jobs[society] then local amount = script.getAccount(society)
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Retrieved account ^7'^6"..society.."^7' ($"..tostring(amount)..")")
exports["qb-banking"]:CreateJobAccount(society, 0) return amount
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
amount = exports["qb-banking"]:GetAccountBalance(society)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- -- Since esx_society does not have a native client export for retrieving money,
-- -- we use a server callback to get the final amount.
-- amount = triggerCallback(getScript()..":getESXSocietyAccount", society) or 0
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
amount = exports["Renewed-Banking"]:getAccountMoney(society)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
amount = exports["fd_banking"]:GetAccount(society)
elseif isStarted("okokBanking") then
bankScript = "okokBanking"
amount = exports['okokBanking']:GetAccount(society)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
amount = exports["tgiann-bank"]:GetJobAccountBalance(society)
else
amount = exports["tgiann-bank"]:GetGangAccountBalance(society)
end end
end end
if bankScript == "" then print("^1Error^7: ^3GetSocietyAccount^7: ^2No supported banking script found")
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 return amount
end end
@@ -82,52 +166,18 @@ end
--- chargeSociety("police", 1000) --- chargeSociety("police", 1000)
--- ``` --- ```
function chargeSociety(society, amount) function chargeSociety(society, amount)
local bankScript, newAmount = "", 0
if isStarted("qb-banking") then for i = 1, #societyFunc do
bankScript = "qb-banking" local script = societyFunc[i]
if not exports["qb-banking"]:GetAccount(society) then if isStarted(script.bankName) then
if Jobs[society] then script.chargeSociety(society, amount)
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") local newAmount = getSocietyAccount(society)
exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null" debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Removing $"..amount.." from account '^6"..society.."^7' ($"..tostring(newAmount)..")")
elseif Gangs[society] then return
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null"
end
end
exports["qb-banking"]:RemoveMoney(society, amount)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- TriggerEvent("esx_society:withdrawMoney", society, amount)
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
exports['Renewed-Banking']:removeAccountMoney(society, amount)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
exports["fd_banking"]:RemoveMoney(society, amount)
elseif isStarted("okokBanking") then
bankScript = "okokBanking"
exports['okokBanking']:RemoveMoney(society, amount)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
exports["tgiann-bank"]:RemoveJobMoney(society, amount)
else
exports["tgiann-bank"]:RemoveGangMoney(society, amount)
end end
end end
if bankScript == "" then print("^1Error^7: ^3ChargeSociety^7: ^2No supported banking script found")
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 end
--- Adds funds to a society's bank account. --- Adds funds to a society's bank account.
@@ -138,58 +188,18 @@ end
--- fundSociety("police", 500) --- fundSociety("police", 500)
--- ``` --- ```
function fundSociety(society, amount) function fundSociety(society, amount)
local bankScript, newAmount, success = "", 0, false
for i = 1, #societyFunc do
if isStarted("qb-banking") then local script = societyFunc[i]
bankScript = "qb-banking" if isStarted(script.bankName) then
if not exports["qb-banking"]:GetAccount(society) then script.fundSociety(society, amount)
if Jobs[society] then local newAmount = getSocietyAccount(society)
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'") debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Adding ^7$"..amount.." ^2to account ^7'^6"..society.."^7' ($"..tostring(newAmount)..")")
exports["qb-banking"]:CreateJobAccount(society, 0) return
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
exports["qb-banking"]:AddMoney(society, amount)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- -- Use the esx_society event to deposit money.
-- TriggerServerEvent('esx_society:depositMoney', society, amount)
-- -- Use callback to return the updated balance.
-- newAmount = triggerCallback(getScript()..":getESXSocietyAccount", society) or 0
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
exports['Renewed-Banking']:addAccountMoney(society, amount)
newAmount = exports["Renewed-Banking"]:getAccountMoney(society)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
exports["fd_banking"]:AddMoney(society, amount)
elseif isStarted("okokBanking") then
bankScript = "okokBanking"
exports['okokBanking']:AddMoney(society, amount)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
exports["tgiann-bank"]:AddJobMoney(society, amount)
else
exports["tgiann-bank"]:AddGangMoney(society, amount)
end end
end end
if bankScript == "" then print("^1Error^7: ^3FundSociety^7: ^2No supported banking script found")
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 end

View File

@@ -1,700 +0,0 @@
--[[
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(JPRInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(QBInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(PSInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
TriggerEvent("ps-inventory:client:SetCurrentStash", data.stash)
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
elseif isStarted(RSGInv) then
TriggerServerEvent(getScript()..':server:openServerStash', {
stashName = data.stash,
label = data.label,
maxweight = data.maxWeight or 600000,
slots = data.slots or 40
})
else
--Fallback to these commands
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash, {
slots = data.slots or 50,
maxWeight = data.maxWeight or 600000
})
end
lookEnt(data.coords)
end
-- Wrapper function for opening stash from the server.
-- Messy but not much else I can do about it.
RegisterNetEvent(getScript()..":server:openServerStash", function(data)
local src = source
if isStarted(TgiannInv) then
exports[TgiannInv]:OpenInventory(source, 'stash', data.stashName, data)
end
if isStarted(JPRInv) then
exports[JPRInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(QBInv) then
exports[QBInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(PSInv) then
exports[PSInv]:OpenInventory(source, data.stashName, data)
end
if isStarted(RSGInv) then
exports[RSGInv]:OpenInventory(source, data.stashName, data)
end
end)
function clearStash(stashId)
if isStarted(JPRInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..JPRInv.."^2 Stash^7", stashId)
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashId,
['items'] = json.encode({})
})
elseif isStarted(QBInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..QBInv.."^2 Stash^7", stashId)
if QBInvNew then
exports[QBInv]:ClearStash(stashId)
else
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashId,
['items'] = json.encode({})
})
end
elseif isStarted(OXInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..OXInv.."^2 Stash^7", stashId)
exports[OXInv]:ClearInventory(stashId)
elseif isStarted(PSInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..PSInv.."^2 Stash^7", stashId)
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashId,
['items'] = json.encode({})
})
elseif isStarted(QSInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..QSInv.."^2 Stash^7", stashId)
exports[QSInv]:ClearOtherInventory('stash', stashId)
elseif isStarted(CoreInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..CoreInv.."^2 Stash^7", stashId)
exports[CoreInv]:clearInventory("stash-"..stashId)
elseif isStarted(CodeMInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..CodeMInv.."^2 Stash^7", stashId)
exports[CodeMInv]:ClearInventory(stashId)
elseif isStarted(OrigenInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..OrigenInv.."^2 Stash^7", stashId)
exports[OrigenInv]:ClearInventory(stashId)
elseif isStarted(TgiannInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..TgiannInv.."^2 Stash^7", stashId)
exports["tgiann-inventory"]:DeleteInventory("stash", stashId)
elseif isStarted(RSGInv) then
debugPrint("^5Bridge^7: ^2Clearing ^3"..RSGInv.."^2 Stash^7", stashId)
exports[RSGInv]:ClearStash(stashId)
end
end
-------------------------------------------------------------
-- Stash Retrieval Function
-------------------------------------------------------------
--- Retrieves stash items from the active inventory system.
---
--- This function converts the raw stash items into a standardized table using the global Items lookup.
---
--- @param stashName string The identifier for the stash.
--- @return stashTable table A table of items from the stash.
---
--- @usage
--- ```lua
--- local items = getStash("playerStash")
--- ```
function getStash(stashName)
local stashResource = ""
if type(stashName) ~= "string" then
print("^6Bridge^7: ^2Stash name was not a string ^3"..stashName.."^7(^3"..type(stashName).."^7)")
return {}
end
local stashItems, items = {}, {}
if isStarted(OXInv) then
stashResource = OXInv
local stash = exports[OXInv]:Inventory(stashName)
-- Add fallback if ox can't find the stash and returns a boolean
stashItems = type(stash) == "table" and stash.items or {}
elseif isStarted(QSInv) then
stashResource = QSInv
stashItems = exports[QSInv]:GetStashItems(stashName)
elseif isStarted(CoreInv) then
stashResource = CoreInv
stashItems = exports[CoreInv]:getInventory(stashName)
elseif isStarted(CodeMInv) then
stashResource = CodeMInv
stashItems = exports[CodeMInv]:GetStashItems(stashName)
elseif isStarted(OrigenInv) then
stashResource = OrigenInv
stashItems = exports[OrigenInv]:getInventory(stashName)
elseif isStarted(TgiannInv) then
stashResource = TgiannInv
stashItems = exports[TgiannInv]:GetSecondaryInventoryItems("stash", stashName)
elseif isStarted(PSInv) then
stashResource = PSInv
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
if result then stashItems = json.decode(result) end
elseif isStarted(JPRInv) then
stashResource = JPRInv
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
if result then stashItems = json.decode(result) end
elseif isStarted(QBInv) then
stashResource = QBInv
if QBInvNew then
local result = exports[QBInv]:GetInventory(stashName) or {}
stashItems = result.items or {}
else
local result = MySQL.scalar.await("SELECT items FROM stashitems WHERE stash = ?", { stashName })
if result then stashItems = json.decode(result) end
end
elseif isStarted(RSGInv) then
stashResource = RSGInv
stashItems = exports[RSGInv]:GetInventory(stashName)
end
debugPrint("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7"..stashResource)
if stashItems then
for _, item in pairs(stashItems) do
local itemInfo = Items[item.name:lower()]
if itemInfo then
local indexNum = #items + 1 -- Fallback index if slot is missing.
items[(item.slot or indexNum)] = {
name = itemInfo.name or nil,
amount = tonumber(item.amount) or tonumber(item.count),
info = item.info or "",
label = itemInfo.label or nil,
description = itemInfo.description or "",
weight = itemInfo.weight or nil,
type = itemInfo.type or nil,
unique = itemInfo.unique or nil,
useable = itemInfo.useable or nil,
image = itemInfo.image or nil,
slot = (item.slot and item.slot) or indexNum,
metadata = (item.metadata and item.metadata) or nil,
}
end
end
debugPrint("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for '^6"..stashName.."^7' retrieved")
end
jsonPrint(items)
return items
end
-------------------------------------------------------------
-- Stash Item Removal Function
-------------------------------------------------------------
--- Removes items from a stash using the active inventory system.
---
--- Iterates over the provided items and adjusts the stash contents accordingly.
---
--- @param stashItems table The current stash items.
--- @param stashName string|table The stash identifier (or table of identifiers).
--- @param items table A table of items to remove (keys are item names, values are amounts).
---
--- @usage
--- ```lua
--- stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 })
--- ```
function stashRemoveItem(stashItems, stashName, items)
if type(stashName) ~= "table" then
stashName = { stashName }
end
if isStarted(OXInv) then
for k, v in pairs(items) do
for _, name in pairs(stashName) do
local success = exports[OXInv]:RemoveItem(name, k, v)
if success then
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
break
end
end
end
elseif isStarted(QSInv) then
for k, v in pairs(items) do
exports[QSInv]:RemoveItemIntoStash(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QSInv, k, v)
end
elseif isStarted(CoreInv) then
for k, v in pairs(items) do
exports[CoreInv]:removeItemExact(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CoreInv, k, v)
end
elseif isStarted(CodeMInv) then
for k, v in pairs(items) do
for l in pairs(stashItems) do
if stashItems[l].name == k then
if (stashItems[l].amount - v) <= 0 then
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
stashItems[l] = nil
else
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CodeMInv, k, v)
stashItems[l].amount -= v
end
end
end
end
exports[CodeMInv]:UpdateStash(stashName[1], stashItems)
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3CodeM^2 stash ^7'^6"..stashName.."^7'")
elseif isStarted(OrigenInv) then
for k, v in pairs(items) do
exports[OrigenInv]:RemoveFromStash(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OrigenInv, k, v)
end
elseif isStarted(TgiannInv) then
for k, v in pairs(items) do
local itemData = exports[TgiannInv]:GetItemByNameFromSecondaryInventory("stash", stashName[1], k)
exports[TgiannInv]:RemoveItemFromSecondaryInventory("stash", stashName[1], k, v, itemData.slot, nil)
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..TgiannInv, k, v)
end
elseif isStarted(JPRInv) then
if not stashItems or not next(stashItems) then
stashItems = getStash(stashName[1])
end
for k, v in pairs(items) do
for l in pairs(stashItems) do
if stashItems[l].name == k then
if (stashItems[l].amount - v) <= 0 then
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
stashItems[l] = nil
else
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with "..JPRInv, k, v)
stashItems[l].amount -= v
end
end
end
end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3JPR^2 stash '^6"..stashName[1].."^7'")
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName[1],
['items'] = json.encode(stashItems)
})
elseif isStarted(QBInv) then
if QBInvNew then
for k, v in pairs(items) do
exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
end
else
if not stashItems or not next(stashItems) then
stashItems = getStash(stashName[1])
end
for k, v in pairs(items) do
for l in pairs(stashItems) do
if stashItems[l].name == k then
if (stashItems[l].amount - v) <= 0 then
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
stashItems[l] = nil
else
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with "..QBInv, k, v)
stashItems[l].amount -= v
end
end
end
end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash '^6"..stashName[1].."^7'")
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName[1],
['items'] = json.encode(stashItems)
})
end
elseif isStarted(PSInv) then
if not stashItems or not next(stashItems) then
stashItems = getStash(stashName[1])
end
for k, v in pairs(items) do
for l in pairs(stashItems) do
if stashItems[l].name == k then
if (stashItems[l].amount - v) <= 0 then
debugPrint("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
stashItems[l] = nil
else
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..PSInv, k, v)
stashItems[l].amount -= v
end
end
end
end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3PS^2 stash ^7'^6"..stashName[1].."^7'")
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName[1],
['items'] = json.encode(stashItems)
})
elseif isStarted(RSGInv) then
for k, v in pairs(items) do
exports[RSGInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..RSGInv, k, v)
end
else
print("^4ERROR^7: ^2No Inventory detected ^7- ^2Check ^3starter^1.^2lua^7")
end
end
RegisterNetEvent(getScript()..":server:stashRemoveItem", stashRemoveItem)
-------------------------------------------------------------
-- Stash Item Availability Check
-------------------------------------------------------------
--- Checks whether a stash has the required amount of specific items.
---
--- It iterates through the provided items and tallies available quantities.
---
--- @param stashItems table The items available in the stash.
--- @param items string|table The item name or table of required items (key: item, value: amount).
--- @param amount number (Optional) The required amount (if a single item is provided).
--- @return boolean, table (`boolean, string`) Returns true (and a table with counts) if all items are available; false otherwise.
---
--- @usage
--- ```lua
--- local hasAll, details = stashhasItem(currentStashItems, { iron = 2, wood = 5 })
--- ```
function stashhasItem(stashItems, items, amount)
local invs = { OXInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv, QBInv, PSInv, RSGInv }
local foundInv = ""
for _, inv in ipairs(invs) do
if isStarted(inv) then
foundInv = inv:gsub("%-", "^7-^6"):gsub("%_", "^7_^6")
break
end
end
-- Ensure items is a table.
if type(items) ~= "table" then items = { [items] = amount and amount or 1, } end
local hasTable = {}
for item, requiredAmount in pairs(items) do
local count = 0
for _, itemData in pairs(stashItems) do
if itemData and (itemData.name == item) then
count += (itemData.amount or 1)
end
end
local debugMsg = string.format("^6Bridge^7: ^3stashHasItem^7[^6%s^7]: %s '%s' ^3%d^7/^3%d^7", foundInv, (count >= requiredAmount and "^5FOUND^7" or "^1NOT FOUND^7"), item, count, requiredAmount)
debugPrint(debugMsg)
hasTable[item] = { hasItem = (count >= requiredAmount), count = count }
end
for k, v in pairs(hasTable) do if v.hasItem == false then return false, hasTable end end
return true, hasTable
end
--- Registers a stash with the active inventory system.
--- Supports either OXInv or QSInv.
---
--- @param name string Unique stash identifier.
--- @param label string Display name for the stash.
--- @param slots number|nil (Optional) Number of slots (default 50).
--- @param weight number|nil (Optional) Maximum weight (default 4000000).
--- @param owner string|nil (Optional) Owner identifier for personal stashes.
--- @param coords table|nil (Optional) Coordinates for the stash location.
--- @usage
--- ```lua
--- registerStash(
--- "playerStash",
--- "Player Stash",
--- 100,
--- 8000000,
--- "player123",
--- { x = 100.0, y = 200.0, z = 30.0 }
--- )
--- ```
function registerStash(name, label, slots, weight, owner, coords)
local stashResource = ""
if isStarted(OXInv) then
stashResource = OXInv
exports[OXInv]:RegisterStash(name, label, slots or 50, weight or 4000000, owner or nil)
elseif isStarted(OrigenInv) then
stashResource = OrigenInv
exports["origen_inventory"]:registerStash(name, label, slots or 50, weight or 4000000)
elseif isStarted(TgiannInv) then
stashResource = TgiannInv
exports[TgiannInv]:RegisterStash(name, label, slots or 50, weight or 4000000)
end
if stashResource ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^4"..stashResource.." ^3Stash^7:", name, "^4Label^7: "..label)
else
debugPrint("^1ERROR^7: ^1Couldn't find supported inventory to register stash^7:", name)
end
end
if isServer() then
--- Registers an event to create an OX stash from the server.
--- When triggered, it calls registerStash with the provided parameters.
---
--- @event server:makeOXStash
--- @param name string Unique stash identifier.
--- @param label string Display name for the stash.
--- @param slots number|nil (Optional) Number of slots.
--- @param weight number|nil (Optional) Maximum weight.
--- @param owner string|nil (Optional) Owner identifier.
--- @param coords table|nil (Optional) Stash coordinates.
--- @usage
--- ```lua
--- TriggerEvent(getScript()..":server:makeOXStash", name, label, slots, weight, owner, coords, token)
--- ```
RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords, token)
local src = source or nil
if src then
if not checkToken(src, token, "stash", name) then
return
end
end
registerStash(name, label, slots, weight, owner, coords)
end)
end
RegisterNetEvent(getScript()..":openGrabBox", function(data)
if isStarted(OXInv) then
return
end
local id = ""
if data.metadata then
id = data.metadata.id
elseif data.info then
id = data.info.id
end
openStash({
stash = id,
})
end)

View File

@@ -17,6 +17,196 @@
the module uses DrawText3D prompts. This is experimental and may not work as expected. the module uses DrawText3D prompts. This is experimental and may not work as expected.
]] ]]
local targetFunc = {
{ targetName = OXTargetExport,
entityTarget =
function(entity, opts, dist)
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addLocalEntity(entity, options)
end,
boxTarget =
function(data, opts, dist)
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].groups or opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
data[5].maxZ = data[5].maxZ or (data[2].z + 0.80)
data[5].minZ = data[5].minZ or data[2].z - 1.05
local thickness = ((data[5].maxZ / 2) - (data[5].minZ / 2)) * 2
local mid = data[5].maxZ - ((data[5].maxZ / 2) - (data[5].minZ / 2))
data[2] = vec3(data[2].x, data[2].y, mid) -- force the coord to middle of the minZ and maxZ
local target = exports[OXTargetExport]:addBoxZone({
coords = data[2],
size = vec3(data[4], data[3], thickness), -- size uses the math to determine how high it needs to be
rotation = data[5].heading,
debug = data[5].debugPoly,
options = options
})
return target
end,
circleTarget =
function(data, opts, dist)
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
local target = exports[OXTargetExport]:addSphereZone({
coords = data[2],
radius = data[3],
debug = data[4].debugPoly,
options = options
})
return target
end,
modelTarget =
function(models, opts, dist)
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addModel(models, options)
end,
removeTargetEntity =
function(entity)
exports[OXTargetExport]:removeLocalEntity(entity, nil)
end,
removeTargetZone =
function(target)
exports[OXTargetExport]:removeZone(target, true)
end,
removeTargetModel =
function(model)
exports[OXTargetExport]:removeModel(model, nil)
end,
},
{ targetName = QBTargetExport,
entityTarget =
function(entity, opts, dist)
local options = { options = opts, distance = dist }
exports[QBTargetExport]:AddTargetEntity(entity, options)
end,
boxTarget =
function(data, opts, dist)
local options = { options = opts, distance = dist }
local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
return data[1]
end,
circleTarget =
function(data, opts, dist)
local options = { options = opts, distance = dist }
local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options)
return data[1]
end,
modelTarget =
function(models, opts, dist)
local options = { options = opts, distance = dist }
exports[QBTargetExport]:AddTargetModel(models, options)
end,
removeTargetEntity =
function(entity)
exports[QBTargetExport]:RemoveTargetEntity(entity)
end,
removeTargetZone =
function(target)
exports[QBTargetExport]:RemoveZone(target)
end,
removeTargetModel =
function(model)
exports[QBTargetExport]:RemoveTargetModel(model, "Test")
end,
},
{ targetName = "jim_bridge",
entityTarget =
function(entity, opts, dist)
return exports.jim_bridge:createEntityTarget(entity, opts, dist)
end,
boxTarget =
function(data, opts, dist)
return exports.jim_bridge:createZoneTarget(data, opts, dist)
end,
circleTarget =
function(data, opts, dist)
return exports.jim_bridge:createZoneTarget(data, opts, dist)
end,
modelTarget =
function(models, opts, dist)
return exports.jim_bridge:createModelTarget(models, opts, dist)
end,
removeTargetEntity =
function(entity)
exports.jim_bridge:removeEntityTarget(entity)
end,
removeTargetZone =
function(target)
exports.jim_bridge:removeZoneTarget(target)
end,
removeTargetModel =
function(model)
exports.jim_bridge:removeZoneTarget(model)
end,
},
}
------------------------------------------------------------- -------------------------------------------------------------
-- Utility Data & Tables -- Utility Data & Tables
------------------------------------------------------------- -------------------------------------------------------------
@@ -63,33 +253,22 @@ function createEntityTarget(entity, opts, dist)
-- Store the target entity for later cleanup. -- Store the target entity for later cleanup.
targetEntities[#targetEntities + 1] = entity targetEntities[#targetEntities + 1] = entity
-- Fallback: Use DrawText3D if targeting systems are disabled or unavailable. -- if force target off, use jim_bridge buiilt in target functions
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then if Config.System.DontUseTarget then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
exports.jim_bridge:createEntityTarget(entity, opts, dist) exports.jim_bridge:createEntityTarget(entity, opts, dist)
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6jim_bridge ^2for entity ^7"..entity)
elseif isStarted(OXTargetExport) then return
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addLocalEntity(entity, options)
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..QBTargetExport.." ^2for entity ^7"..entity)
local options = { options = opts, distance = dist }
exports[QBTargetExport]:AddTargetEntity(entity, options)
end end
-- Check for target script and use that
for i = 1, #targetFunc do
local script = targetFunc[i]
if isStarted(script.targetName) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..script.targetName.." ^2for entity ^7"..entity)
return script.entityTarget(entity, opts, dist)
end
end
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -146,46 +325,24 @@ end
---}, 2.0) ---}, 2.0)
---``` ---```
function createBoxTarget(data, opts, dist) function createBoxTarget(data, opts, dist)
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^2 for zone ^7"..data[1]) -- if force target off, use jim_bridge buiilt in target functions
if Config.System.DontUseTarget then
debugPrint("^6Bridge^7: ^2Creating new ^3Box ^2target with ^6jim_bridge ^7"..data[1])
return exports.jim_bridge:createZoneTarget(data, opts, dist) return exports.jim_bridge:createZoneTarget(data, opts, dist)
elseif isStarted(OXTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local options = {}
for i = 1, #opts do
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
if not data[5].useZ then
local z = data[2].z + math.abs(data[5].maxZ - data[5].minZ) / 2
data[2] = vec3(data[2].x, data[2].y, z)
end
local target = exports[OXTargetExport]:addBoxZone({
coords = data[2],
size = vec3(data[4], data[3], (data[5].useZ or not data[5].maxZ) and data[2].z or math.abs(data[5].maxZ - data[5].minZ)),
rotation = data[5].heading,
debug = data[5].debugPoly,
options = options
})
boxTargets[#boxTargets + 1] = target
return target
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..QBTargetExport.." ^2for zone ^7"..data[1])
local options = { options = opts, distance = dist }
local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
boxTargets[#boxTargets + 1] = target
return data[1]
end end
-- Check for target script and use that
for i = 1, #targetFunc do
local script = targetFunc[i]
if isStarted(script.targetName) then
debugPrint("^6Bridge^7: ^2Creating new ^3Box ^2target with ^6"..script.targetName.." ^7"..data[1])
local target = script.boxTarget(data, opts, dist)
boxTargets[#boxTargets + 1] = target
return target
end
end
return nil
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -225,39 +382,25 @@ end
--- }, 2.0) --- }, 2.0)
--- ``` --- ```
function createCircleTarget(data, opts, dist) function createCircleTarget(data, opts, dist)
if Config.System.DontUseTarget then
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6DrawText ^2for zone ^7"..data[1])
return exports.jim_bridge:createZoneTarget(data, opts, dist)
elseif isStarted(OXTargetExport) then -- if force target off, use jim_bridge buiilt in target functions
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1]) if Config.System.DontUseTarget then
local options = {} debugPrint("^6Bridge^7: ^2Creating new ^3Sphere ^2target with ^6jim_bridge ^7"..data[1])
for i = 1, #opts do return exports.jim_bridge:createZoneTarget(data, opts, dist)
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
local target = exports[OXTargetExport]:addSphereZone({
coords = data[2],
radius = data[3],
debug = data[4].debugPoly,
options = options
})
circleTargets[#circleTargets + 1] = target
return target
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..QBTargetExport.." ^2for zone ^7"..data[1])
local options = { options = opts, distance = dist }
local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options)
circleTargets[#circleTargets + 1] = target
return data[1]
end end
-- Check for target script and use that
for i = 1, #targetFunc do
local script = targetFunc[i]
if isStarted(script.targetName) then
debugPrint("^6Bridge^7: ^2Creating new ^3Sphere ^2target with ^6"..script.targetName.." ^7"..data[1])
local target = script.circleTarget(data, opts, dist)
circleTargets[#circleTargets + 1] = target
return target
end
end
return nil
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -286,29 +429,24 @@ end
---}, 2.0) ---}, 2.0)
---``` ---```
function createModelTarget(models, opts, dist) function createModelTarget(models, opts, dist)
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
return exports.jim_bridge:createModelTarget(models, opts, dist)
elseif isStarted(OXTargetExport) then -- if force target off, use jim_bridge buiilt in target functions
debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..OXTargetExport) if Config.System.DontUseTarget then
local options = {} debugPrint("^6Bridge^7: ^2Creating new ^3Model ^2target with ^6jim_bridge^7")
for i = 1, #opts do return exports.jim_bridge:createModelTarget(models, opts, dist)
options[i] = {
icon = opts[i].icon,
label = opts[i].label,
items = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addModel(models, options)
elseif isStarted(QBTargetExport) then
debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..QBTargetExport)
local options = { options = opts, distance = dist }
exports[QBTargetExport]:AddTargetModel(models, options)
end end
-- Check for target script and use that
for i = 1, #targetFunc do
local script = targetFunc[i]
if isStarted(script.targetName) then
debugPrint("^6Bridge^7: ^2Creating new ^3Model ^2target with ^6"..script.targetName.."^7")
local target = script.modelTarget(models, opts, dist)
circleTargets[#circleTargets + 1] = target
return target
end
end
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -324,15 +462,20 @@ end
--- removeEntityTarget(entityId) --- removeEntityTarget(entityId)
--- ``` --- ```
function removeEntityTarget(entity) function removeEntityTarget(entity)
if isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveTargetEntity(entity) if Config.System.DontUseTarget then
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeLocalEntity(entity, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeEntityTarget(entity) exports.jim_bridge:removeEntityTarget(entity)
end end
-- Check for target script and use that
for i = 1, #targetFunc do
local script = targetFunc[i]
if isStarted(script.targetName) then
script.removeTargetEntity(entity)
break
end
end
end end
--- Removes a previously created zone target. --- Removes a previously created zone target.
@@ -345,15 +488,19 @@ end
--- removeZoneTarget(targetObject) --- removeZoneTarget(targetObject)
--- ``` --- ```
function removeZoneTarget(target) function removeZoneTarget(target)
if isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveZone(target) if Config.System.DontUseTarget then
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeZone(target, true)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeZoneTarget(target) exports.jim_bridge:removeZoneTarget(target)
end end
-- Check for target script and use that
for i = 1, #targetFunc do
local script = targetFunc[i]
if isStarted(script.targetName) then
script.removeTargetZone(target)
break
end
end
end end
--- Removes a previously created model target. --- Removes a previously created model target.
@@ -365,122 +512,21 @@ end
--- removeModelTarget(model) --- removeModelTarget(model)
--- ``` --- ```
function removeModelTarget(model) function removeModelTarget(model)
if isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveTargetModel(model, "Test") if Config.System.DontUseTarget then
exports.jim_bridge:removeZoneTarget(model)
end end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeModel(model, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeZoneTarget(target)
end
end
-------------------------------------------------------------
-- Fallback: DrawText3D Targets (Experimental)
-------------------------------------------------------------
-- If no targeting system is detected and this is a client script, use DrawText3D for targets. -- Check for target script and use that
if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) and not isServer() then for i = 1, #targetFunc do
CreateThread(function() local script = targetFunc[i]
while true do if isStarted(script.targetName) then
local ped = PlayerPedId() script.removeTargetModel(model)
local pedCoords = GetEntityCoords(ped) break
local camCoords = GetGameplayCamCoord()
local camRot = GetGameplayCamRot(2)
local camForward = RotationToDirection(camRot)
local closestTarget = nil
local closestDist = math.huge
local targetEntity = nil
-- Shallow copy for safety
local targetsCopy = {}
for k, v in pairs(TextTargets) do
targetsCopy[k] = v
end
-- Detect models and update coords
for _, target in pairs(targetsCopy) do
if target.models then
if not target.entity or not DoesEntityExist(target.entity) then
for _, model in ipairs(target.models) do
local entity = GetClosestObjectOfType(pedCoords.x, pedCoords.y, pedCoords.z, target.dist, model, false, false, false)
if entity and entity ~= 0 then
target.entity = entity
target.coords = GetEntityCoords(entity)
break
end
end
else
target.coords = GetEntityCoords(target.entity)
end
end
end
-- Identify closest visible target
for _, target in pairs(targetsCopy) do
if target.coords then
local dist = #(pedCoords - target.coords)
local vecToTarget = target.coords - camCoords
local normVec = normalizeVector(vecToTarget)
local dot = camForward.x * normVec.x + camForward.y * normVec.y + camForward.z * normVec.z
local isFacing = dot > 0.5
if dist <= target.dist and isFacing then
if dist < closestDist then
closestDist = dist
closestTarget = target
targetEntity = target.entity
end
end
end
end
-- Render + handle input
for _, target in pairs(targetsCopy) do
if target.coords and #(pedCoords - target.coords) <= target.dist then
local isClosest = (target == closestTarget)
for _, opt in ipairs(target.options) do
if IsControlJustPressed(0, opt.key) and isClosest then
local canInteract = (not target.canInteract or target.canInteract())
local hasItem = (not opt.item or hasItem(opt.item))
local hasJob = (not opt.job or hasJob(opt.job, nil))
if canInteract and hasItem and hasJob then
if opt.onSelect then opt.onSelect(targetEntity) end
if opt.action then opt.action(targetEntity) end
end
end
end
-- Draw each eligible text line
local baseZ = target.coords.z + 1.0
local lineHeight = -0.16
local lineOffset = 0
for i, opt in ipairs(target.options) do
local canInteract = (not target.canInteract or target.canInteract())
local hasItem = (not opt.item or hasItem(opt.item))
local hasJob = (not opt.job or hasJob(opt.job, nil))
if canInteract and hasItem and hasJob then
local text = target.buttontext[i]
local zOffset = lineOffset * lineHeight
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + zOffset), text, isClosest)
lineOffset += 1
end
end
end
end
Wait(0)
end end
end) end
end end
function ShowFloatingHelpNotification(coord, text, highlight) function ShowFloatingHelpNotification(coord, text, highlight)
AddTextEntry("FloatingText", text) AddTextEntry("FloatingText", text)
SetFloatingHelpTextWorldPosition(1, coord.x, coord.y, coord.z) SetFloatingHelpTextWorldPosition(1, coord.x, coord.y, coord.z)
@@ -489,16 +535,11 @@ function ShowFloatingHelpNotification(coord, text, highlight)
EndTextCommandDisplayHelp(2, false, false, -1) EndTextCommandDisplayHelp(2, false, false, -1)
end end
function updateCachedText(target)
target.text = table.concat(target.buttontext, "\n")
end
------------------------------------------------------------- -------------------------------------------------------------
-- Cleanup on Resource Stop -- Cleanup on Resource Stop
------------------------------------------------------------- -------------------------------------------------------------
-- When the current resource stops, remove all targets. local function CleanupTargets()
onResourceStop(function()
-- Remove entity targets. -- Remove entity targets.
for i = 1, #targetEntities do for i = 1, #targetEntities do
if isStarted(OXTargetExport) then if isStarted(OXTargetExport) then
@@ -523,4 +564,13 @@ onResourceStop(function()
exports[QBTargetExport]:RemoveZone(circleTargets[i].name) exports[QBTargetExport]:RemoveZone(circleTargets[i].name)
end end
end end
end
onPlayerUnload(function()
CleanupTargets()
end)
-- When the current resource stops, remove all targets.
onResourceStop(function()
CleanupTargets()
end, true) end, true)

View File

@@ -57,7 +57,7 @@ function searchCar(vehicle)
} }
if Vehicles then if Vehicles then
for k, v in pairs(Vehicles) do for k, v in pairs(Vehicles) do
if tonumber(v.hash) == model or GetHashKey(v.hash) == model or GetHashKey(v.model) == model then if tonumber(v.hash) == model or joaat(v.hash) == model or joaat(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)") 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.name = Vehicles[k].name.." "..Vehicles[k].brand
carInfo.price = Vehicles[k].price carInfo.price = Vehicles[k].price
@@ -105,16 +105,28 @@ end
function getVehicleProperties(vehicle) function getVehicleProperties(vehicle)
if not vehicle then return nil end if not vehicle then return nil end
local properties = {} local propertyFunc = {
if isStarted(QBExport) and not isStarted(QBXExport) then { framework = OXLibExport,
properties = Core.Functions.GetVehicleProperties(vehicle) func = function(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]") return lib.getVehicleProperties(vehicle)
end,
elseif isStarted(OXLibExport) then },
properties = lib.getVehicleProperties(vehicle) { framework = QBExport,
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]") func = function(vehicle)
return Core.Functions.GetVehicleProperties(vehicle)
end,
},
}
for i = 1, #propertyFunc do
local prop = propertyFunc[i]
if isStarted(prop.framework) then
local properties = prop.func(vehicle)
debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..prop.framework.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
return properties
end
end end
return properties
return nil
end end
--- Sets the properties of a given vehicle if changes are detected. --- Sets the properties of a given vehicle if changes are detected.
@@ -228,7 +240,7 @@ function pushVehicle(entity)
if entity ~= 0 and DoesEntityExist(entity) then if entity ~= 0 and DoesEntityExist(entity) then
-- Request network control if not already controlled. -- Request network control if not already controlled.
if not NetworkHasControlOfEntity(entity) then if not NetworkHasControlOfEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.") debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Requesting network control of vehicle^7.")
NetworkRequestControlOfEntity(entity) NetworkRequestControlOfEntity(entity)
local timeout = 2000 local timeout = 2000
while timeout > 0 and not NetworkHasControlOfEntity(entity) do while timeout > 0 and not NetworkHasControlOfEntity(entity) do
@@ -236,13 +248,13 @@ function pushVehicle(entity)
timeout = timeout - 100 timeout = timeout - 100
end end
if NetworkHasControlOfEntity(entity) then if NetworkHasControlOfEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Network now has control of the entity^7.") debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Network now has control of the entity^7.")
end end
end end
-- Set as mission entity if not already set. -- Set as mission entity if not already set.
if not IsEntityAMissionEntity(entity) then if not IsEntityAMissionEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.") debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.")
SetEntityAsMissionEntity(entity, true, true) SetEntityAsMissionEntity(entity, true, true)
local timeout = 2000 local timeout = 2000
while timeout > 0 and not IsEntityAMissionEntity(entity) do while timeout > 0 and not IsEntityAMissionEntity(entity) do
@@ -250,18 +262,23 @@ function pushVehicle(entity)
timeout = timeout - 100 timeout = timeout - 100
end end
if IsEntityAMissionEntity(entity) then if IsEntityAMissionEntity(entity) then
debugPrint("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.")
end end
end end
end end
end end
-- add entitty named version
function pushEnt(...) pushVehicle(...) end
--- Finds the closest vehicle to the specified coordinates. --- Finds the closest vehicle to the specified coordinates.
--- The function uses different APIs based on whether a source is provided. --- 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 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. --- @param src boolean (Optional) If true, uses GetPlayerPed(source) and GetAllVehicles.
--- @return number|number closestVehicle|closestDistance The closest vehicle entity and its distance. --- @return number closestVehicle The closest vehicle entity and its distance.
--- @return number closestDistance The distance of the closest vehicle.
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
@@ -271,9 +288,11 @@ function getClosestVehicle(coords, src)
local ped, vehicles, closestDistance, closestVehicle local ped, vehicles, closestDistance, closestVehicle
if src then if src then
-- if checking server side cache src's ped and use server native
ped = GetPlayerPed(src) ped = GetPlayerPed(src)
vehicles = GetAllVehicles() vehicles = GetAllVehicles()
else else
-- if checking client side cache local ped and use client native
ped = PlayerPedId() ped = PlayerPedId()
vehicles = GetGamePool('CVehicle') vehicles = GetGamePool('CVehicle')
end end
@@ -290,7 +309,7 @@ function getClosestVehicle(coords, src)
for i = 1, #vehicles, 1 do for i = 1, #vehicles, 1 do
local vehicleCoords = GetEntityCoords(vehicles[i]) local vehicleCoords = GetEntityCoords(vehicles[i])
local distance = #(vehicleCoords - coords) local distance = #(vehicleCoords - coords.xyz)
if closestDistance == -1 or distance < closestDistance then if closestDistance == -1 or distance < closestDistance then
closestDistance = distance closestDistance = distance
@@ -300,3 +319,61 @@ function getClosestVehicle(coords, src)
return closestVehicle, closestDistance return closestVehicle, closestDistance
end end
--- Checks whether a vehicle is owned or not using the plate as reference
---
--- @param plate string The plate of the vehicle to check
--- @return boolean whether vehicle is owned
---
--- @usage
--- ```lua
--- local plate = "ABCD1234"
--- local isVehicleOwned = isVehicleOwned(plate)
--- ```
local vehiclesOwned = {}
function isVehicleOwned(plate)
-- If already checked, cache it to reduce database calls
if vehiclesOwned[plate] == true then
return true
else
-- Find frameworks vehicle table and search sql for if vehicle plate is owned
local sqlTable = "player_vehicles"
local vehDatabase = {
{ framework = ESXExport,
sqlTable = "owned_vehicles"
},
{ framework = QBExport,
sqlTable = "player_vehicles"
},
{ framework = QBXExport,
sqlTable = "player_vehicles"
},
{ framework = OXCoreExport,
sqlTable = "owned_vehicles"
},
}
for i = 1, #vehDatabase do
local framework = vehDatabase[i]
if isStarted(framework.framework) then
sqlTable = framework.sqlTable
break
end
end
local result = MySQL.query.await("SELECT 1 from "..sqlTable.." WHERE plate = ?", { plate })
if json.encode(result) ~= "[]" then
vehiclesOwned[plate] = true -- Cache ownership for later checks
return true
else
return false
end
end
end

View File

@@ -15,30 +15,62 @@
--- registerCommand("greet", { --- registerCommand("greet", {
--- "Greets the player", --- "Greets the player",
--- { name = "name", help = "Name of the player to greet" }, --- { name = "name", help = "Name of the player to greet" },
--- function(source, args) print("Hello, "..args[1].."!") end,
--- nil, --- nil,
--- function(source, args) print("Hello, "..args[1].."!") end,
--- "admin" --- "admin"
--- }) --- })
--- ``` --- ```
function registerCommand(command, options) function registerCommand(command, options)
-- Name the options
local optionTable = {
helpInfo = options[1] or nil, -- help info that pops up in chat box
subText = options[2] or nil, -- sub help info for each arg
argsRequired = options[3] or nil, -- command only works if you enter args
funct = options[4] or nil, -- function that will run when command is triggered
restriction = options[5] or nil -- admin group that can use the group or `nil`
}
local commandResource = "" local commandResource = ""
if isStarted(OXLibExport) then if isStarted(OXLibExport) then
commandResource = OXLibExport commandResource = OXLibExport
lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4]) lib.addCommand(command,
{
help = optionTable.helpInfo,
params = optionTable.subText,
restricted = optionTable.restriction and "group."..optionTable.restriction or nil
},
optionTable.funct)
elseif isStarted(QBExport) and not isStarted(QBXExport) then elseif isStarted(QBExport) and not isStarted(QBXExport) then
commandResource = QBExport commandResource = QBExport
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil) Core.Commands.Add(command,
optionTable.helpInfo,
optionTable.subText,
optionTable.argsRequired,
optionTable.funt,
optionTable.restriction or nil
)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
commandResource = RSGExport commandResource = RSGExport
Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil) Core.Commands.Add(command,
optionTable.helpInfo,
optionTable.subText,
optionTable.argsRequired,
optionTable.funt,
optionTable.restriction or nil
)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
commandResource = ESXExport commandResource = ESXExport
ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError) ESX.RegisterCommand(command,
options[4](xPlayer.source, args, showError) optionTable.restriction or 'admin',
end, false, { help = options[1] }) function(xPlayer, args, showError)
optionTable.funct(xPlayer.source, args, showError)
end,
false,
{ help = options[1] }
)
end end
if commandResource ~= "" then if commandResource ~= "" then

View File

@@ -9,7 +9,6 @@ Exports = {
OXInv = "ox_inventory", OXInv = "ox_inventory",
QBInv = "qb-inventory", QBInv = "qb-inventory",
PSInv = "ps-inventory", PSInv = "ps-inventory",
QSInv = "qs-inventory",
CoreInv = "core_inventory", CoreInv = "core_inventory",
CodeMInv = "codem-inventory", CodeMInv = "codem-inventory",
OrigenInv = "origen_inventory", OrigenInv = "origen_inventory",
@@ -25,7 +24,12 @@ Exports = {
-- REDM -- REDM
RSGExport = "rsg-core", RSGExport = "rsg-core",
RSGInv = "rsg-inventory" RSGInv = "rsg-inventory",
VorpExport = "vorp_core",
VorpInv = "vorp_inventory",
VorpMenu = "vorp_menu",
} }
-- Required variables -- Required variables
@@ -149,7 +153,6 @@ for _, v in pairs({ -- This is a specific load order
'wrapperfunctions.lua', 'wrapperfunctions.lua',
'polyZone.lua', 'polyZone.lua',
'inventories.lua',
'itemcontrol.lua', 'itemcontrol.lua',
'playerfunctions.lua', 'playerfunctions.lua',
'metaHandlers.lua', 'metaHandlers.lua',
@@ -167,8 +170,6 @@ for _, v in pairs({ -- This is a specific load order
-- Crafting / Shops / Stashes -- Crafting / Shops / Stashes
'crafting.lua', 'crafting.lua',
'shops.lua',
'stashcontrol.lua',
-- Kind of "other" -- Kind of "other"
'isAnimal.lua', 'isAnimal.lua',

View File

@@ -22,7 +22,7 @@ end
local function stopAnim(animDict, animName, ped) local function stopAnim(animDict, animName, ped)
StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5) StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5) StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
unloadAnimDict(animDict) RemoveAnimDict(animDict)
end end
function redProgressBar(data) function redProgressBar(data)
@@ -53,27 +53,11 @@ function redProgressBar(data)
Wait(0) Wait(0)
local elapsed = GetGameTimer() local elapsed = GetGameTimer()
local percentage = ((elapsed - (endTime - wait)) / wait) * 100 local percentage = ((elapsed - (endTime - wait)) / wait) * 100
if percentage < 0 then percentage = 0 end
if percentage > 100 then percentage = 100 end
-- Convert to segmented progress (assuming 5 segments here)
local segments = 1 -- Number of segments in the bar
local segmentProgress = {}
local progressPerSegment = 100 / segments
for i = 1, segments do
local segmentStart = (i - 1) * progressPerSegment
local segmentEnd = i * progressPerSegment
if percentage >= segmentEnd then
segmentProgress[i] = 100
elseif percentage <= segmentStart then
segmentProgress[i] = 0
else
segmentProgress[i] = ((percentage - segmentStart) / progressPerSegment) * 100
end
end
percentage = percentage >= 100 and 100 or percentage
-- Draw your segmented progress bar -- Draw your segmented progress bar
ShowRedProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage)) ShowRedProgressBar(percentage, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress -- Controls to disable during progress
if data.disableMouse then if data.disableMouse then
@@ -151,40 +135,48 @@ function redProgressBar(data)
return result return result
end end
function ShowRedProgressBar(currentProg, title, level) function ShowRedProgressBar(percentage, title, level)
local loc = vec2(0.40, 0.90) local loc = vec2(0.40, 0.90)
local size = vec2(0.3, 0.03) local size = vec2(0.3, 0.03)
local tickCount = 10 -- visually split into 10 segments (9 inner lines)
local barHeight = size.y / 3.4
local barWidth = 0.21 -- tuned to your existing layout
local barLeft = (loc.x - size.x / 4) + 0.075
local barCenter = barLeft + barWidth / 2
local lineW = 0.001
local lineH = barHeight + 0.00
-- Draw background box -- Background plate
DrawSprite("generic_textures", "inkroller_1a", loc.x+0.1, loc.y-0.01, 0.25, 0.07, 180.0, 0, 0, 0, 200) DrawSprite("generic_textures", "inkroller_1a", loc.x + 0.1, loc.y - 0.01, 0.25, 0.07, 180.0, 0, 0, 0, 200)
-- Title (left)
SetTextFontForCurrentCommand(6) SetTextFontForCurrentCommand(6)
SetTextScale(0.35, 0.35) SetTextScale(0.35, 0.35)
SetTextColor(255, 255, 255, 255) SetTextColor(255, 255, 255, 255)
SetTextDropshadow(1, 0, 0, 0, 200) SetTextDropshadow(1, 0, 0, 0, 200)
BgDisplayText(title, loc.x - size.x / 4 + 0.074, loc.y - 0.034) BgDisplayText(title, loc.x - size.x / 4 + 0.074, loc.y - 0.034)
-- Percentage (right)
SetTextFontForCurrentCommand(1) SetTextFontForCurrentCommand(1)
SetTextScale(0.35, 0.35) SetTextScale(0.35, 0.35)
SetTextColor(255, 255, 255, 255) SetTextColor(255, 255, 255, 255)
SetTextDropshadow(1, 0, 0, 0, 200) SetTextDropshadow(1, 0, 0, 0, 200)
BgDisplayText(level, loc.x - size.x / 4 + 0.246, loc.y - 0.030) BgDisplayText(level, loc.x - size.x / 4 + 0.246, loc.y - 0.030)
local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each) -- Track (bg)
local gap = segmentWidth / #currentProg -- Smaller gap between segments DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
for i = 1, #currentProg do -- Fill
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap) local fillWidth = barWidth * (percentage / 100.0)
local fillPercentage = currentProg[i] if fillWidth > 0.0 then
local progressBarWidth = segmentWidth * (fillPercentage / 100) local fillCenter = barLeft + (fillWidth / 2.0)
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 255, 0, 0, 200) -- red fill
end
-- Semi-transparent background for each segment -- Tick lines (9 inner lines for 10 segments)
DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255) for i = 1, (tickCount - 1) do
local x = barLeft + (barWidth * (i / tickCount))
-- Filling progress for each segment DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 255, 0, 0, 200) -- Blue progress
end
end end
end end
@@ -213,29 +205,14 @@ function gtaProgressBar(data)
local elapsed = GetGameTimer() local elapsed = GetGameTimer()
local percentage = ((elapsed - (endTime - wait)) / wait) * 100 local percentage = ((elapsed - (endTime - wait)) / wait) * 100
-- Convert to segmented progress (assuming 5 segments here) if percentage < 0 then percentage = 0 end
local segments = 1 -- Number of segments in the bar if percentage > 100 then percentage = 100 end
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 -- Draw your segmented progress bar
ShowGTAProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage)) ShowGTAProgressBar(percentage, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress -- Controls to disable during progress
DisablePlayerFiring(ped, true) DisablePlayerFiring(PlayerId(), true)
DisableControlAction(0, 25, true) -- Disable aim DisableControlAction(0, 25, true) -- Disable aim
DisableControlAction(0, 21, true) -- Disable sprint DisableControlAction(0, 21, true) -- Disable sprint
DisableControlAction(0, 30, true) -- Disable move left/right DisableControlAction(0, 30, true) -- Disable move left/right
@@ -246,13 +223,15 @@ function gtaProgressBar(data)
inProgress = false inProgress = false
end end
end end
result = inProgress
end) end)
-- Wait for completion or cancel
while GetGameTimer() < endTime and inProgress do
Wait(100)
end
-- Wait for completion or cancel
while result == nil do Wait(10) end
inProgress = false
print(tostring(result))
-- Cleanup animations/tasks -- Cleanup animations/tasks
if data.dict then if data.dict then
stopAnim(data.dict, data.anim, ped) stopAnim(data.dict, data.anim, ped)
@@ -261,10 +240,6 @@ function gtaProgressBar(data)
ClearPedTasks(ped) ClearPedTasks(ped)
end end
result = inProgress
inProgress = false
while result == nil do Wait(10) end
-- Cleanup -- Cleanup
FreezeEntityPosition(ped, false) FreezeEntityPosition(ped, false)
@@ -276,14 +251,22 @@ function gtaProgressBar(data)
return result return result
end end
function ShowGTAProgressBar(currentProg, title, level) function ShowGTAProgressBar(percentage, title, level)
local loc = vec2(0.37, 0.90) local loc = vec2(0.37, 0.90)
local size = vec2(0.3, 0.03) local size = vec2(0.3, 0.03)
local tickCount = 10
local barHeight = size.y / 3.4
local barWidth = 0.19
local barLeft = (loc.x - size.x / 4) + 0.075
local barCenter = barLeft + barWidth / 2
local lineW = 0.001
local lineH = barHeight + 0.00
-- Draw background box -- Background plates
DrawSprite("timerbars", "all_black_bg", loc.x +0.028, loc.y-0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255) DrawSprite("timerbars", "all_black_bg", loc.x + 0.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) DrawSprite("timerbars", "all_black_bg", loc.x + 0.170, loc.y - 0.01, 0.15, 0.07, 180.0, 255, 255, 255, 255)
-- Title (left)
SetTextFont(0) SetTextFont(0)
SetTextProportional(1) SetTextProportional(1)
SetTextScale(0.35, 0.35) SetTextScale(0.35, 0.35)
@@ -293,34 +276,35 @@ function ShowGTAProgressBar(currentProg, title, level)
SetTextOutline() SetTextOutline()
SetTextEntry("STRING") SetTextEntry("STRING")
AddTextComponentString(title) AddTextComponentString(title)
DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034) -- Adjust text position DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034)
-- Percentage (right)
SetTextFont(0) SetTextFont(0)
SetTextProportional(1) SetTextProportional(1)
SetTextScale(0.35, 0.25) SetTextScale(0.35, 0.25)
SetTextColour(255, 255, 255, 255) SetTextColour(255, 255, 255, 255)
SetTextEntry("STRING") SetTextEntry("STRING")
AddTextComponentString(level) AddTextComponentString(level)
DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030) -- Right-aligned additional text DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030)
local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each) -- Track (bg)
local gap = segmentWidth / #currentProg -- Smaller gap between segments DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
for i = 1, #currentProg do -- Fill
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap) local fillWidth = barWidth * (percentage / 100.0)
local fillPercentage = currentProg[i] if fillWidth > 0.0 then
local progressBarWidth = segmentWidth * (fillPercentage / 100) local fillCenter = barLeft + (fillWidth / 2.0)
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 93, 182, 229, 255) -- GTA blue
end
-- Semi-transparent background for each segment -- Tick lines
DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255) for i = 1, (tickCount - 1) do
local x = barLeft + (barWidth * (i / tickCount))
-- Filling progress for each segment DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 93, 182, 229, 255) -- Blue progress
end
end end
end end
function stopProgressBar() inProgress = false end function stopProgressBar() inProgress = false end
function isProgressBar() return inProgress end function isProgressBar() return inProgress end

View File

@@ -53,7 +53,7 @@ function gtaSkillCheck()
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, 228, 52, 52, 255) 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) DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
drawSuccessText(x, y, "Failed", 228, 52, 52) drawGTASuccessText(x, y, "Failed", 228, 52, 52)
end end
return false return false
end end

View File

@@ -1,5 +1,4 @@
-- Global Key Table, defined once. -- Global Key Table, defined once.
---
local KEY_TABLE = { 38, 29, 47, 23, 45, 159, 162, 163 } local KEY_TABLE = { 38, 29, 47, 23, 45, 159, 162, 163 }
-- Mapping of key codes to human-readable key names. -- Mapping of key codes to human-readable key names.
@@ -17,122 +16,196 @@ local Keys = {
[20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N", [20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N",
[244] = "M", [82] = ",", [81] = "." [244] = "M", [82] = ",", [81] = "."
} }
-- Tables for storing created targets.
local TextTargets = {} -- For fallback DrawText3D targets.
local targetEntities = {} -- For entity targets.
function createEntityTarget(entity, opts, dist)
startTargetLoop()
targetEntities[#targetEntities + 1] = entity
local entityCoords = GetEntityCoords(entity)
local existingTarget = nil -- ===== Ownership + indexes =====
for _, target in pairs(TextTargets) do -- TextTargets: key -> target data (coords/entity/models/options/etc.)
if #(target.coords - entityCoords) < 0.01 then local TextTargets = {}
existingTarget = target -- targetEntities kept for parity (not strictly required)
break local targetEntities = {}
end
-- registry: key -> owner, owner -> set(keys)
local TargetRegistry = { byKey = {}, byResource = {} }
local function getOwnerResource()
return GetInvokingResource() or GetCurrentResourceName() or "unknown"
end
local function registerTarget(owner, key)
-- move key across owners if replacing
local prev = TargetRegistry.byKey[key]
if prev and TargetRegistry.byResource[prev] then
TargetRegistry.byResource[prev][key] = nil
end end
TargetRegistry.byKey[key] = owner
TargetRegistry.byResource[owner] = TargetRegistry.byResource[owner] or {}
TargetRegistry.byResource[owner][key] = true
end
if existingTarget then local function removeTargetKey(key, reason)
for i = 1, #opts do local owner = TargetRegistry.byKey[key]
local key = KEY_TABLE[#existingTarget.options + i] if TextTargets[key] then
opts[i].key = key TextTargets[key] = nil
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label -- print("^6Bridge^7:^5 Target^7: ^2Removed target '%s'%s", key, reason and (" ("..reason..")") or "")
existingTarget.options[#existingTarget.options + 1] = opts[i] end
if owner then
if TargetRegistry.byResource[owner] then
TargetRegistry.byResource[owner][key] = nil
end end
updateCachedText(existingTarget) TargetRegistry.byKey[key] = nil
else
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
TextTargets[entity] = {
coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z),
buttontext = tempText,
options = opts,
dist = dist,
text = table.concat(tempText, "\n")
}
end end
end end
function createZoneTarget(data, opts, dist) AddEventHandler("onResourceStop", function(res)
startTargetLoop() local owned = TargetRegistry.byResource[res]
local existingTarget = nil if not owned then return end
for _, target in pairs(TextTargets) do local cnt = 0
if #(target.coords - data[2]) < 0.01 then for key in pairs(owned) do
existingTarget = target removeTargetKey(key, "resource stopped: "..res)
break cnt = cnt + 1
end
end end
TargetRegistry.byResource[res] = nil
-- print("^6Bridge^7:^5 Target^7: ^2Cleared "..cnt.." target(s) from '"..res.."'")
end)
if existingTarget then -- ===== Helpers =====
for i = 1, #opts do local function vecKey(v)
local key = KEY_TABLE[#existingTarget.options + i] -- stable rounded coord string for entity dedupe when name isn't provided
opts[i].key = key return ("%.3f,%.3f,%.3f"):format(v.x, v.y, v.z)
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
existingTarget.options[#existingTarget.options + 1] = opts[i]
end
updateCachedText(existingTarget)
else
local tempText = {}
for i = 1, #opts do
opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end
TextTargets[data[1]] = {
coords = data[2],
buttontext = tempText,
options = opts,
dist = dist,
text = table.concat(tempText, "\n")
}
end
return data[1]
end end
function createModelTarget(models, opts, dist) local function bakeButtons(opts)
startTargetLoop()
if type(models) ~= "table" then
models = { models }
end
local tempText = {} local tempText = {}
for i = 1, #opts do for i = 1, #opts do
opts[i].key = KEY_TABLE[i] opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label tempText[#tempText + 1] =
(" ~b~[~w~%s~b~] ~w~%s"):format(Keys[opts[i].key] or ("K"..opts[i].key), opts[i].label or ("Option "..i))
end end
return tempText
end
local keyStr = "" -- Update cached text blob
for i, m in ipairs(models) do local function updateCachedText(target)
keyStr = keyStr .. tostring(m) .. (i < #models and "_" or "") target.text = table.concat(target.buttontext, "\n")
end end
local targetKey = "model_" .. keyStr
TextTargets[targetKey] = { -- ===== Public API: Create targets =====
models = models, -- ENTITY: createEntityTarget(entity, opts, dist, nameOpt?)
buttontext = tempText, function createEntityTarget(entity, opts, dist, name)
options = opts, startTargetLoop()
dist = dist,
coords = vec3(0, 0, 0), if not entity or entity == 0 then return end
text = table.concat(tempText, "\n") targetEntities[#targetEntities + 1] = entity
local owner = getOwnerResource()
local coords = GetEntityCoords(entity)
local key = tostring(name) or ("entity@" .. vecKey(coords))
-- Always overwrite on same key
local buttontext = bakeButtons(opts)
TextTargets[key] = {
_key = key,
_type = "entity",
_owner = owner,
entity = entity,
coords = vec3(coords.x, coords.y, coords.z),
buttontext = buttontext,
options = opts,
dist = dist,
} }
updateCachedText(TextTargets[key])
return targetKey registerTarget(owner, key)
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ENTITY target '"..key.."' by '"..owner.."' @ "..formatCoord(coords))
return key
end end
function removeEntityTarget(entity) -- ZONE: createZoneTarget(data, opts, dist)
TextTargets[entity] = nil -- Expect data[1] = id/name, data[2] = vec3 coords (as in your original)
function createZoneTarget(data, opts, dist)
startTargetLoop()
local owner = getOwnerResource()
local zname = tostring(data[1] or ("zone@"..vecKey(data[2] or vec3(0,0,0))))
local coords = data[2]
local buttontext = bakeButtons(opts)
TextTargets[zname] = {
_key = zname,
_type = "zone",
_owner = owner,
coords = coords,
buttontext = buttontext,
options = opts,
dist = dist,
}
updateCachedText(TextTargets[zname])
registerTarget(owner, zname)
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ZONE target '"..zname.."' by '"..owner.."' @ "..formatCoord(coords))
return zname
end end
function removeZoneTarget(target) -- MODEL: createModelTarget(models, opts, dist, nameOpt?)
TextTargets[target] = nil function createModelTarget(models, opts, dist, name)
startTargetLoop()
local owner = getOwnerResource()
if type(models) ~= "table" then models = { models } end
local key
if name then
key = tostring(name)
else
local parts = {}
for i, m in ipairs(models) do parts[i] = tostring(m) end
key = "model_" .. table.concat(parts, "_")
end
local buttontext = bakeButtons(opts)
TextTargets[key] = {
_key = key,
_type = "model",
_owner = owner,
models = models,
buttontext = buttontext,
options = opts,
dist = dist,
coords = vec3(0, 0, 0), -- will be updated by the refresher
}
updateCachedText(TextTargets[key])
registerTarget(owner, key)
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated MODEL target '"..key.."' by '"..owner.."' (models: "..table.concat(models, ",")..")")
return key
end end
function removeModelTarget(model) -- ===== Public API: Remove targets =====
TextTargets[model] = nil -- Entity removal accepts entity handle or key string.
function removeEntityTarget(entityOrKey)
local key = nil
if type(entityOrKey) == "string" then
key = entityOrKey
elseif type(entityOrKey) == "number" then
for k, t in pairs(TextTargets) do
if t._type == "entity" and t.entity == entityOrKey then key = k break end
end
if not key then
-- Fallback: try coord-key match
local c = GetEntityCoords(entityOrKey)
local guess = "entity@"..vecKey(c)
if TextTargets[guess] then key = guess end
end
end
if key then removeTargetKey(key, "removeEntityTarget") end
end
function removeZoneTarget(key)
if not key then return end
removeTargetKey(key, "removeZoneTarget")
end
-- For models, pass the returned key from createModelTarget (recommended).
function removeModelTarget(key)
if not key then return end
removeTargetKey(key, "removeModelTarget")
end end
exports("createEntityTarget", createEntityTarget) exports("createEntityTarget", createEntityTarget)
@@ -143,22 +216,20 @@ exports("removeEntityTarget", removeEntityTarget)
exports("removeZoneTarget", removeZoneTarget) exports("removeZoneTarget", removeZoneTarget)
exports("removeModelTarget", removeModelTarget) exports("removeModelTarget", removeModelTarget)
------------------------------------------------------------- -------------------------------------------------------------
-- Fallback: DrawText3D Targets (Experimental) -- Fallback: DrawText3D Targets (Experimental)
------------------------------------------------------------- -------------------------------------------------------------
local started = false local started = false
function startTargetLoop() function startTargetLoop()
if started then return end if started then return end
Config = {
System = {
}
}
started = true started = true
-- lazy include (unchanged from your original)
Config = { System = {} }
local fileLoader = assert(load(LoadResourceFile("jim_bridge", ('starter.lua')), ('@@jim_bridge/starter.lua'))) local fileLoader = assert(load(LoadResourceFile("jim_bridge", ('starter.lua')), ('@@jim_bridge/starter.lua')))
fileLoader() fileLoader()
-- Model Entity Refresher
-- Model Entity Refresher (kept)
CreateThread(function() CreateThread(function()
while true do while true do
local pedCoords = GetEntityCoords(PlayerPedId()) local pedCoords = GetEntityCoords(PlayerPedId())
@@ -178,7 +249,7 @@ function startTargetLoop()
end end
end) end)
-- Main Target Loop -- Main Target Loop (unchanged logic, just uses new TextTargets entries)
CreateThread(function() CreateThread(function()
while true do while true do
local ped = PlayerPedId() local ped = PlayerPedId()
@@ -215,10 +286,10 @@ function startTargetLoop()
for i, opt in ipairs(target.options) do for i, opt in ipairs(target.options) do
if IsControlJustPressed(0, opt.key) and isClosest then if IsControlJustPressed(0, opt.key) and isClosest then
if (not target.canInteract or target.canInteract()) and if (not target.canInteract or target.canInteract()) and
(not opt.item or hasItem(opt.item)) and (not opt.item or hasItem(opt.item)) and
(not opt.job or hasJob(opt.job, nil)) then (not opt.job or hasJob(opt.job, nil)) then
if opt.onSelect then opt.onSelect(targetEntity) end if opt.onSelect then opt.onSelect(targetEntity) end
if opt.action then opt.action(targetEntity) end if opt.action then opt.action(targetEntity) end
end end
end end
end end
@@ -228,8 +299,8 @@ function startTargetLoop()
for i, opt in ipairs(target.options) do for i, opt in ipairs(target.options) do
if (not target.canInteract or target.canInteract()) and if (not target.canInteract or target.canInteract()) and
(not opt.item or hasItem(opt.item)) and (not opt.item or hasItem(opt.item)) and
(not opt.job or hasJob(opt.job, nil)) then (not opt.job or hasJob(opt.job, nil)) then
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + lineHeight * lineOffset), target.buttontext[i], isClosest) DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + lineHeight * lineOffset), target.buttontext[i], isClosest)
lineOffset = lineOffset + 1 lineOffset = lineOffset + 1
end end
@@ -238,12 +309,11 @@ function startTargetLoop()
::continue:: ::continue::
end end
Wait(1) -- Throttled Wait(1)
end end
end) end)
end end
function DrawText3D(coord, text, highlight) function DrawText3D(coord, text, highlight)
SetTextScale(0.30, 0.30) SetTextScale(0.30, 0.30)
SetTextFont(0) SetTextFont(0)
@@ -253,37 +323,28 @@ function DrawText3D(coord, text, highlight)
SetTextCentre(true) SetTextCentre(true)
local totalLength = string.len(text) local totalLength = string.len(text)
local textMaxLength = 99 -- max 99 local textMaxLength = 99
local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text local txt = totalLength > textMaxLength and text:sub(1, textMaxLength) or text
AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text) AddTextComponentString(highlight and txt:gsub("%~w~", "~y~") or txt)
SetDrawOrigin(coord.x, coord.y, coord.z, 0) SetDrawOrigin(coord.x, coord.y, coord.z, 0)
DrawText(0.0, 0.0) DrawText(0.0, 0.0)
local count, length = GetLineCountAndMaxLength(text) local count, length = GetLineCountAndMaxLength(txt)
local padding = 0.005 local padding = 0.005
local heightFactor = (count / 43) + padding local heightFactor = (count / 43) + padding
local weightFactor = (length / 150) + padding local weightFactor = (length / 150) + padding
local height = (heightFactor / 2) - padding / 1 local height = (heightFactor / 2) - padding / 1
local width = (weightFactor / 2) - padding / 1 local width = (weightFactor / 2) - padding / 1
DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150) DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
ClearDrawOrigin() ClearDrawOrigin()
end 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) function GetLineCountAndMaxLength(text)
local lineCount, maxLength = 0, 0 local lineCount, maxLength = 0, 0
for line in text:gmatch("[^\n]+") do for line in text:gmatch("[^\n]+") do
lineCount += 1 lineCount = lineCount + 1
local lineLength = string.len(line) local lineLength = string.len(line)
if lineLength > maxLength then if lineLength > maxLength then
maxLength = lineLength maxLength = lineLength
@@ -293,7 +354,6 @@ function GetLineCountAndMaxLength(text)
return lineCount, maxLength return lineCount, maxLength
end end
function RotationToDirection(rot) function RotationToDirection(rot)
local adjust = math.pi / 180 local adjust = math.pi / 180
return vec3( return vec3(
@@ -311,8 +371,3 @@ function normalizeVector(vec)
return vec3(0, 0, 0) return vec3(0, 0, 0)
end end
end end
-- Helper to update cached text.
function updateCachedText(target)
target.text = table.concat(target.buttontext, "\n")
end

View File

@@ -1,8 +1,16 @@
2.0.19 2.1.0
- SetMetadata function now handles full metadata, not just durability - Script Overhaul, replace whole script
- Completely rework framework caching, it was lagging behind and breaking - Very alpha support for VorpCore
- Add new hasFreeInvnentorySlots() function - Improve security
- Add inv config file grabber, checks inventory configs for inventory weight/slots - Removal of Quasar Script Support
- Support Tgiann-inv's new item system
- Refactor most multi-functions to be more optimized and smarter
- Added multifunction bossmenu and clothing functions
- Added ability to supress update notifcations in fxmanfiest.lua
- Added coordinate checks to registerStash, registerShop and sellShop
- Optimize and Enhance built-in progressbar + target functions
- Improve weird multicharacter issue triggering onPlayerLoaded more than once
- Scripts that use this will need updating too, as things like stashes won't open
https://github.com/jimathy/jim_bridge https://github.com/jimathy/jim_bridge/releases/latest