This commit is contained in:
oosayeroo
2025-09-03 11:18:57 +01:00
22 changed files with 1395 additions and 1040 deletions

12
.gitattributes vendored Normal file
View File

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

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

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

View File

@@ -1,12 +1,22 @@
local function parseVersion(version) local function readBoolMeta(key, default)
local val = GetResourceMetadata(GetCurrentResourceName(), key, 0)
if not val then return default end
val = tostring(val):lower()
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
end
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
if not SUPPRESS_UPDATES then
local function parseVersion(version)
local parts = {} local parts = {}
for num in version:gmatch("%d+") do for num in version:gmatch("%d+") do
table.insert(parts, tonumber(num)) table.insert(parts, tonumber(num))
end end
return parts return parts
end end
local function compareVersions(current, newest) local function compareVersions(current, newest)
local currentParts = parseVersion(current) local currentParts = parseVersion(current)
local newestParts = parseVersion(newest) local newestParts = parseVersion(newest)
for i = 1, math.max(#currentParts, #newestParts) do for i = 1, math.max(#currentParts, #newestParts) do
@@ -16,9 +26,9 @@ local function compareVersions(current, newest)
elseif c > n then return 1 end elseif c > n then return 1 end
end end
return 0 -- equal return 0 -- equal
end end
function CheckBridgeVersion() function CheckBridgeVersion()
if IsDuplicityVersion() then if IsDuplicityVersion() then
CreateThread(function() CreateThread(function()
Wait(4000) Wait(4000)
@@ -61,7 +71,8 @@ function CheckBridgeVersion()
end) end)
end) end)
end end
end
CheckBridgeVersion()
end end
CheckBridgeVersion()

View File

