Compare commits

..

1 Commits

Author SHA1 Message Date
Jim Shield
d5af0334d5 Version Bump
`2.0.19` - `2.0.20`
2025-07-06 19:14:50 +01:00
49 changed files with 4711 additions and 7561 deletions

12
.gitattributes vendored
View File

@@ -1,12 +0,0 @@
# 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,20 +1,17 @@
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: Validate event JSON (sanity check) - name: Discord Commits
run: jq -e . "$GITHUB_EVENT_PATH" >/dev/null uses: Sniddl/discord-commits@v1.6
with:
- name: Send commit payload to Discord Bot webhook: ${{ secrets.DISCORD_WEBHOOK }}
env: template: "avatar-with-link"
URL: http://${{ secrets.DISCORDBOT }}:3000/github-commits include-extras: true
shell: bash
run: |
curl -sS --fail-with-body -X POST "$URL" \
-H 'Content-Type: application/json' \
--data-binary "@$GITHUB_EVENT_PATH"

View File

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

View File

@@ -1,88 +0,0 @@
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,13 +1,3 @@
local function readBoolMeta(key, default)
local val = GetResourceMetadata(GetCurrentResourceName(), key, 0)
if not val then return default end
val = tostring(val):lower()
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
end
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
if not SUPPRESS_UPDATES then
local function parseVersion(version) local function parseVersion(version)
local parts = {} local parts = {}
for num in version:gmatch("%d+") do for num in version:gmatch("%d+") do
@@ -62,11 +52,11 @@ if not SUPPRESS_UPDATES then
print((line:find("http") and "^7" or "^5")..line) print((line:find("http") and "^7" or "^5")..line)
end end
print("^1----------------------------------------------------------------------^7") print("^1----------------------------------------------------------------------^7")
SetTimeout(3600000, function() SetTimeout(1200000, function()
CheckBridgeVersion() CheckBridgeVersion()
end) end)
else else
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7) (^1Expect Errors^7)") print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
end end
end) end)
end) end)
@@ -75,4 +65,3 @@ if not SUPPRESS_UPDATES then
CheckBridgeVersion() CheckBridgeVersion()
end

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)
``` ```
- **setPlayerMetadata(player, key, value)** - **SetMetadata(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
setPlayerMetadata(player, "stress", 0) SetMetadata(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, etc). Provides wrapper compatibility functions for command and inventory stash systems across different frameworks (OX, QB, ESX, QS, 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, or Origen inventory systems. - Registers a stash using OX, QS, or Origen inventory systems.
- **Example:** - **Example:**
```lua ```lua
registerStash( registerStash(

View File

@@ -15,6 +15,7 @@ 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",
@@ -30,18 +31,17 @@ 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 = { Items = {}, Vehicles = {}, Jobs = {}, Gangs = {}, } local cache = {
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,60 +49,27 @@ 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).."s)" timers[label] = timers[label] / 1000
end end
startTimer("Cache") startTimer("Items") startTimer("Vehicles") startTimer("Jobs") startTimer("InvWeight") startTimer("InvSlots") startTimer("Cache")
-- 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)
local state = GetResourceState(resourceName) return GetResourceState(resourceName):find("start") or GetResourceState(resourceName):find("stopped")
return state and (state:find("start") or state:find("stopped"))
end end
local function waitStarted(resourceName) -- Ensure oxmysql resource is loaded
while GetResourceState(resourceName) ~= "started" do Wait(100) end
end
local function waitStartedOrStopped(resourceName)
local state = GetResourceState(resourceName)
while state ~= "started" and state ~= "stopped" do
Wait(100)
state = GetResourceState(resourceName)
end
end
local function dupLowercaseWeapons(items)
for k, v in pairs(items or {}) do
if type(k) == "string" then
if k:find("WEAPON") then
items[k:lower()] = v
end
else
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
print("^1Possible Issue found^7:")
print(json.encode(items[k], { indent = true }))
end
end
end
-- Forceload libs --
-- Ensure oxmysql resource is loaded for jim_bridge internally
-- For some core's it needs to get data from the database
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) or checkExists(Exports.VorpExport) then
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua'))) local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua')))
fileLoader() 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
waitStartedOrStopped(Exports.OXCoreExport) while GetResourceState(Exports.OXCoreExport) ~= "started" do Wait(100) end
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
waitStarted(Exports.ESXExport) while GetResourceState(Exports.ESXExport) ~= "started" do Wait(100) end
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
@@ -120,36 +87,46 @@ 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 itemFunc = {
{ script = Exports.OXInv,
cacheItem = function()
local success, result = pcall(function() local success, result = pcall(function()
return exports[Exports.OXInv]:Items() return exports[Exports.OXInv]:Items()
end) end)
if success and result then if success and result then
cache.Items = result cache.Items = result
end end
end,
}, -- Get Weapon info and duplicate them if they are uppercase
{ script = Exports.TgiannInv, -- (duplicate incase anything checks for the uppercase version)
cacheItem = function() for k, v in pairs(cache.Items) do
local success, result = pcall(function() if type(k) == "string" then
return exports[Exports.TgiannInv]:Items() if k:find("WEAPON") then
end) cache.Items[k:lower()] = cache.Items[k]
if success and result then
cache.Items = result
end end
end, else
}, print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
{ script = Exports.QBXExport, print("^1Possible Issue found^7:")
cacheItem = function() print(json.encode(Items[k], {indent = true}))
end
end
elseif checkExists(Exports.QBXExport) then
while GetResourceState(Exports.QBXExport) ~= "started" do Wait(100) end
itemResource = Exports.QBXExport
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
-- If this is nil, they need to update to qbx_core 1.23.0+ -- If this is nil, they need to update to qbx_core 1.23.0+
if not cache.Items then if not cache.Items then
-- if their inventory doesn't allow that (they refuse to update their butchered core replacement): -- 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()
if GetResourceState(Exports.OrigenInv):find("start") then elseif GetResourceState(Exports.OrigenInv):find("start") then
itemResource = Exports.OrigenInv itemResource = Exports.OrigenInv
cache.Items = exports[Exports.OrigenInv]:Items() cache.Items = exports[Exports.OrigenInv]:Items()
@@ -166,145 +143,91 @@ local itemFunc = {
cache.Items = exports[Exports.TgiannInv]:Items() cache.Items = exports[Exports.TgiannInv]:Items()
end end
end end
end,
}, elseif checkExists(Exports.QBExport) then
{ script = Exports.QBExport, while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end
cacheItem = function() itemResource = Exports.QBExport
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
end,
}, elseif checkExists(Exports.ESXExport) then
{ script = Exports.ESXExport, itemResource = Exports.ESXExport
cacheItem = function() if GetResourceState(Exports.QSInv):find("start") then
cache.Items = exports[Exports.QSInv]:GetItemList()
else
cache.Items = ESX.GetItems() cache.Items = ESX.GetItems()
while not next(cache.Items) do while not next(cache.Items) do
cache.Items = ESX.GetItems() cache.Items = ESX.GetItems()
Wait(1000) Wait(1000)
end 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 end
cache.Items = tempItems
end,
},
}
for i = 1, #itemFunc do elseif checkExists(Exports.RSGExport) then
local data = itemFunc[i] while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end
if checkExists(data.script) then itemResource = Exports.RSGExport
waitStarted(data.script) -- Wait for detected script to start fully cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
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")
local vehicleFunc = { -- Vehicle loading depending on framework
if checkExists(Exports.QBXExport) then
vehResource = Exports.QBXExport
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
{ script = Exports.QBXExport, elseif checkExists(Exports.QBExport)then
cacheVehicle = function() vehResource = Exports.QBExport
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
end,
}, elseif checkExists(Exports.OXCoreExport) then
{ script = Exports.QBExport, vehResource = Exports.OXCoreExport
cacheVehicle = function()
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.OXCoreExport,
cacheVehicle = function()
cache.Vehicles = {} cache.Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do for k, v in pairs(Ox.GetVehicleData()) do
cache.Vehicles[k] = { cache.Vehicles[k] = {
model = k, hash = joaat(k), model = k, hash = GetHashKey(k),
price = v.price, price = v.price,
name = v.name, name = v.name,
brand = v.make brand = v.make
} }
end end
end,
}, elseif checkExists(Exports.ESXExport) then
{ script = Exports.ESXExport, vehResource = Exports.ESXExport
cacheVehicle = function() while not MySQL do Wait(1000) end
while not MySQL do Wait(100) end
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
cache.Vehicles[v.model] = { cache.Vehicles[v.model] = {
model = v.model, model = v.model,
hash = joaat(v.model), hash = GetHashKey(v.model),
price = v.price, price = v.price,
name = v.name, name = v.name,
} }
end end
end,
},
{ script = Exports.RSGExport,
cacheVehicle = function()
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.VorpExport,
cacheVehicle = function()
cache.Vehicles = { ["unkown"] = {} }
end,
},
}
for i = 1, #vehicleFunc do elseif checkExists(Exports.RSGExport) then
local data = vehicleFunc[i] vehResource = Exports.RSGExport
if checkExists(data.script) then cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheVehicle() -- run tablized function for core
vehResource = data.script -- Grab script name to announce later
endTimer("Vehicles") -- end timer
break -- break loop so it doesn't keep checking
end
end end
endTimer("Vehicles")
--------------------- ---------------------
----- Load Jobs ----- ----- Load Jobs -----
--------------------- ---------------------
startTimer("Jobs")
local jobFunc = { -- Jobs loading based on framework
if checkExists(Exports.QBXExport) then
{ script = Exports.QBXExport, jobResource = Exports.QBXExport
cacheJob = function()
cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs() cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
end,
}, elseif checkExists(Exports.QBExport) then
{ script = Exports.QBExport, jobResource = Exports.QBExport
cacheJob = function() cache.Jobs, cache.Gangs = exports[Exports.QBExport]:GetCoreObject().Shared.Jobs, exports[Exports.QBExport]:GetCoreObject().Shared.Gangs
Core = exports[Exports.QBExport]:GetCoreObject()
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs elseif checkExists(Exports.OXCoreExport) then
end, jobResource = Exports.OXCoreExport
}, while not MySQL do Wait(1000) end
{ script = Exports.OXCoreExport,
cacheJob = function()
while not MySQL do Wait(100) end
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`') local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`') local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
local gradeMap = {} local gradeMap = {}
@@ -319,11 +242,10 @@ local jobFunc = {
} }
end end
cache.Gangs = cache.Jobs cache.Gangs = cache.Jobs
end,
}, elseif checkExists(Exports.ESXExport) then
{ script = Exports.ESXExport, jobResource = Exports.ESXExport
cacheJob = function() ESX = exports[Exports.ESXExport]:getSharedObject()
ESX = ESX or exports[Exports.ESXExport]:getSharedObject()
cache.Jobs = ESX.GetJobs() cache.Jobs = ESX.GetJobs()
while not next(cache.Jobs) do while not next(cache.Jobs) do
Wait(100) Wait(100)
@@ -354,33 +276,13 @@ local jobFunc = {
::continue:: ::continue::
end end
cache.Gangs = cache.Jobs 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 elseif checkExists(Exports.RSGExport) then
local data = jobFunc[i] jobResource = Exports.RSGExport
if checkExists(data.script) then cache.Jobs, cache.Gangs = exports[Exports.RSGExport]:GetCoreObject().Shared.Jobs, exports[Exports.RSGExport]:GetCoreObject().Shared.Gangs
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
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
@@ -406,6 +308,8 @@ 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)
@@ -419,17 +323,12 @@ 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, GetConvar = GetConvar, vector3 = vector3, Citizen = Citizen,
vector3 = vector3, GetResourceState = GetResourceState, exports = exports,
vector4 = vector4, DependencyCheck = DependencyCheck or function() return nil end,
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
@@ -460,6 +359,7 @@ 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" } },
@@ -472,8 +372,6 @@ 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
@@ -521,17 +419,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"]) print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventoryWeight^7: ^3"..cache.InventoryWeight.."^7 (^3"..(cache.InventoryWeight / 1000).."kg^7) ^7("..timers["InvWeight"].."s)")
end end
if cache.InventorySlots then if cache.InventorySlots then
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7"..timers["InvSlots"]) print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7("..timers["InvSlots"].."s)")
end end
print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7"..timers["Items"]) print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7("..timers["Items"].."s)")
print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7"..timers["Vehicles"]) print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7("..timers["Vehicles"].."s)")
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7"..timers["Jobs"]) print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7("..timers["Jobs"].."s)")
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7"..timers["Jobs"]) print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7("..timers["Jobs"].."s)")
endTimer("Cache") endTimer("Cache")
print("^6FrameworkCache^7: ^2Cache Ready ^7"..timers["Cache"]) print("^6FrameworkCache^7: ^2Cache Ready ^7("..timers["Cache"].."s)")
cacheReady = true cacheReady = true
end) end)

View File

