mirror of
https://github.com/jimathy/jim_bridge.git
synced 2026-08-17 22:16:03 +01:00
Merge branch 'main' of https://github.com/oosayeroo/jim_bridge
This commit is contained in:
12
.gitattributes
vendored
Normal file
12
.gitattributes
vendored
Normal 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
88
.github/workflows/release.yml
vendored
Normal 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 }}
|
||||
@@ -1,67 +1,78 @@
|
||||
local function parseVersion(version)
|
||||
local parts = {}
|
||||
for num in version:gmatch("%d+") do
|
||||
table.insert(parts, tonumber(num))
|
||||
end
|
||||
return parts
|
||||
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 function compareVersions(current, newest)
|
||||
local currentParts = parseVersion(current)
|
||||
local newestParts = parseVersion(newest)
|
||||
for i = 1, math.max(#currentParts, #newestParts) do
|
||||
local c = currentParts[i] or 0
|
||||
local n = newestParts[i] or 0
|
||||
if c < n then return -1
|
||||
elseif c > n then return 1 end
|
||||
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
|
||||
|
||||
if not SUPPRESS_UPDATES then
|
||||
local function parseVersion(version)
|
||||
local parts = {}
|
||||
for num in version:gmatch("%d+") do
|
||||
table.insert(parts, tonumber(num))
|
||||
end
|
||||
return parts
|
||||
end
|
||||
return 0 -- equal
|
||||
end
|
||||
|
||||
function CheckBridgeVersion()
|
||||
if IsDuplicityVersion() then
|
||||
CreateThread(function()
|
||||
Wait(4000)
|
||||
local currentVersionRaw = GetResourceMetadata("jim_bridge", 'version')
|
||||
--PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/test.txt', function(err, body, headers)
|
||||
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, body, headers)
|
||||
if not body then
|
||||
print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)")
|
||||
return
|
||||
end
|
||||
local function compareVersions(current, newest)
|
||||
local currentParts = parseVersion(current)
|
||||
local newestParts = parseVersion(newest)
|
||||
for i = 1, math.max(#currentParts, #newestParts) do
|
||||
local c = currentParts[i] or 0
|
||||
local n = newestParts[i] or 0
|
||||
if c < n then return -1
|
||||
elseif c > n then return 1 end
|
||||
end
|
||||
return 0 -- equal
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for line in body:gmatch("[^\r\n]+") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
local newestVersionRaw = lines[1] or "0.0.0"
|
||||
local changelog = {}
|
||||
for i = 2, #lines do
|
||||
table.insert(changelog, lines[i])
|
||||
end
|
||||
|
||||
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
|
||||
|
||||
if compareResult == 0 then
|
||||
print("^7'^3jim_bridge^7' - ^2You are running the latest version^7. ^7(^3"..currentVersionRaw.."^7)")
|
||||
elseif compareResult < 0 then
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
|
||||
for _, line in ipairs(changelog) do
|
||||
print((line:find("http") and "^7" or "^5")..line)
|
||||
function CheckBridgeVersion()
|
||||
if IsDuplicityVersion() then
|
||||
CreateThread(function()
|
||||
Wait(4000)
|
||||
local currentVersionRaw = GetResourceMetadata("jim_bridge", 'version')
|
||||
--PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/test.txt', function(err, body, headers)
|
||||
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, body, headers)
|
||||
if not body then
|
||||
print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)")
|
||||
return
|
||||
end
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
SetTimeout(3600000, function()
|
||||
CheckBridgeVersion()
|
||||
end)
|
||||
else
|
||||
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for line in body:gmatch("[^\r\n]+") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
local newestVersionRaw = lines[1] or "0.0.0"
|
||||
local changelog = {}
|
||||
for i = 2, #lines do
|
||||
table.insert(changelog, lines[i])
|
||||
end
|
||||
|
||||
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
|
||||
|
||||
if compareResult == 0 then
|
||||
print("^7'^3jim_bridge^7' - ^2You are running the latest version^7. ^7(^3"..currentVersionRaw.."^7)")
|
||||
elseif compareResult < 0 then
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
|
||||
for _, line in ipairs(changelog) do
|
||||
print((line:find("http") and "^7" or "^5")..line)
|
||||
end
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
SetTimeout(3600000, function()
|
||||
CheckBridgeVersion()
|
||||
end)
|
||||
else
|
||||
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7)")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
CheckBridgeVersion()
|
||||
CheckBridgeVersion()
|
||||
end
|
||||
@@ -31,17 +31,18 @@ local Exports = {
|
||||
|
||||
-- REDM
|
||||
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
|
||||
local cache = {
|
||||
Items = {},
|
||||
Vehicles = {},
|
||||
Jobs = {},
|
||||
Gangs = {},
|
||||
}
|
||||
local cache = { Items = {}, Vehicles = {}, Jobs = {}, Gangs = {}, }
|
||||
local cacheReady = false
|
||||
|
||||
-- Timer info, for debugging more then anything
|
||||
local timers = {}
|
||||
local function startTimer(label)
|
||||
timers[label] = GetGameTimer()
|
||||
@@ -52,26 +53,57 @@ local function endTimer(label)
|
||||
timers[label] = "("..(timers[label] / 1000).."s)"
|
||||
end
|
||||
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)
|
||||
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
|
||||
|
||||
-- Ensure oxmysql resource is loaded
|
||||
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) then
|
||||
local function waitStarted(resourceName)
|
||||
while GetResourceState(resourceName) ~= "started" do Wait(100) end
|
||||
end
|
||||
|
||||
local function waitStartedOrStopped(resourceName)
|
||||
local state = GetResourceState(resourceName)
|
||||
while state ~= "started" and state ~= "stopped" do
|
||||
Wait(100)
|
||||
state = GetResourceState(resourceName)
|
||||
end
|
||||
end
|
||||
|
||||
local function dupLowercaseWeapons(items)
|
||||
for k, v in pairs(items or {}) do
|
||||
if type(k) == "string" then
|
||||
if k:find("WEAPON") then
|
||||
items[k:lower()] = v
|
||||
end
|
||||
else
|
||||
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
|
||||
print("^1Possible Issue found^7:")
|
||||
print(json.encode(items[k], { indent = true }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Forceload libs --
|
||||
|
||||
-- Ensure oxmysql resource is loaded for jim_bridge internally
|
||||
-- For some core's it needs to get data from the database
|
||||
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) or checkExists(Exports.VorpExport) then
|
||||
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua')))
|
||||
fileLoader()
|
||||
end
|
||||
|
||||
if checkExists(Exports.OXCoreExport) then
|
||||
-- Detected OX_Core in server, wait for it to be started if needed
|
||||
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')))
|
||||
fileLoader()
|
||||
end
|
||||
if checkExists(Exports.ESXExport) then
|
||||
-- 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')))
|
||||
fileLoader()
|
||||
end
|
||||
@@ -89,225 +121,274 @@ end
|
||||
---------------------
|
||||
---- 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 success, result = pcall(function()
|
||||
return exports[Exports.OXInv]:Items()
|
||||
end)
|
||||
if success and result then
|
||||
cache.Items = result
|
||||
end
|
||||
|
||||
-- Get Weapon info and duplicate them if they are uppercase
|
||||
-- (duplicate incase anything checks for the uppercase version)
|
||||
for k, v in pairs(cache.Items) do
|
||||
if type(k) == "string" then
|
||||
if k:find("WEAPON") then
|
||||
cache.Items[k:lower()] = cache.Items[k]
|
||||
local itemFunc = {
|
||||
{ script = Exports.OXInv,
|
||||
cacheItem = function()
|
||||
local success, result = pcall(function()
|
||||
return exports[Exports.OXInv]:Items()
|
||||
end)
|
||||
if success and result then
|
||||
cache.Items = result
|
||||
end
|
||||
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()
|
||||
return exports[Exports.TgiannInv]:Items()
|
||||
end)
|
||||
if success and result then
|
||||
cache.Items = result
|
||||
end
|
||||
|
||||
-- Get Weapon info and duplicate them if they are uppercase
|
||||
-- (duplicate incase anything checks for the uppercase version)
|
||||
for k, v in pairs(cache.Items) do
|
||||
if type(k) == "string" then
|
||||
if k:find("WEAPON") then
|
||||
cache.Items[k:lower()] = cache.Items[k]
|
||||
end,
|
||||
},
|
||||
{ script = Exports.TgiannInv,
|
||||
cacheItem = function()
|
||||
local success, result = pcall(function()
|
||||
return exports[Exports.TgiannInv]:Items()
|
||||
end)
|
||||
if success and result then
|
||||
cache.Items = result
|
||||
end
|
||||
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,
|
||||
},
|
||||
{ script = Exports.QBXExport,
|
||||
cacheItem = function()
|
||||
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
|
||||
-- If this is nil, they need to update to qbx_core 1.23.0+
|
||||
if not cache.Items then
|
||||
-- if their inventory doesn't allow that (they refuse to update their butchered core replacement):
|
||||
if GetResourceState(Exports.QSInv):find("start") then
|
||||
itemResource = Exports.QSInv
|
||||
cache.Items = exports[Exports.QSInv]:GetItemList()
|
||||
|
||||
elseif GetResourceState(Exports.OrigenInv):find("start") then
|
||||
itemResource = Exports.OrigenInv
|
||||
cache.Items = exports[Exports.OrigenInv]:Items()
|
||||
|
||||
elseif GetResourceState(Exports.CodeMInv):find("start") then
|
||||
itemResource = Exports.CodeMInv
|
||||
cache.Items = exports[Exports.CodeMInv]:GetItemList()
|
||||
|
||||
elseif GetResourceState(Exports.CoreInv):find("start") then
|
||||
itemResource = Exports.CoreInv
|
||||
cache.Items = exports[Exports.CoreInv]:getItemsList()
|
||||
|
||||
elseif GetResourceState(Exports.TgiannInv):find("start") then
|
||||
itemResource = Exports.TgiannInv
|
||||
cache.Items = exports[Exports.TgiannInv]:Items()
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
{ script = Exports.QBExport,
|
||||
cacheItem = function()
|
||||
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
|
||||
end,
|
||||
},
|
||||
{ script = Exports.ESXExport,
|
||||
cacheItem = function()
|
||||
if GetResourceState(Exports.QSInv):find("start") then
|
||||
cache.Items = exports[Exports.QSInv]:GetItemList()
|
||||
else
|
||||
cache.Items = ESX.GetItems()
|
||||
while not next(cache.Items) do
|
||||
cache.Items = ESX.GetItems()
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
-- If this is nil, they need to update to qbx_core 1.23.0+
|
||||
if not cache.Items then
|
||||
-- if their inventory doesn't allow that (they refuse to update their butchered core replacement):
|
||||
if GetResourceState(Exports.QSInv):find("start") then
|
||||
itemResource = Exports.QSInv
|
||||
cache.Items = exports[Exports.QSInv]:GetItemList()
|
||||
|
||||
elseif GetResourceState(Exports.OrigenInv):find("start") then
|
||||
itemResource = Exports.OrigenInv
|
||||
cache.Items = exports[Exports.OrigenInv]:Items()
|
||||
|
||||
elseif GetResourceState(Exports.CodeMInv):find("start") then
|
||||
itemResource = Exports.CodeMInv
|
||||
cache.Items = exports[Exports.CodeMInv]:GetItemList()
|
||||
|
||||
elseif GetResourceState(Exports.CoreInv):find("start") then
|
||||
itemResource = Exports.CoreInv
|
||||
cache.Items = exports[Exports.CoreInv]:getItemsList()
|
||||
|
||||
elseif GetResourceState(Exports.TgiannInv):find("start") then
|
||||
itemResource = Exports.TgiannInv
|
||||
cache.Items = exports[Exports.TgiannInv]:Items()
|
||||
end
|
||||
end
|
||||
|
||||
elseif checkExists(Exports.QBExport) then
|
||||
while GetResourceState(Exports.QBExport) ~= "started" do Wait(100) end
|
||||
itemResource = Exports.QBExport
|
||||
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
|
||||
|
||||
elseif checkExists(Exports.ESXExport) then
|
||||
itemResource = Exports.ESXExport
|
||||
if GetResourceState(Exports.QSInv):find("start") then
|
||||
cache.Items = exports[Exports.QSInv]:GetItemList()
|
||||
else
|
||||
cache.Items = ESX.GetItems()
|
||||
while not next(cache.Items) do
|
||||
cache.Items = ESX.GetItems()
|
||||
Wait(1000)
|
||||
end
|
||||
end
|
||||
|
||||
elseif checkExists(Exports.RSGExport) then
|
||||
while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end
|
||||
itemResource = Exports.RSGExport
|
||||
cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
|
||||
end
|
||||
endTimer("Items")
|
||||
|
||||
---------------------
|
||||
--- Load Vehicles ---
|
||||
---------------------
|
||||
-- Vehicle loading depending on framework
|
||||
if checkExists(Exports.QBXExport) then
|
||||
vehResource = Exports.QBXExport
|
||||
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
|
||||
---
|
||||
local vehicleFunc = {
|
||||
|
||||
elseif checkExists(Exports.QBExport)then
|
||||
vehResource = Exports.QBExport
|
||||
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
|
||||
{ script = Exports.QBXExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
|
||||
end,
|
||||
},
|
||||
{ script = Exports.QBExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
|
||||
end,
|
||||
},
|
||||
{ script = Exports.OXCoreExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = {}
|
||||
for k, v in pairs(Ox.GetVehicleData()) do
|
||||
cache.Vehicles[k] = {
|
||||
model = k, hash = joaat(k),
|
||||
price = v.price,
|
||||
name = v.name,
|
||||
brand = v.make
|
||||
}
|
||||
end
|
||||
end,
|
||||
},
|
||||
{ script = Exports.ESXExport,
|
||||
cacheVehicle = function()
|
||||
while not MySQL do Wait(100) end
|
||||
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
|
||||
cache.Vehicles[v.model] = {
|
||||
model = v.model,
|
||||
hash = joaat(v.model),
|
||||
price = v.price,
|
||||
name = v.name,
|
||||
}
|
||||
end
|
||||
end,
|
||||
},
|
||||
{ script = Exports.RSGExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
|
||||
end,
|
||||
},
|
||||
{ script = Exports.VorpExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = { ["unkown"] = {} }
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
elseif checkExists(Exports.OXCoreExport) then
|
||||
vehResource = Exports.OXCoreExport
|
||||
cache.Vehicles = {}
|
||||
for k, v in pairs(Ox.GetVehicleData()) do
|
||||
cache.Vehicles[k] = {
|
||||
model = k, hash = joaat(k),
|
||||
price = v.price,
|
||||
name = v.name,
|
||||
brand = v.make
|
||||
}
|
||||
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
|
||||
|
||||
elseif checkExists(Exports.ESXExport) then
|
||||
vehResource = Exports.ESXExport
|
||||
while not MySQL do Wait(1000) end
|
||||
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
|
||||
cache.Vehicles[v.model] = {
|
||||
model = v.model,
|
||||
hash = joaat(v.model),
|
||||
price = v.price,
|
||||
name = v.name,
|
||||
}
|
||||
end
|
||||
|
||||
elseif checkExists(Exports.RSGExport) then
|
||||
vehResource = Exports.RSGExport
|
||||
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
|
||||
|
||||
end
|
||||
endTimer("Vehicles")
|
||||
|
||||
---------------------
|
||||
----- Load Jobs -----
|
||||
---------------------
|
||||
-- Jobs loading based on framework
|
||||
if checkExists(Exports.QBXExport) then
|
||||
jobResource = Exports.QBXExport
|
||||
cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
|
||||
|
||||
elseif checkExists(Exports.QBExport) then
|
||||
jobResource = Exports.QBExport
|
||||
cache.Jobs, cache.Gangs = exports[Exports.QBExport]:GetCoreObject().Shared.Jobs, exports[Exports.QBExport]:GetCoreObject().Shared.Gangs
|
||||
local jobFunc = {
|
||||
|
||||
elseif checkExists(Exports.OXCoreExport) then
|
||||
jobResource = Exports.OXCoreExport
|
||||
while not MySQL do Wait(1000) end
|
||||
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
|
||||
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
|
||||
local gradeMap = {}
|
||||
for _, grade in pairs(tempGrades) do
|
||||
gradeMap[grade.group] = gradeMap[grade.group] or {}
|
||||
gradeMap[grade.group][grade.grade] = { name = grade.label }
|
||||
end
|
||||
for _, job in pairs(tempJobs) do
|
||||
cache.Jobs[job.name] = {
|
||||
label = job.label,
|
||||
grades = gradeMap[job.name] or {}
|
||||
}
|
||||
end
|
||||
cache.Gangs = cache.Jobs
|
||||
|
||||
elseif checkExists(Exports.ESXExport) then
|
||||
jobResource = Exports.ESXExport
|
||||
ESX = exports[Exports.ESXExport]:getSharedObject()
|
||||
cache.Jobs = ESX.GetJobs()
|
||||
while not next(cache.Jobs) do
|
||||
Wait(100)
|
||||
cache.Jobs = ESX.GetJobs()
|
||||
end
|
||||
for Role, Grades in pairs(cache.Jobs) do
|
||||
-- Check for if user has added grades
|
||||
if Grades.grades == nil or not next(Grades.grades) then
|
||||
goto continue
|
||||
end
|
||||
for grade, info in pairs(Grades.grades) do
|
||||
if info.label and info.label:find("[Bb]oss") then
|
||||
cache.Jobs[Role].grades[grade].isBoss = true
|
||||
goto continue
|
||||
{ script = Exports.QBXExport,
|
||||
cacheJob = function()
|
||||
cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
|
||||
end,
|
||||
},
|
||||
{ script = Exports.QBExport,
|
||||
cacheJob = function()
|
||||
Core = exports[Exports.QBExport]:GetCoreObject()
|
||||
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
|
||||
end,
|
||||
},
|
||||
{ script = Exports.OXCoreExport,
|
||||
cacheJob = function()
|
||||
while not MySQL do Wait(100) end
|
||||
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
|
||||
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
|
||||
local gradeMap = {}
|
||||
for _, grade in pairs(tempGrades) do
|
||||
gradeMap[grade.group] = gradeMap[grade.group] or {}
|
||||
gradeMap[grade.group][grade.grade] = { name = grade.label }
|
||||
end
|
||||
end
|
||||
local highestGrade = nil
|
||||
for k in pairs(Grades.grades) do
|
||||
local num = tonumber(k)
|
||||
if num and (not highestGrade or num > highestGrade) then
|
||||
highestGrade = num
|
||||
for _, job in pairs(tempJobs) do
|
||||
cache.Jobs[job.name] = {
|
||||
label = job.label,
|
||||
grades = gradeMap[job.name] or {}
|
||||
}
|
||||
end
|
||||
end
|
||||
cache.Gangs = cache.Jobs
|
||||
end,
|
||||
},
|
||||
{ script = Exports.ESXExport,
|
||||
cacheJob = function()
|
||||
ESX = ESX or exports[Exports.ESXExport]:getSharedObject()
|
||||
cache.Jobs = ESX.GetJobs()
|
||||
while not next(cache.Jobs) do
|
||||
Wait(100)
|
||||
cache.Jobs = ESX.GetJobs()
|
||||
end
|
||||
for Role, Grades in pairs(cache.Jobs) do
|
||||
-- Check for if user has added grades
|
||||
if Grades.grades == nil or not next(Grades.grades) then
|
||||
goto continue
|
||||
end
|
||||
for grade, info in pairs(Grades.grades) do
|
||||
if info.label and info.label:find("[Bb]oss") then
|
||||
cache.Jobs[Role].grades[grade].isBoss = true
|
||||
goto continue
|
||||
end
|
||||
end
|
||||
local highestGrade = nil
|
||||
for k in pairs(Grades.grades) do
|
||||
local num = tonumber(k)
|
||||
if num and (not highestGrade or num > highestGrade) then
|
||||
highestGrade = num
|
||||
end
|
||||
end
|
||||
|
||||
if highestGrade then
|
||||
cache.Jobs[Role].grades[tostring(highestGrade)].isBoss = true
|
||||
end
|
||||
::continue::
|
||||
if highestGrade then
|
||||
cache.Jobs[Role].grades[tostring(highestGrade)].isBoss = true
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
cache.Gangs = cache.Jobs
|
||||
end,
|
||||
},
|
||||
{ script = Exports.RSGExport,
|
||||
cacheJob = function()
|
||||
Core = exports[Exports.RSGExport]:GetCoreObject()
|
||||
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
|
||||
end,
|
||||
},
|
||||
{ script = Exports.VorpExport,
|
||||
cacheJob = function()
|
||||
cache.Jobs = { ["unkown"] = {} }
|
||||
cache.Gangs = cache.Jobs
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
for i = 1, #jobFunc do
|
||||
local data = jobFunc[i]
|
||||
if checkExists(data.script) then
|
||||
waitStarted(data.script) -- Wait for detected script to start fully
|
||||
data.cacheJob() -- run tablized function for core
|
||||
jobResource = data.script -- Grab script name to announce later
|
||||
endTimer("Jobs") -- end timer
|
||||
break -- break loop so it doesn't keep checking
|
||||
end
|
||||
cache.Gangs = cache.Jobs
|
||||
|
||||
elseif checkExists(Exports.RSGExport) then
|
||||
jobResource = Exports.RSGExport
|
||||
cache.Jobs, cache.Gangs = exports[Exports.RSGExport]:GetCoreObject().Shared.Jobs, exports[Exports.RSGExport]:GetCoreObject().Shared.Gangs
|
||||
|
||||
end
|
||||
endTimer("Jobs")
|
||||
|
||||
|
||||
-- Fallback if nil or empty
|
||||
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 script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end
|
||||
|
||||
while GetResourceState(script) ~= "started" and GetResourceState(script) ~= "stopped" do
|
||||
Wait(100)
|
||||
print("Waiting for script start")
|
||||
end
|
||||
waitStartedOrStopped(script)
|
||||
|
||||
local attempts = data.fallback or { data }
|
||||
local lookup, used, err
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aw
|
||||
games { 'gta5', 'rdr3' }
|
||||
lua54 'yes'
|
||||
|
||||
|
||||
files {
|
||||
'starter.lua',
|
||||
'shared/*.lua',
|
||||
@@ -24,4 +23,6 @@ server_scripts {
|
||||
client_scripts {
|
||||
'clientFrameworkCache.lua',
|
||||
'ui_modules/*.lua',
|
||||
}
|
||||
}
|
||||
|
||||
suppress_updates 'false' -- set to 'true' to disable update pings
|
||||
@@ -12,7 +12,7 @@ if isServer() then
|
||||
local src = source
|
||||
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
|
||||
--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")
|
||||
return ""
|
||||
end
|
||||
@@ -45,8 +45,9 @@ if isServer() then
|
||||
|
||||
createCallback(getScript()..":callback:GetAuthEvent", function(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")
|
||||
return ""
|
||||
end
|
||||
|
||||
@@ -62,6 +62,9 @@ function onPlayerLoaded(func, onStart)
|
||||
elseif isStarted(RSGExport) then
|
||||
onPlayerFramework = RSGExport
|
||||
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', tempFunc)
|
||||
elseif isStarted(VorpExport) then
|
||||
onPlayerFramework = VorpExport
|
||||
RegisterNetEvent("vorp_core:Client:OnPlayerSpawned", tempFunc)
|
||||
end
|
||||
|
||||
if onPlayerFramework ~= "" then
|
||||
@@ -174,6 +177,12 @@ function waitForLogin()
|
||||
break
|
||||
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
|
||||
-- For other frameworks, use LocalPlayer.state.isLoggedIn.
|
||||
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
|
||||
|
||||
@@ -1,100 +1,111 @@
|
||||
function parseVersion(version)
|
||||
local parts = {}
|
||||
for num in version:gmatch("%d+") do
|
||||
table.insert(parts, tonumber(num))
|
||||
local function readBoolMeta(key, default)
|
||||
local val = GetResourceMetadata("jim_bridge", key, 0)
|
||||
if not val then return default end
|
||||
val = tostring(val):lower()
|
||||
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
|
||||
end
|
||||
|
||||
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
|
||||
|
||||
if not SUPPRESS_UPDATES then
|
||||
function parseVersion(version)
|
||||
local parts = {}
|
||||
for num in version:gmatch("%d+") do
|
||||
table.insert(parts, tonumber(num))
|
||||
end
|
||||
return parts
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
function compareVersions(current, newest)
|
||||
local currentParts = parseVersion(current)
|
||||
local newestParts = parseVersion(newest)
|
||||
for i = 1, math.max(#currentParts, #newestParts) do
|
||||
local c = currentParts[i] or 0
|
||||
local n = newestParts[i] or 0
|
||||
if c < n then return -1
|
||||
elseif c > n then return 1 end
|
||||
function compareVersions(current, newest)
|
||||
local currentParts = parseVersion(current)
|
||||
local newestParts = parseVersion(newest)
|
||||
for i = 1, math.max(#currentParts, #newestParts) do
|
||||
local c = currentParts[i] or 0
|
||||
local n = newestParts[i] or 0
|
||||
if c < n then return -1
|
||||
elseif c > n then return 1 end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function capitalize(str)
|
||||
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
|
||||
end
|
||||
function capitalize(str)
|
||||
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
|
||||
end
|
||||
|
||||
local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
|
||||
local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
|
||||
local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
|
||||
local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
|
||||
local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
|
||||
local scriptVersion = ("^5"..GetResourceMetadata(getScript(), 'version', nil):gsub("%.", "^7.^5")) or ""
|
||||
local scriptDescription = ("^2"..GetResourceMetadata(getScript(), 'description', nil)) or ""
|
||||
local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
|
||||
|
||||
print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
|
||||
print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
|
||||
|
||||
function CheckVersion()
|
||||
if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then
|
||||
CreateThread(function()
|
||||
Wait(4000)
|
||||
local script = getScript()
|
||||
local currentVersionRaw = GetResourceMetadata(script, 'version')
|
||||
function CheckVersion()
|
||||
if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then
|
||||
CreateThread(function()
|
||||
Wait(4000)
|
||||
local script = getScript()
|
||||
local currentVersionRaw = GetResourceMetadata(script, 'version')
|
||||
|
||||
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..script..'.txt', function(err, newestVersionRaw, headers)
|
||||
if not newestVersionRaw then
|
||||
-- fallback
|
||||
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..script..'/master/version.txt', function(err, fallbackVersionRaw, headers)
|
||||
if not fallbackVersionRaw then
|
||||
print("^1Currently unable to run a version check for ^7'^3"..script.."^7' (^3"..currentVersionRaw.."^7)")
|
||||
return
|
||||
end
|
||||
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..script..'.txt', function(err, newestVersionRaw, headers)
|
||||
if not newestVersionRaw then
|
||||
-- fallback
|
||||
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..script..'/master/version.txt', function(err, fallbackVersionRaw, headers)
|
||||
if not fallbackVersionRaw then
|
||||
print("^1Currently unable to run a version check for ^7'^3"..script.."^7' (^3"..currentVersionRaw.."^7)")
|
||||
return
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for line in fallbackVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
|
||||
local fallbackVersion = (lines[1] or "0.0.0"):gsub("v", "")
|
||||
local changelog = {}
|
||||
for i = 2, #lines do table.insert(changelog, lines[i]) end
|
||||
|
||||
local compareResult = compareVersions(currentVersionRaw, fallbackVersion)
|
||||
if compareResult == 0 then
|
||||
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
|
||||
elseif compareResult < 0 then
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersion.."^7)")
|
||||
if #changelog > 0 then
|
||||
for _, line in ipairs(changelog) do
|
||||
print((line:find("http") and "^7" or "^5")..line)
|
||||
end
|
||||
end
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
SetTimeout(1200000, function() CheckVersion() end)
|
||||
else
|
||||
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersion.."^7)")
|
||||
end
|
||||
end)
|
||||
else
|
||||
local lines = {}
|
||||
for line in fallbackVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
|
||||
local fallbackVersion = (lines[1] or "0.0.0"):gsub("v", "")
|
||||
for line in newestVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
|
||||
local newestVersion = (lines[1] or "0.0.0"):gsub("v", "")
|
||||
local changelog = {}
|
||||
for i = 2, #lines do table.insert(changelog, lines[i]) end
|
||||
|
||||
local compareResult = compareVersions(currentVersionRaw, fallbackVersion)
|
||||
local compareResult = compareVersions(currentVersionRaw, newestVersion)
|
||||
if compareResult == 0 then
|
||||
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
|
||||
elseif compareResult < 0 then
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersion.."^7)")
|
||||
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersion.."^7)")
|
||||
if #changelog > 0 then
|
||||
for _, line in ipairs(changelog) do
|
||||
print((line:find("http") and "^7" or "^5")..line)
|
||||
end
|
||||
end
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
SetTimeout(1200000, function() CheckVersion() end)
|
||||
SetTimeout(3600000, function() CheckVersion() end)
|
||||
else
|
||||
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersion.."^7)")
|
||||
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
|
||||
end
|
||||
end)
|
||||
else
|
||||
local lines = {}
|
||||
for line in newestVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
|
||||
local newestVersion = (lines[1] or "0.0.0"):gsub("v", "")
|
||||
local changelog = {}
|
||||
for i = 2, #lines do table.insert(changelog, lines[i]) end
|
||||
|
||||
local compareResult = compareVersions(currentVersionRaw, newestVersion)
|
||||
if compareResult == 0 then
|
||||
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
|
||||
elseif compareResult < 0 then
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersion.."^7)")
|
||||
if #changelog > 0 then
|
||||
for _, line in ipairs(changelog) do
|
||||
print((line:find("http") and "^7" or "^5")..line)
|
||||
end
|
||||
end
|
||||
print("^1----------------------------------------------------------------------^7")
|
||||
SetTimeout(3600000, function() CheckVersion() end)
|
||||
else
|
||||
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CheckVersion()
|
||||
CheckVersion()
|
||||
end
|
||||
@@ -37,6 +37,10 @@ function createCallback(callbackName, funct)
|
||||
debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
|
||||
Core = Core or exports[QBExport]:GetCoreObject()
|
||||
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
|
||||
debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
|
||||
ESX.RegisterServerCallback(callbackName, adaptedFunction)
|
||||
@@ -76,6 +80,13 @@ function triggerCallback(callbackName, ...)
|
||||
end, ...)
|
||||
result = Citizen.Await(p)
|
||||
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
|
||||
local p = promise.new()
|
||||
ESX.TriggerServerCallback(callbackName, function(cbResult)
|
||||
|
||||
@@ -10,6 +10,263 @@
|
||||
• esx (using ESX.UI.Menu)
|
||||
]]
|
||||
|
||||
local contextFunc = {
|
||||
["ox"] =
|
||||
function(Menu, data)
|
||||
local index = nil
|
||||
if data.onBack and not data.onSelected then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
title = "Return",
|
||||
onSelect = data.onBack,
|
||||
label = "Return",
|
||||
})
|
||||
end
|
||||
for k in pairs(Menu) do
|
||||
if data.onSelected and Menu[k].arrow then
|
||||
Menu[k].icon = "fas fa-angle-right"
|
||||
end
|
||||
-- If no title, use header or txt as title/label.
|
||||
if not Menu[k].title then
|
||||
if Menu[k].header ~= nil and Menu[k].header ~= "" then
|
||||
Menu[k].title = Menu[k].header
|
||||
Menu[k].label = Menu[k].header
|
||||
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
|
||||
else
|
||||
Menu[k].title = Menu[k].txt
|
||||
Menu[k].label = Menu[k].txt
|
||||
end
|
||||
end
|
||||
-- Copy parameters from 'params' if available.
|
||||
if Menu[k].params then
|
||||
Menu[k].event = Menu[k].params.event
|
||||
Menu[k].args = Menu[k].params.args or {}
|
||||
end
|
||||
if Menu[k].isMenuHeader then
|
||||
Menu[k].readOnly = true
|
||||
end
|
||||
end
|
||||
local menuID = 'Menu'
|
||||
(data.onSelected and lib.registerMenu or lib.registerContext)({
|
||||
id = menuID,
|
||||
title = data.header..br..br..(data.headertxt and data.headertxt or ""),
|
||||
position = 'top-right',
|
||||
options = Menu,
|
||||
canClose = data.canClose and data.canClose or nil,
|
||||
onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
|
||||
onExit = data.onExit and data.onExit or nil,
|
||||
onSelected = data.onSelected and (function(selected) index = selected end) or nil,
|
||||
}, data.onSelected and (function(x, y, args)
|
||||
if Menu[x].refresh then
|
||||
if Menu[x].onSelect then
|
||||
Menu[x].onSelect()
|
||||
end
|
||||
lib.showMenu(menuID, index)
|
||||
else
|
||||
if Menu[x].onSelect then
|
||||
Menu[x].onSelect()
|
||||
else
|
||||
lib.showMenu(menuID, index)
|
||||
end
|
||||
end
|
||||
end) or nil)
|
||||
if data.onSelected then
|
||||
lib.showMenu(menuID, 1)
|
||||
else
|
||||
lib.showContext(menuID)
|
||||
end
|
||||
end,
|
||||
|
||||
["qb"] =
|
||||
function(Menu, data)
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
header = " ",
|
||||
txt = "Return",
|
||||
params = {
|
||||
isAction = true,
|
||||
event = data.onBack,
|
||||
},
|
||||
})
|
||||
elseif data.canClose then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-xmark",
|
||||
header = " ",
|
||||
txt = "Close",
|
||||
params = {
|
||||
isAction = true,
|
||||
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
|
||||
},
|
||||
})
|
||||
end
|
||||
if data.header ~= nil then
|
||||
local tempMenu = {}
|
||||
for k, v in pairs(Menu) do tempMenu[k + 1] = v end
|
||||
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
|
||||
Menu = tempMenu
|
||||
end
|
||||
for k in pairs(Menu) do
|
||||
if not Menu[k].params or not Menu[k].params.event then
|
||||
Menu[k].params = {
|
||||
isAction = true,
|
||||
event = Menu[k].onSelect or function() end,
|
||||
}
|
||||
end
|
||||
if not Menu[k].header then Menu[k].header = " " end
|
||||
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
|
||||
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
|
||||
end
|
||||
exports[QBMenuExport]:openMenu(Menu)
|
||||
end,
|
||||
|
||||
["gta"] =
|
||||
function(Menu, data)
|
||||
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
|
||||
titleColor = { 222, 255, 255 },
|
||||
maxOptionCountOnScreen = 15,
|
||||
width = 0.25,
|
||||
x = 0.7,
|
||||
})
|
||||
if WarMenu.IsAnyMenuOpened() then return end
|
||||
WarMenu.OpenMenu(tostring(Menu))
|
||||
CreateThread(function()
|
||||
local close = true
|
||||
while true do
|
||||
if WarMenu.Begin(tostring(Menu)) then
|
||||
if data.onBack then
|
||||
if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
|
||||
WarMenu.CloseMenu()
|
||||
Wait(10)
|
||||
data.onBack()
|
||||
end
|
||||
end
|
||||
for k in pairs(Menu) do
|
||||
local pressed = WarMenu.Button(Menu[k].header)
|
||||
if not Menu[k].header then
|
||||
Menu[k].header = Menu[k].txt
|
||||
Menu[k].txt = nil
|
||||
end
|
||||
if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
|
||||
if Menu[k].disabled or Menu[k].isMenuHeader then
|
||||
WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
|
||||
else
|
||||
WarMenu.ToolTip(
|
||||
(Menu[k].blip and "~BLIP_".."8".."~ " or "")..
|
||||
Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
|
||||
true)
|
||||
end
|
||||
end
|
||||
if pressed and not Menu[k].isMenuHeader then
|
||||
WarMenu.CloseMenu()
|
||||
close = false
|
||||
Wait(10)
|
||||
Menu[k].onSelect()
|
||||
end
|
||||
end
|
||||
WarMenu.End()
|
||||
else
|
||||
return
|
||||
end
|
||||
if not WarMenu.IsAnyMenuOpened() and close then
|
||||
stopTempCam(cam)
|
||||
if data.onExit then data.onExit() end
|
||||
end
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end,
|
||||
|
||||
["esx"] =
|
||||
function(Menu, data)
|
||||
for k in pairs(Menu) do
|
||||
Menu[k].label = Menu[k].header
|
||||
Menu[k].name = "button"..k
|
||||
end
|
||||
if data.canClose then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-xmark",
|
||||
label = "Close",
|
||||
name = "close",
|
||||
onSelect = data.onExit,
|
||||
})
|
||||
end
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
label = "Return",
|
||||
name = "return",
|
||||
onSelect = data.onBack,
|
||||
})
|
||||
end
|
||||
ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
|
||||
title = data.header,
|
||||
align = 'top-right',
|
||||
elements = Menu,
|
||||
},
|
||||
function(menuData, menu)
|
||||
for k in pairs(Menu) do
|
||||
if menuData.current.name == Menu[k].name then
|
||||
menu.close()
|
||||
Wait(10)
|
||||
Menu[k].onSelect()
|
||||
end
|
||||
end
|
||||
end,
|
||||
function(data, menu)
|
||||
menu.close()
|
||||
end)
|
||||
end,
|
||||
|
||||
["lation"] =
|
||||
function(Menu, data)
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
onSelect = data.onBack,
|
||||
header = "Return",
|
||||
})
|
||||
end
|
||||
|
||||
for k in pairs(Menu) do
|
||||
if data.onSelected and Menu[k].arrow then
|
||||
Menu[k].icon = "fas fa-angle-right"
|
||||
end
|
||||
-- If no title, use header or txt as title/label.
|
||||
if not Menu[k].title then
|
||||
if Menu[k].header ~= nil and Menu[k].header ~= "" then
|
||||
Menu[k].title = Menu[k].header
|
||||
Menu[k].label = Menu[k].header
|
||||
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
|
||||
else
|
||||
Menu[k].title = Menu[k].txt
|
||||
Menu[k].label = Menu[k].txt
|
||||
end
|
||||
end
|
||||
-- Copy parameters from 'params' if available.
|
||||
if Menu[k].params then
|
||||
Menu[k].event = Menu[k].params.event
|
||||
Menu[k].args = Menu[k].params.args or {}
|
||||
end
|
||||
if Menu[k].isMenuHeader then
|
||||
Menu[k].readOnly = true
|
||||
end
|
||||
end
|
||||
|
||||
exports.lation_ui:registerMenu({
|
||||
id = 'menu',
|
||||
title = data.header,
|
||||
onExit = data.onExit and data.onExit or nil,
|
||||
subtitle = (data.headertxt and data.headertxt or ""),
|
||||
options = Menu,
|
||||
})
|
||||
|
||||
-- Show menu
|
||||
exports.lation_ui:showMenu('menu')
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
--- Opens a menu using the configured menu system.
|
||||
---
|
||||
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
|
||||
@@ -47,255 +304,22 @@
|
||||
--- })
|
||||
--- ```
|
||||
function openMenu(Menu, data)
|
||||
if Config.System.Menu == "ox" then
|
||||
local index = nil
|
||||
if data.onBack and not data.onSelected then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
title = "Return",
|
||||
onSelect = data.onBack,
|
||||
label = "Return",
|
||||
})
|
||||
end
|
||||
for k in pairs(Menu) do
|
||||
if data.onSelected and Menu[k].arrow then
|
||||
Menu[k].icon = "fas fa-angle-right"
|
||||
end
|
||||
-- If no title, use header or txt as title/label.
|
||||
if not Menu[k].title then
|
||||
if Menu[k].header ~= nil and Menu[k].header ~= "" then
|
||||
Menu[k].title = Menu[k].header
|
||||
Menu[k].label = Menu[k].header
|
||||
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
|
||||
else
|
||||
Menu[k].title = Menu[k].txt
|
||||
Menu[k].label = Menu[k].txt
|
||||
end
|
||||
end
|
||||
-- Copy parameters from 'params' if available.
|
||||
if Menu[k].params then
|
||||
Menu[k].event = Menu[k].params.event
|
||||
Menu[k].args = Menu[k].params.args or {}
|
||||
end
|
||||
if Menu[k].isMenuHeader then
|
||||
Menu[k].readOnly = true
|
||||
end
|
||||
end
|
||||
local menuID = 'Menu'
|
||||
(data.onSelected and lib.registerMenu or lib.registerContext)({
|
||||
id = menuID,
|
||||
title = data.header..br..br..(data.headertxt and data.headertxt or ""),
|
||||
position = 'top-right',
|
||||
options = Menu,
|
||||
canClose = data.canClose and data.canClose or nil,
|
||||
onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
|
||||
onExit = data.onExit and data.onExit or nil,
|
||||
onSelected = data.onSelected and (function(selected) index = selected end) or nil,
|
||||
}, data.onSelected and (function(x, y, args)
|
||||
if Menu[x].refresh then
|
||||
if Menu[x].onSelect then
|
||||
Menu[x].onSelect()
|
||||
end
|
||||
lib.showMenu(menuID, index)
|
||||
else
|
||||
if Menu[x].onSelect then
|
||||
Menu[x].onSelect()
|
||||
else
|
||||
lib.showMenu(menuID, index)
|
||||
end
|
||||
end
|
||||
end) or nil)
|
||||
if data.onSelected then
|
||||
lib.showMenu(menuID, 1)
|
||||
else
|
||||
lib.showContext(menuID)
|
||||
end
|
||||
|
||||
elseif Config.System.Menu == "qb" then
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
header = " ",
|
||||
txt = "Return",
|
||||
params = {
|
||||
isAction = true,
|
||||
event = data.onBack,
|
||||
},
|
||||
})
|
||||
elseif data.canClose then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-xmark",
|
||||
header = " ",
|
||||
txt = "Close",
|
||||
params = {
|
||||
isAction = true,
|
||||
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
|
||||
},
|
||||
})
|
||||
end
|
||||
if data.header ~= nil then
|
||||
local tempMenu = {}
|
||||
for k, v in pairs(Menu) do tempMenu[k + 1] = v end
|
||||
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
|
||||
Menu = tempMenu
|
||||
end
|
||||
for k in pairs(Menu) do
|
||||
if not Menu[k].params or not Menu[k].params.event then
|
||||
Menu[k].params = {
|
||||
isAction = true,
|
||||
event = Menu[k].onSelect or function() end,
|
||||
}
|
||||
end
|
||||
if not Menu[k].header then Menu[k].header = " " end
|
||||
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
|
||||
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
|
||||
end
|
||||
exports[QBMenuExport]:openMenu(Menu)
|
||||
|
||||
elseif Config.System.Menu == "gta" then
|
||||
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
|
||||
titleColor = { 222, 255, 255 },
|
||||
maxOptionCountOnScreen = 15,
|
||||
width = 0.25,
|
||||
x = 0.7,
|
||||
})
|
||||
if WarMenu.IsAnyMenuOpened() then return end
|
||||
WarMenu.OpenMenu(tostring(Menu))
|
||||
CreateThread(function()
|
||||
local close = true
|
||||
while true do
|
||||
if WarMenu.Begin(tostring(Menu)) then
|
||||
if data.onBack then
|
||||
if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
|
||||
WarMenu.CloseMenu()
|
||||
Wait(10)
|
||||
data.onBack()
|
||||
end
|
||||
end
|
||||
for k in pairs(Menu) do
|
||||
local pressed = WarMenu.Button(Menu[k].header)
|
||||
if not Menu[k].header then
|
||||
Menu[k].header = Menu[k].txt
|
||||
Menu[k].txt = nil
|
||||
end
|
||||
if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
|
||||
if Menu[k].disabled or Menu[k].isMenuHeader then
|
||||
WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
|
||||
else
|
||||
WarMenu.ToolTip(
|
||||
(Menu[k].blip and "~BLIP_".."8".."~ " or "")..
|
||||
Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
|
||||
true)
|
||||
end
|
||||
end
|
||||
if pressed and not Menu[k].isMenuHeader then
|
||||
WarMenu.CloseMenu()
|
||||
close = false
|
||||
Wait(10)
|
||||
Menu[k].onSelect()
|
||||
end
|
||||
end
|
||||
WarMenu.End()
|
||||
else
|
||||
return
|
||||
end
|
||||
if not WarMenu.IsAnyMenuOpened() and close then
|
||||
stopTempCam(cam)
|
||||
if data.onExit then data.onExit() end
|
||||
end
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
|
||||
elseif Config.System.Menu == "esx" then
|
||||
for k in pairs(Menu) do
|
||||
Menu[k].label = Menu[k].header
|
||||
Menu[k].name = "button"..k
|
||||
end
|
||||
if data.canClose then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-xmark",
|
||||
label = "Close",
|
||||
name = "close",
|
||||
onSelect = data.onExit,
|
||||
})
|
||||
end
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
label = "Return",
|
||||
name = "return",
|
||||
onSelect = data.onBack,
|
||||
})
|
||||
end
|
||||
ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
|
||||
title = data.header,
|
||||
align = 'top-right',
|
||||
elements = Menu,
|
||||
},
|
||||
function(menuData, menu)
|
||||
for k in pairs(Menu) do
|
||||
if menuData.current.name == Menu[k].name then
|
||||
menu.close()
|
||||
Wait(10)
|
||||
Menu[k].onSelect()
|
||||
end
|
||||
end
|
||||
end,
|
||||
function(data, menu)
|
||||
menu.close()
|
||||
end)
|
||||
|
||||
elseif Config.System.Menu == "lation" then
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
onSelect = data.onBack,
|
||||
header = "Return",
|
||||
})
|
||||
end
|
||||
|
||||
for k in pairs(Menu) do
|
||||
if data.onSelected and Menu[k].arrow then
|
||||
Menu[k].icon = "fas fa-angle-right"
|
||||
end
|
||||
-- If no title, use header or txt as title/label.
|
||||
if not Menu[k].title then
|
||||
if Menu[k].header ~= nil and Menu[k].header ~= "" then
|
||||
Menu[k].title = Menu[k].header
|
||||
Menu[k].label = Menu[k].header
|
||||
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
|
||||
else
|
||||
Menu[k].title = Menu[k].txt
|
||||
Menu[k].label = Menu[k].txt
|
||||
end
|
||||
end
|
||||
-- Copy parameters from 'params' if available.
|
||||
if Menu[k].params then
|
||||
Menu[k].event = Menu[k].params.event
|
||||
Menu[k].args = Menu[k].params.args or {}
|
||||
end
|
||||
if Menu[k].isMenuHeader then
|
||||
Menu[k].readOnly = true
|
||||
end
|
||||
end
|
||||
|
||||
exports.lation_ui:registerMenu({
|
||||
id = 'menu',
|
||||
title = data.header,
|
||||
onExit = data.onExit and data.onExit or nil,
|
||||
subtitle = (data.headertxt and data.headertxt or ""),
|
||||
options = Menu,
|
||||
})
|
||||
|
||||
-- Show menu
|
||||
exports.lation_ui:showMenu('menu')
|
||||
|
||||
end
|
||||
contextFunc[Config.System.Menu](Menu, data)
|
||||
end
|
||||
|
||||
-- Line break conversion table, basically, to automate the linebreak in functions `br`
|
||||
-- This is declared and automatically sets it for what menu you are using (depends if one is uding MD formatting or HTML formatting)
|
||||
local lineBreakConversion = {
|
||||
["ox"] = "\n",
|
||||
["gta"] = "\n",
|
||||
["lation"] = "\n",
|
||||
|
||||
["qb"] = "<br>",
|
||||
["esx"] = "<br>",
|
||||
}
|
||||
|
||||
--- A line break constant used for menu header formatting.
|
||||
br = (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.
|
||||
--- @return boolean boolean True if using ox or gta menus, otherwise false.
|
||||
|
||||
@@ -22,6 +22,9 @@ OXInv, QBInv, PSInv, QSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
|
||||
Exports.JPRInv or ""
|
||||
|
||||
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
|
||||
VorpExport, VorpInv = Exports.VorpExport or "", Exports.VorpInv or ""
|
||||
|
||||
|
||||
QBMenuExport = Exports.QBMenuExport 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()
|
||||
elseif isStarted(RSGExport) then
|
||||
Core = Core or exports[RSGExport]:GetCoreObject()
|
||||
elseif isStarted(VorpExport) then
|
||||
Core = Core or exports[VorpExport]:GetCore()
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -115,12 +115,12 @@ function craftingMenu(data)
|
||||
local metaTable = {}
|
||||
-- Build ingredient details.
|
||||
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 missingMark = not hasItem and " ❌" or " "
|
||||
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
|
||||
end
|
||||
|
||||
@@ -128,7 +128,7 @@ function craftingMenu(data)
|
||||
Wait(10)
|
||||
end
|
||||
disable = not checkStashItem(data.stashName, itemTable)
|
||||
setheader = ((metadata and metadata.label) or (Items[tostring(k)] and Items[tostring(k)].label) or "error - "..tostring(k))
|
||||
setheader = ((metadata and metadata.label) or getItemLabel(k))
|
||||
..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")
|
||||
|
||||
local statusEmoji = disable and " " or not canCarryTable[k] and " 📦" or " ✔️"
|
||||
@@ -359,7 +359,7 @@ function makeItem(data)
|
||||
return
|
||||
end
|
||||
if crafting and progressBar({
|
||||
label = "Using "..b.." "..Items[l].label,
|
||||
label = "Using "..b.." "..getItemLabel(l),
|
||||
time = 1000,
|
||||
cancel = true,
|
||||
dict = 'pickup_object',
|
||||
@@ -398,7 +398,7 @@ function makeItem(data)
|
||||
PlaySoundFromEntity(s.soundId, s.audioName, PlayerPedId(), s.audioRef, true, 0)
|
||||
end
|
||||
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,
|
||||
cancel = true,
|
||||
dict = animDict,
|
||||
@@ -437,7 +437,7 @@ function makeItem(data)
|
||||
-- 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),
|
||||
label = bartext..((metadata and metadata.label) or getItemLabel(data.item)),
|
||||
time = bartime,
|
||||
cancel = true,
|
||||
dict = animDict,
|
||||
|
||||
@@ -180,6 +180,7 @@ function GetPrintTime()
|
||||
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)..")"
|
||||
else
|
||||
if gameName == "rdr3" then return "" end
|
||||
local _, _, _, hour, min, sec = GetLocalTime()
|
||||
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
|
||||
end
|
||||
|
||||
@@ -201,7 +201,7 @@ local InvFunc = {
|
||||
stashRemoveItem =
|
||||
function(stashItems, stashName, items)
|
||||
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)
|
||||
end
|
||||
end,
|
||||
@@ -291,7 +291,7 @@ local InvFunc = {
|
||||
stashRemoveItem =
|
||||
function(stashItems, stashName, items)
|
||||
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)
|
||||
end
|
||||
end,
|
||||
@@ -381,7 +381,7 @@ local InvFunc = {
|
||||
stashRemoveItem =
|
||||
function(stashItems, stashName, items)
|
||||
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)
|
||||
end
|
||||
end,
|
||||
@@ -494,7 +494,7 @@ local InvFunc = {
|
||||
end
|
||||
end
|
||||
end
|
||||
exports[CodeMInv]:UpdateStash(stashName, stashItems)
|
||||
exports[CodeMInv]:UpdateStash(stashName[1], stashItems)
|
||||
end,
|
||||
registerStash =
|
||||
function(name, label, slots, weight, owner, coords)
|
||||
@@ -588,8 +588,8 @@ local InvFunc = {
|
||||
stashRemoveItem =
|
||||
function(stashItems, stashName, items)
|
||||
for k, v in pairs(items) do
|
||||
local itemData = exports[TgiannInv]:GetItemByNameFromSecondaryInventory("stash", stashName, k)
|
||||
exports[TgiannInv]:RemoveItemFromSecondaryInventory("stash", stashName, k, v, itemData.slot, nil)
|
||||
local itemData = exports[TgiannInv]:GetItemByNameFromSecondaryInventory("stash", stashName[1], k)
|
||||
exports[TgiannInv]:RemoveItemFromSecondaryInventory("stash", stashName[1], k, v, itemData.slot, nil)
|
||||
debugPrint("^6Bridge^7: ^2Removing ^3"..TgiannInv.." ^2Stash item^7:", k, v)
|
||||
end
|
||||
end,
|
||||
@@ -743,7 +743,7 @@ local InvFunc = {
|
||||
stashRemoveItem =
|
||||
function(stashItems, stashName, items)
|
||||
if not stashItems or not next(stashItems) then
|
||||
stashItems = getStash(stashName)
|
||||
stashItems = getStash(stashName[1])
|
||||
end
|
||||
for k, v in pairs(items) do
|
||||
for l in pairs(stashItems) do
|
||||
@@ -757,9 +757,9 @@ local InvFunc = {
|
||||
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', {
|
||||
['stash'] = stashName,
|
||||
['stash'] = stashName[1],
|
||||
['items'] = json.encode(stashItems)
|
||||
})
|
||||
end,
|
||||
@@ -924,12 +924,12 @@ local InvFunc = {
|
||||
function(stashItems, stashName, items)
|
||||
if checkExportExists(QBInv, "RemoveItem") then
|
||||
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)
|
||||
end
|
||||
else
|
||||
if not stashItems or not next(stashItems) then
|
||||
stashItems = getStash(stashName)
|
||||
stashItems = getStash(stashName[1])
|
||||
end
|
||||
for k, v in pairs(items) do
|
||||
for l in pairs(stashItems) do
|
||||
@@ -943,9 +943,9 @@ local InvFunc = {
|
||||
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', {
|
||||
['stash'] = stashName,
|
||||
['stash'] = stashName[1],
|
||||
['items'] = json.encode(stashItems)
|
||||
})
|
||||
end
|
||||
@@ -1105,7 +1105,7 @@ local InvFunc = {
|
||||
stashRemoveItem =
|
||||
function(stashItems, stashName, items)
|
||||
if not stashItems or not next(stashItems) then
|
||||
stashItems = getStash(stashName)
|
||||
stashItems = getStash(stashName[1])
|
||||
end
|
||||
for k, v in pairs(items) do
|
||||
for l in pairs(stashItems) do
|
||||
@@ -1119,9 +1119,9 @@ local InvFunc = {
|
||||
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', {
|
||||
['stash'] = stashName,
|
||||
['stash'] = stashName[1],
|
||||
['items'] = json.encode(stashItems)
|
||||
})
|
||||
end,
|
||||
@@ -1244,7 +1244,7 @@ local InvFunc = {
|
||||
stashRemoveItem =
|
||||
function(stashItems, stashName, items)
|
||||
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)
|
||||
end
|
||||
end,
|
||||
@@ -1585,7 +1585,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
|
||||
|
||||
end
|
||||
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
|
||||
else
|
||||
@@ -1610,7 +1610,7 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
|
||||
ESX.GetPlayerFromId(src).addInventoryItem(item, amountToAdd)
|
||||
end
|
||||
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)
|
||||
@@ -1951,6 +1951,7 @@ end
|
||||
--- if doesItemExist(item) then print("item exists") end
|
||||
--- ```
|
||||
function doesItemExist(item)
|
||||
local item = type(item) == "string" and item or tostring(item)
|
||||
if not item or item == "" then
|
||||
return false
|
||||
end
|
||||
@@ -1965,16 +1966,18 @@ end
|
||||
|
||||
|
||||
function getItemLabel(item)
|
||||
local item = type(item) == "string" and item or tostring(item)
|
||||
|
||||
if not item or item == "" then
|
||||
return ""
|
||||
end
|
||||
if not Items or not next(Items) then
|
||||
return item.." (Missing)"
|
||||
return item.." (Missing item error)"
|
||||
end
|
||||
if Items[item] ~= nil then
|
||||
return Items[item].label
|
||||
end
|
||||
return item.." (Missing)"
|
||||
return item.." (Missing item error)"
|
||||
end
|
||||
|
||||
|
||||
@@ -2280,7 +2283,7 @@ function stashRemoveItem(stashItems, stashName, items)
|
||||
for i = 1, #InvFunc do
|
||||
local inv = InvFunc[i]
|
||||
if isStarted(inv.invName) then
|
||||
inv.stashRemoveItem(stashItems, stashName[1], items)
|
||||
inv.stashRemoveItem(stashItems, stashName, items)
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -2476,7 +2479,7 @@ function sellMenu(data)
|
||||
Menu[#Menu + 1] = {
|
||||
isMenuHeader = not hasTable[k].hasItem,
|
||||
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"]
|
||||
or "Sell ALL at $"..v.." each",
|
||||
onSelect = function()
|
||||
@@ -2534,7 +2537,7 @@ end
|
||||
--- ```
|
||||
function sellAnim(data, token)
|
||||
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
|
||||
end
|
||||
|
||||
@@ -2596,7 +2599,7 @@ RegisterNetEvent(getScript()..":Sellitems", function(data, token)
|
||||
print((hasTable[data.item].count * data.price), data.price)
|
||||
fundPlayer((hasTable[data.item].count * data.price), "cash", src)
|
||||
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)
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced, fade)
|
||||
model = data.model
|
||||
loadModel(data.model)
|
||||
ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
|
||||
SetEntityAlpha(ped, 0, false)
|
||||
-- Inheritance
|
||||
SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)
|
||||
|
||||
@@ -145,7 +144,8 @@ function makePed(data, coords, freeze, collision, scenario, anim, synced, fade)
|
||||
end
|
||||
unloadModel(model)
|
||||
Peds[keyGen()..keyGen()] = ped
|
||||
if fade ~= false then
|
||||
if fade ~= false and gameName ~= "rdr3" then
|
||||
SetEntityAlpha(ped, 0, false)
|
||||
CreateThread(function()
|
||||
fadeInEnt(ped)
|
||||
end)
|
||||
|
||||
@@ -24,14 +24,14 @@ local distProps = {}
|
||||
function makeProp(data, freeze, synced, fade)
|
||||
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)
|
||||
SetEntityAlpha(veh, 0, false)
|
||||
SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
|
||||
FreezeEntityPosition(prop, freeze or false)
|
||||
|
||||
debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords))
|
||||
SetModelAsNoLongerNeeded(data.prop)
|
||||
Props[keyGen()..keyGen()] = prop
|
||||
if fade ~= false then
|
||||
if fade ~= false and gameName ~= "rdr3" then
|
||||
SetEntityAlpha(prop, 0, false)
|
||||
CreateThread(function()
|
||||
fadeInEnt(prop)
|
||||
end)
|
||||
|
||||
@@ -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))
|
||||
unloadModel(model)
|
||||
Vehicles[#Vehicles + 1] = veh
|
||||
if fade ~= false then
|
||||
if fade ~= false and gameName ~= "rdr3" then
|
||||
SetEntityAlpha(veh, 0, false)
|
||||
CreateThread(function()
|
||||
fadeInEnt(veh)
|
||||
|
||||
@@ -4,220 +4,236 @@ local storedPID = nil
|
||||
|
||||
local progressFunc = {
|
||||
ox = {
|
||||
start = function(data)
|
||||
local options = {
|
||||
duration = debugMode and 1000 or data.time,
|
||||
label = data.label,
|
||||
position = data.position or "bottom",
|
||||
useWhileDead = data.dead or false,
|
||||
canCancel = data.cancel or true,
|
||||
anim = {
|
||||
dict = data.dict,
|
||||
clip = data.anim,
|
||||
flag = (data.flag == 8 and 32 or data.flag) or nil,
|
||||
scenario = data.task
|
||||
},
|
||||
disable = {
|
||||
combat = data.combat or true,
|
||||
move = data.disableMovement or false,
|
||||
car = data.disableMovement or false,
|
||||
mouse = data.mouse or false
|
||||
},
|
||||
}
|
||||
if data.prop and data.prop.model then
|
||||
options.prop = {
|
||||
model = data.prop.model,
|
||||
pos = data.prop.pos or vec3(0, 0, 0),
|
||||
rot = data.prop.rot or vec3(0, 0, 0),
|
||||
bone = data.prop.bone or 0
|
||||
start =
|
||||
function(data)
|
||||
local options = {
|
||||
duration = debugMode and 1000 or data.time,
|
||||
label = data.label,
|
||||
position = data.position or "bottom",
|
||||
useWhileDead = data.dead or false,
|
||||
canCancel = data.cancel or true,
|
||||
anim = {
|
||||
dict = data.dict,
|
||||
clip = data.anim,
|
||||
flag = (data.flag == 8 and 32 or data.flag) or nil,
|
||||
scenario = data.task
|
||||
},
|
||||
disable = {
|
||||
combat = data.combat or true,
|
||||
move = data.disableMovement or false,
|
||||
car = data.disableMovement or false,
|
||||
mouse = data.mouse or false
|
||||
},
|
||||
}
|
||||
end
|
||||
if data.propTwo and data.propTwo.model then
|
||||
options.propTwo = {
|
||||
model = data.propTwo.model,
|
||||
pos = data.propTwo.pos or vec3(0, 0, 0),
|
||||
rot = data.propTwo.rot or vec3(0, 0, 0),
|
||||
bone = data.propTwo.bone or 0
|
||||
}
|
||||
end
|
||||
if data.progressType == "circle" then
|
||||
if exports[OXLibExport]:progressCircle(options) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
if data.prop and data.prop.model then
|
||||
options.prop = {
|
||||
model = data.prop.model,
|
||||
pos = data.prop.pos or vec3(0, 0, 0),
|
||||
rot = data.prop.rot or vec3(0, 0, 0),
|
||||
bone = data.prop.bone or 0
|
||||
}
|
||||
end
|
||||
end
|
||||
if not data.progressType or data.progressType == "bar" then
|
||||
if exports[OXLibExport]:progressBar(options) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
if data.propTwo and data.propTwo.model then
|
||||
options.propTwo = {
|
||||
model = data.propTwo.model,
|
||||
pos = data.propTwo.pos or vec3(0, 0, 0),
|
||||
rot = data.propTwo.rot or vec3(0, 0, 0),
|
||||
bone = data.propTwo.bone or 0
|
||||
}
|
||||
end
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
exports[OXLibExport]:cancelProgress()
|
||||
end,
|
||||
if data.progressType == "circle" then
|
||||
if exports[OXLibExport]:progressCircle(options) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
if not data.progressType or data.progressType == "bar" then
|
||||
if exports[OXLibExport]:progressBar(options) then
|
||||
return true
|
||||
else
|
||||
print("^1progressBar was not successful")
|
||||
end
|
||||
end
|
||||
end,
|
||||
stop =
|
||||
function()
|
||||
exports[OXLibExport]:cancelProgress()
|
||||
end,
|
||||
},
|
||||
|
||||
qb = {
|
||||
start = function(data)
|
||||
Core.Functions.Progressbar("progbar",
|
||||
data.label,
|
||||
debugMode and 1000 or data.time,
|
||||
data.dead or false,
|
||||
data.cancel or true,
|
||||
{
|
||||
disableMovement = data.disableMovement or false,
|
||||
disableCarMovement = data.disableMovement or false,
|
||||
disableMouse = data.disableMouse or false,
|
||||
disableCombat = data.disableCombat or true,
|
||||
},
|
||||
{
|
||||
animDict = data.dict,
|
||||
anim = data.anim,
|
||||
flags = data.flag or 32,
|
||||
task = data.task
|
||||
},
|
||||
{}, {},
|
||||
function()
|
||||
return true
|
||||
end, function()
|
||||
return false
|
||||
end, data.icon)
|
||||
start =
|
||||
function(data)
|
||||
local p = promise.new()
|
||||
Core.Functions.Progressbar("progbar",
|
||||
data.label,
|
||||
debugMode and 1000 or data.time,
|
||||
data.dead or false,
|
||||
data.cancel or true,
|
||||
{
|
||||
disableMovement = data.disableMovement or false,
|
||||
disableCarMovement = data.disableMovement or false,
|
||||
disableMouse = data.disableMouse or false,
|
||||
disableCombat = data.disableCombat or true,
|
||||
},
|
||||
{
|
||||
animDict = data.dict,
|
||||
anim = data.anim,
|
||||
flags = data.flag or 32,
|
||||
task = data.task
|
||||
},
|
||||
{}, {},
|
||||
function() p:resolve(true) end,
|
||||
function() p:resolve(false) end,
|
||||
data.icon)
|
||||
return Citizen.Await(p)
|
||||
end,
|
||||
stop =
|
||||
function()
|
||||
TriggerEvent("progressbar:client:cancel")
|
||||
end,
|
||||
stop = function()
|
||||
TriggerEvent("progressbar:client:cancel")
|
||||
end,
|
||||
},
|
||||
|
||||
qs = {
|
||||
start = function(data)
|
||||
if exports['qs-interface']:ProgressBar({
|
||||
duration = debugMode and 1000 or data.time,
|
||||
label = data.label,
|
||||
position = 'bottom',
|
||||
useWhileDead = data.dead or false,
|
||||
canCancel = data.cancel or true,
|
||||
disable = data.disableMovement or false,
|
||||
anim = {
|
||||
dict = data.dict,
|
||||
clip = data.anim,
|
||||
flag = data.flag or 32
|
||||
},
|
||||
prop = nil
|
||||
}) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
--??
|
||||
TriggerEvent("progressbar:client:cancel")
|
||||
end,
|
||||
start =
|
||||
function(data)
|
||||
if exports['qs-interface']:ProgressBar({
|
||||
duration = debugMode and 1000 or data.time,
|
||||
label = data.label,
|
||||
position = 'bottom',
|
||||
useWhileDead = data.dead or false,
|
||||
canCancel = data.cancel or true,
|
||||
disable = data.disableMovement or false,
|
||||
anim = {
|
||||
dict = data.dict,
|
||||
clip = data.anim,
|
||||
flag = data.flag or 32
|
||||
},
|
||||
prop = nil
|
||||
}) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop =
|
||||
function()
|
||||
--??
|
||||
TriggerEvent("progressbar:client:cancel")
|
||||
end,
|
||||
},
|
||||
|
||||
esx = {
|
||||
start = function(data)
|
||||
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
|
||||
FreezePlayer = true,
|
||||
animation = {
|
||||
type = data.anim,
|
||||
dict = data.dict,
|
||||
scenario = data.task,
|
||||
},
|
||||
onFinish = function()
|
||||
return true
|
||||
end,
|
||||
onCancel = function()
|
||||
return false
|
||||
end
|
||||
})
|
||||
end,
|
||||
stop = function()
|
||||
ESX.CancelProgressbar()
|
||||
end,
|
||||
start =
|
||||
function(data)
|
||||
local p = promise.new()
|
||||
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
|
||||
FreezePlayer = true,
|
||||
animation = {
|
||||
type = data.anim,
|
||||
dict = data.dict,
|
||||
scenario = data.task,
|
||||
},
|
||||
onFinish = function()
|
||||
p:resolve(true)
|
||||
end,
|
||||
onCancel = function()
|
||||
p:resolve(false)
|
||||
return false
|
||||
end
|
||||
})
|
||||
return Citizen.Await(p)
|
||||
end,
|
||||
stop =
|
||||
function()
|
||||
ESX.CancelProgressbar()
|
||||
end,
|
||||
},
|
||||
|
||||
lation = {
|
||||
start = function(data)
|
||||
if exports.lation_ui:progressBar({
|
||||
label = data.label,
|
||||
description = nil,
|
||||
duration = debugMode and 1000 or data.time,
|
||||
icon = data.icon,
|
||||
useWhileDead = data.dead or false,
|
||||
disable = {
|
||||
combat = data.combat or true,
|
||||
move = data.disableMovement or false,
|
||||
car = data.disableMovement or false,
|
||||
},
|
||||
anim = {
|
||||
dict = data.dict,
|
||||
clip = data.anim,
|
||||
flag = data.flag
|
||||
},
|
||||
prop = {
|
||||
model = data.prop and data.prop.model,
|
||||
pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
|
||||
rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
|
||||
bone = data.prop and (data.prop.bone or 0)
|
||||
}
|
||||
}) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
exports.lation_ui:cancelProgress()
|
||||
end,
|
||||
start =
|
||||
function(data)
|
||||
if exports.lation_ui:progressBar({
|
||||
label = data.label,
|
||||
description = nil,
|
||||
duration = debugMode and 1000 or data.time,
|
||||
icon = data.icon,
|
||||
useWhileDead = data.dead or false,
|
||||
disable = {
|
||||
combat = data.combat or true,
|
||||
move = data.disableMovement or false,
|
||||
car = data.disableMovement or false,
|
||||
},
|
||||
anim = {
|
||||
dict = data.dict,
|
||||
clip = data.anim,
|
||||
flag = data.flag
|
||||
},
|
||||
prop = {
|
||||
model = data.prop and data.prop.model,
|
||||
pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
|
||||
rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
|
||||
bone = data.prop and (data.prop.bone or 0)
|
||||
}
|
||||
}) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop =
|
||||
function()
|
||||
exports.lation_ui:cancelProgress()
|
||||
end,
|
||||
},
|
||||
|
||||
red = {
|
||||
start = function(data)
|
||||
if exports.jim_bridge:redProgressBar({
|
||||
label = data.label,
|
||||
time = debugMode and 1000 or data.time,
|
||||
dict = data.dict,
|
||||
anim = data.anim,
|
||||
flag = data.flag or 32,
|
||||
task = data.task,
|
||||
disableMovement = data.disableMovement or false,
|
||||
cancel = data.cancel or true,
|
||||
}) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
exports.jim_bridge:stopProgressBar()
|
||||
end,
|
||||
start =
|
||||
function(data)
|
||||
if exports.jim_bridge:redProgressBar({
|
||||
label = data.label,
|
||||
time = debugMode and 1000 or data.time,
|
||||
dict = data.dict,
|
||||
anim = data.anim,
|
||||
flag = data.flag or 32,
|
||||
task = data.task,
|
||||
disableMovement = data.disableMovement or false,
|
||||
cancel = data.cancel or true,
|
||||
}) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop =
|
||||
function()
|
||||
exports.jim_bridge:stopProgressBar()
|
||||
end,
|
||||
},
|
||||
|
||||
gta = {
|
||||
start = function(data)
|
||||
|
||||
if exports.jim_bridge:gtaProgressBar({
|
||||
label = data.label,
|
||||
time = debugMode and 1000 or data.time,
|
||||
dict = data.dict,
|
||||
anim = data.anim,
|
||||
flag = data.flag or 32,
|
||||
task = data.task,
|
||||
disableMovement = data.disableMovement or false,
|
||||
cancel = data.cancel or true,
|
||||
}) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
exports.jim_bridge:stopProgressBar()
|
||||
end,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -254,7 +270,11 @@ local progressFunc = {
|
||||
--- 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")
|
||||
@@ -273,6 +293,7 @@ function progressBar(data)
|
||||
while result == nil do
|
||||
Wait(10)
|
||||
end
|
||||
debugPrint("^5Debug^7: ^2ProgressBar result^7: ^3"..tostring(result).."^7")
|
||||
|
||||
-- Cleanup
|
||||
FreezeEntityPosition(ped, false)
|
||||
@@ -293,6 +314,7 @@ function progressBar(data)
|
||||
TriggerServerEvent(getScript()..":clearAuthToken")
|
||||
currentToken = triggerCallback(AuthEvent)
|
||||
end
|
||||
progresssBarActive = false
|
||||
return result
|
||||
end
|
||||
|
||||
@@ -301,6 +323,7 @@ end
|
||||
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
|
||||
function stopProgressBar()
|
||||
progressFunc[Config.System.ProgressBar].stop()
|
||||
progresssBarActive = false
|
||||
end
|
||||
|
||||
-- System to handle sending/sharing progress bars between players --
|
||||
|
||||
@@ -21,24 +21,56 @@
|
||||
--- })
|
||||
--- ```
|
||||
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 = ""
|
||||
if isStarted(OXLibExport) then
|
||||
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
|
||||
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
|
||||
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
|
||||
commandResource = ESXExport
|
||||
ESX.RegisterCommand(command, options[5] or 'admin', function(xPlayer, args, showError)
|
||||
options[4](xPlayer.source, args, showError)
|
||||
end, false, { help = options[1] })
|
||||
ESX.RegisterCommand(command,
|
||||
optionTable.restriction or 'admin',
|
||||
function(xPlayer, args, showError)
|
||||
optionTable.funct(xPlayer.source, args, showError)
|
||||
end,
|
||||
false,
|
||||
{ help = options[1] }
|
||||
)
|
||||
|
||||
end
|
||||
if commandResource ~= "" then
|
||||
|
||||
@@ -25,7 +25,12 @@ Exports = {
|
||||
|
||||
-- REDM
|
||||
RSGExport = "rsg-core",
|
||||
RSGInv = "rsg-inventory"
|
||||
RSGInv = "rsg-inventory",
|
||||
|
||||
VorpExport = "vorp_core",
|
||||
VorpInv = "vorp_inventory",
|
||||
VorpMenu = "vorp_menu",
|
||||
|
||||
}
|
||||
|
||||
-- Required variables
|
||||
|
||||
@@ -53,27 +53,11 @@ function redProgressBar(data)
|
||||
Wait(0)
|
||||
local elapsed = GetGameTimer()
|
||||
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
|
||||
if percentage < 0 then percentage = 0 end
|
||||
if percentage > 100 then percentage = 100 end
|
||||
|
||||
-- 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
|
||||
ShowRedProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
|
||||
ShowRedProgressBar(percentage, data.label, ("%.0f%%"):format(percentage))
|
||||
|
||||
-- Controls to disable during progress
|
||||
if data.disableMouse then
|
||||
@@ -151,40 +135,48 @@ function redProgressBar(data)
|
||||
return result
|
||||
end
|
||||
|
||||
function ShowRedProgressBar(currentProg, title, level)
|
||||
function ShowRedProgressBar(percentage, title, level)
|
||||
local loc = vec2(0.40, 0.90)
|
||||
local size = vec2(0.3, 0.03)
|
||||
local tickCount = 10 -- visually split into 10 segments (9 inner lines)
|
||||
local barHeight = size.y / 3.4
|
||||
local barWidth = 0.21 -- tuned to your existing layout
|
||||
local barLeft = (loc.x - size.x / 4) + 0.075
|
||||
local barCenter = barLeft + barWidth / 2
|
||||
local lineW = 0.001
|
||||
local lineH = barHeight + 0.00
|
||||
|
||||
-- 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)
|
||||
-- Background plate
|
||||
DrawSprite("generic_textures", "inkroller_1a", loc.x + 0.1, loc.y - 0.01, 0.25, 0.07, 180.0, 0, 0, 0, 200)
|
||||
|
||||
-- Title (left)
|
||||
SetTextFontForCurrentCommand(6)
|
||||
SetTextScale(0.35, 0.35)
|
||||
SetTextColor(255, 255, 255, 255)
|
||||
SetTextDropshadow(1, 0, 0, 0, 200)
|
||||
BgDisplayText(title, loc.x - size.x / 4 + 0.074, loc.y - 0.034)
|
||||
SetTextColor(255, 255, 255, 255)
|
||||
SetTextDropshadow(1, 0, 0, 0, 200)
|
||||
BgDisplayText(title, loc.x - size.x / 4 + 0.074, loc.y - 0.034)
|
||||
|
||||
-- Percentage (right)
|
||||
SetTextFontForCurrentCommand(1)
|
||||
SetTextScale(0.35, 0.35)
|
||||
SetTextColor(255, 255, 255, 255)
|
||||
SetTextDropshadow(1, 0, 0, 0, 200)
|
||||
BgDisplayText(level, loc.x - size.x / 4 + 0.246, loc.y - 0.030)
|
||||
SetTextColor(255, 255, 255, 255)
|
||||
SetTextDropshadow(1, 0, 0, 0, 200)
|
||||
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)
|
||||
local gap = segmentWidth / #currentProg -- Smaller gap between segments
|
||||
-- Track (bg)
|
||||
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
|
||||
|
||||
for i = 1, #currentProg do
|
||||
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
|
||||
local fillPercentage = currentProg[i]
|
||||
local progressBarWidth = segmentWidth * (fillPercentage / 100)
|
||||
-- Fill
|
||||
local fillWidth = barWidth * (percentage / 100.0)
|
||||
if fillWidth > 0.0 then
|
||||
local fillCenter = barLeft + (fillWidth / 2.0)
|
||||
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 255, 0, 0, 200) -- red fill
|
||||
end
|
||||
|
||||
-- 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
|
||||
-- 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
|
||||
|
||||
@@ -213,29 +205,14 @@ function gtaProgressBar(data)
|
||||
local elapsed = GetGameTimer()
|
||||
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
|
||||
|
||||
-- Convert to segmented progress (assuming 5 segments here)
|
||||
local segments = 1 -- Number of segments in the bar
|
||||
local segmentProgress = {}
|
||||
local progressPerSegment = 100 / segments
|
||||
if percentage < 0 then percentage = 0 end
|
||||
if percentage > 100 then percentage = 100 end
|
||||
|
||||
for i = 1, segments do
|
||||
local segmentStart = (i - 1) * progressPerSegment
|
||||
local segmentEnd = i * progressPerSegment
|
||||
if percentage >= segmentEnd then
|
||||
segmentProgress[i] = 100
|
||||
elseif percentage <= segmentStart then
|
||||
segmentProgress[i] = 0
|
||||
else
|
||||
segmentProgress[i] = ((percentage - segmentStart) / progressPerSegment) * 100
|
||||
end
|
||||
end
|
||||
|
||||
percentage = percentage >= 100 and 100 or percentage
|
||||
-- Draw your segmented progress bar
|
||||
ShowGTAProgressBar(segmentProgress, data.label, ("%.0f%%"):format(percentage))
|
||||
ShowGTAProgressBar(percentage, data.label, ("%.0f%%"):format(percentage))
|
||||
|
||||
-- Controls to disable during progress
|
||||
DisablePlayerFiring(ped, true)
|
||||
DisablePlayerFiring(PlayerId(), true)
|
||||
DisableControlAction(0, 25, true) -- Disable aim
|
||||
DisableControlAction(0, 21, true) -- Disable sprint
|
||||
DisableControlAction(0, 30, true) -- Disable move left/right
|
||||
@@ -246,13 +223,15 @@ function gtaProgressBar(data)
|
||||
inProgress = false
|
||||
end
|
||||
end
|
||||
result = inProgress
|
||||
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
|
||||
if data.dict then
|
||||
stopAnim(data.dict, data.anim, ped)
|
||||
@@ -261,10 +240,6 @@ function gtaProgressBar(data)
|
||||
ClearPedTasks(ped)
|
||||
end
|
||||
|
||||
result = inProgress
|
||||
inProgress = false
|
||||
|
||||
while result == nil do Wait(10) end
|
||||
|
||||
-- Cleanup
|
||||
FreezeEntityPosition(ped, false)
|
||||
@@ -276,14 +251,22 @@ function gtaProgressBar(data)
|
||||
return result
|
||||
end
|
||||
|
||||
function ShowGTAProgressBar(currentProg, title, level)
|
||||
function ShowGTAProgressBar(percentage, title, level)
|
||||
local loc = vec2(0.37, 0.90)
|
||||
local size = vec2(0.3, 0.03)
|
||||
local tickCount = 10
|
||||
local barHeight = size.y / 3.4
|
||||
local barWidth = 0.19
|
||||
local barLeft = (loc.x - size.x / 4) + 0.075
|
||||
local barCenter = barLeft + barWidth / 2
|
||||
local lineW = 0.001
|
||||
local lineH = barHeight + 0.00
|
||||
|
||||
-- Draw background box
|
||||
DrawSprite("timerbars", "all_black_bg", loc.x +0.028, loc.y-0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255)
|
||||
DrawSprite("timerbars", "all_black_bg", loc.x +0.170, loc.y-0.01, 0.15, 0.07, 180.0, 255, 255, 255, 255)
|
||||
-- Background plates
|
||||
DrawSprite("timerbars", "all_black_bg", loc.x + 0.028, loc.y - 0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255)
|
||||
DrawSprite("timerbars", "all_black_bg", loc.x + 0.170, loc.y - 0.01, 0.15, 0.07, 180.0, 255, 255, 255, 255)
|
||||
|
||||
-- Title (left)
|
||||
SetTextFont(0)
|
||||
SetTextProportional(1)
|
||||
SetTextScale(0.35, 0.35)
|
||||
@@ -293,34 +276,35 @@ function ShowGTAProgressBar(currentProg, title, level)
|
||||
SetTextOutline()
|
||||
SetTextEntry("STRING")
|
||||
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)
|
||||
SetTextProportional(1)
|
||||
SetTextScale(0.35, 0.25)
|
||||
SetTextColour(255, 255, 255, 255)
|
||||
SetTextEntry("STRING")
|
||||
AddTextComponentString(level)
|
||||
DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030) -- Right-aligned additional text
|
||||
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)
|
||||
local gap = segmentWidth / #currentProg -- Smaller gap between segments
|
||||
-- Track (bg)
|
||||
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
|
||||
|
||||
for i = 1, #currentProg do
|
||||
local segmentX = (loc.x - size.x / 4 ) + 0.075 + (i - 1) * (segmentWidth + gap)
|
||||
local fillPercentage = currentProg[i]
|
||||
local progressBarWidth = segmentWidth * (fillPercentage / 100)
|
||||
-- Fill
|
||||
local fillWidth = barWidth * (percentage / 100.0)
|
||||
if fillWidth > 0.0 then
|
||||
local fillCenter = barLeft + (fillWidth / 2.0)
|
||||
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 93, 182, 229, 255) -- GTA blue
|
||||
end
|
||||
|
||||
-- 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
|
||||
-- Tick lines
|
||||
for i = 1, (tickCount - 1) do
|
||||
local x = barLeft + (barWidth * (i / tickCount))
|
||||
DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function stopProgressBar() inProgress = false end
|
||||
function isProgressBar() return inProgress end
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
-- Global Key Table, defined once.
|
||||
---
|
||||
local KEY_TABLE = { 38, 29, 47, 23, 45, 159, 162, 163 }
|
||||
|
||||
-- Mapping of key codes to human-readable key names.
|
||||
@@ -17,122 +16,196 @@ local Keys = {
|
||||
[20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N",
|
||||
[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
|
||||
for _, target in pairs(TextTargets) do
|
||||
if #(target.coords - entityCoords) < 0.01 then
|
||||
existingTarget = target
|
||||
break
|
||||
end
|
||||
-- ===== Ownership + indexes =====
|
||||
-- TextTargets: key -> target data (coords/entity/models/options/etc.)
|
||||
local TextTargets = {}
|
||||
-- targetEntities kept for parity (not strictly required)
|
||||
local targetEntities = {}
|
||||
|
||||
-- registry: key -> owner, owner -> set(keys)
|
||||
local TargetRegistry = { byKey = {}, byResource = {} }
|
||||
|
||||
local function getOwnerResource()
|
||||
return GetInvokingResource() or GetCurrentResourceName() or "unknown"
|
||||
end
|
||||
|
||||
local function registerTarget(owner, key)
|
||||
-- move key across owners if replacing
|
||||
local prev = TargetRegistry.byKey[key]
|
||||
if prev and TargetRegistry.byResource[prev] then
|
||||
TargetRegistry.byResource[prev][key] = nil
|
||||
end
|
||||
TargetRegistry.byKey[key] = owner
|
||||
TargetRegistry.byResource[owner] = TargetRegistry.byResource[owner] or {}
|
||||
TargetRegistry.byResource[owner][key] = true
|
||||
end
|
||||
|
||||
if existingTarget then
|
||||
for i = 1, #opts do
|
||||
local key = KEY_TABLE[#existingTarget.options + i]
|
||||
opts[i].key = key
|
||||
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
|
||||
existingTarget.options[#existingTarget.options + 1] = opts[i]
|
||||
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
|
||||
updateCachedText(existingTarget)
|
||||
else
|
||||
local tempText = {}
|
||||
for i = 1, #opts do
|
||||
opts[i].key = KEY_TABLE[i]
|
||||
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
|
||||
end
|
||||
TextTargets[entity] = {
|
||||
coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z),
|
||||
buttontext = tempText,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
text = table.concat(tempText, "\n")
|
||||
}
|
||||
TargetRegistry.byKey[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function createZoneTarget(data, opts, dist)
|
||||
startTargetLoop()
|
||||
local existingTarget = nil
|
||||
for _, target in pairs(TextTargets) do
|
||||
if #(target.coords - data[2]) < 0.01 then
|
||||
existingTarget = target
|
||||
break
|
||||
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)
|
||||
|
||||
if existingTarget then
|
||||
for i = 1, #opts do
|
||||
local key = KEY_TABLE[#existingTarget.options + i]
|
||||
opts[i].key = key
|
||||
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
|
||||
existingTarget.options[#existingTarget.options + 1] = opts[i]
|
||||
end
|
||||
updateCachedText(existingTarget)
|
||||
else
|
||||
local tempText = {}
|
||||
for i = 1, #opts do
|
||||
opts[i].key = KEY_TABLE[i]
|
||||
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
|
||||
end
|
||||
TextTargets[data[1]] = {
|
||||
coords = data[2],
|
||||
buttontext = tempText,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
text = table.concat(tempText, "\n")
|
||||
}
|
||||
end
|
||||
return data[1]
|
||||
-- ===== 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
|
||||
|
||||
function createModelTarget(models, opts, dist)
|
||||
startTargetLoop()
|
||||
if type(models) ~= "table" then
|
||||
models = { models }
|
||||
end
|
||||
|
||||
local function bakeButtons(opts)
|
||||
local tempText = {}
|
||||
for i = 1, #opts do
|
||||
opts[i].key = KEY_TABLE[i]
|
||||
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
|
||||
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
|
||||
|
||||
local keyStr = ""
|
||||
for i, m in ipairs(models) do
|
||||
keyStr = keyStr .. tostring(m) .. (i < #models and "_" or "")
|
||||
end
|
||||
local targetKey = "model_" .. keyStr
|
||||
-- Update cached text blob
|
||||
local function updateCachedText(target)
|
||||
target.text = table.concat(target.buttontext, "\n")
|
||||
end
|
||||
|
||||
TextTargets[targetKey] = {
|
||||
models = models,
|
||||
buttontext = tempText,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
coords = vec3(0, 0, 0),
|
||||
text = table.concat(tempText, "\n")
|
||||
-- ===== 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,
|
||||
}
|
||||
|
||||
return targetKey
|
||||
updateCachedText(TextTargets[key])
|
||||
registerTarget(owner, key)
|
||||
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ENTITY target '"..key.."' by '"..owner.."' @ "..formatCoord(coords))
|
||||
return key
|
||||
end
|
||||
|
||||
function removeEntityTarget(entity)
|
||||
TextTargets[entity] = nil
|
||||
-- ZONE: createZoneTarget(data, opts, dist)
|
||||
-- Expect data[1] = id/name, data[2] = vec3 coords (as in your original)
|
||||
function createZoneTarget(data, opts, dist)
|
||||
startTargetLoop()
|
||||
|
||||
local owner = getOwnerResource()
|
||||
local zname = tostring(data[1] or ("zone@"..vecKey(data[2] or vec3(0,0,0))))
|
||||
local coords = data[2]
|
||||
|
||||
local buttontext = bakeButtons(opts)
|
||||
TextTargets[zname] = {
|
||||
_key = zname,
|
||||
_type = "zone",
|
||||
_owner = owner,
|
||||
coords = coords,
|
||||
buttontext = buttontext,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
}
|
||||
updateCachedText(TextTargets[zname])
|
||||
registerTarget(owner, zname)
|
||||
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ZONE target '"..zname.."' by '"..owner.."' @ "..formatCoord(coords))
|
||||
return zname
|
||||
end
|
||||
|
||||
function removeZoneTarget(target)
|
||||
TextTargets[target] = nil
|
||||
-- MODEL: createModelTarget(models, opts, dist, nameOpt?)
|
||||
function createModelTarget(models, opts, dist, name)
|
||||
startTargetLoop()
|
||||
|
||||
local owner = getOwnerResource()
|
||||
if type(models) ~= "table" then models = { models } end
|
||||
|
||||
local key
|
||||
if name then
|
||||
key = tostring(name)
|
||||
else
|
||||
local parts = {}
|
||||
for i, m in ipairs(models) do parts[i] = tostring(m) end
|
||||
key = "model_" .. table.concat(parts, "_")
|
||||
end
|
||||
|
||||
local buttontext = bakeButtons(opts)
|
||||
TextTargets[key] = {
|
||||
_key = key,
|
||||
_type = "model",
|
||||
_owner = owner,
|
||||
models = models,
|
||||
buttontext = buttontext,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
coords = vec3(0, 0, 0), -- will be updated by the refresher
|
||||
}
|
||||
updateCachedText(TextTargets[key])
|
||||
registerTarget(owner, key)
|
||||
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated MODEL target '"..key.."' by '"..owner.."' (models: "..table.concat(models, ",")..")")
|
||||
return key
|
||||
end
|
||||
|
||||
function removeModelTarget(model)
|
||||
TextTargets[model] = nil
|
||||
-- ===== Public API: Remove targets =====
|
||||
-- Entity removal accepts entity handle or key string.
|
||||
function removeEntityTarget(entityOrKey)
|
||||
local key = nil
|
||||
if type(entityOrKey) == "string" then
|
||||
key = entityOrKey
|
||||
elseif type(entityOrKey) == "number" then
|
||||
for k, t in pairs(TextTargets) do
|
||||
if t._type == "entity" and t.entity == entityOrKey then key = k break end
|
||||
end
|
||||
if not key then
|
||||
-- Fallback: try coord-key match
|
||||
local c = GetEntityCoords(entityOrKey)
|
||||
local guess = "entity@"..vecKey(c)
|
||||
if TextTargets[guess] then key = guess end
|
||||
end
|
||||
end
|
||||
if key then removeTargetKey(key, "removeEntityTarget") end
|
||||
end
|
||||
|
||||
function removeZoneTarget(key)
|
||||
if not key then return end
|
||||
removeTargetKey(key, "removeZoneTarget")
|
||||
end
|
||||
|
||||
-- For models, pass the returned key from createModelTarget (recommended).
|
||||
function removeModelTarget(key)
|
||||
if not key then return end
|
||||
removeTargetKey(key, "removeModelTarget")
|
||||
end
|
||||
|
||||
exports("createEntityTarget", createEntityTarget)
|
||||
@@ -143,22 +216,20 @@ exports("removeEntityTarget", removeEntityTarget)
|
||||
exports("removeZoneTarget", removeZoneTarget)
|
||||
exports("removeModelTarget", removeModelTarget)
|
||||
|
||||
|
||||
-------------------------------------------------------------
|
||||
-- Fallback: DrawText3D Targets (Experimental)
|
||||
-------------------------------------------------------------
|
||||
local started = false
|
||||
function startTargetLoop()
|
||||
if started then return end
|
||||
Config = {
|
||||
System = {
|
||||
|
||||
}
|
||||
}
|
||||
started = true
|
||||
|
||||
-- lazy include (unchanged from your original)
|
||||
Config = { System = {} }
|
||||
local fileLoader = assert(load(LoadResourceFile("jim_bridge", ('starter.lua')), ('@@jim_bridge/starter.lua')))
|
||||
fileLoader()
|
||||
-- Model Entity Refresher
|
||||
|
||||
-- Model Entity Refresher (kept)
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local pedCoords = GetEntityCoords(PlayerPedId())
|
||||
@@ -178,7 +249,7 @@ function startTargetLoop()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Main Target Loop
|
||||
-- Main Target Loop (unchanged logic, just uses new TextTargets entries)
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local ped = PlayerPedId()
|
||||
@@ -215,10 +286,10 @@ function startTargetLoop()
|
||||
for i, opt in ipairs(target.options) do
|
||||
if IsControlJustPressed(0, opt.key) and isClosest then
|
||||
if (not target.canInteract or target.canInteract()) and
|
||||
(not opt.item or hasItem(opt.item)) and
|
||||
(not opt.job or hasJob(opt.job, nil)) then
|
||||
(not opt.item or hasItem(opt.item)) and
|
||||
(not opt.job or hasJob(opt.job, nil)) then
|
||||
if opt.onSelect then opt.onSelect(targetEntity) end
|
||||
if opt.action then opt.action(targetEntity) end
|
||||
if opt.action then opt.action(targetEntity) end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -228,8 +299,8 @@ function startTargetLoop()
|
||||
|
||||
for i, opt in ipairs(target.options) do
|
||||
if (not target.canInteract or target.canInteract()) and
|
||||
(not opt.item or hasItem(opt.item)) and
|
||||
(not opt.job or hasJob(opt.job, nil)) then
|
||||
(not opt.item or hasItem(opt.item)) and
|
||||
(not opt.job or hasJob(opt.job, nil)) then
|
||||
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + lineHeight * lineOffset), target.buttontext[i], isClosest)
|
||||
lineOffset = lineOffset + 1
|
||||
end
|
||||
@@ -238,12 +309,11 @@ function startTargetLoop()
|
||||
::continue::
|
||||
end
|
||||
|
||||
Wait(1) -- Throttled
|
||||
Wait(1)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function DrawText3D(coord, text, highlight)
|
||||
SetTextScale(0.30, 0.30)
|
||||
SetTextFont(0)
|
||||
@@ -253,37 +323,28 @@ function DrawText3D(coord, text, highlight)
|
||||
SetTextCentre(true)
|
||||
|
||||
local totalLength = string.len(text)
|
||||
local textMaxLength = 99 -- max 99
|
||||
local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text
|
||||
AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text)
|
||||
local textMaxLength = 99
|
||||
local txt = totalLength > textMaxLength and text:sub(1, textMaxLength) or text
|
||||
AddTextComponentString(highlight and txt:gsub("%~w~", "~y~") or txt)
|
||||
SetDrawOrigin(coord.x, coord.y, coord.z, 0)
|
||||
DrawText(0.0, 0.0)
|
||||
local count, length = GetLineCountAndMaxLength(text)
|
||||
local count, length = GetLineCountAndMaxLength(txt)
|
||||
|
||||
local padding = 0.005
|
||||
local heightFactor = (count / 43) + padding
|
||||
local weightFactor = (length / 150) + padding
|
||||
|
||||
local height = (heightFactor / 2) - padding / 1
|
||||
local width = (weightFactor / 2) - padding / 1
|
||||
local width = (weightFactor / 2) - padding / 1
|
||||
|
||||
DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
|
||||
ClearDrawOrigin()
|
||||
end
|
||||
|
||||
--- Calculates the number of lines and the maximum line length from the given text.
|
||||
---
|
||||
--- @param text string The text to analyze.
|
||||
--- @return number, number The line count and maximum line length.
|
||||
---
|
||||
--- @usage
|
||||
--- ```lua
|
||||
--- local count, maxLen = GetLineCountAndMaxLength("Hello World")
|
||||
--- ```
|
||||
function GetLineCountAndMaxLength(text)
|
||||
local lineCount, maxLength = 0, 0
|
||||
for line in text:gmatch("[^\n]+") do
|
||||
lineCount += 1
|
||||
lineCount = lineCount + 1
|
||||
local lineLength = string.len(line)
|
||||
if lineLength > maxLength then
|
||||
maxLength = lineLength
|
||||
@@ -293,7 +354,6 @@ function GetLineCountAndMaxLength(text)
|
||||
return lineCount, maxLength
|
||||
end
|
||||
|
||||
|
||||
function RotationToDirection(rot)
|
||||
local adjust = math.pi / 180
|
||||
return vec3(
|
||||
@@ -311,8 +371,3 @@ function normalizeVector(vec)
|
||||
return vec3(0, 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper to update cached text.
|
||||
function updateCachedText(target)
|
||||
target.text = table.concat(target.buttontext, "\n")
|
||||
end
|
||||
Reference in New Issue
Block a user