@@ -31,17 +31,18 @@ local Exports = {
-- REDM -- REDM
RSGExport = "rsg-core", RSGExport = "rsg-core",
RSGInv = "rsg-inventory" RSGInv = "rsg-inventory",
VorpExport = "vorp_core",
VorpInv = "vorp_inventory",
VorpMenu = "vorp_menu",
} }
-- Prevent reloading if cache is already initialized -- Prevent reloading if cache is already initialized
local cache = { local cache = { Items = {}, Vehicles = {}, Jobs = {}, Gangs = {}, }
Items = {},
Vehicles = {},
Jobs = {},
Gangs = {},
}
local cacheReady = false local cacheReady = false
-- Timer info, for debugging more then anything
local timers = {} local timers = {}
local function startTimer(label) local function startTimer(label)
timers[label] = GetGameTimer() timers[label] = GetGameTimer()
@@ -52,26 +53,57 @@ local function endTimer(label)
timers[label] = "("..(timers[label] / 1000).."s)" timers[label] = "("..(timers[label] / 1000).."s)"
end end
startTimer("Cache") startTimer("Items") startTimer("Vehicles") startTimer("Jobs") startTimer("InvWeight") startTimer("InvSlots") startTimer("Cache") startTimer("Items") startTimer("Vehicles") startTimer("Jobs") startTimer("InvWeight") startTimer("InvSlots")
-- Helper function to check if resource exists in server (instead of if it is already started)
-- Helper functions --
local function checkExists(resourceName) local function checkExists(resourceName)
return GetResourceState(resourceName):find("start") or GetResourceState(resourceName):find("stopped") local state = GetResourceState(resourceName)
return state and (state:find("start") or state:find("stopped"))
end end
-- Ensure oxmysql resource is loaded local function waitStarted(resourceName)
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) then 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 end
if checkExists(Exports.OXCoreExport) then if checkExists(Exports.OXCoreExport) then
-- Detected OX_Core in server, wait for it to be started if needed -- Detected OX_Core in server, wait for it to be started if needed
while GetResourceState(Exports.OXCoreExport) ~= "started" do Wait(100) end waitStartedOrStopped(Exports.OXCoreExport)
local fileLoader = assert(load(LoadResourceFile(Exports.OXCoreExport, ('lib/init.lua')), ('@@'..Exports.OXCoreExport..'/lib/init.lua'))) local fileLoader = assert(load(LoadResourceFile(Exports.OXCoreExport, ('lib/init.lua')), ('@@'..Exports.OXCoreExport..'/lib/init.lua')))
fileLoader() fileLoader()
end end
if checkExists(Exports.ESXExport) then if checkExists(Exports.ESXExport) then
-- Detected ESX in server, wait for it to be started if needed -- Detected ESX in server, wait for it to be started if needed
while GetResourceState(Exports.ESXExport) ~= "started" do Wait(100) end waitStarted(Exports.ESXExport)
local fileLoader = assert(load(LoadResourceFile(Exports.ESXExport, ('/imports.lua')), ('@@'..Exports.ESXExport..'/imports.lua'))) local fileLoader = assert(load(LoadResourceFile(Exports.ESXExport, ('/imports.lua')), ('@@'..Exports.ESXExport..'/imports.lua')))
fileLoader() fileLoader()
end end
@@ -89,62 +121,30 @@ end
--------------------- ---------------------
---- Load Items ----- ---- Load Items -----
--------------------- ---------------------
-- Items initialization based on detected inventory system
if checkExists(Exports.OXInv) then
-- Wait for OX Inventory to start if it's not already started
while GetResourceState(Exports.OXInv) ~= "started" do Wait(100) end
itemResource = Exports.OXInv
local 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 },
-- (duplicate incase anything checks for the uppercase version) { script = Exports.TgiannInv,
for k, v in pairs(cache.Items) do cacheItem = function()
if type(k) == "string" then
if k:find("WEAPON") then
cache.Items[k:lower()] = cache.Items[k]
end
else
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
print("^1Possible Issue found^7:")
print(json.encode(cache.Items[k], {indent = true}))
end
end
elseif checkExists(Exports.TgiannInv) then
-- Wait for OX Inventory to start if it's not already started
while GetResourceState(Exports.TgiannInv) ~= "started" do Wait(100) end
itemResource = Exports.TgiannInv
local success, result = pcall(function() local success, result = pcall(function()
return exports[Exports.TgiannInv]:Items() return exports[Exports.TgiannInv]: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 },
-- (duplicate incase anything checks for the uppercase version) { script = Exports.QBXExport,
for k, v in pairs(cache.Items) do cacheItem = function()
if type(k) == "string" then
if k:find("WEAPON") then
cache.Items[k:lower()] = cache.Items[k]
end
else
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
print("^1Possible Issue found^7:")
print(json.encode(cache.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
@@ -170,14 +170,15 @@ elseif checkExists(Exports.QBXExport) then
cache.Items = exports[Exports.TgiannInv]:Items() cache.Items = exports[Exports.TgiannInv]:Items()
end end
end end
end,
elseif checkExists(Exports.QBExport) then },
while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end { script = Exports.QBExport,
itemResource = Exports.QBExport cacheItem = function()
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
end,
elseif checkExists(Exports.ESXExport) then },
itemResource = Exports.ESXExport { script = Exports.ESXExport,
cacheItem = function()
if GetResourceState(Exports.QSInv):find("start") then if GetResourceState(Exports.QSInv):find("start") then
cache.Items = exports[Exports.QSInv]:GetItemList() cache.Items = exports[Exports.QSInv]:GetItemList()
else else
@@ -187,28 +188,64 @@ elseif checkExists(Exports.ESXExport) then
Wait(1000) Wait(1000)
end end
end end
end,
elseif checkExists(Exports.RSGExport) then },
while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end { script = Exports.RSGExport,
itemResource = Exports.RSGExport cacheItem = function()
cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
end,
},
{ script = Exports.VorpInv,
cacheItem = function()
local dbItems = MySQL.query.await('SELECT * FROM `items`')
local tempItems = {}
for i = 1, #dbItems do
local v = dbItems[i]
tempItems[v.item] = {
name = v.item,
label = v.label,
weight = v.weight,
info = v.metadata,
usable = v.usable,
type = v.type,
description = v.desc,
}
end
cache.Items = tempItems
end,
},
}
for i = 1, #itemFunc do
local data = itemFunc[i]
if checkExists(data.script) then
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheItem() -- run tablized function for core/inv
dupLowercaseWeapons(cache.Items) -- make usable weapon item names for jim_bridge
itemResource = data.script -- Grab script name to announce later
endTimer("Items") -- end timer
break -- break loop so it doesn't keep checking
end
end end
endTimer("Items")
--------------------- ---------------------
--- Load Vehicles --- --- Load Vehicles ---
--------------------- ---------------------
-- Vehicle loading depending on framework ---
if checkExists(Exports.QBXExport) then local vehicleFunc = {
vehResource = Exports.QBXExport
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
elseif checkExists(Exports.QBExport)then { script = Exports.QBXExport,
vehResource = Exports.QBExport cacheVehicle = function()
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
end,
elseif checkExists(Exports.OXCoreExport) then },
vehResource = Exports.OXCoreExport { script = Exports.QBExport,
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] = {
@@ -218,10 +255,11 @@ elseif checkExists(Exports.OXCoreExport) then
brand = v.make brand = v.make
} }
end end
end,
elseif checkExists(Exports.ESXExport) then },
vehResource = Exports.ESXExport { script = Exports.ESXExport,
while not MySQL do Wait(1000) end cacheVehicle = function()
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,
@@ -230,29 +268,51 @@ elseif checkExists(Exports.ESXExport) then
name = v.name, name = v.name,
} }
end end
end,
elseif checkExists(Exports.RSGExport) then },
vehResource = Exports.RSGExport { script = Exports.RSGExport,
cacheVehicle = function()
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
end,
},
{ script = Exports.VorpExport,
cacheVehicle = function()
cache.Vehicles = { ["unkown"] = {} }
end,
},
}
for i = 1, #vehicleFunc do
local data = vehicleFunc[i]
if checkExists(data.script) then
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheVehicle() -- run tablized function for core
vehResource = data.script -- Grab script name to announce later
endTimer("Vehicles") -- end timer
break -- break loop so it doesn't keep checking
end
end end
endTimer("Vehicles")
--------------------- ---------------------
----- Load Jobs ----- ----- Load Jobs -----
--------------------- ---------------------
-- Jobs loading based on framework
if checkExists(Exports.QBXExport) then local jobFunc = {
jobResource = Exports.QBXExport
{ script = 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 },
jobResource = Exports.QBExport { script = Exports.QBExport,
cache.Jobs, cache.Gangs = exports[Exports.QBExport]:GetCoreObject().Shared.Jobs, exports[Exports.QBExport]:GetCoreObject().Shared.Gangs cacheJob = function()
Core = exports[Exports.QBExport]:GetCoreObject()
elseif checkExists(Exports.OXCoreExport) then cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
jobResource = Exports.OXCoreExport end,
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 = {}
@@ -267,10 +327,11 @@ elseif checkExists(Exports.OXCoreExport) then
} }
end end
cache.Gangs = cache.Jobs cache.Gangs = cache.Jobs
end,
elseif checkExists(Exports.ESXExport) then },
jobResource = Exports.ESXExport { script = Exports.ESXExport,
ESX = exports[Exports.ESXExport]:getSharedObject() cacheJob = function()
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)
@@ -301,13 +362,33 @@ elseif checkExists(Exports.ESXExport) then
::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,
},
}
elseif checkExists(Exports.RSGExport) then for i = 1, #jobFunc do
jobResource = Exports.RSGExport local data = jobFunc[i]
cache.Jobs, cache.Gangs = exports[Exports.RSGExport]:GetCoreObject().Shared.Jobs, exports[Exports.RSGExport]:GetCoreObject().Shared.Gangs if checkExists(data.script) then
waitStarted(data.script) -- Wait for detected script to start fully
data.cacheJob() -- run tablized function for core
jobResource = data.script -- Grab script name to announce later
endTimer("Jobs") -- end timer
break -- break loop so it doesn't keep checking
end
end 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
@@ -402,10 +483,8 @@ for script, data in pairs(invWeightTable) do
if checkExists(script) then if checkExists(script) then
if script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end if script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end
while GetResourceState(script) ~= "started" and GetResourceState(script) ~= "stopped" do waitStartedOrStopped(script)
Wait(100)
print("Waiting for script start")
end
local attempts = data.fallback or { data } local attempts = data.fallback or { data }
local lookup, used, err local lookup, used, err