@@ -1,12 +1,13 @@
name "Jim_Bridge" name "Jim_Bridge"
author "Jimathy" author "Jimathy"
version "2.1.06" version "2.0.20"
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',
@@ -24,5 +25,3 @@ 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,28 +1,18 @@
------------------------------------------------------------- -------------------------------------------------------------
-- 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
local excludeRes = {
[Exports.QBExport] = true,
[Exports.ESXExport] = true,
[Exports.VorpExport] = true,
}
local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen() local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen()
validTokens = validTokens or {} validTokens = validTokens or {}
createCallback(AuthEvent, function(source) createCallback(AuthEvent, function(source)
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
local invokingRes = GetInvokingResource() --debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint(invokingRes)
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7") debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return "" return ""
end end
@@ -55,22 +45,21 @@ if isServer() then
createCallback(getScript()..":callback:GetAuthEvent", function(source) createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source local src = source
local invokingRes = GetInvokingResource()
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7") 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 AuthEvent return ""
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(60000, function() -- 1 minute cooldown SetTimeout(5000, function() -- 5 second cooldown
authCooldown[src] = nil authCooldown[src] = nil
end) end)
@@ -91,8 +80,6 @@ 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
@@ -129,29 +116,3 @@ 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].xy - srcCoords.xy) <= 10 then
return true
else
allow = false
end
end
if not allow then
print(src and ("^1Src ^3"..src.." ") or "", "^1Tried to open a registered shop/stash from over the distance limit^7")
return false
end
end

View File

@@ -7,152 +7,6 @@
• 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
local frameworkLoadFunc = {
{ framework = Exports.QBXExport,
onPlayerLoaded =
function(func)
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', func)
end,
onPlayerUnload =
function(func)
RegisterNetEvent('QBCore:Client:OnPlayerUnload', func)
end,
waitforLogin =
function(timeout)
local startTime = GetGameTimer()
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
return LocalPlayer.state.isLoggedIn
end,
},
{ framework = Exports.QBExport,
onPlayerLoaded =
function(func)
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', func)
end,
onPlayerUnload =
function(func)
RegisterNetEvent('QBCore:Client:OnPlayerUnload', func)
end,
waitforLogin =
function(timeout)
local startTime = GetGameTimer()
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
return LocalPlayer.state.isLoggedIn
end,
},
{ framework = Exports.ESXExport,
onPlayerLoaded =
function(func)
RegisterNetEvent("esx:playerLoaded", function()
-- make sure shared has loaded (because sql)
if waitForSharedLoad() then func() end
end)
end,
onPlayerUnload =
function(func)
RegisterNetEvent("esx:onPlayerLogout", func)
end,
waitforLogin =
function(timeout)
local startTime = GetGameTimer()
while (GetGameTimer() - startTime) < timeout do
local playerData = ESX.GetPlayerData()
if playerData and playerData.job then
return true
end
Wait(100)
end
end,
},
{ framework = Exports.OXCoreExport,
onPlayerLoaded =
function(func)
RegisterNetEvent('ox:playerLoaded', func)
end,
onPlayerUnload =
function(func)
RegisterNetEvent('ox:playerLogout', func)
end,
waitforLogin =
function(timeout)
if OxPlayer["stateId"] then
return true
end
while not OxPlayer["stateId"] do
Wait(1000)
if OxPlayer.get["stateId"] then
return true
end
end
end,
},
{ framework = Exports.RSGExport,
onPlayerLoaded =
function(func)
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', func)
end,
onPlayerUnload =
function(func)
RegisterNetEvent('RSGCore:Client:OnPlayerUnload', func)
end,
waitforLogin =
function(timeout)
local startTime = GetGameTimer()
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
return LocalPlayer.state.isLoggedIn
end,
},
{ framework = Exports.VorpExport,
onPlayerLoaded =
function(func)
RegisterNetEvent('vorp_core:Client:OnPlayerSpawned', func)
end,
onPlayerUnload =
function(func)
--??
end,
waitforLogin =
function(timeout)
local startTime = GetGameTimer()
while not LocalPlayer.state.IsInSession and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
return LocalPlayer.state.IsInSession
end,
},
}
------------------------------------------------------------- -------------------------------------------------------------
-- Player Loaded and Unloaded Events -- Player Loaded and Unloaded Events
------------------------------------------------------------- -------------------------------------------------------------
@@ -170,28 +24,52 @@ local frameworkLoadFunc = {
--- end, true) --- end, true)
--- ``` --- ```
function onPlayerLoaded(func, onStart) function onPlayerLoaded(func, onStart)
local onPlayerFramework = ""
local loaded = false
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()")
_debouncedRun(func) loaded = true
debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 executed through ^3onPlayerLoaded^7()")
Wait(2000)
func()
end, true) end, true)
end end
local handler = function() if not loaded then
_debouncedRun(func) local tempFunc = function()
Wait(2000)
debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded")
func()
end end
for i = 1, #frameworkLoadFunc do if isStarted(QBExport) or isStarted(QBXExport) then
local data = frameworkLoadFunc[i] onPlayerFramework = QBExport
jsonPrint(data) RegisterNetEvent('QBCore:Client:OnPlayerLoaded', tempFunc)
if isStarted(data.framework) then elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^3"..data.framework.." ^5onPlayerLoaded^7()") onPlayerFramework = ESXExport
data.onPlayerLoaded(handler) RegisterNetEvent('esx:playerLoaded', function()
return if waitForSharedLoad() then
tempFunc()
end
end
)
elseif isStarted(OXCoreExport) then
onPlayerFramework = OXCoreExport
RegisterNetEvent('ox:playerLoaded', tempFunc)
elseif isStarted(RSGExport) then
onPlayerFramework = RSGExport
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
end
if onPlayerFramework ~= "" then
debugPrint("^6Bridge^7: ^2Registering ^3onPlayerLoaded^7()^2 with ^3" .. onPlayerFramework.."^7")
else
print("^4ERROR^7: No supported core detected for onPlayerLoaded - Check starter.lua")
end end
end end
print("^1ERROR^7: ^1No supported core detected for onPlayerLoaded - Check starter.lua^7")
end end
--- Executes a function when the player character is unloaded. --- Executes a function when the player character is unloaded.
@@ -204,15 +82,15 @@ end
--- end) --- end)
--- ``` --- ```
function onPlayerUnload(func) function onPlayerUnload(func)
for i = 1, #frameworkLoadFunc do debugPrint("^6Bridge^7: ^2Registering ^3onPlayerUnload^7()")
local data = frameworkLoadFunc[i] RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() func() end)
if isStarted(data.framework) then
debugPrint("^6Bridge^7: ^2Registering ^3"..data.framework.." ^5onPlayerUnload^7()") RegisterNetEvent('ox:playerLogout', function() func() end)
data.onPlayerUnload(func)
return RegisterNetEvent('RSGCore:Client:OnPlayerUnload', function() func() end)
end
end RegisterNetEvent('esx:onPlayerLogout', function() func() end)
print("^1ERROR^7: ^1No supported core detected for onPlayerUnload - Check starter.lua^7")
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -239,7 +117,8 @@ function onResourceStart(func, thisScript)
debugPrint("^6Bridge^7: ^2Shared Load Detected^7.") debugPrint("^6Bridge^7: ^2Shared Load Detected^7.")
hasPrinted = true hasPrinted = true
end end
func(resourceName) if isStarted(ESXExport) then Wait(10000) end
func()
end end
end end
end) end)
@@ -258,7 +137,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(resourceName) func()
end end
end) end)
end end
@@ -271,30 +150,58 @@ end
--- @usage --- @usage
--- waitForLogin() --- waitForLogin()
function waitForLogin() function waitForLogin()
for i = 1, #frameworkLoadFunc do local timeout = 10000 -- 10 seconds in milliseconds
local data = frameworkLoadFunc[i] local startTime = GetGameTimer()
if isStarted(data.framework) then local loggedIn = false
debugPrint("^6Bridge^7: ^2Waiting for ^3"..data.framework.."^2 player login^7.")
local result = data.waitforLogin(10000) if isStarted(ESXExport) then
if result == true then while (GetGameTimer() - startTime) < timeout do
debugPrint("^6Bridge^7: ^3"..data.framework.."^2 Player Login Detected^7.") local playerData = ESX.GetPlayerData()
if playerData and playerData.job then
loggedIn = true
break
end
Wait(100)
end
elseif isStarted(OXCoreExport) then
if OxPlayer["stateId"] then
loggedIn = true
end
while not OxPlayer["stateId"] do
Wait(1000)
debugPrint("Waiting for stateId to class as logged in")
if OxPlayer.get["stateId"] then
loggedIn = true
break
end
end
else else
-- For other frameworks, use LocalPlayer.state.isLoggedIn.
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
loggedIn = LocalPlayer.state.isLoggedIn
end
if not loggedIn then
print("^4Error^7: ^2Timeout reached while waiting for player login^7.") print("^4Error^7: ^2Timeout reached while waiting for player login^7.")
end return false
return result else
end debugPrint("^6Bridge^7: ^2Player Login Detected^7.")
return true
end end
end end
local messageShown = false local messageShown = false
function waitForSharedLoad() function waitForSharedLoad()
local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers) local timeout = 100000 -- 10 seconds in milliseconds
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 (timeout and GetGameTimer() < timeout) do ) and ((GetGameTimer() - startTime) < 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")
@@ -307,16 +214,19 @@ 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,13 +1,3 @@
local function readBoolMeta(key, default)
local val = GetResourceMetadata("jim_bridge", key, 0)
if not val then return default end
val = tostring(val):lower()
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
end
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
if not SUPPRESS_UPDATES then
function parseVersion(version) function parseVersion(version)
local parts = {} local parts = {}
for num in version:gmatch("%d+") do for num in version:gmatch("%d+") do
@@ -97,7 +87,7 @@ if not SUPPRESS_UPDATES then
end end
end end
print("^1----------------------------------------------------------------------^7") print("^1----------------------------------------------------------------------^7")
SetTimeout(3600000, function() CheckVersion() end) SetTimeout(1200000, function() CheckVersion() end)
else else
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)") print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
end end
@@ -108,4 +98,3 @@ if not SUPPRESS_UPDATES then
end end
CheckVersion() CheckVersion()
end

View File

@@ -1,30 +1,3 @@
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.
@@ -64,10 +37,6 @@ 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)
@@ -86,7 +55,6 @@ 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
@@ -97,54 +65,25 @@ 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
local ok, res = pcall(function() result = lib.callback.await(callbackName, false, ...)
return lib.callback.await(callbackName, false, table.unpack(args)) elseif isStarted(QBExport) then
end) local p = promise.new()
if ok then return res, "nil" end Core.Functions.TriggerCallback(callbackName, function(cbResult)
return nil, tostring(res) p:resolve(cbResult)
end end, ...)
result = Citizen.Await(p)
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) Wait(10)
until attempts > (1 + CALLBACK_RETRIES) elseif isStarted(ESXExport) then
local p = promise.new()
return nil, lastErr or "timeout" ESX.TriggerServerCallback(callbackName, function(cbResult)
p:resolve(cbResult)
end, ...)
result = Citizen.Await(p)
else
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with", callbackName)
end
return result
end end

View File