View File

@@ -7,7 +7,6 @@ rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aw
games { 'gta5', 'rdr3' } games { 'gta5', 'rdr3' }
lua54 'yes' lua54 'yes'
files { files {
'starter.lua', 'starter.lua',
'shared/*.lua', 'shared/*.lua',
@@ -25,3 +24,5 @@ client_scripts {
'clientFrameworkCache.lua', 'clientFrameworkCache.lua',
'ui_modules/*.lua', 'ui_modules/*.lua',
} }
suppress_updates 'false' -- set to 'true' to disable update pings

View File

@@ -12,7 +12,7 @@ if isServer() then
local src = source local src = source
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
--debugPrint(GetInvokingResource()) --debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= Exports.QBExport and GetInvokingResource() ~= Exports.VorpExport then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7") debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
return "" return ""
end end
@@ -45,8 +45,9 @@ if isServer() then
createCallback(getScript()..":callback:GetAuthEvent", function(source) createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source local src = source
--debugPrint(GetInvokingResource())
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= Exports.QBExport and GetInvokingResource() ~= Exports.VorpExport then
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7") debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7")
return "" return ""
end end

View File

@@ -62,6 +62,9 @@ function onPlayerLoaded(func, onStart)
elseif isStarted(RSGExport) then elseif isStarted(RSGExport) then
onPlayerFramework = RSGExport onPlayerFramework = RSGExport
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc) RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
elseif isStarted(VorpExport) then
onPlayerFramework = VorpExport
RegisterNetEvent("vorp_core:Client:OnPlayerSpawned", tempFunc)
end end
if onPlayerFramework ~= "" then if onPlayerFramework ~= "" then
@@ -174,6 +177,12 @@ function waitForLogin()
break break
end end
end end
elseif isStarted(VorpExport) then
-- For other frameworks, use LocalPlayer.state.isLoggedIn.
while not LocalPlayer.state.IsInSession and (GetGameTimer() - startTime) < timeout do
Wait(100)
end
loggedIn = LocalPlayer.state.IsInSession
else else
-- For other frameworks, use LocalPlayer.state.isLoggedIn. -- For other frameworks, use LocalPlayer.state.isLoggedIn.
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do

View File

@@ -1,12 +1,22 @@
function parseVersion(version) local function readBoolMeta(key, default)
local val = GetResourceMetadata("jim_bridge", key, 0)
if not val then return default end
val = tostring(val):lower()
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
end
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
if not SUPPRESS_UPDATES then
function parseVersion(version)
local parts = {} local parts = {}
for num in version:gmatch("%d+") do for num in version:gmatch("%d+") do
table.insert(parts, tonumber(num)) table.insert(parts, tonumber(num))
end end
return parts return parts
end end
function compareVersions(current, newest) function compareVersions(current, newest)
local currentParts = parseVersion(current) local currentParts = parseVersion(current)
local newestParts = parseVersion(newest) local newestParts = parseVersion(newest)
for i = 1, math.max(#currentParts, #newestParts) do for i = 1, math.max(#currentParts, #newestParts) do
@@ -16,20 +26,20 @@ function compareVersions(current, newest)
elseif c > n then return 1 end elseif c > n then return 1 end
end end
return 0 return 0
end end
function capitalize(str) function capitalize(str)
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end)) return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
end end
local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or "" local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or "" local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or "" local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or "" local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7") print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
function CheckVersion() function CheckVersion()
if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then
CreateThread(function() CreateThread(function()
Wait(4000) Wait(4000)
@@ -95,6 +105,7 @@ function CheckVersion()
end) end)
end) end)
end end
end end
CheckVersion() CheckVersion()
end

View File