@@ -10,9 +10,44 @@
• esx (using ESX.UI.Menu) • esx (using ESX.UI.Menu)
]] ]]
local contextFunc = { --- Opens a menu using the configured menu system.
["ox"] = ---
function(Menu, data) --- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
---
---@param Menu table A table containing the menu options to display.
--- Each menu item can include:
--- - header (`string`): The text to display for the menu item.
--- - txt (`string`, optional): Additional text or description.
--- - icon (`string`, optional): Icon to display with the menu item.
--- - onSelect (`function`, optional): Function to execute when the menu item is selected.
--- - arrow (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
--- - params (`table`, optional): Additional parameters, such as events and arguments.
--- - isMenuHeader (`boolean`, optional): Marks the item as a header.
--- - disabled (`boolean`, optional): Disables the menu item if `true`.
---
---@param data table A table containing configuration data for the menu.
--- - header (`string`): The header/title of the menu.
--- - headertxt (`string`, optional): Additional header text.
--- - onBack (`function`, optional): Function to call when the "Return" option is selected.
--- - onExit (`function`, optional): Function to call when the menu is exited.
--- - onSelected (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
--- - canClose (`boolean`, optional): Whether the menu can be closed by the user.
---
---@usage
--- ```lua
--- openMenu({
--- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
--- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
--- }, {
--- header = "Main Menu",
--- headertxt = "Select an option",
--- onBack = function() print("Return selected") end,
--- onExit = function() print("Menu closed") end,
--- canClose = true,
--- })
--- ```
function openMenu(Menu, data)
if Config.System.Menu == "ox" then
local index = nil local index = nil
if data.onBack and not data.onSelected then if data.onBack and not data.onSelected then
table.insert(Menu, 1, { table.insert(Menu, 1, {
@@ -75,10 +110,8 @@ local contextFunc = {
else else
lib.showContext(menuID) lib.showContext(menuID)
end end
end,
["qb"] = elseif Config.System.Menu == "qb" then
function(Menu, data)
if data.onBack then if data.onBack then
table.insert(Menu, 1, { table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left", icon = "fas fa-circle-arrow-left",
@@ -118,10 +151,8 @@ local contextFunc = {
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
end end
exports[QBMenuExport]:openMenu(Menu) exports[QBMenuExport]:openMenu(Menu)
end,
["gta"] = elseif Config.System.Menu == "gta" then
function(Menu, data)
WarMenu.CreateMenu(tostring(Menu), data.header, " ", { WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
titleColor = { 222, 255, 255 }, titleColor = { 222, 255, 255 },
maxOptionCountOnScreen = 15, maxOptionCountOnScreen = 15,
@@ -175,10 +206,8 @@ local contextFunc = {
Wait(0) Wait(0)
end end
end) end)
end,
["esx"] = elseif Config.System.Menu == "esx" then
function(Menu, data)
for k in pairs(Menu) do for k in pairs(Menu) do
Menu[k].label = Menu[k].header Menu[k].label = Menu[k].header
Menu[k].name = "button"..k Menu[k].name = "button"..k
@@ -216,10 +245,8 @@ local contextFunc = {
function(data, menu) function(data, menu)
menu.close() menu.close()
end) end)
end,
["lation"] = elseif Config.System.Menu == "lation" then
function(Menu, data)
if data.onBack then if data.onBack then
table.insert(Menu, 1, { table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left", icon = "fas fa-circle-arrow-left",
@@ -263,63 +290,12 @@ local contextFunc = {
-- Show menu -- Show menu
exports.lation_ui:showMenu('menu') exports.lation_ui:showMenu('menu')
end,
}
end
--- Opens a menu using the configured menu system.
---
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
---
---@param Menu table A table containing the menu options to display.
--- Each menu item can include:
--- - header (`string`): The text to display for the menu item.
--- - txt (`string`, optional): Additional text or description.
--- - icon (`string`, optional): Icon to display with the menu item.
--- - onSelect (`function`, optional): Function to execute when the menu item is selected.
--- - arrow (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
--- - params (`table`, optional): Additional parameters, such as events and arguments.
--- - isMenuHeader (`boolean`, optional): Marks the item as a header.
--- - disabled (`boolean`, optional): Disables the menu item if `true`.
---
---@param data table A table containing configuration data for the menu.
--- - header (`string`): The header/title of the menu.
--- - headertxt (`string`, optional): Additional header text.
--- - onBack (`function`, optional): Function to call when the "Return" option is selected.
--- - onExit (`function`, optional): Function to call when the menu is exited.
--- - onSelected (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
--- - canClose (`boolean`, optional): Whether the menu can be closed by the user.
---
---@usage
--- ```lua
--- openMenu({
--- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
--- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
--- }, {
--- header = "Main Menu",
--- headertxt = "Select an option",
--- onBack = function() print("Return selected") end,
--- onExit = function() print("Menu closed") end,
--- canClose = true,
--- })
--- ```
function openMenu(Menu, data)
contextFunc[Config.System.Menu](Menu, data)
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 = lineBreakConversion[Config.System.Menu] br = (Config.System.Menu == "ox" or Config.System.Menu == "gta" or Config.System.Menu == "lation") and "\n" or "<br>"
--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes. --- 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,10 +10,11 @@ OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
Exports.ESXExport or "", Exports.ESXExport or "",
Exports.OXCoreExport or "" Exports.OXCoreExport or ""
OXInv, QBInv, PSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv = OXInv, QBInv, PSInv, QSInv, 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 "",
@@ -21,9 +22,6 @@ OXInv, QBInv, PSInv, 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 ""
@@ -31,17 +29,15 @@ 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() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers) local timeout = GetGameTimer() + 120000 -- 2 minutes max wait (had to up this from 5 seconds 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 (timeout and GetGameTimer() < timeout) do while not cache 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()
@@ -53,7 +49,7 @@ if IsDuplicityVersion() then
Wait(100) Wait(100)
end end
if timeout and not cache then if 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

@@ -17,8 +17,7 @@ local excludeKeys = {
amount = true, metadata = true, description = true, info = true, amount = true, metadata = true, description = true, info = true,
job = true, gang = true, oneUse = true, slot = true, job = true, gang = true, oneUse = true, slot = true,
blueprintRef = true, craftingLevel = true, craftedItems = true, blueprintRef = true, craftingLevel = true, craftedItems = true,
hasCrafted = true, exp = true, anim = true, time = true, id = true, hasCrafted = true, exp = true, anim = true, time = true,
ingredients = true,
} }
------------------------------------------------------------- -------------------------------------------------------------
@@ -41,15 +40,10 @@ local excludeKeys = {
--- craftable = { --- craftable = {
--- Header = "Weapon Crafting", --- Header = "Weapon Crafting",
--- Recipes = { --- Recipes = {
--- weapon_pistol = { --- [1] = {
--- id = 1, --- ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
--- ingredients = {
--- steel = 5, plastic = 5,
--- },
--- info = {
--- amount = 1, --- amount = 1,
--- }, --- },
--- },
--- -- More recipes... --- -- More recipes...
--- }, --- },
--- Anims = { --- Anims = {
@@ -62,10 +56,9 @@ local excludeKeys = {
--- job = "mechanic", --- job = "mechanic",
--- onBack = function() print("Returning to previous menu") end, --- onBack = function() print("Returning to previous menu") end,
--- }) --- })
--- ```
function craftingMenu(data) function craftingMenu(data)
if CraftLock then return end if CraftLock then return end
local data = cloneTable(data)
-- Job or gang check; exit if not authorized. -- Job or gang check; exit if not authorized.
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
@@ -79,49 +72,16 @@ function craftingMenu(data)
-- Normalize stash name. -- Normalize stash name.
data.stashName = data.stashTable or data.stashName data.stashName = data.stashTable or data.stashName
-- Wrapper to convert old style crafting recipes to be handled by the menu properly
--if data.craftable.Recipes[1] then -- assume old style crafting table
-- local compatTable = {}
-- local id = 0
-- for k, v in ipairs(data.craftable.Recipes) do
-- local Recipe = v
-- for l, b in pairs(Recipe) do
-- if doesItemExist(l) then
-- id += 1
-- compatTable[l] = {
-- ingredients = b,
-- id = id,
-- info = {
-- amount = Recipe.amount or 1,
-- metadata = Recipe.metadata or nil,
-- job = Recipe.job or nil,
-- gang = Recipe.gang or nil,
-- hasCrafted = Recipe.hasCrafted or nil,
-- },
-- }
-- end
-- end
-- end
-- data.craftable.Recipes = compatTable
--end
-- Convert to array
--local RecipesArray = {}
--for k, v in pairs(data.craftable.Recipes) do
-- RecipesArray[v.id] = { [k] = v }
--end
local Menu = {} local Menu = {}
local Recipes = cloneTable(data.craftable.Recipes) local Recipes = data.craftable.Recipes
local craftedItems = {} local craftedItems = {}
local tempCarryTable = {} local tempCarryTable = {}
-- Build a temporary table of all required ingredients (default quantity is 1).
-- Build a table of all required ingredients (default quantity is 1).
for i = 1, #Recipes do for i = 1, #Recipes do
for k, v in pairs(Recipes[i]) do for k in pairs(Recipes[i]) do
if not excludeKeys[k] then if k ~= "amount" and k ~= "metadata" and k ~= "job" and k ~= "gang" then
if not Recipes[i].amount then Recipes[i].amount = 1 end tempCarryTable[k] = Recipes[i].amount or 1
tempCarryTable[k] = tempCarryTable[k] and (tempCarryTable[k] < Recipes[i].amount) or Recipes[i].amount
end end
end end
end end
@@ -129,84 +89,64 @@ 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 and data.stashName ~= "" local usingStash = data.stashName ~= nil
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",
header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"), header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"),
disabled = true, disabled = true,
} }
-- Process each recipe to create menu entries.
for i = 1, #Recipes do for i = 1, #Recipes do
local menuId = #Menu+1 if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
local item = "" for k, _ in pairs(Recipes[i]) do
local Recipe = {}
for k, v in pairs(Recipes[i]) do
if not excludeKeys[k] then if not excludeKeys[k] then
item = k local hasjob = true
Recipe = Recipes[i] if Recipes[i].job then
Recipe.amount = Recipe.amount or 1 for l, b in pairs(Recipes[i].job) do
break hasjob = hasJob(l, nil, b)
if hasjob then break end
end end
end end
if hasjob then
-- Job Check local setheader, settext, disable, metadata = "", "", false, (Recipes[i]["metadata"] or Recipes[i]["info"] or nil)
local hasGroup = true
if Recipe.job then
for l, b in pairs(Recipe.job) do
hasGroup = hasJob(l, nil, b)
if hasGroup then goto skipcheck end
end
end
if Recipe.gang then
for l, b in pairs(Recipe.gang) do
hasGroup = hasJob(l, nil, b)
if hasGroup then goto skipcheck end
end
end
::skipcheck::
-- if has group requirement, continue
if hasGroup then
local setheader, settext, disable, metadata = "", "", false, (Recipe.metadata or Recipe.info or nil)
local itemTable = {} local itemTable = {}
local metaTable = {} local metaTable = {}
-- Build ingredient details. -- Build ingredient details.
for l, b in pairs(Recipe[item]) do for l, b in pairs(Recipes[i][tostring(k)]) do
local label = getItemLabel(l) local label = Items[l] and Items[l].label or "error - "..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[label] = b metaTable[Items[l] and Items[l].label or "error - "..l] = b
itemTable[l] = b itemTable[l] = b
end end
-- Make sure "canCarryTable" exists 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 getItemLabel(item)) setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k))
..(Recipe.amount > 1 and " x"..Recipe.amount or "") ..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
local statusEmoji = disable and " " or not canCarryTable[item] and " 📦" or " ✔️" local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️"
local isNew = (Recipe.hasCrafted ~= nil and craftedItems[item] == nil) and "" or "" local isNew = (Recipes[i]["hasCrafted"] ~= nil and craftedItems[k] == nil) and "" or ""
setheader = isNew .. setheader .. statusEmoji setheader = isNew .. setheader .. statusEmoji
-- Build menu option using info Menu[#Menu + 1] = {
Menu[menuId] = { arrow = isOx() and (not disable and canCarryTable[k]),
arrow = isOx() and (not disable and canCarryTable[item]), isMenuHeader = disable or not canCarryTable[k],
isMenuHeader = disable or not canCarryTable[item], icon = invImg((metadata and metadata.image) or tostring(k)),
icon = invImg((metadata and metadata.image) or item), image = invImg((metadata and metadata.image) or tostring(k)),
image = invImg((metadata and metadata.image) or item),
header = setheader, header = setheader,
txt = settext or nil, txt = settext or nil,
metadata = metaTable, metadata = metaTable,
onSelect = (not disable and canCarryTable[item]) and function() onSelect = (not disable and canCarryTable[k]) and function()
local transdata = { local transdata = {
item = item, item = k,
craft = Recipe, craft = data.craftable.Recipes[i],
craftable = data.craftable, craftable = data.craftable,
coords = data.coords, coords = data.coords,
amount = Recipe.amount,
stashName = data.stashName, stashName = data.stashName,
onBack = data.onBack, onBack = data.onBack,
metadata = metadata, metadata = metadata,
@@ -220,7 +160,10 @@ function craftingMenu(data)
} }
end end
end end
-- open context menu --Wait(0)
end
end
openMenu(Menu, { openMenu(Menu, {
header = data.craftable.Header, header = data.craftable.Header,
headertxt = data.craftable.Headertxt, headertxt = data.craftable.Headertxt,
@@ -377,11 +320,9 @@ end
--- }) --- })
--- ``` --- ```
function makeItem(origData) Config.Crafting.SingleProgress = true -- Set false to use individual progress bars per craft
local data = cloneTable(origData) function makeItem(data)
local Ped = PlayerPedId()
if CraftLock then return end if CraftLock then return end
CraftLock = true CraftLock = true
data.stashName = data.stashTable or data.stashName data.stashName = data.stashTable or data.stashName
@@ -395,60 +336,108 @@ function makeItem(origData)
local craftAmount = (data.amount and data.amount ~= 1) and data.amount or 1 local craftAmount = (data.amount and data.amount ~= 1) and data.amount or 1
local metadata = data.metadata or nil local metadata = data.metadata or nil
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
local canReturn = true local canReturn = true
local crafted, crafting = true, true local crafted, crafting = true, true
local cam = createCam(Ped, data.coords.xyz) local cam = createTempCam(PlayerPedId(), data.coords)
startCam(cam, 5000) startTempCam(cam)
-- Calculate total bartime if SingleProgress is enabled -- Calculate total bartime if SingleProgress is enabled
local totalBartime = (bartime * craftAmount) local totalBartime = bartime * craftAmount
local craftProp = nil
if prop then
craftProp = makeProp({ prop = prop.model, coords = GetEntityCoords(PlayerPedId()), true, true })
AttachEntityToEntity(craftProp, Ped, GetPedBoneIndex(Ped, prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true)
end
if data.sound then
local s = data.sound
PlaySoundFromEntity(s.soundId, s.audioName, Ped, s.audioRef, true, 0)
end
if not Config.Crafting.SingleProgress then -- if SingleProgress is disabled, dont do ingredient progressbars
-- Run ingredient check and usage separately first -- Run ingredient check and usage separately first
for i = 1, craftAmount do for i = 1, craftAmount do
for k, v in pairs(data.craft[data.item]) do for k, v in pairs(data.craft) do
if not excludeKeys[k] and type(v) == "table" then
for l, b in pairs(v) do
if isInventoryOpen() then if isInventoryOpen() then
print("^1Error^7: ^2Inventory is open, you tried to break things") print("^1Error^7: ^2Inventory is open, you tried to break things")
stopCam(0) stopTempCam()
ClearPedTasks(Ped) ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(origData) end if canReturn then craftingMenu(data) end
CraftLock = false CraftLock = false
return return
end end
if crafting and progressBar({ if crafting and progressBar({
label = "Using "..v.." "..getItemLabel(k), label = "Using "..b.." "..Items[l].label,
time = 800, time = 1000,
cancel = true, cancel = true,
dict = 'pickup_object', dict = 'pickup_object',
anim = "putdown_low", anim = "putdown_low",
flag = 49, flag = 49,
icon = k, icon = l,
}) then }) then
TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[k], "use", v) TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[l], "use", b)
else else
crafted, crafting = false, false crafted, crafting = false, false
break break
end end
Wait(200) Wait(200)
end end
end
if not crafted then end
goto finishEarly
end end
if not crafted then
stopTempCam()
ClearPedTasks(PlayerPedId())
if canReturn then craftingMenu(data) end
CraftLock = false
return
end
-- Handle SingleProgress option
if Config.Crafting.SingleProgress then
local craftProp = nil
if prop then
craftProp = makeProp({ prop = prop.model, coords = vec4(0, 0, 0, 0), true, true })
AttachEntityToEntity(craftProp, PlayerPedId(), GetPedBoneIndex(PlayerPedId(), prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true)
end
if data.sound then
local s = data.sound
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end
if crafting and progressBar({ if crafting and progressBar({
label = bartext..((metadata and metadata.label) or getItemLabel(data.item)), label = bartext..((metadata and metadata.label) or Items[data.item].label).." x"..craftAmount,
time = totalBartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
request = true,
}) then
data.craft.amount = craftAmount
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil -- clear client cached token
-- handle metadata and experience in a single go
if data.craft["hasCrafted"] ~= nil then
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:SetMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft["exp"] ~= nil then
craftingLevel += data.craft["exp"].give * craftAmount
triggerCallback(getScript()..":server:SetMetadata", "craftingLevel", craftingLevel)
end
if data.craftable.Recipes[1].oneUse == true then
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false
end
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then
data.requiredItemfunc()
end
end
if craftProp then destroyProp(craftProp) end
else
-- Run the original loop for multiple progress bars
for i = 1, craftAmount do
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label),
time = bartime, time = bartime,
cancel = true, cancel = true,
dict = animDict, dict = animDict,
@@ -459,75 +448,34 @@ function makeItem(origData)
}) then }) then
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken) TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
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:setPlayerMetadata", "craftedItems", data.craftable.craftedItems) triggerCallback(getScript()..":server:SetMetadata", "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:setPlayerMetadata", "craftingLevel", craftingLevel) triggerCallback(getScript()..":server:SetMetadata", "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)
local breakId = GetSoundId() local breakId = GetSoundId()
PlaySoundFromEntity(breakId, "Drill_Pin_Break", Ped, "DLC_HEIST_FLEECA_SOUNDSET", 1, 0) PlaySoundFromEntity(breakId, "Drill_Pin_Break", PlayerPedId(), "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
canReturn = false canReturn = false
end end
else
break
end
end
else
if crafting and progressBar({
label = bartext..((metadata and metadata.label) or getItemLabel(data.item)).." x"..craftAmount,
time = totalBartime,
cancel = true,
dict = animDict,
anim = anim,
flag = 49,
icon = data.item,
request = true,
}) then
data.craft.amount *= craftAmount
for k, v in pairs(data.craft[data.item]) do
-- multiply igredient requirement in sent crafting table for removal
data.craft[data.item][k] = (v * craftAmount)
end
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
currentToken = nil -- clear client cached token
-- handle metadata and experience in a single go
if data.craft.hasCrafted ~= nil then
data.craftable.craftedItems[data.item] = true
triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
end
if data.craft.exp ~= nil then
craftingLevel += data.craft["exp"].give * craftAmount
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
end
--if data.craft.Recipes[1].oneUse == true then
-- removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
-- local breakId = GetSoundId()
-- PlaySoundFromEntity(breakId, "Drill_Pin_Break", Ped, "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
-- canReturn = false
--end
end
end
::finishEarly::
if craftProp then destroyProp(craftProp) end
if data.sound then
StopSound(data.sound.soundId)
end
if data.requiredItemfunc then if data.requiredItemfunc then
data.requiredItemfunc() data.requiredItemfunc()
end end
else
break
end
end
end
--Wait(500) Wait(500)
stopCam(0) stopTempCam()
CraftLock = false CraftLock = false
if canReturn then craftingMenu(origData) end if canReturn then craftingMenu(data) end
ClearPedTasks(Ped) ClearPedTasks(PlayerPedId())
end end
@@ -557,13 +505,14 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
return return
end end
local hasItems, hasTable = hasItem(ItemMake, 1, src) local hasItems, hasTable = hasItem(ItemMake, 1, src)
if stashName then if stashName then
local itemRemove = {} local itemRemove = {}
if type(stashName) == "table" then if type(stashName) == "table" then
for _, name in pairs(stashName) do for _, name in pairs(stashName) do
stashItems = getStash(name) stashItems = getStash(name)
for k, v in pairs(craftable[ItemMake]) do for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do for _, b in pairs(stashItems or {}) do
if k == b.name then if k == b.name then
itemRemove[k] = v itemRemove[k] = v
@@ -573,7 +522,7 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
end end
else else
stashItems = getStash(stashName) stashItems = getStash(stashName)
for k, v in pairs(craftable[ItemMake]) do for k, v in pairs(craftable[ItemMake] or {}) do
for _, b in pairs(stashItems or {}) do for _, b in pairs(stashItems or {}) do
if k == b.name then if k == b.name then
itemRemove[k] = v itemRemove[k] = v
@@ -583,8 +532,8 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
end end
stashRemoveItem(stashItems, stashName, itemRemove) stashRemoveItem(stashItems, stashName, itemRemove)
else else
if craftable[ItemMake] then if craftable then
for k, v in pairs(craftable[ItemMake]) do for k, v in pairs(craftable[ItemMake] or {}) do
removeItem(tostring(k), v, src) removeItem(tostring(k), v, src)
end end
end end

View File

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

View File

@@ -7,10 +7,7 @@ function AlienEffect()
Wait(math.random(5000, 8000)) Wait(math.random(5000, 8000))
local Ped = PlayerPedId() local Ped = PlayerPedId()
local animDict = "MOVE_M@DRUNK@VERYDRUNK" local animDict = "MOVE_M@DRUNK@VERYDRUNK"
RequestAnimSet(animDict) loadAnimDict(animDict)
while not HasAnimSetLoaded(animDict) do
Wait(100)
end
SetPedCanRagdoll(Ped, true) SetPedCanRagdoll(Ped, true)
ShakeGameplayCam('DRUNK_SHAKE', 2.80) ShakeGameplayCam('DRUNK_SHAKE', 2.80)
SetTimecycleModifier("Drunk") SetTimecycleModifier("Drunk")

View File

@@ -56,14 +56,6 @@ 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
@@ -81,6 +73,8 @@ function checkExportExists(resource, export)
end end
end end
------------------------------------------------------------- -------------------------------------------------------------
-- Debugging and JSON Utilities -- Debugging and JSON Utilities
------------------------------------------------------------- -------------------------------------------------------------
@@ -188,7 +182,6 @@ 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
@@ -233,9 +226,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
local newFormatted, count = formatted:gsub("^(-?%d+)(%d%d%d)", '%1,%2') formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
if count == 0 then break end if k == 0 then break end
formatted = newFormatted Wait(0)
end end
return formatted return formatted
end end
@@ -250,14 +243,13 @@ end
--- ``` --- ```
function formatCoord(coord) function formatCoord(coord)
local vecType = type(coord):gsub("tor", "") local vecType = type(coord):gsub("tor", "")
local parts = {} local components = {
[1] = coord.x and ("^6"..string.format("%.1f", coord.x)) or "",
if coord.x then parts[#parts + 1] = string.format("^6%.1f", coord.x) end [2] = coord.y and ("^7, ^6"..string.format("%.1f", coord.y)) or "",
if coord.y then parts[#parts + 1] = string.format("^6%.1f", coord.y) end [3] = coord.z and ("^7, ^6"..string.format("%.1f", coord.z)) or "",
if coord.z then parts[#parts + 1] = string.format("^6%.1f", coord.z) end [4] = coord.w and ("^7, ^6"..string.format("%.1f", coord.w)) or "",
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.
@@ -269,17 +261,13 @@ 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 i = 1, count do for _, coord in ipairs(tbl) 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
@@ -292,9 +280,9 @@ end
--- print("Number of keys:", count) --- print("Number of keys:", count)
--- ``` --- ```
function countTable(tbl) function countTable(tbl)
local count = 0 local i = 0
for _ in pairs(tbl) do count = count + 1 end for _ in pairs(tbl) do i += 1 end
return count return i
end end
--- Returns an iterator over a table's keys in sorted order. --- Returns an iterator over a table's keys in sorted order.
@@ -311,19 +299,7 @@ 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.
@@ -337,18 +313,15 @@ end
--- end --- end
--- ``` --- ```
function createConsecutiveTable(originalTable) function createConsecutiveTable(originalTable)
local entries = {} local sortedEntries = {}
for _, entry in pairs(originalTable) do for _, entry in pairs(originalTable) do table.insert(sortedEntries, entry) end
entries[#entries + 1] = entry table.sort(sortedEntries, function(a, b) return a.id < b.id end)
local newTable = {}
for newIndex, entry in ipairs(sortedEntries) do
entry.id = newIndex
newTable[newIndex] = entry
end 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.
@@ -360,7 +333,11 @@ end
--- print(combinedText) --- print(combinedText)
--- ``` --- ```
function concatenateText(tbl) function concatenateText(tbl)
return table.concat(tbl, "\n") local result = ""
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.
@@ -372,14 +349,11 @@ end
--- print(direction) --- print(direction)
--- ``` --- ```
function RotationToDirection(rot) function RotationToDirection(rot)
local radianConvert = math.pi / 180 local adjust = 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(rotZ) * cosX, -math.sin(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
math.cos(rotZ) * cosX, math.cos(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
math.sin(rotX) math.sin(adjust * rot.x)
) )
end end
@@ -392,9 +366,11 @@ 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(math.min(math.max(percentage, 0), 100) / 100 * total) local filled = math.floor((perc / 100) * total)
return string.rep("", filled) .. string.rep("", total - filled) local empty = total - filled
return string.rep("", filled)..string.rep("", empty)
end end
--- Normalizes a 3D vector. --- Normalizes a 3D vector.
@@ -406,8 +382,12 @@ end
--- print(normalizedVec) --- print(normalizedVec)
--- ``` --- ```
function normalizeVector(vec) function normalizeVector(vec)
local length = math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) local len = math.sqrt(vec.x^2 + vec.y^2 + vec.z^2)
return length > 0 and vec3(vec.x / length, vec.y / length, vec.z / length) or vec3(0, 0, 0) if len ~= 0 then
return vec3(vec.x / len, vec.y / len, vec.z / len)
else
return vec3(0, 0, 0)
end
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -423,16 +403,17 @@ 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 not debugMode then return end if debugMode then
local col = col or vec4(255, 255, 255, 150)
CreateThread(function() CreateThread(function()
for i = 100, 0, -1 do local count = 1000
while count >= 0 do
DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w) DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w)
count -= 10
Wait(0) 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).
--- @param coords vector3 The center of the sphere. --- @param coords vector3 The center of the sphere.
@@ -442,16 +423,17 @@ 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 not debugMode then return end if debugMode then
local col = col or vec4(255, 255, 255, 150)
CreateThread(function() CreateThread(function()
for i = 100, 0, -1 do local count = 1000
while count >= 0 do
DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w) DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w)
count -= 1
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.
--- @param startCoords vector3 The starting coordinate. --- @param startCoords vector3 The starting coordinate.
@@ -469,14 +451,14 @@ 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(
local shapeTest = StartExpensiveSynchronousShapeTestLosProbe( 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, 4 flags or 4294967295, entity, 0
) )
)
return GetShapeTestResult(shapeTest) return val1, val2, val3, val4, val5, val6
end end
--- Adjusts the Z-coordinate of a position to the ground level. --- Adjusts the Z-coordinate of a position to the ground level.
@@ -488,34 +470,16 @@ end
--- print("Ground Position:", groundCoords) --- print("Ground Position:", groundCoords)
--- ``` --- ```
function adjustForGround(coords) function adjustForGround(coords)
local foundGround, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0) local foundGround, zPos = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0)
if foundGround then if foundGround then
return coords.w and vec4(coords.x, coords.y, groundZ, coords.w) or vec3(coords.x, coords.y, groundZ) if coords.w then
return vec4(coords.x, coords.y, zPos, coords.w)
else
return vec3(coords.x, coords.y, zPos)
end end
else
return coords return coords
end 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.
@@ -529,7 +493,21 @@ end
--- end --- end
--- ``` --- ```
function ensureNetToVeh(vehNetID) function ensureNetToVeh(vehNetID)
return waitForNetworkEntity(vehNetID, NetToVeh) --debugPrint("^6Bridge^7: ^3ensureNetToVeh^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..vehNetID.."^7)")
local timeout = 100
while not NetworkDoesNetworkIdExist(vehNetID) and timeout > 0 do
timeout -= 1
Wait(10)
end
if not NetworkDoesNetworkIdExist(vehNetID) then return 0 end
timeout = 100
local vehicle = NetToVeh(vehNetID)
while not DoesEntityExist(vehicle) and vehicle ~= 0 and timeout > 0 do
timeout -= 1
Wait(10)
end
if not DoesEntityExist(vehicle) then return 0 end
return vehicle
end end
--- Ensures a network entity exists from its network ID. --- Ensures a network entity exists from its network ID.
@@ -543,7 +521,21 @@ end
--- end --- end
--- ``` --- ```
function ensureNetToEnt(entNetID) function ensureNetToEnt(entNetID)
return waitForNetworkEntity(entNetID, NetworkGetEntityFromNetworkId) --debugPrint("^6Bridge^7: ^3ensureNetToEnt^7: ^2Requesting NetworkDoesNetworkIdExist^7(^6"..entNetID.."^7)")
local timeout = 100
while not NetworkDoesNetworkIdExist(entNetID) and timeout > 0 do
timeout -= 1
Wait(10)
end
if not NetworkDoesNetworkIdExist(entNetID) then return 0 end
timeout = 100
local entity = NetworkGetEntityFromNetworkId(entNetID)
while not DoesEntityExist(entity) and entity ~= 0 and timeout > 0 do
timeout -= 1
Wait(10)
end
if not DoesEntityExist(entity) then return 0 end
return entity
end end
function sendLog(text) function sendLog(text)
@@ -588,92 +580,12 @@ 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)
return type(tbl) == "table" and math.random(tbl[1], tbl[2]) or tbl if type(tbl) == "table" then
end return math.random(tbl[1], tbl[2])
-------------------------------------------------------------
-- 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 else
if DoesEntityExist(entity) then return tbl
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
end
end
-- Function to clone tables, to use when referencing tables that need to be hard set
function cloneTable(obj, opts, seen)
if type(obj) ~= "table" then return obj end
seen = seen or {}
if seen[obj] then return seen[obj] end
local copy = {}
seen[obj] = copy
-- Copy entries
for k, v in pairs(obj) do
local k2 = (opts and opts.copy_keys) and cloneTable(k, opts, seen) or k
copy[k2] = cloneTable(v, opts, seen)
end
-- Preserve metatable unless told not to
if not (opts and opts.strip_meta) then
local mt = getmetatable(obj)
if mt ~= nil then setmetatable(copy, mt) end
end
return copy
end
------------------------------------------------------------- -------------------------------------------------------------
-- Material and Prop Functions -- Material and Prop Functions
@@ -908,9 +820,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, Ped) function GetGroundMaterialAtPosition(coords)
local endCoords = vec3(coords.x, coords.y, coords.z - 1.1) local endX, endY, endZ = coords.x, coords.y, coords.z - 1.0
local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endCoords.x, endCoords.y, endCoords.z, 1.0, 1, Ped or PlayerPedId(), 7) local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endX, endY, endZ, 1.0, 1, playerPed, 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

246
shared/inventories.lua Normal file
View File

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

View File

@@ -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,73 +9,6 @@
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)
TriggerServerEvent(getScript()..":registerESXSociety", 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
-- Register ESX Society account in the server
RegisterNetEvent(getScript()..":registerESXSociety", function(group)
local checkExist = exports.esx_society:GetSociety(group)
if checkExist == nil then
exports.esx_society:registerSociety(
group,
Gangs[group] and Gangs[group].label or Jobs[group] and Jobs[group].label,
group,
group,
group,
{type = "public"}
)
end
end)
------------------------------------------------------------- -------------------------------------------------------------
-- Global Duty Status -- Global Duty Status
------------------------------------------------------------- -------------------------------------------------------------
@@ -167,58 +100,21 @@ 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()
local dutyFunc = { if isStarted(QBExport) or isStarted(QBXExport) then
{ framework = QBExport,
func = function()
TriggerServerEvent("QBCore:ToggleDuty") TriggerServerEvent("QBCore:ToggleDuty")
Wait(100) Wait(100)
onDuty = getPlayer().onDuty onDuty = getPlayer().onDuty
end elseif isStarted(RSGExport) then
},
{ framework = QBXExport,
func = function()
TriggerServerEvent("QBCore:ToggleDuty")
Wait(100)
onDuty = getPlayer().onDuty
end
},
{ framework = RSGExport,
func = function()
TriggerServerEvent("RSGCore:ToggleDuty") TriggerServerEvent("RSGCore:ToggleDuty")
Wait(100) Wait(100)
onDuty = getPlayer().onDuty onDuty = getPlayer().onDuty
end else
},
{ framework = ESXExport,
func = function()
local tempJob = ESX.GetPlayerData().job
tempJob.onDuty = not onDuty
ESX.SetPlayerData("job", tempJob)
onDuty = getPlayer().onDuty
if onDuty then if onDuty then
triggerNotify(nil, "Now on duty", "success") triggerNotify(nil, "Now on duty", "success")
else else
triggerNotify(nil, "Now off duty", "success") triggerNotify(nil, "Now off duty", "success")
end end
end end
},
}
for i = 1, #dutyFunc do
local framework = dutyFunc[i]
if isStarted(framework.framework) then
framework.func()
return
end
end
-- fallback
onDuty = not onDuty
if onDuty then
triggerNotify(nil, "Now on duty", "success")
else
triggerNotify(nil, "Now off duty", "success")
end
end end
-- Framework specific functions to keep duty status synced -- Framework specific functions to keep duty status synced
@@ -335,3 +231,19 @@ 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,5 +1,3 @@
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.
@@ -9,137 +7,58 @@ local camCache = {}
-- 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|entityId The target `vector3` coordinates that the camera will point at. ---@param coords vector3 The target `vector3` coordinates that the camera will point at.
-- --
---@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`). ---@return cam camID The handle of the created camera, or `nil` if the camera was not created (e.g., if `Config.Crafting.craftCam` is `false`).
-- --
---@usage ---@usage
-- ```lua -- ```lua
-- local cam = createTempCam(entity, targetCoords) -- local cam = createTempCam(entity, targetCoords)
-- ``` -- ```
function createTempCam(ent, coords) function createTempCam(ent, coords)
local camID = nil local cam = nil
if Config.Crafting?.craftCam or Config.System.enableCam then if Config.Crafting.craftCam then
if debugMode then
-- if not ent or coords are provided, make a basic camera to control later triggerNotify(nil, "ModCam Created", "success")
if not ent and not coords then end
camID = CreateCam("DEFAULT_SCRIPTED_CAMERA", true) local camCoords = nil
camCache[#camCache+1] = camID local pointCoords = nil
return camID if type(ent) ~= "vector3" then
camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
else
camCoords = ent
end end
-- if received a vector3 or vector4 use those coords for origin point, otherwrise get offset from entity cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
local camCoords = type(ent) ~= "number" and ent or GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
-- Create the camera if type(coords) == "number" then
camID = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0) SetCamCoord(cam, GetCamCoord(cam) + vec3(0, 0, 1.0))
camCache[#camCache+1] = camID PointCamAtEntity(cam, coords)
else
debugPrint("^6Bridge^7: ^2Custom Camera Created", camID) PointCamAtCoord(cam, coords)
end
--if type(coords) == "number" then end
-- SetCamCoord(camID, GetCamCoord(camID) + vec3(0, 0, 1.0)) return cam
--end
if coords then
camLookAt(camID, coords)
end end
end
return camID
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 number The handle of the camera to activate and render. ---@param cam camID 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(camID, 1000, true, { postFx = "HeistCelebEnd" }) -- startTempCam(cam)
-- ``` -- ```
function startTempCam(cam, renderTime, loadScene, filter, switchCam) function startTempCam(cam)
if cam and DoesCamExist(cam) then if Config.Crafting.craftCam then
debugPrint("Starting camera")
-- 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)
SetCamActive(cachePrevCam, false) -- Set previous cam inactive (shouldn't be needed but just in case)
else
SetCamActive(cam, true) SetCamActive(cam, true)
end RenderScriptCams(true, true, 1000, true, true)
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.
@@ -151,34 +70,12 @@ end
-- ```lua -- ```lua
-- stopTempCam() -- stopTempCam()
-- ``` -- ```
function stopTempCam(renderTime) function stopTempCam()
if Config.Crafting.craftCam then
CreateThread(function() CreateThread(function()
Wait(1000) Wait(1000)
RenderScriptCams(false, true, 500, true, true)
cachePrevCam = nil DestroyAllCams()
-- Clear previous filters
if cacheCameraEffect.postFx then
AnimpostfxStop(cacheCameraEffect.postFx)
cacheCameraEffect.postFx = nil
end
if cacheCameraEffect.timecycle then
ClearTimecycleModifier()
cacheCameraEffect.timecycle = nil
end
RenderScriptCams(false, true, renderTime or 500, true, true)
clearLoadLocation()
Wait(renderTime or 0)
for i = 1, #camCache do
DestroyCam(camCache[i], true)
end
camCache = {}
end) end)
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, flag) debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName)
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false) TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
end end
@@ -239,24 +239,3 @@ 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,19 +133,16 @@ 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("jim-blipcontroller") then if isStarted("fs_smallresources") or isStarted("blip-info") or isStarted("blipinfo") then
if data.preview then if data.preview then
local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "") local txname = string.gsub(tostring(data.name..'preview'), "[ ()~]", "")
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["jim-blipcontroller"]:ShowBlipInfo(blip, { exports["fs_smallresources"]:SetBlipInfoImage(blip, getScript()..'previewTxd', txname)
title = data.name, exports["fs_smallresources"]:SetBlipInfoTitle(blip, data.name, false)
dict = getScript()..'scriptTxd',
tex = txname,
})
end end
end end
end end

View File

@@ -1,6 +1,5 @@
--- 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.
-- --
@@ -19,20 +18,18 @@ local distPeds = {}
-- ```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, func) function makeDistPed(data, coords, freeze, collision, scenario, anim, synced)
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()
distPeds[#distPeds+1] = createCirclePoly({ 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,
}) })
@@ -58,13 +55,14 @@ 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, fade) function makePed(data, coords, freeze, collision, scenario, anim, synced)
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)
@@ -118,7 +116,6 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced, fade)
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
@@ -144,12 +141,6 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced, fade)
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
@@ -247,16 +238,6 @@ 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,5 +1,4 @@
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.
--- ---
@@ -21,7 +20,7 @@ local distProps = {}
--- } --- }
--- local prop = makeProp(propData, true, false) --- local prop = makeProp(propData, true, false)
--- ``` --- ```
function makeProp(data, freeze, synced, fade) function makeProp(data, freeze, synced)
loadModel(data.prop) loadModel(data.prop)
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false) local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
SetEntityHeading(prop, (data.coords.w or 0) + 180.0) SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
@@ -30,12 +29,6 @@ function makeProp(data, freeze, synced, fade)
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
@@ -58,21 +51,17 @@ end
--- } --- }
--- makeDistProp(propData, true, false) --- makeDistProp(propData, true, false)
--- ``` --- ```
function makeDistProp(data, freeze, synced, range, func) function makeDistProp(data, freeze, synced, range)
local name = keyGen()..keyGen() local name = keyGen()..keyGen()
distProps[#distProps + 1] = createCirclePoly({ 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,
}) })
@@ -97,16 +86,6 @@ 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,9 +13,9 @@ 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, synced, fade) function makeVeh(model, coords)
loadModel(model) loadModel(model)
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, synced ~= false, false) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false)
SetVehicleHasBeenOwnedByPlayer(veh, true) SetVehicleHasBeenOwnedByPlayer(veh, true)
if gameName ~= "rdr3" then if gameName ~= "rdr3" then
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true) SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
@@ -24,18 +24,12 @@ function makeVeh(model, coords, synced, fade)
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
@@ -58,7 +52,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, false) vehicle = makeVeh(data.model, data.coords)
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,11 +1,49 @@
local inProgress = false local inProgress = false
local storedPID = nil local storedPID = nil
--- Displays a progress bar using the configured progress bar system.
local progressFunc = { ---
ox = { --- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
start = --- It supports shared progress bars between players, animations, camera effects, and more.
function(data) ---
---@param data table A table containing the progress bar configuration.
--- - **label** (`string`): The text label to display on the progress bar.
--- - **time** (`number`): The duration of the progress bar in milliseconds.
--- - **dict** (`string`, optional): The animation dictionary to use.
--- - **anim** (`string`, optional): The animation name to play.
--- - **task** (`string`, optional): The task scenario to perform.
--- - **flag** (`number`, optional): The animation flag.
--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
--- - **icon** (`string`, optional): The icon to display (for qb progress bar).
--- - **cam** (`number`, optional): The camera handle to use.
--- - **shared** (`table`, optional): Data for shared progress bars.
--- - **pid** (`number`): The player ID to share the progress bar with.
--- - **label** (`string`): The label to display on the shared progress bar.
---
--- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
---
---@usage
--- ```lua
--- local success = progressBar({
--- label = "Processing...",
--- time = 5000,
--- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
--- anim = "base",
--- flag = 49,
--- cancel = true,
--- })
--- ```
function progressBar(data)
local ped = PlayerPedId()
if data.shared then
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
storedPID = data.shared.pid
TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
end
local result = nil
if data.cam then startTempCam(data.cam) end
if Config.System.ProgressBar == "ox" then
local options = { local options = {
duration = debugMode and 1000 or data.time, duration = debugMode and 1000 or data.time,
label = data.label, label = data.label,
@@ -43,29 +81,20 @@ local progressFunc = {
end end
if data.progressType == "circle" then if data.progressType == "circle" then
if exports[OXLibExport]:progressCircle(options) then if exports[OXLibExport]:progressCircle(options) then
return true result = true
else else
return false result = false
end end
end end
if not data.progressType or data.progressType == "bar" then if not data.progressType or data.progressType == "bar" then
if exports[OXLibExport]:progressBar(options) then if exports[OXLibExport]:progressBar(options) then
return true result = true
else else
return false result = false
end end
end end
end,
stop =
function()
exports[OXLibExport]:cancelProgress()
end,
},
qb = { elseif Config.System.ProgressBar == "qb" then
start =
function(data)
local p = promise.new()
Core.Functions.Progressbar("progbar", Core.Functions.Progressbar("progbar",
data.label, data.label,
debugMode and 1000 or data.time, debugMode and 1000 or data.time,
@@ -84,21 +113,13 @@ local progressFunc = {
task = data.task task = data.task
}, },
{}, {}, {}, {},
function() p:resolve(true) end,
function() p:resolve(false) end,
data.icon)
return Citizen.Await(p)
end,
stop =
function() function()
TriggerEvent("progressbar:client:cancel") result = true
end, end, function()
}, result = false
end, data.icon)
esx = { elseif Config.System.ProgressBar == "esx" then
start =
function(data)
local p = promise.new()
ESX.Progressbar(data.label, debugMode and 1000 or data.time, { ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
FreezePlayer = true, FreezePlayer = true,
animation = { animation = {
@@ -107,24 +128,14 @@ local progressFunc = {
scenario = data.task, scenario = data.task,
}, },
onFinish = function() onFinish = function()
p:resolve(true) result = true
end, end,
onCancel = function() onCancel = function()
p:resolve(false) result = false
return false
end end
}) })
return Citizen.Await(p)
end,
stop =
function()
ESX.CancelProgressbar()
end,
},
lation = { elseif Config.System.ProgressBar == "lation" then
start =
function(data)
if exports.lation_ui:progressBar({ if exports.lation_ui:progressBar({
label = data.label, label = data.label,
description = nil, description = nil,
@@ -139,7 +150,6 @@ local progressFunc = {
anim = { anim = {
dict = data.dict, dict = data.dict,
clip = data.anim, clip = data.anim,
flag = data.flag
}, },
prop = { prop = {
model = data.prop and data.prop.model, model = data.prop and data.prop.model,
@@ -148,21 +158,14 @@ local progressFunc = {
bone = data.prop and (data.prop.bone or 0) bone = data.prop and (data.prop.bone or 0)
} }
}) then }) then
return true result = true
else else
return false result = true
end end
end,
stop =
function()
exports.lation_ui:cancelProgress()
end,
},
red = { elseif Config.System.ProgressBar == "red" then
start = -- Currently only uses jim-redui if you choose this option
function(data) if exports["jim_bridge"]:redProgressBar({
if exports.jim_bridge:redProgressBar({
label = data.label, label = data.label,
time = debugMode and 1000 or data.time, time = debugMode and 1000 or data.time,
dict = data.dict, dict = data.dict,
@@ -172,99 +175,32 @@ local progressFunc = {
disableMovement = data.disableMovement or false, disableMovement = data.disableMovement or false,
cancel = data.cancel or true, cancel = data.cancel or true,
}) then }) 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.
---
--- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
--- It supports shared progress bars between players, animations, camera effects, and more.
---
---@param data table A table containing the progress bar configuration.
--- - **label** (`string`): The text label to display on the progress bar.
--- - **time** (`number`): The duration of the progress bar in milliseconds.
--- - **dict** (`string`, optional): The animation dictionary to use.
--- - **anim** (`string`, optional): The animation name to play.
--- - **task** (`string`, optional): The task scenario to perform.
--- - **flag** (`number`, optional): The animation flag.
--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
--- - **icon** (`string`, optional): The icon to display (for qb progress bar).
--- - **cam** (`number`, optional): The camera handle to use.
--- - **shared** (`table`, optional): Data for shared progress bars.
--- - **pid** (`number`): The player ID to share the progress bar with.
--- - **label** (`string`): The label to display on the shared progress bar.
---
--- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
---
---@usage
--- ```lua
--- local success = progressBar({
--- label = "Processing...",
--- time = 5000,
--- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
--- anim = "base",
--- flag = 49,
--- cancel = true,
--- })
--- ```
local progresssBarActive = false
function progressBar(data)
if progresssBarActive then return else progresssBarActive = true end
local ped = PlayerPedId()
if data.shared then
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
storedPID = data.shared.pid
TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
end
local result = nil
if data.cam then startTempCam(data.cam) end
if progressFunc[Config.System.ProgressBar].start(data) then
result = true result = true
else else
result = false result = false
end end
elseif Config.System.ProgressBar == "gta" then
if exports["jim_bridge"]:gtaProgressBar({
label = data.label,
time = debugMode and 1000 or data.time,
dict = data.dict,
anim = data.anim,
flag = data.flag or 32,
task = data.task,
disableMovement = data.disableMovement or false,
cancel = data.cancel or true,
}) then
result = true
else
result = false
end
end
while result == nil do 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)
@@ -285,7 +221,6 @@ 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
@@ -293,8 +228,15 @@ 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()
progressFunc[Config.System.ProgressBar].stop() if Config.System.ProgressBar == "ox" then
progresssBarActive = false exports[OXLibExport]:cancelProgress()
elseif Config.System.ProgressBar == "qb" then
TriggerEvent("progressbar:client:cancel")
elseif Config.System.ProgressBar == "lation" then
exports.lation_ui:cancelProgress()
elseif Config.System.ProgressBar == "gta" or Config.System.ProgressBar == "red" then
exports["jim_bridge"]:stopProgressBar()
end
end end
-- System to handle sending/sharing progress bars between players -- -- System to handle sending/sharing progress bars between players --

View File

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

View File

@@ -6,102 +6,6 @@
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
------------------------------------------------------------- -------------------------------------------------------------
@@ -116,11 +20,26 @@ local metaDataFunc = {
--- local player = GetPlayer(playerId) --- local player = GetPlayer(playerId)
--- ``` --- ```
function GetPlayer(source) function GetPlayer(source)
for i = 1, #metaDataFunc do if isStarted(QBExport) then
local framework = metaDataFunc[i] debugPrint("^6Bridge^7: ^3GetPlayer^7() QBExport")
if isStarted(framework.framework) then return exports[QBExport]:GetCoreObject().Functions.GetPlayer(source)
return framework.GetPlayer(source)
end elseif isStarted(QBXExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() QBOXExport")
return exports[QBXExport]:GetCoreObject().Functions.GetPlayer(source)
elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() ESXExport")
return ESX.GetPlayerFromId(source)
elseif isStarted(OXCoreExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() OXCoreExport")
return exports[OXCoreExport]:GetPlayer(source)
elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^3GetPlayer^7() RSGExport")
return exports[RSGExport]:GetCoreObject().Functions.GetPlayer(source)
end end
return nil return nil
end end
@@ -139,22 +58,29 @@ end
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- local myMeta = getPlayerMetadata(player, "myKey") --- local myMeta = GetMetadata(player, "myKey")
--- ``` --- ```
function getPlayerMetadata(player, key) function GetMetadata(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
-- else grab server metadata about the player if isStarted(QBExport) or isStarted(QBXExport) then
for i = 1, #metaDataFunc do debugPrint("^6Bridge^7: ^3GetMetadata^7() QBExport/QBXExport", key)
local framework = metaDataFunc[i] return player.PlayerData.metadata[key]
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
@@ -172,11 +98,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] = getPlayerMetadata(player, k) Metadata[k] = GetMetadata(player, k)
end end
return Metadata return Metadata
elseif type(key) == "string" then elseif type(key) == "string" then
return getPlayerMetadata(player, key) return GetMetadata(player, key)
end end
end) end)
@@ -194,26 +120,38 @@ end)
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
--- setPlayerMetadata(player, "myKey", "newValue") --- SetMetadata(player, "myKey", "newValue")
--- ``` --- ```
function setPlayerMetadata(player, key, value) function SetMetadata(player, key, value)
debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata^7...") debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata for key: "..key)
for i = 1, #metaDataFunc do if isStarted(QBExport) or isStarted(QBXExport) then
local framework = metaDataFunc[i] debugPrint("^6Bridge^7: ^3SetMetadata^7() using QBExport/QBXExport")
if isStarted(framework.framework) then player.Functions.SetMetaData(key, value)
debugPrint("^6Bridge^7: ^3SetMetadata^7() ^3"..framework.framework.."^7", key, value)
return framework.GetPlayerMetadata(player, key) elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using ESXExport")
player.setMeta(key, value)
elseif isStarted(OXCoreExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using OXCoreExport")
player.set(key, value)
elseif isStarted(RSGExport) then
debugPrint("^6Bridge^7: ^3SetMetadata^7() using RSGExport")
player.Functions.SetMetaData(key, value)
end end
end end
debugPrint("^6Bridge^7: ^1Error setting metadata^7, ^1framework not supported^7?")
end
-- Register a server callback for setting metadata. -- Register a server callback for setting metadata.
createCallback(getScript()..":server:setPlayerMetadata", function(source, key, value) createCallback(getScript()..":server:SetMetadata", function(source, key, value)
local src = source debugPrint("SetMetadata callback triggered for source:", source, "key:", key, "value:", value)
debugPrint("SetMetadata callback triggered for source:", src, "key:", key, "value:", value) local player = GetPlayer(source)
local player = GetPlayer(src) --[[if not player then
setPlayerMetadata(player, key, value) print("Error setting metadata: player not found for source "..tostring(source))
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,90 +8,10 @@
• 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
@@ -111,12 +31,60 @@ local notifyFunc = {
--- 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?.Notify then debugPrint("Notify triggered but not set up") return end if not Config.System or not Config.System.Notify then debugPrint("Notify triggered but not set up") return end
if Config.System.Notify == "okok" then
if src then if not src then
notifyFunc[Config.System.Notify].server(title, message, type, src) TriggerEvent('okokNotify:Alert', title, message, 6000, type)
else else
notifyFunc[Config.System.Notify].client(title, message, type) 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 isStarted("jim-redui") then
if not src then
TriggerEvent("jim-redui:Notify", title, message, type)
else
TriggerClientEvent("jim-redui:Notify", src, title, message, type)
end
end
end end
end end
@@ -129,6 +97,7 @@ 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,74 +5,110 @@
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
]] ]]
local phoneFunc = { --- Sends a phone mail using the detected phone system.
{ phone = "gksphone", --- The function iterates through a prioritized list of supported phone systems.
sendMail = function(mailData) --- Once an active system is found (via `isStarted`), the corresponding mail function is executed.
---
--- @param data table A table containing the mail data.
--- - subject (string): The email subject.
--- - sender (string): The sender identifier.
--- - message (string): The email body content.
--- - actions (table|nil): Optional action buttons for the email.
--- @usage
--- sendPhoneMail({
--- subject = "Welcome!",
--- sender = "Admin",
--- message = "Thank you for joining our server.",
--- actions = {
--- { label = "Reply", action = replyFunction }
--- }
--- })
function sendPhoneMail(data)
-- Define each supported phone system and its corresponding mail-sending function.
local phoneSystems = {
{ name = "gksphone",
send = function(mailData)
exports["gksphone"]:SendNewMail(mailData) exports["gksphone"]:SendNewMail(mailData)
end, end,
sendInvoice =
function(mailData)
-- Defensive check for required fields
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "label" }
for _, field in ipairs(required) do
if mailData[field] == nil then
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
return
end
end
MySQL.Async.execute(
'INSERT INTO gksphone_invoices (citizenid, amount, society, sender, sendercitizenid, label) VALUES (@citizenid, @amount, @society, @sender, @sendercitizenid, @label)',
{
['@citizenid'] = mailData.billedCitizenid,
['@amount'] = mailData.amount,
['@society'] = mailData.job,
['@sender'] = mailData.name,
['@sendercitizenid'] = mailData.billerCitizenid,
['@label'] = mailData.label
}
)
end,
}, },
{ name = "yflip-phone",
{ phone = "yflip-phone", send = function(mailData)
sendMail =
function(mailData)
TriggerServerEvent(getScript()..":yflip:SendMail", mailData) TriggerServerEvent(getScript()..":yflip:SendMail", mailData)
end, end,
}, },
{ name = "qs-smartphone",
{ phone = "roadphone", send = function(mailData)
sendMail = TriggerServerEvent('qs-smartphone:server:sendNewMail', mailData)
function(mailData) end,
},
{ name = "qs-smartphone-pro",
send = function(mailData)
TriggerServerEvent('phone:sendNewMail', mailData)
end,
},
{ name = "roadphone",
send = function(mailData)
-- Convert HTML line breaks to newlines for roadphone. -- 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",
{ phone = "lb-phone", send = function(mailData)
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",
{ phone = "qb-phone", send = function(mailData)
sendMail =
function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData) TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end, end,
sendInvoice = },
function(mailData) { name = "npwd_qbx_mail",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
{ name = "jpr-phonesystem",
send = function(mailData)
TriggerServerEvent(getScript()..":jpr:SendMail", mailData)
end,
},
{ name = "ef-phone",
send = function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
},
}
-- Check each phone system in order and use the first active one.
for _, phone in ipairs(phoneSystems) do
if isStarted(phone.name) then
debugPrint("^6Bridge^7[^3"..phone.name.."^7]: ^2Sending mail to player")
phone.send(data)
return true
end
end
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
return false
end
function sendPhoneInvoice(data)
if not data.src then return end
-- Define each supported phone system and its corresponding mail-sending function.
local phoneSystems = {
{
name = "qb-phone",
send = function(mailData)
-- Defensive check for required fields -- 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
@@ -98,76 +134,75 @@ local phoneFunc = {
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
{ phone = "npwd_qbx_mail", -- Safe insert with all parameters present
sendMail = MySQL.Async.insert(
function(mailData) 'INSERT INTO phone_invoices (citizenid, amount, society, sender, sendercitizenid) VALUES (?, ?, ?, ?, ?)',
TriggerServerEvent('qb-phone:server:sendNewMail', mailData) {
end, mailData.billedCitizenid,
mailData.amount,
mailData.job,
mailData.name,
mailData.billerCitizenid
}, },
function(id)
-- if id then
-- TriggerClientEvent('qb-phone:client:AcceptorDenyInvoice', mailData.src, id, mailData.name, mailData.job, mailData.billerCitizenid, mailData.amount, GetInvokingResource())
-- end
end
)
{ phone = "jpr-phonesystem",
sendMail = end
function(mailData)
TriggerServerEvent(getScript()..":jpr:SendMail", mailData)
end,
},
{ phone = "ef-phone",
sendMail =
function(mailData)
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
end,
}, },
{
name = "gks-phone",
send = function(mailData)
-- Defensive check for required fields
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "label" }
for _, field in ipairs(required) do
if mailData[field] == nil then
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
return
end
end
MySQL.Async.execute(
'INSERT INTO gksphone_invoices (citizenid, amount, society, sender, sendercitizenid, label) VALUES (@citizenid, @amount, @society, @sender, @sendercitizenid, @label)',
{
['@citizenid'] = mailData.billedCitizenid,
['@amount'] = mailData.amount,
['@society'] = mailData.job,
['@sender'] = mailData.name,
['@sendercitizenid'] = mailData.billerCitizenid,
['@label'] = mailData.label
}
)
end
}
} }
--- Sends a phone mail using the detected phone system.
--- The function iterates through a prioritized list of supported phone systems.
--- Once an active system is found (via `isStarted`), the corresponding mail function is executed.
---
--- @param data table A table containing the mail data.
--- - subject (string): The email subject.
--- - sender (string): The sender identifier.
--- - message (string): The email body content.
--- - actions (table|nil): Optional action buttons for the email.
--- @usage
--- sendPhoneMail({
--- subject = "Welcome!",
--- sender = "Admin",
--- message = "Thank you for joining our server.",
--- actions = {
--- { label = "Reply", action = replyFunction }
--- }
--- })
function sendPhoneMail(mailData)
-- Check each phone system in order and use the first active one. -- Check each phone system in order and use the first active one.
for i = 1, #phoneFunc do for _, phone in ipairs(phoneSystems) do
local script = phoneFunc[i] if isStarted(phone.name) then
if isStarted(script.phone) and script.sendMail then debugPrint("^6Bridge^7[^3"..phone.name.."^7]: ^2Sending mail to player^7", data.src)
debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player") phone.send(data)
script.sendMail(mailData)
return true
end
end
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
return false
end
function sendPhoneInvoice(data)
if not data.src then return end
-- Check each phone system in order and use the first active one.
for i = 1, #phoneFunc do
local script = phoneFunc[i]
if isStarted(script.phone) and script.sendInvoice then
debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player^7", data.src)
script.sendInvoice(data)
return true return true
end end
end end
@@ -240,6 +275,4 @@ 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,68 +11,6 @@
• 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
------------------------------------------------------------- -------------------------------------------------------------
@@ -103,16 +41,26 @@ local polyCreation = {
---}) ---})
---``` ---```
function createPoly(data) function createPoly(data)
for i = 1, #polyCreation do local Location = nil
local script = polyCreation[i] if isStarted(OXLibExport) then
if isStarted(script.polyZoneScript) then debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..OXLibExport.."^7': "..data.name)
debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..script.polyZoneScript.."^7': "..data.name) -- Convert 2D points to 3D with a fixed Z coordinate (e.g., 12.0)
return script.createPoly(data) for i = 1, #data.points do
end 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
print("^4ERROR^7: ^2No PolyZone creation script detected ^7") end)
return nil else
print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3exports^1.^2lua^7")
end
return Location
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -144,17 +92,25 @@ end
--- }) --- })
--- ``` --- ```
function createCirclePoly(data) function createCirclePoly(data)
for i = 1, #polyCreation do local Location = nil
local script = polyCreation[i] if isStarted(OXLibExport) then
if isStarted(script.polyZoneScript) then debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..OXLibExport.." "..data.name)
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..script.polyZoneScript.." "..data.name) Location = lib.zones.sphere(data)
elseif isStarted("PolyZone") then
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7PolyZone ".. data.name)
Location = CircleZone:Create(data.coords, data.radius, { name = data.name, debugPoly = debugMode })
Location:onPlayerInOut(function(isPointInside)
if isPointInside then
data.onEnter()
else
data.onExit()
end
end)
else
print("^4ERROR^7: ^2No PolyZone creation script detected ^7- ^2Check ^3starter^1.^2lua^7")
end
debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius) debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
return script.createCircle(data) return Location
end
end
print("^4ERROR^7: ^2No PolyZone creation script detected ^7")
return nil
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -174,12 +130,11 @@ end
--- removePolyZone(zone) --- removePolyZone(zone)
--- ``` --- ```
function removePolyZone(Location) function removePolyZone(Location)
for i = 1, #polyCreation do if isStarted(OXLibExport) then
local script = polyCreation[i] debugPrint("^6Bridge^7: ^2Removing ^2poly with ^7"..OXLibExport)
if isStarted(script.polyZoneScript) then Location:remove()
debugPrint("^6Bridge^7: ^2Removing ^3"..script.polyZoneScript.." ^2Zone^7") elseif isStarted("PolyZone") then
script.removeZone(Location) debugPrint("^6Bridge^7: ^2poly with ^7PolyZone")
break Location:destroy()
end
end end
end end

266
shared/shops.lua Normal file
View File

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

View File

@@ -1,49 +1,18 @@
local skillCheckFunc = { local activeSkillCheck = false
qb = {
start = function(data) function skillCheck(data)
local result = false
if Config.System.skillCheck == "qb" then
local Skillbar = exports["qb-minigames"]:Skillbar() local Skillbar = exports["qb-minigames"]:Skillbar()
if Skillbar then if Skillbar then
return true result = true
else else
return false result = false
end end
end,
}, elseif Config.System.skillCheck == "ox" then
ox = {
start = function(data)
local Skillbar = exports[OXLibExport]:skillCheck( 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", "easy",
@@ -56,18 +25,34 @@ local skillCheckFunc = {
"4" "4"
}) })
if Skillbar then if Skillbar then
return true result = true
else else
return false result = false
end end
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
function skillCheck(data) else
if Config.System.skillCheck then result = false
return skillCheckFunc[Config.System.skillCheck].start(data)
end end
return true else
result = true
end
return result
end end

View File

@@ -11,190 +11,6 @@
• Tgiann-bank • Tgiann-bank
]] ]]
local societyFunc = {
{ bankName = "Renewed-Banking",
getAccount =
function(society)
return exports["Renewed-Banking"]:getAccountMoney(society)
end,
chargeSociety =
function(society, amount)
exports['Renewed-Banking']:removeAccountMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports['Renewed-Banking']:addAccountMoney(society, amount)
end,
},
{ bankName = "fd_banking",
getAccount =
function(society)
return exports["fd_banking"]:GetAccount(society)
end,
chargeSociety =
function(society, amount)
exports["fd_banking"]:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports["fd_banking"]:AddMoney(society, amount)
end,
},
{ bankName = "okokBanking",
getAccount =
function(society)
return exports['okokBanking']:GetAccount(society)
end,
chargeSociety =
function(society, amount)
exports['okokBanking']:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
exports['okokBanking']:AddMoney(society, amount)
end,
},
{ bankName = "tgiann-bank",
getAccount =
function(society)
if Jobs[society] then
return exports["tgiann-bank"]:GetJobAccountBalance(society)
else
return exports["tgiann-bank"]:GetGangAccountBalance(society)
end
end,
chargeSociety =
function(society, amount)
if Jobs[society] then
exports["tgiann-bank"]:RemoveJobMoney(society, amount)
else
exports["tgiann-bank"]:RemoveGangMoney(society, amount)
end
end,
fundSociety =
function(society, amount)
if Jobs[society] then
exports["tgiann-bank"]:AddJobMoney(society, amount)
else
exports["tgiann-bank"]:AddGangMoney(society, amount)
end
end,
},
{ bankName = "qb-banking",
getAccount =
function(society)
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
return exports["qb-banking"]:GetAccountBalance(society)
end,
chargeSociety =
function(society, amount)
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null"
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null"
end
end
exports["qb-banking"]:RemoveMoney(society, amount)
end,
fundSociety =
function(society, amount)
if not exports["qb-banking"]:GetAccount(society) then
if Jobs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
exports["qb-banking"]:AddMoney(society, amount)
end,
},
{ bankName = "esx_society",
getAccount =
function(society)
local checkExist = exports.esx_society:GetSociety(society)
if checkExist == nil then
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
exports.esx_society:registerSociety(
society,
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
society,
society,
society,
{type = "public"}
)
end
local p = promise.new()
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
p:resolve(account.money)
end)
local newAmount = Citizen.Await(p)
return newAmount
end,
chargeSociety =
function(society, amount)
local checkExist = exports.esx_society:GetSociety(society)
if checkExist == nil then
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
exports.esx_society:registerSociety(
society,
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
society,
society,
society,
{type = "public"}
)
end
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
if amount > 0 and account.money >= amount then
account.removeMoney(amount)
end
end)
end,
fundSociety =
function(society, amount)
local checkExist = exports.esx_society:GetSociety(society)
if checkExist == nil then
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
exports.esx_society:registerSociety(
society,
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
society,
society,
society,
{type = "public"}
)
end
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
account.addMoney(amount)
end)
end,
},
}
--- Retrieves the current balance of a society's bank account. --- 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.
@@ -204,19 +20,56 @@ local societyFunc = {
--- print("Police account balance: $"..balance) --- print("Police account balance: $"..balance)
--- ``` --- ```
function getSocietyAccount(society) function getSocietyAccount(society)
local amount = 0 local bankScript, amount = "", 0
if society == nil or society == "none" then return amount end if society == nil or society == "none" then return amount end
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
local amount = script.getAccount(society) if Jobs[society] then
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Retrieved account ^7'^6"..society.."^7' ($"..tostring(amount)..")") print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
return amount exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
amount = exports["qb-banking"]:GetAccountBalance(society)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- -- Since esx_society does not have a native client export for retrieving money,
-- -- we use a server callback to get the final amount.
-- amount = triggerCallback(getScript()..":getESXSocietyAccount", society) or 0
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
amount = exports["Renewed-Banking"]:getAccountMoney(society)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
amount = exports["fd_banking"]:GetAccount(society)
elseif isStarted("okokBanking") then
bankScript = "okokBanking"
amount = exports['okokBanking']:GetAccount(society)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
amount = exports["tgiann-bank"]:GetJobAccountBalance(society)
else
amount = exports["tgiann-bank"]:GetGangAccountBalance(society)
end end
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
@@ -229,18 +82,52 @@ end
--- chargeSociety("police", 1000) --- chargeSociety("police", 1000)
--- ``` --- ```
function chargeSociety(society, amount) function chargeSociety(society, amount)
local bankScript, newAmount = "", 0
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.chargeSociety(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: ^2Removing $"..amount.." from account '^6"..society.."^7' ($"..tostring(newAmount)..")") exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null"
return elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null"
end
end
exports["qb-banking"]:RemoveMoney(society, amount)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- TriggerEvent("esx_society:withdrawMoney", society, amount)
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
exports['Renewed-Banking']:removeAccountMoney(society, amount)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
exports["fd_banking"]:RemoveMoney(society, amount)
elseif isStarted("okokBanking") then
bankScript = "okokBanking"
exports['okokBanking']:RemoveMoney(society, amount)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
exports["tgiann-bank"]:RemoveJobMoney(society, amount)
else
exports["tgiann-bank"]:RemoveGangMoney(society, amount)
end end
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.
@@ -251,16 +138,66 @@ 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
local script = societyFunc[i] if isStarted("qb-banking") then
if isStarted(script.bankName) then bankScript = "qb-banking"
script.fundSociety(society, amount) if not exports["qb-banking"]:GetAccount(society) then
local newAmount = getSocietyAccount(society) if Jobs[society] then
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Adding ^7$"..amount.." ^2to account ^7'^6"..society.."^7' ($"..tostring(newAmount)..")") print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
return exports["qb-banking"]:CreateJobAccount(society, 0)
Wait(150)
elseif Gangs[society] then
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
exports["qb-banking"]:CreateGangAccount(society, 0)
Wait(150)
end
end
exports["qb-banking"]:AddMoney(society, amount)
--elseif isStarted("esx_society") then
-- bankScript = "esx_society"
-- -- Use the esx_society event to deposit money.
-- TriggerServerEvent('esx_society:depositMoney', society, amount)
-- -- Use callback to return the updated balance.
-- newAmount = triggerCallback(getScript()..":getESXSocietyAccount", society) or 0
elseif isStarted("Renewed-Banking") then
bankScript = "Renewed-Banking"
exports['Renewed-Banking']:addAccountMoney(society, amount)
newAmount = exports["Renewed-Banking"]:getAccountMoney(society)
elseif isStarted("fd_banking") then
bankScript = "fd_banking"
exports["fd_banking"]:AddMoney(society, amount)
elseif isStarted("okokBanking") then
bankScript = "okokBanking"
exports['okokBanking']:AddMoney(society, amount)
elseif isStarted("tgiann-bank") then
bankScript = "tgiann-bank"
if Jobs[society] then
exports["tgiann-bank"]:AddJobMoney(society, amount)
else
exports["tgiann-bank"]:AddGangMoney(society, amount)
end end
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
-- other
if isStarted("esx_society") then
createCallback(getScript()..":getESXSocietyAccount", function(source, society)
-- Example query adjust table/field names to match your esx_society implementation.
local result = MySQL.scalar.await('SELECT money FROM society_money WHERE society = ?', { society })
return result or 0
end)
end end

700
shared/stashcontrol.lua Normal file
View File

@@ -0,0 +1,700 @@
--[[
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,206 +17,17 @@
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
return 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
return 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 }
return 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 }
return 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
------------------------------------------------------------- -------------------------------------------------------------
-- Tables for storing created targets for the fallback system and zone management. -- Tables for storing created targets for the fallback system and zone management.
local TextTargets = {} -- For fallback DrawText3D targets.
local targetEntities = {} -- For entity targets. local targetEntities = {} -- For entity targets.
local boxTargets = {} -- For box-shaped zone targets. local boxTargets = {} -- For box-shaped zone targets.
local circleTargets = {} -- For circular zone targets. local circleTargets = {} -- For circular zone targets.
local modelTargets = {}
------------------------------------------------------------- -------------------------------------------------------------
-- Entity Target Creation -- Entity Target Creation
------------------------------------------------------------- -------------------------------------------------------------
@@ -252,22 +63,33 @@ 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
-- if force target off, use jim_bridge buiilt in target functions -- Fallback: Use DrawText3D if targeting systems are disabled or unavailable.
if Config.System.DontUseTarget then if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
exports.jim_bridge:createEntityTarget(entity, opts, dist) exports.jim_bridge:createEntityTarget(entity, opts, dist)
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6jim_bridge ^2for entity ^7"..entity)
return
end
-- Check for target script and use that elseif isStarted(OXTargetExport) then
for i = 1, #targetFunc do debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..OXTargetExport.." ^2for entity ^7"..entity)
local script = targetFunc[i] local options = {}
if isStarted(script.targetName) then for i = 1, #opts do
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..script.targetName.." ^2for entity ^7"..entity) options[i] = {
return script.entityTarget(entity, opts, dist) icon = opts[i].icon,
end 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 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 end
------------------------------------------------------------- -------------------------------------------------------------
@@ -324,25 +146,47 @@ 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
-- if force target off, use jim_bridge buiilt in target functions debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6DrawText ^2 for zone ^7"..data[1])
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)
end
-- Check for target script and use that elseif isStarted(OXTargetExport) then
for i = 1, #targetFunc do debugPrint("^6Bridge^7: ^2Creating new ^3Box^2 target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local script = targetFunc[i] local options = {}
if isStarted(script.targetName) then for i = 1, #opts do
debugPrint("^6Bridge^7: ^2Creating new ^3Box ^2target with ^6"..script.targetName.." ^7"..data[1]) options[i] = {
local target = script.boxTarget(data, opts, dist) 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 boxTargets[#boxTargets + 1] = target
return 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
end end
return nil
end
------------------------------------------------------------- -------------------------------------------------------------
-- Circle Zone Target Creation -- Circle Zone Target Creation
@@ -381,27 +225,41 @@ end
--- }, 2.0) --- }, 2.0)
--- ``` --- ```
function createCircleTarget(data, opts, dist) function createCircleTarget(data, opts, dist)
-- if force target off, use jim_bridge buiilt in target functions
if Config.System.DontUseTarget then if Config.System.DontUseTarget then
debugPrint("^6Bridge^7: ^2Creating new ^3Sphere ^2target with ^6jim_bridge ^7"..data[1]) debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6DrawText ^2for zone ^7"..data[1])
return exports.jim_bridge:createZoneTarget(data, opts, dist) return exports.jim_bridge:createZoneTarget(data, opts, dist)
end
-- Check for target script and use that elseif isStarted(OXTargetExport) then
for i = 1, #targetFunc do debugPrint("^6Bridge^7: ^2Creating new ^3Circle ^2target with ^6"..OXTargetExport.." ^2for zone ^7"..data[1])
local script = targetFunc[i] local options = {}
if isStarted(script.targetName) then for i = 1, #opts do
debugPrint("^6Bridge^7: ^2Creating new ^3Sphere ^2target with ^6"..script.targetName.." ^7"..data[1]) options[i] = {
local target = script.circleTarget(data, opts, dist) 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 circleTargets[#circleTargets + 1] = target
return 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
end end
return nil
end
------------------------------------------------------------- -------------------------------------------------------------
-- Model Target Creation -- Model Target Creation
------------------------------------------------------------- -------------------------------------------------------------
@@ -428,24 +286,29 @@ 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
-- if force target off, use jim_bridge buiilt in target functions
if Config.System.DontUseTarget then
debugPrint("^6Bridge^7: ^2Creating new ^3Model ^2target with ^6jim_bridge^7")
return exports.jim_bridge:createModelTarget(models, opts, dist) return exports.jim_bridge:createModelTarget(models, opts, dist)
end
-- Check for target script and use that elseif isStarted(OXTargetExport) then
for i = 1, #targetFunc do debugPrint("^6Bridge^7: ^2Creating new ^3Model^2 target with ^6"..OXTargetExport)
local script = targetFunc[i] local options = {}
if isStarted(script.targetName) then for i = 1, #opts do
debugPrint("^6Bridge^7: ^2Creating new ^3Model ^2target with ^6"..script.targetName.."^7") options[i] = {
local target = script.modelTarget(models, opts, dist) icon = opts[i].icon,
modelTargets[#modelTargets + 1] = target label = opts[i].label,
return target 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 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
end end
------------------------------------------------------------- -------------------------------------------------------------
@@ -461,20 +324,15 @@ end
--- removeEntityTarget(entityId) --- removeEntityTarget(entityId)
--- ``` --- ```
function removeEntityTarget(entity) function removeEntityTarget(entity)
if isStarted(QBTargetExport) then
if Config.System.DontUseTarget then exports[QBTargetExport]:RemoveTargetEntity(entity)
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeLocalEntity(entity, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeEntityTarget(entity) 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.
@@ -487,19 +345,15 @@ end
--- removeZoneTarget(targetObject) --- removeZoneTarget(targetObject)
--- ``` --- ```
function removeZoneTarget(target) function removeZoneTarget(target)
if isStarted(QBTargetExport) then
if Config.System.DontUseTarget then exports[QBTargetExport]:RemoveZone(target)
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeZone(target, true)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeZoneTarget(target) 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.
@@ -511,20 +365,121 @@ end
--- removeModelTarget(model) --- removeModelTarget(model)
--- ``` --- ```
function removeModelTarget(model) function removeModelTarget(model)
if isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveTargetModel(model, "Test")
end
if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeModel(model, nil)
end
if Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport)) then
exports.jim_bridge:removeZoneTarget(target)
end
end
-------------------------------------------------------------
-- Fallback: DrawText3D Targets (Experimental)
-------------------------------------------------------------
if Config.System.DontUseTarget then -- If no targeting system is detected and this is a client script, use DrawText3D for targets.
exports.jim_bridge:removeZoneTarget(model) if (Config.System.DontUseTarget or (not isStarted(OXTargetExport) and not isStarted(QBTargetExport))) and not isServer() then
CreateThread(function()
while true do
local ped = PlayerPedId()
local pedCoords = GetEntityCoords(ped)
local camCoords = GetGameplayCamCoord()
local camRot = GetGameplayCamRot(2)
local camForward = RotationToDirection(camRot)
local closestTarget = nil
local closestDist = math.huge
local targetEntity = nil
-- Shallow copy for safety
local targetsCopy = {}
for k, v in pairs(TextTargets) do
targetsCopy[k] = v
end end
-- Check for target script and use that -- Detect models and update coords
for i = 1, #targetFunc do for _, target in pairs(targetsCopy) do
local script = targetFunc[i] if target.models then
if isStarted(script.targetName) then if not target.entity or not DoesEntityExist(target.entity) then
script.removeTargetModel(model) 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 break
end end
end end
else
target.coords = GetEntityCoords(target.entity)
end end
end
end
-- Identify closest visible target
for _, target in pairs(targetsCopy) do
if target.coords then
local dist = #(pedCoords - target.coords)
local vecToTarget = target.coords - camCoords
local normVec = normalizeVector(vecToTarget)
local dot = camForward.x * normVec.x + camForward.y * normVec.y + camForward.z * normVec.z
local isFacing = dot > 0.5
if dist <= target.dist and isFacing then
if dist < closestDist then
closestDist = dist
closestTarget = target
targetEntity = target.entity
end
end
end
end
-- Render + handle input
for _, target in pairs(targetsCopy) do
if target.coords and #(pedCoords - target.coords) <= target.dist then
local isClosest = (target == closestTarget)
for _, opt in ipairs(target.options) do
if IsControlJustPressed(0, opt.key) and isClosest then
local canInteract = (not target.canInteract or target.canInteract())
local hasItem = (not opt.item or hasItem(opt.item))
local hasJob = (not opt.job or hasJob(opt.job, nil))
if canInteract and hasItem and hasJob then
if opt.onSelect then opt.onSelect(targetEntity) end
if opt.action then opt.action(targetEntity) end
end
end
end
-- Draw each eligible text line
local baseZ = target.coords.z + 1.0
local lineHeight = -0.16
local lineOffset = 0
for i, opt in ipairs(target.options) do
local canInteract = (not target.canInteract or target.canInteract())
local hasItem = (not opt.item or hasItem(opt.item))
local hasJob = (not opt.job or hasJob(opt.job, nil))
if canInteract and hasItem and hasJob then
local text = target.buttontext[i]
local zOffset = lineOffset * lineHeight
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + zOffset), text, isClosest)
lineOffset += 1
end
end
end
end
Wait(0)
end
end)
end
function ShowFloatingHelpNotification(coord, text, highlight) function ShowFloatingHelpNotification(coord, text, highlight)
AddTextEntry("FloatingText", text) AddTextEntry("FloatingText", text)
@@ -534,11 +489,16 @@ 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
------------------------------------------------------------- -------------------------------------------------------------
local function CleanupTargets() -- When the current resource stops, remove all targets.
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
@@ -552,7 +512,7 @@ local function CleanupTargets()
if isStarted(OXTargetExport) then if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeZone(boxTargets[i], true) exports[OXTargetExport]:removeZone(boxTargets[i], true)
elseif isStarted(QBTargetExport) then elseif isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveZone(boxTargets[i]) exports[QBTargetExport]:RemoveZone(boxTargets[i].name)
end end
end end
-- Remove circle zone targets. -- Remove circle zone targets.
@@ -560,16 +520,7 @@ local function CleanupTargets()
if isStarted(OXTargetExport) then if isStarted(OXTargetExport) then
exports[OXTargetExport]:removeZone(circleTargets[i], true) exports[OXTargetExport]:removeZone(circleTargets[i], true)
elseif isStarted(QBTargetExport) then elseif isStarted(QBTargetExport) then
exports[QBTargetExport]:RemoveZone(circleTargets[i]) 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 joaat(v.hash) == model or joaat(v.model) == model then if tonumber(v.hash) == model or GetHashKey(v.hash) == model or GetHashKey(v.model) == model then
debugPrint("^6Bridge^7: ^2Vehicle info found in^7 ^4Vehicles^7 ^2table^7: ^6"..(v.hash and v.hash or v.model).. " ^7(^6"..Vehicles[k].name.."^7)") 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,28 +105,16 @@ end
function getVehicleProperties(vehicle) function getVehicleProperties(vehicle)
if not vehicle then return nil end if not vehicle then return nil end
local propertyFunc = { local properties = {}
{ framework = OXLibExport, if isStarted(QBExport) and not isStarted(QBXExport) then
func = function(vehicle) properties = Core.Functions.GetVehicleProperties(vehicle)
return lib.getVehicleProperties(vehicle) debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
end,
},
{ framework = QBExport,
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
return nil elseif isStarted(OXLibExport) then
properties = lib.getVehicleProperties(vehicle)
debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
end
return properties
end end
--- Sets the properties of a given vehicle if changes are detected. --- Sets the properties of a given vehicle if changes are detected.
@@ -240,7 +228,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: ^3pushEnt^7: ^2Requesting network control of vehicle^7.") debugPrint("^6Bridge^7: ^3pushVehicle^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
@@ -248,13 +236,13 @@ function pushVehicle(entity)
timeout = timeout - 100 timeout = timeout - 100
end end
if NetworkHasControlOfEntity(entity) then if NetworkHasControlOfEntity(entity) then
debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Network now has control of the entity^7.") debugPrint("^6Bridge^7: ^3pushVehicle^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: ^3pushEnt^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.") debugPrint("^6Bridge^7: ^3pushVehicle^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
@@ -262,23 +250,18 @@ function pushVehicle(entity)
timeout = timeout - 100 timeout = timeout - 100
end end
if IsEntityAMissionEntity(entity) then if IsEntityAMissionEntity(entity) then
debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") debugPrint("^6Bridge^7: ^3pushVehicle^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 closestVehicle The closest vehicle entity and its distance. --- @return number|number closestVehicle|closestDistance The closest vehicle entity and its distance.
--- @return number closestDistance The distance of the closest vehicle.
--- ---
--- @usage --- @usage
--- ```lua --- ```lua
@@ -288,11 +271,9 @@ 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
@@ -309,7 +290,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.xyz) local distance = #(vehicleCoords - coords)
if closestDistance == -1 or distance < closestDistance then if closestDistance == -1 or distance < closestDistance then
closestDistance = distance closestDistance = distance
@@ -319,61 +300,3 @@ 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

@@ -1,3 +1,4 @@
--- Registers a command with the active command system. --- Registers a command with the active command system.
--- This function supports multiple command systems (OXLib, qb-core, ESX Legacy). --- This function supports multiple command systems (OXLib, qb-core, ESX Legacy).
--- ---
@@ -14,62 +15,30 @@
--- 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" },
--- nil,
--- function(source, args) print("Hello, "..args[1].."!") end, --- function(source, args) print("Hello, "..args[1].."!") end,
--- nil,
--- "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, lib.addCommand(command, { help = options[1], restricted = options[5] and "group."..options[5] or nil }, options[4])
{
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, Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
optionTable.helpInfo,
optionTable.subText,
optionTable.argsRequired,
optionTable.funct,
optionTable.restriction or nil
)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
commandResource = RSGExport commandResource = RSGExport
Core.Commands.Add(command, Core.Commands.Add(command, options[1], options[2], options[3], options[4], options[5] or nil)
optionTable.helpInfo,
optionTable.subText,
optionTable.argsRequired,
optionTable.funct,
optionTable.restriction or nil
)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
commandResource = ESXExport commandResource = ESXExport
ESX.RegisterCommand(command, ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError)
optionTable.restriction or 'admin', options[4](xPlayer.source, args, showError)
function(xPlayer, args, showError) end, false, { help = options[1] })
optionTable.funct(xPlayer.source, args, showError)
end,
false,
{ help = options[1] }
)
end end
if commandResource ~= "" then if commandResource ~= "" then

View File

@@ -9,6 +9,7 @@ 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",
@@ -24,12 +25,7 @@ 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
@@ -153,6 +149,7 @@ 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',
@@ -170,13 +167,14 @@ 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',
'scaleEntity.lua', 'scaleEntity.lua',
'vehicles.lua', 'vehicles.lua',
'effects.lua', 'effects.lua',
'make/ropeControl.lua',
--'warmenu.lua', --'warmenu.lua',
'_authToken.lua', '_authToken.lua',

View File

@@ -1,289 +0,0 @@
local alcoholCount = 0
local drugCount = 0
local purgeTimer = 300000
local minDrugCount = 3
local maxDrugCount = 7
local minAlcoholCount = 2
local midAlcoholCount = 4
local maxAlcoholCount = 7
local alcoholEffectData = {
levels = {
["min"] = {
effect = "DrugsMichaelAliensFightIn",
movement = "move_m@drunk@slightlydrunk",
camShake = 0.5,
canStumble = false,
},
["mid"] = {
effect = "DrugsMichaelAliensFightIn",
movement = "move_m@drunk@moderatedrunk",
canRagdoll = true,
camShake = 0.5,
canStumble = true,
stumbleChance = 0.8,
drunkDriving = true,
},
["max"] = {
effect = "DrugsMichaelAliensFightIn",
movement = "move_m@drunk@verydrunk",
canRagdoll = true,
camShake = 2.8,
canStumble = true,
stumbleChance = 0.6,
drunkDriving = true,
},
},
}
local drugEffectData = {
levels = {
["min"] = {
--effect = "SwitchHUDTrevorIn",
--movement = "move_m@drunk@slightlydrunk",
},
["max"] = {
effect = "SwitchHUDTrevorIn",
movement = "move_m@drunk@slightlydrunk",
camShake = 0.2,
},
}
}
-- Alcohol Thread
function addAlocholCount(count, canOD)
alcoholCount = alcoholCount + count
--print("Alcohol count increased, current amount:", alcoholCount)
CreateThread(function()
runAlcoholThread()
end)
if alcoholCount >= maxAlcoholCount then
-- max
startAlcoholEffect(alcoholEffectData.levels["max"], "max")
if canOD then
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) - math.random(10, 15))
end
elseif alcoholCount < maxAlcoholCount and alcoholCount >= midAlcoholCount then
-- mid
startAlcoholEffect(alcoholEffectData.levels["mid"], "mid")
TriggerEvent("evidence:client:SetStatus", "heavyalcohol", 200)
if canOD then
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) - math.random(5, 10))
end
elseif alcoholCount >= 1 then
-- min
TriggerEvent("evidence:client:SetStatus", "alcohol", 200)
startAlcoholEffect(alcoholEffectData.levels["min"], "min")
end
end
function getAlcoholCount()
return alcoholCount
end
function removeAlcoholCount(count)
alcoholCount = alcoholCount - count
if alcoholCount < minAlcoholCount then
clearCurrentAlcoholEffect()
end
--print("Alcohol count decreased, current amount:", alcoholCount)
end
local alcoholThreadRun = false
function runAlcoholThread()
if alcoholThreadRun == true then
return
else
alcoholThreadRun = true
end
--print("Alcohol Thread triggered")
while alcoholCount > 0 do
Wait(purgeTimer)
removeAlcoholCount(1)
if alcoholCount <= 0 then
clearCurrentAlcoholEffect()
alcoholThreadRun = false
break
end
end
end
exports("getAlcoholCount",getAlcoholCount)
exports("addAlocholCount", addAlocholCount)
exports("removeAlcoholCount", removeAlcoholCount)
-- EFFECT Management
local currentAlcoholEffect = ""
local alcoholEffect = ""
function clearCurrentAlcoholEffect()
local Ped = PlayerPedId()
if alcoholEffect ~= "" then
AnimpostfxStop(alcoholEffect)
ResetPedMovementClipset(Ped, 0.0)
ClearTimecycleModifier()
ResetScenarioTypesEnabled()
SetPedIsDrunk(Ped, false)
SetPedMotionBlur(Ped, false)
end
currentAlcoholEffect = ""
alcoholEffect = ""
end
function startAlcoholEffect(data, level)
local Ped = PlayerPedId()
if currentAlcoholEffect == level then
return
else
clearCurrentAlcoholEffect()
currentAlcoholEffect = level
end
-- Start effect using sent data
if alcoholEffect ~= data.effect then
alcoholEffect = data.effect
AnimpostfxPlay(data.effect, 0, true)
if data.movement then
RequestAnimSet(data.movement)
while not HasAnimSetLoaded(data.movement) do
Wait(100)
end
SetPedMovementClipset(Ped, data.movement, 3.0)
end
SetPedCanRagdoll(Ped, true)
if data.camShake then
ShakeGameplayCam("DRUNK_SHAKE", data.camShake)
end
SetTimecycleModifier("Drunk")
SetPedMotionBlur(Ped, true)
SetPedIsDrunk(Ped, true)
if data.drivingEffect or data.canStumble then
CreateThread(function()
local effectCheck = level
local lastStumbleTime = GetGameTimer() + 1200
local lastDriveTime = GetGameTimer() + 6000
while effectCheck == currentAlcoholEffect do
if data.camStumble and GetGameTimer() > lastStumbleTime and math.random() > (data.stumbleChance or 0.8) then
lastStumbleTime = GetGameTimer() + 1200
SetPedToRagdoll(Ped, 5000, 5000, 0, true, true, false)
end
if data.driving and GetGameTimer() > lastDriveTime then
local inVeh, veh, seat = GetSeatPedIsIn()
if inVeh then
lastDriveTime = GetGameTimer() + 6000
if seat == -1 then
if math.random() < 0.62 then
StartVehicleHorn(veh, 1000, "NORMAL", false)
end
end
end
end
Wait(1000)
end
--print("Effect loop broken")
end)
end
end
end
-- Helper
function GetSeatPedIsIn()
local inVeh, veh, seat = false, 0, 0
local veh = GetVehiclePedIsIn(PlayerPedId(), false)
if veh ~= 0 then
if GetPedInVehicleSeat(veh, -1) == PlayerPedId() then
inVeh = true
seat = -1
end
end
return inVeh, veh, seat
end
-- Drug thread
function addDrugCount(count, canOD)
drugCount = drugCount + count
--print("Drug count increased, current amount:", drugCount)
if drugCount >= minDrugCount and drugCount <= maxDrugCount then
startDrugEffect(drugEffectData.levels["min"], "min")
elseif drugCount > maxDrugCount then
if canOD then
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) - math.random(10, 15))
end
startDrugEffect(drugEffectData.levels["max"], "max")
end
end
function getDrugCount()
return drugCount
end
function removeDrugCount(count)
--print("Drug count decreased, current amount:", drugCount)
drugCount = drugCount - count
end
local drugThreadRun = false
function runDrugThread()
if drugThreadRun == true then
return
else
drugThreadRun = true
end
--print("Drug Thread triggered")
while drugCount > 0 do
Wait(purgeTimer)
removeDrugCount(1)
if drugCount <= 0 then
drugThreadRun = false
break
end
end
end
exports("getDrugCount", getDrugCount)
exports("addDrugCount", addDrugCount)
exports("removeDrugCount", removeDrugCount)
-- EFFECT Management
local currentDrugEffect = ""
local drugEffect = ""
function startDrugEffect(data, level)
local Ped = PlayerPedId()
if currentDrugEffect == level then
return
else
clearCurrentAlcoholEffect()
currentDrugEffect = level
end
-- Start effect using sent data
if drugEffect ~= data.effect then
drugEffect = data.effect
AnimpostfxPlay(data.effect, 0, true)
if data.movement then
RequestAnimSet(data.movement)
while not HasAnimSetLoaded(data.movement) do
Wait(100)
end
SetPedMovementClipset(Ped, data.movement, 3.0)
end
SetPedCanRagdoll(Ped, true)
if data.camShake then
ShakeGameplayCam("DRUNK_SHAKE", data.camShake)
end
SetTimecycleModifier("Drunk")
SetPedMotionBlur(Ped, true)
SetPedIsDrunk(Ped, true)
end
end

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)
RemoveAnimDict(animDict) unloadAnimDict(animDict)
end end
function redProgressBar(data) function redProgressBar(data)
@@ -53,11 +53,27 @@ 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(percentage, data.label, ("%.0f%%"):format(percentage)) ShowRedProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress -- Controls to disable during progress
if data.disableMouse then if data.disableMouse then
@@ -135,48 +151,40 @@ function redProgressBar(data)
return result return result
end end
function ShowRedProgressBar(percentage, title, level) function ShowRedProgressBar(currentProg, 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
-- Background plate -- Draw background box
DrawSprite("generic_textures", "inkroller_1a", loc.x+0.1, loc.y-0.01, 0.25, 0.07, 180.0, 0, 0, 0, 200) 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)
-- Track (bg) local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each)
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255) local gap = segmentWidth / #currentProg -- Smaller gap between segments
-- Fill for i = 1, #currentProg do
local fillWidth = barWidth * (percentage / 100.0) local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
if fillWidth > 0.0 then local fillPercentage = currentProg[i]
local fillCenter = barLeft + (fillWidth / 2.0) local progressBarWidth = segmentWidth * (fillPercentage / 100)
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 255, 0, 0, 200) -- red fill
-- Semi-transparent background for each segment
DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255)
-- Filling progress for each segment
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 255, 0, 0, 200) -- Blue progress
end end
-- Tick lines (9 inner lines for 10 segments)
for i = 1, (tickCount - 1) do
local x = barLeft + (barWidth * (i / tickCount))
DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
end end
end end
@@ -205,14 +213,29 @@ function gtaProgressBar(data)
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 -- Convert to segmented progress (assuming 5 segments here)
if percentage > 100 then percentage = 100 end 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
ShowGTAProgressBar(percentage, data.label, ("%.0f%%"):format(percentage)) ShowGTAProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
-- Controls to disable during progress -- Controls to disable during progress
DisablePlayerFiring(PlayerId(), true) DisablePlayerFiring(ped, 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
@@ -223,13 +246,12 @@ function gtaProgressBar(data)
inProgress = false inProgress = false
end end
end end
result = inProgress
end) end)
-- Wait for completion or cancel -- Wait for completion or cancel
while result == nil do Wait(10) end while GetGameTimer() < endTime and inProgress do
inProgress = false Wait(100)
end
-- Cleanup animations/tasks -- Cleanup animations/tasks
if data.dict then if data.dict then
@@ -239,6 +261,11 @@ 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)
@@ -249,22 +276,14 @@ function gtaProgressBar(data)
return result return result
end end
function ShowGTAProgressBar(percentage, title, level) function ShowGTAProgressBar(currentProg, 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
-- Background plates -- Draw background box
DrawSprite("timerbars", "all_black_bg", loc.x +0.028, loc.y-0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255) DrawSprite("timerbars", "all_black_bg", loc.x +0.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)
@@ -274,34 +293,33 @@ function ShowGTAProgressBar(percentage, title, level)
SetTextOutline() SetTextOutline()
SetTextEntry("STRING") SetTextEntry("STRING")
AddTextComponentString(title) AddTextComponentString(title)
DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034) DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034) -- Adjust text position
-- 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) DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030) -- Right-aligned additional text
-- Track (bg) local segmentWidth = (size.x + 0.05) / (#currentProg * 2) -- Divide the total width by 18 (9 segments * 2 gaps for each)
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255) local gap = segmentWidth / #currentProg -- Smaller gap between segments
-- Fill for i = 1, #currentProg do
local fillWidth = barWidth * (percentage / 100.0) local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
if fillWidth > 0.0 then local fillPercentage = currentProg[i]
local fillCenter = barLeft + (fillWidth / 2.0) local progressBarWidth = segmentWidth * (fillPercentage / 100)
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 93, 182, 229, 255) -- GTA blue
end
-- Tick lines -- Semi-transparent background for each segment
for i = 1, (tickCount - 1) do DrawRect(segmentX + segmentWidth / 2, loc.y, segmentWidth, size.y / 3.4, 100, 100, 100, 255)
local x = barLeft + (barWidth * (i / tickCount))
DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120) -- Filling progress for each segment
if progressBarWidth > 0 then
DrawRect(segmentX + progressBarWidth / 2, loc.y, progressBarWidth, size.y / 3.4, 93, 182, 229, 255) -- Blue progress
end
end end
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)
drawGTASuccessText(x, y, "Failed", 228, 52, 52) drawSuccessText(x, y, "Failed", 228, 52, 52)
end end
return false return false
end end

View File

@@ -1,4 +1,5 @@
-- 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.
@@ -16,196 +17,122 @@ 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)
-- ===== Ownership + indexes ===== local existingTarget = nil
-- TextTargets: key -> target data (coords/entity/models/options/etc.) for _, target in pairs(TextTargets) do
local TextTargets = {} if #(target.coords - entityCoords) < 0.01 then
-- targetEntities kept for parity (not strictly required) existingTarget = target
local targetEntities = {} break
-- registry: key -> owner, owner -> set(keys)
local TargetRegistry = { byKey = {}, byResource = {} }
local function getOwnerResource()
return GetInvokingResource() or GetCurrentResourceName() or "unknown"
end
local function registerTarget(owner, key)
-- move key across owners if replacing
local prev = TargetRegistry.byKey[key]
if prev and TargetRegistry.byResource[prev] then
TargetRegistry.byResource[prev][key] = nil
end
TargetRegistry.byKey[key] = owner
TargetRegistry.byResource[owner] = TargetRegistry.byResource[owner] or {}
TargetRegistry.byResource[owner][key] = true
end
local function removeTargetKey(key, reason)
local owner = TargetRegistry.byKey[key]
if TextTargets[key] then
TextTargets[key] = nil
-- print("^6Bridge^7:^5 Target^7: ^2Removed target '%s'%s", key, reason and (" ("..reason..")") or "")
end
if owner then
if TargetRegistry.byResource[owner] then
TargetRegistry.byResource[owner][key] = nil
end
TargetRegistry.byKey[key] = nil
end end
end end
AddEventHandler("onResourceStop", function(res) if existingTarget then
local owned = TargetRegistry.byResource[res] for i = 1, #opts do
if not owned then return end local key = KEY_TABLE[#existingTarget.options + i]
local cnt = 0 opts[i].key = key
for key in pairs(owned) do existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
removeTargetKey(key, "resource stopped: "..res) existingTarget.options[#existingTarget.options + 1] = opts[i]
cnt = cnt + 1
end end
TargetRegistry.byResource[res] = nil updateCachedText(existingTarget)
-- print("^6Bridge^7:^5 Target^7: ^2Cleared "..cnt.." target(s) from '"..res.."'") else
end)
-- ===== Helpers =====
local function vecKey(v)
-- stable rounded coord string for entity dedupe when name isn't provided
return ("%.3f,%.3f,%.3f"):format(v.x, v.y, v.z)
end
local function bakeButtons(opts)
local tempText = {} 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] = tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
(" ~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 TextTargets[entity] = {
end coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z),
buttontext = tempText,
-- Update cached text blob
local function updateCachedText(target)
target.text = table.concat(target.buttontext, "\n")
end
-- ===== Public API: Create targets =====
-- ENTITY: createEntityTarget(entity, opts, dist, nameOpt?)
function createEntityTarget(entity, opts, dist, name)
startTargetLoop()
if not entity or entity == 0 then return end
targetEntities[#targetEntities + 1] = entity
local owner = getOwnerResource()
local coords = GetEntityCoords(entity)
local key = tostring(name) or ("entity@" .. vecKey(coords))
-- Always overwrite on same key
local buttontext = bakeButtons(opts)
TextTargets[key] = {
_key = key,
_type = "entity",
_owner = owner,
entity = entity,
coords = vec3(coords.x, coords.y, coords.z),
buttontext = buttontext,
options = opts, options = opts,
dist = dist, dist = dist,
text = table.concat(tempText, "\n")
} }
updateCachedText(TextTargets[key]) end
registerTarget(owner, key)
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ENTITY target '"..key.."' by '"..owner.."' @ "..formatCoord(coords))
return key
end end
-- ZONE: createZoneTarget(data, opts, dist)
-- Expect data[1] = id/name, data[2] = vec3 coords (as in your original)
function createZoneTarget(data, opts, dist) function createZoneTarget(data, opts, dist)
startTargetLoop() startTargetLoop()
local existingTarget = nil
local owner = getOwnerResource() for _, target in pairs(TextTargets) do
local zname = tostring(data[1] or ("zone@"..vecKey(data[2] or vec3(0,0,0)))) if #(target.coords - data[2]) < 0.01 then
local coords = data[2] existingTarget = target
break
local buttontext = bakeButtons(opts) end
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
-- MODEL: createModelTarget(models, opts, dist, nameOpt?) if existingTarget then
function createModelTarget(models, opts, dist, name) for i = 1, #opts do
startTargetLoop() local key = KEY_TABLE[#existingTarget.options + i]
opts[i].key = key
local owner = getOwnerResource() existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
if type(models) ~= "table" then models = { models } end existingTarget.options[#existingTarget.options + 1] = opts[i]
end
local key updateCachedText(existingTarget)
if name then
key = tostring(name)
else else
local parts = {} local tempText = {}
for i, m in ipairs(models) do parts[i] = tostring(m) end for i = 1, #opts do
key = "model_" .. table.concat(parts, "_") opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end end
TextTargets[data[1]] = {
local buttontext = bakeButtons(opts) coords = data[2],
TextTargets[key] = { buttontext = tempText,
_key = key,
_type = "model",
_owner = owner,
models = models,
buttontext = buttontext,
options = opts, options = opts,
dist = dist, dist = dist,
coords = vec3(0, 0, 0), -- will be updated by the refresher text = table.concat(tempText, "\n")
} }
updateCachedText(TextTargets[key]) end
registerTarget(owner, key) return data[1]
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated MODEL target '"..key.."' by '"..owner.."' (models: "..table.concat(models, ",")..")")
return key
end end
-- ===== Public API: Remove targets ===== function createModelTarget(models, opts, dist)
-- Entity removal accepts entity handle or key string. startTargetLoop()
function removeEntityTarget(entityOrKey) if type(models) ~= "table" then
local key = nil models = { models }
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 end
function removeZoneTarget(key) local tempText = {}
if not key then return end for i = 1, #opts do
removeTargetKey(key, "removeZoneTarget") opts[i].key = KEY_TABLE[i]
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
end end
-- For models, pass the returned key from createModelTarget (recommended). local keyStr = ""
function removeModelTarget(key) for i, m in ipairs(models) do
if not key then return end keyStr = keyStr .. tostring(m) .. (i < #models and "_" or "")
removeTargetKey(key, "removeModelTarget") end
local targetKey = "model_" .. keyStr
TextTargets[targetKey] = {
models = models,
buttontext = tempText,
options = opts,
dist = dist,
coords = vec3(0, 0, 0),
text = table.concat(tempText, "\n")
}
return targetKey
end
function removeEntityTarget(entity)
TextTargets[entity] = nil
end
function removeZoneTarget(target)
TextTargets[target] = nil
end
function removeModelTarget(model)
TextTargets[model] = nil
end end
exports("createEntityTarget", createEntityTarget) exports("createEntityTarget", createEntityTarget)
@@ -216,20 +143,22 @@ 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
started = true Config = {
System = {
-- lazy include (unchanged from your original) }
Config = { System = {} } }
started = true
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())
@@ -249,7 +178,7 @@ function startTargetLoop()
end end
end) end)
-- Main Target Loop (unchanged logic, just uses new TextTargets entries) -- Main Target Loop
CreateThread(function() CreateThread(function()
while true do while true do
local ped = PlayerPedId() local ped = PlayerPedId()
@@ -309,11 +238,12 @@ function startTargetLoop()
::continue:: ::continue::
end end
Wait(1) Wait(1) -- Throttled
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)
@@ -323,12 +253,12 @@ function DrawText3D(coord, text, highlight)
SetTextCentre(true) SetTextCentre(true)
local totalLength = string.len(text) local totalLength = string.len(text)
local textMaxLength = 99 local textMaxLength = 99 -- max 99
local txt = totalLength > textMaxLength and text:sub(1, textMaxLength) or text local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text
AddTextComponentString(highlight and txt:gsub("%~w~", "~y~") or txt) AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text)
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(txt) local count, length = GetLineCountAndMaxLength(text)
local padding = 0.005 local padding = 0.005
local heightFactor = (count / 43) + padding local heightFactor = (count / 43) + padding
@@ -341,10 +271,19 @@ function DrawText3D(coord, text, highlight)
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 = lineCount + 1 lineCount += 1
local lineLength = string.len(line) local lineLength = string.len(line)
if lineLength > maxLength then if lineLength > maxLength then
maxLength = lineLength maxLength = lineLength
@@ -354,6 +293,7 @@ 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(
@@ -371,3 +311,8 @@ 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,14 @@
2.1.06 2.0.20
- Fix stress calculations adding instead of taking away - Add Full support for lation_ui
- Add basic support for fallback functions for inventory management (esx isn't very well supported currently) - Add "Open Wheel" to searchCar()
- Create ui_modules/alcohol.lua to handle alcohol and drug effects between scripts - Increase timeout for cache timer from 5 seconds to 2 minutes
- Fix AnimSet loading for "AlienEffect()" - Fix GTA progressbar erroring on playAnim()
- Add Igredient location indicator when crafting
- Fix JPRInv checks breaking early when caching framework info
- Enhance doesItemExist() and add getItemLabel() function
- Add Support for old qb-inventory config loading
- Fix typo in cancarry when using Tgiann Inv
- Add support for single progressBar when crafting
https://github.com/jimathy/jim_bridge/releases/latest https://github.com/jimathy/jim_bridge