@@ -37,6 +37,10 @@ function createCallback(callbackName, funct)
debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName) debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
Core = Core or exports[QBExport]:GetCoreObject() Core = Core or exports[QBExport]:GetCoreObject()
Core.Functions.CreateCallback(callbackName, adaptedFunction) Core.Functions.CreateCallback(callbackName, adaptedFunction)
elseif isStarted(VorpExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..VorpExport.." ^3Callback^7:", callbackName)
Core = Core or exports.vorp_core:GetCore()
Core.Callback.Register(callbackName, adaptedFunction)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName) debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
ESX.RegisterServerCallback(callbackName, adaptedFunction) ESX.RegisterServerCallback(callbackName, adaptedFunction)
@@ -76,6 +80,13 @@ function triggerCallback(callbackName, ...)
end, ...) end, ...)
result = Citizen.Await(p) result = Citizen.Await(p)
Wait(10) Wait(10)
elseif isStarted(VorpExport) then
local p = promise.new()
Core.Callback.TriggerAwait(callbackName, function(cbResult)
p:resolve(cbResult)
end, ...)
result = Citizen.Await(p)
Wait(10)
elseif isStarted(ESXExport) then elseif isStarted(ESXExport) then
local p = promise.new() local p = promise.new()
ESX.TriggerServerCallback(callbackName, function(cbResult) ESX.TriggerServerCallback(callbackName, function(cbResult)

View File

@@ -10,44 +10,9 @@
• esx (using ESX.UI.Menu) • esx (using ESX.UI.Menu)
]] ]]
--- Opens a menu using the configured menu system. local contextFunc = {
--- ["ox"] =
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`. function(Menu, data)
---
---@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, {
@@ -110,8 +75,10 @@ function openMenu(Menu, data)
else else
lib.showContext(menuID) lib.showContext(menuID)
end end
end,
elseif Config.System.Menu == "qb" then ["qb"] =
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",
@@ -151,8 +118,10 @@ function openMenu(Menu, data)
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,
elseif Config.System.Menu == "gta" then ["gta"] =
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,
@@ -206,8 +175,10 @@ function openMenu(Menu, data)
Wait(0) Wait(0)
end end
end) end)
end,
elseif Config.System.Menu == "esx" then ["esx"] =
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
@@ -245,8 +216,10 @@ function openMenu(Menu, data)
function(data, menu) function(data, menu)
menu.close() menu.close()
end) end)
end,
elseif Config.System.Menu == "lation" then ["lation"] =
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",
@@ -290,12 +263,63 @@ function openMenu(Menu, data)
-- 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 = (Config.System.Menu == "ox" or Config.System.Menu == "gta" or Config.System.Menu == "lation") and "\n" or "<br>" br = lineBreakConversion[Config.System.Menu]
--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes. --- Checks if the current menu system is 'ox' or 'gta' for formatting purposes.
--- @return boolean boolean True if using ox or gta menus, otherwise false. --- @return boolean boolean True if using ox or gta menus, otherwise false.

View File

@@ -22,6 +22,9 @@ OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
Exports.JPRInv or "" Exports.JPRInv or ""
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or "" RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
VorpExport, VorpInv = Exports.VorpExport or "", Exports.VorpInv or ""
QBMenuExport = Exports.QBMenuExport or "" QBMenuExport = Exports.QBMenuExport or ""
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or "" QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
@@ -29,6 +32,8 @@ 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

View File

@@ -115,12 +115,12 @@ function craftingMenu(data)
local metaTable = {} local metaTable = {}
-- Build ingredient details. -- Build ingredient details.
for l, b in pairs(Recipes[i][tostring(k)]) do for l, b in pairs(Recipes[i][tostring(k)]) do
local label = Items[l] and Items[l].label or "error - "..l local label = getItemLabel(l)
local hasItem = checkStashItem(data.stashName, { [l] = b }) local hasItem = checkStashItem(data.stashName, { [l] = b })
local missingMark = not hasItem and "" or " " local missingMark = not hasItem and "" or " "
settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark
metaTable[Items[l] and Items[l].label or "error - "..l] = b metaTable[label] = b
itemTable[l] = b itemTable[l] = b
end end
@@ -128,7 +128,7 @@ function craftingMenu(data)
Wait(10) Wait(10)
end end
disable = not checkStashItem(data.stashName, itemTable) disable = not checkStashItem(data.stashName, itemTable)
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k)) setheader = ((metadata and metadata.label) or getItemLabel(k))
..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "") ..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️" local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️"
@@ -359,7 +359,7 @@ function makeItem(data)
return return
end end
if crafting and progressBar({ if crafting and progressBar({
label = "Using "..b.." "..Items[l].label, label = "Using "..b.." "..getItemLabel(l),
time = 1000, time = 1000,
cancel = true, cancel = true,
dict = 'pickup_object', dict = 'pickup_object',
@@ -398,7 +398,7 @@ function makeItem(data)
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0) PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
end end
if crafting and progressBar({ if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label).." x"..craftAmount, label = bartext..((metadata and metadata.label) or getItemLabel(data.item)).." x"..craftAmount,
time = totalBartime, time = totalBartime,
cancel = true, cancel = true,
dict = animDict, dict = animDict,
@@ -437,7 +437,7 @@ function makeItem(data)
-- Run the original loop for multiple progress bars -- Run the original loop for multiple progress bars
for i = 1, craftAmount do for i = 1, craftAmount do
if crafting and progressBar({ if crafting and progressBar({
label = bartext..((metadata and metadata.label) or Items[data.item].label), label = bartext..((metadata and metadata.label) or getItemLabel(data.item)),
time = bartime, time = bartime,
cancel = true, cancel = true,
dict = animDict, dict = animDict,

View File

@@ -180,6 +180,7 @@ function GetPrintTime()
local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S') local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")" return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
else else
if gameName == "rdr3" then return "" end
local _, _, _, hour, min, sec = GetLocalTime() local _, _, _, hour, min, sec = GetLocalTime()
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")" return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
end end

View File

@@ -201,7 +201,7 @@ local InvFunc = {
stashRemoveItem = stashRemoveItem =
function(stashItems, stashName, items) function(stashItems, stashName, items)
for k, v in pairs(items) do for k, v in pairs(items) do
exports[QSInv]:RemoveItemIntoStash(stashName, k, v) exports[QSInv]:RemoveItemIntoStash(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing ^3"..QSInv.." ^2Stash item^7:", k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..QSInv.." ^2Stash item^7:", k, v)
end end
end, end,
@@ -291,7 +291,7 @@ local InvFunc = {
stashRemoveItem = stashRemoveItem =
function(stashItems, stashName, items) function(stashItems, stashName, items)
for k, v in pairs(items) do for k, v in pairs(items) do
exports[CoreInv]:removeItemExact(stashName, k, v) exports[CoreInv]:removeItemExact(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing ^3"..CoreInv.." ^2Stash item^7:", k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..CoreInv.." ^2Stash item^7:", k, v)
end end
end, end,
@@ -381,7 +381,7 @@ local InvFunc = {
stashRemoveItem = stashRemoveItem =
function(stashItems, stashName, items) function(stashItems, stashName, items)
for k, v in pairs(items) do for k, v in pairs(items) do
exports[OrigenInv]:RemoveFromStash(stashName, k, v) exports[OrigenInv]:RemoveFromStash(stashName[1], k, v)
debugPrint("^6Bridge^7: ^2Removing ^3"..OrigenInv.." ^2Stash item^7:", k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..OrigenInv.." ^2Stash item^7:", k, v)
end end
end, end,
@@ -494,7 +494,7 @@ local InvFunc = {
end end
end end
end end
exports[CodeMInv]:UpdateStash(stashName, stashItems) exports[CodeMInv]:UpdateStash(stashName[1], stashItems)
end, end,
registerStash = registerStash =
function(name, label, slots, weight, owner, coords) function(name, label, slots, weight, owner, coords)
@@ -588,8 +588,8 @@ local InvFunc = {
stashRemoveItem = stashRemoveItem =
function(stashItems, stashName, items) function(stashItems, stashName, items)
for k, v in pairs(items) do for k, v in pairs(items) do
local itemData = exports[TgiannInv]:GetItemByNameFromSecondaryInventory("stash", stashName, k) local itemData = exports[TgiannInv]:GetItemByNameFromSecondaryInventory("stash", stashName[1], k)
exports[TgiannInv]:RemoveItemFromSecondaryInventory("stash", stashName, k, v, itemData.slot, nil) exports[TgiannInv]:RemoveItemFromSecondaryInventory("stash", stashName[1], k, v, itemData.slot, nil)
debugPrint("^6Bridge^7: ^2Removing ^3"..TgiannInv.." ^2Stash item^7:", k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..TgiannInv.." ^2Stash item^7:", k, v)
end end
end, end,
@@ -743,7 +743,7 @@ local InvFunc = {
stashRemoveItem = stashRemoveItem =
function(stashItems, stashName, items) function(stashItems, stashName, items)
if not stashItems or not next(stashItems) then if not stashItems or not next(stashItems) then
stashItems = getStash(stashName) stashItems = getStash(stashName[1])
end end
for k, v in pairs(items) do for k, v in pairs(items) do
for l in pairs(stashItems) do for l in pairs(stashItems) do
@@ -757,9 +757,9 @@ local InvFunc = {
end end
end end
end end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3JPR^2 stash '^6"..stashName.."^7'") 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', { MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName, ['stash'] = stashName[1],
['items'] = json.encode(stashItems) ['items'] = json.encode(stashItems)
}) })
end, end,
@@ -924,12 +924,12 @@ local InvFunc = {
function(stashItems, stashName, items) function(stashItems, stashName, items)
if checkExportExists(QBInv, "RemoveItem") then if checkExportExists(QBInv, "RemoveItem") then
for k, v in pairs(items) do for k, v in pairs(items) do
exports[QBInv]:RemoveItem(stashName, k, v, false, 'crafting') exports[QBInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
debugPrint("^6Bridge^7: ^2Removing ^3"..QBInv.." ^2Stash item^7:", k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..QBInv.." ^2Stash item^7:", k, v)
end end
else else
if not stashItems or not next(stashItems) then if not stashItems or not next(stashItems) then
stashItems = getStash(stashName) stashItems = getStash(stashName[1])
end end
for k, v in pairs(items) do for k, v in pairs(items) do
for l in pairs(stashItems) do for l in pairs(stashItems) do
@@ -943,9 +943,9 @@ local InvFunc = {
end end
end end
end end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash '^6"..stashName.."^7'") 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', { MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName, ['stash'] = stashName[1],
['items'] = json.encode(stashItems) ['items'] = json.encode(stashItems)
}) })
end end
@@ -1105,7 +1105,7 @@ local InvFunc = {
stashRemoveItem = stashRemoveItem =
function(stashItems, stashName, items) function(stashItems, stashName, items)
if not stashItems or not next(stashItems) then if not stashItems or not next(stashItems) then
stashItems = getStash(stashName) stashItems = getStash(stashName[1])
end end
for k, v in pairs(items) do for k, v in pairs(items) do
for l in pairs(stashItems) do for l in pairs(stashItems) do
@@ -1119,9 +1119,9 @@ local InvFunc = {
end end
end end
end end
debugPrint("^6Bridge^7: ^3saveStash^7: ^2Saving ^3PS^2 stash ^7'^6"..stashName.."^7'") 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', { MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', {
['stash'] = stashName, ['stash'] = stashName[1],
['items'] = json.encode(stashItems) ['items'] = json.encode(stashItems)
}) })
end, end,
@@ -1244,7 +1244,7 @@ local InvFunc = {
stashRemoveItem = stashRemoveItem =
function(stashItems, stashName, items) function(stashItems, stashName, items)
for k, v in pairs(items) do for k, v in pairs(items) do
exports[RSGInv]:RemoveItem(stashName, k, v, false, 'crafting') exports[RSGInv]:RemoveItem(stashName[1], k, v, false, 'crafting')
debugPrint("^6Bridge^7: ^2Removing ^3"..RSGInv.." ^2Stash item^7:", k, v) debugPrint("^6Bridge^7: ^2Removing ^3"..RSGInv.." ^2Stash item^7:", k, v)
end end
end, end,
@@ -1585,7 +1585,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
end end
else else
debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..Items[item].label.."("..item..") x"..(amount or 1)) debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..getItemLabel(item).."("..item..") x"..(amount or 1))
end end
end end
else else
@@ -1610,7 +1610,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd) ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd)
end end
else else
debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..Items[item].label.."("..item..") x"..(amount or 1)) debugPrint("^6Bridge^7: ^3"..action.."^7["..invName.."] Player("..src..") "..getItemLabel(item).."("..item..") x"..(amount or 1))
end end
end end
end) end)
@@ -1951,6 +1951,7 @@ end
--- if doesItemExist(item) then print("item exists") end --- if doesItemExist(item) then print("item exists") end
--- ``` --- ```
function doesItemExist(item) function doesItemExist(item)
local item = type(item) == "string" and item or tostring(item)
if not item or item == "" then if not item or item == "" then
return false return false
end end
@@ -1965,16 +1966,18 @@ end
function getItemLabel(item) function getItemLabel(item)
local item = type(item) == "string" and item or tostring(item)
if not item or item == "" then if not item or item == "" then
return "" return ""
end end
if not Items or not next(Items) then if not Items or not next(Items) then
return item.." (Missing)" return item.." (Missing item error)"
end end
if Items[item] ~= nil then if Items[item] ~= nil then
return Items[item].label return Items[item].label
end end
return item.." (Missing)" return item.." (Missing item error)"
end end
@@ -2280,7 +2283,7 @@ function stashRemoveItem(stashItems, stashName, items)
for i = 1, #InvFunc do for i = 1, #InvFunc do
local inv = InvFunc[i] local inv = InvFunc[i]
if isStarted(inv.invName) then if isStarted(inv.invName) then
inv.stashRemoveItem(stashItems, stashName[1], items) inv.stashRemoveItem(stashItems, stashName, items)
return return
end end
end end
@@ -2476,7 +2479,7 @@ function sellMenu(data)
Menu[#Menu + 1] = { Menu[#Menu + 1] = {
isMenuHeader = not hasTable[k].hasItem, isMenuHeader = not hasTable[k].hasItem,
icon = invImg(k), icon = invImg(k),
header = Items[k].label..(hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""), header = getItemLabel(k)..(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"] 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", or "Sell ALL at $"..v.." each",
onSelect = function() onSelect = function()
@@ -2534,7 +2537,7 @@ end
--- ``` --- ```
function sellAnim(data, token) function sellAnim(data, token)
if not hasItem(data.item, 1) then if not hasItem(data.item, 1) then
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error") triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..getItemLabel(data.item), "error")
return return
end end
@@ -2596,7 +2599,7 @@ RegisterNetEvent(getScript()..":Sellitems", function(data, token)
print((hasTable[data.item].count * data.price), data.price) print((hasTable[data.item].count * data.price), data.price)
fundPlayer((hasTable[data.item].count * data.price), "cash", src) fundPlayer((hasTable[data.item].count * data.price), "cash", src)
else else
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src) triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..getItemLabel(data.item), "error", src)
end end
end) end)

View File

@@ -65,7 +65,6 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced, fade)
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)
SetEntityAlpha(ped, 0, 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)
@@ -145,7 +144,8 @@ 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 then if fade ~= false and gameName ~= "rdr3" then
SetEntityAlpha(ped, 0, false)
CreateThread(function() CreateThread(function()
fadeInEnt(ped) fadeInEnt(ped)
end) end)

View File

@@ -24,14 +24,14 @@ local distProps = {}
function makeProp(data, freeze, synced, fade) function makeProp(data, freeze, synced, fade)
loadModel(data.prop) loadModel(data.prop)
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false) local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
SetEntityAlpha(veh, 0, false)
SetEntityHeading(prop, (data.coords.w or 0) + 180.0) SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
FreezeEntityPosition(prop, freeze or false) FreezeEntityPosition(prop, freeze or false)
debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords)) 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 then if fade ~= false and gameName ~= "rdr3" then
SetEntityAlpha(prop, 0, false)
CreateThread(function() CreateThread(function()
fadeInEnt(prop) fadeInEnt(prop)
end) end)

View File

@@ -30,7 +30,7 @@ function makeVeh(model, coords, synced, fade)
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 then if fade ~= false and gameName ~= "rdr3" then
SetEntityAlpha(veh, 0, false) SetEntityAlpha(veh, 0, false)
CreateThread(function() CreateThread(function()
fadeInEnt(veh) fadeInEnt(veh)

View File

@@ -4,7 +4,8 @@ local storedPID = nil
local progressFunc = { local progressFunc = {
ox = { ox = {
start = function(data) start =
function(data)
local options = { local options = {
duration = debugMode and 1000 or data.time, duration = debugMode and 1000 or data.time,
label = data.label, label = data.label,
@@ -51,17 +52,20 @@ local progressFunc = {
if exports[OXLibExport]:progressBar(options) then if exports[OXLibExport]:progressBar(options) then
return true return true
else else
return false print("^1progressBar was not successful")
end end
end end
end, end,
stop = function() stop =
function()
exports[OXLibExport]:cancelProgress() exports[OXLibExport]:cancelProgress()
end, end,
}, },
qb = { qb = {
start = function(data) 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,
@@ -80,19 +84,20 @@ local progressFunc = {
task = data.task task = data.task
}, },
{}, {}, {}, {},
function() function() p:resolve(true) end,
return true function() p:resolve(false) end,
end, function() data.icon)
return false return Citizen.Await(p)
end, data.icon)
end, end,
stop = function() stop =
function()
TriggerEvent("progressbar:client:cancel") TriggerEvent("progressbar:client:cancel")
end, end,
}, },
qs = { qs = {
start = function(data) start =
function(data)
if exports['qs-interface']:ProgressBar({ if exports['qs-interface']:ProgressBar({
duration = debugMode and 1000 or data.time, duration = debugMode and 1000 or data.time,
label = data.label, label = data.label,
@@ -112,14 +117,17 @@ local progressFunc = {
return false return false
end end
end, end,
stop = function() stop =
function()
--?? --??
TriggerEvent("progressbar:client:cancel") TriggerEvent("progressbar:client:cancel")
end, end,
}, },
esx = { esx = {
start = function(data) 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 = {
@@ -128,20 +136,24 @@ local progressFunc = {
scenario = data.task, scenario = data.task,
}, },
onFinish = function() onFinish = function()
return true p:resolve(true)
end, end,
onCancel = function() onCancel = function()
p:resolve(false)
return false return false
end end
}) })
return Citizen.Await(p)
end, end,
stop = function() stop =
function()
ESX.CancelProgressbar() ESX.CancelProgressbar()
end, end,
}, },
lation = { lation = {
start = function(data) start =
function(data)
if exports.lation_ui:progressBar({ if exports.lation_ui:progressBar({
label = data.label, label = data.label,
description = nil, description = nil,
@@ -170,13 +182,15 @@ local progressFunc = {
return false return false
end end
end, end,
stop = function() stop =
function()
exports.lation_ui:cancelProgress() exports.lation_ui:cancelProgress()
end, end,
}, },
red = { red = {
start = function(data) start =
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,
@@ -192,14 +206,15 @@ local progressFunc = {
return false return false
end end
end, end,
stop = function() stop =
function()
exports.jim_bridge:stopProgressBar() exports.jim_bridge:stopProgressBar()
end, end,
}, },
gta = { gta = {
start = function(data) start =
function(data)
if exports.jim_bridge:gtaProgressBar({ if exports.jim_bridge:gtaProgressBar({
label = data.label, label = data.label,
time = debugMode and 1000 or data.time, time = debugMode and 1000 or data.time,
@@ -215,7 +230,8 @@ local progressFunc = {
return false return false
end end
end, end,
stop = function() stop =
function()
exports.jim_bridge:stopProgressBar() exports.jim_bridge:stopProgressBar()
end, end,
}, },
@@ -254,7 +270,11 @@ local progressFunc = {
--- cancel = true, --- cancel = true,
--- }) --- })
--- ``` --- ```
local progresssBarActive = false
function progressBar(data) function progressBar(data)
if progresssBarActive then return else progresssBarActive = true end
local ped = PlayerPedId() local ped = PlayerPedId()
if data.shared then if data.shared then
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7") debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
@@ -273,6 +293,7 @@ function progressBar(data)
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)
@@ -293,6 +314,7 @@ function progressBar(data)
TriggerServerEvent(getScript()..":clearAuthToken") TriggerServerEvent(getScript()..":clearAuthToken")
currentToken = triggerCallback(AuthEvent) currentToken = triggerCallback(AuthEvent)
end end
progresssBarActive = false
return result return result
end end
@@ -301,6 +323,7 @@ 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() progressFunc[Config.System.ProgressBar].stop()
progresssBarActive = false
end end
-- System to handle sending/sharing progress bars between players -- -- System to handle sending/sharing progress bars between players --

View File

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

View File

@@ -25,7 +25,12 @@ Exports = {
-- REDM -- REDM
RSGExport = "rsg-core", RSGExport = "rsg-core",
RSGInv = "rsg-inventory" RSGInv = "rsg-inventory",
VorpExport = "vorp_core",
VorpInv = "vorp_inventory",
VorpMenu = "vorp_menu",
} }
-- Required variables -- Required variables

View File

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

View File

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