mirror of
https://github.com/jimathy/jim_bridge.git
synced 2026-08-19 14:56:02 +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,12 +1,22 @@
|
||||
local function parseVersion(version)
|
||||
local function readBoolMeta(key, default)
|
||||
local val = GetResourceMetadata(GetCurrentResourceName(), key, 0)
|
||||
if not val then return default end
|
||||
val = tostring(val):lower()
|
||||
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
|
||||
end
|
||||
|
||||
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
|
||||
|
||||
if not SUPPRESS_UPDATES then
|
||||
local function parseVersion(version)
|
||||
local parts = {}
|
||||
for num in version:gmatch("%d+") do
|
||||
table.insert(parts, tonumber(num))
|
||||
end
|
||||
return parts
|
||||
end
|
||||
end
|
||||
|
||||
local function compareVersions(current, newest)
|
||||
local function compareVersions(current, newest)
|
||||
local currentParts = parseVersion(current)
|
||||
local newestParts = parseVersion(newest)
|
||||
for i = 1, math.max(#currentParts, #newestParts) do
|
||||
@@ -16,9 +26,9 @@ local function compareVersions(current, newest)
|
||||
elseif c > n then return 1 end
|
||||
end
|
||||
return 0 -- equal
|
||||
end
|
||||
end
|
||||
|
||||
function CheckBridgeVersion()
|
||||
function CheckBridgeVersion()
|
||||
if IsDuplicityVersion() then
|
||||
CreateThread(function()
|
||||
Wait(4000)
|
||||
@@ -61,7 +71,8 @@ function CheckBridgeVersion()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
CheckBridgeVersion()
|
||||
end
|
||||
|
||||
|
||||
CheckBridgeVersion()
|
||||
@@ -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,62 +121,30 @@ 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 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
|
||||
|
||||
-- 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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
else
|
||||
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
|
||||
print("^1Possible Issue found^7:")
|
||||
print(json.encode(cache.Items[k], {indent = true}))
|
||||
end
|
||||
end
|
||||
|
||||
elseif checkExists(Exports.QBXExport) then
|
||||
while GetResourceState(Exports.QBXExport) ~= "started" do Wait(100) end
|
||||
itemResource = Exports.QBXExport
|
||||
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
|
||||
@@ -170,14 +170,15 @@ elseif checkExists(Exports.QBXExport) then
|
||||
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
|
||||
end,
|
||||
},
|
||||
{ script = Exports.QBExport,
|
||||
cacheItem = function()
|
||||
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
|
||||
|
||||
elseif checkExists(Exports.ESXExport) then
|
||||
itemResource = Exports.ESXExport
|
||||
end,
|
||||
},
|
||||
{ script = Exports.ESXExport,
|
||||
cacheItem = function()
|
||||
if GetResourceState(Exports.QSInv):find("start") then
|
||||
cache.Items = exports[Exports.QSInv]:GetItemList()
|
||||
else
|
||||
@@ -187,28 +188,64 @@ elseif checkExists(Exports.ESXExport) then
|
||||
Wait(1000)
|
||||
end
|
||||
end
|
||||
|
||||
elseif checkExists(Exports.RSGExport) then
|
||||
while GetResourceState(Exports.RSGExport) ~= "started" do Wait(100) end
|
||||
itemResource = Exports.RSGExport
|
||||
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
|
||||
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
|
||||
{ script = Exports.QBXExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
|
||||
|
||||
elseif checkExists(Exports.OXCoreExport) then
|
||||
vehResource = Exports.OXCoreExport
|
||||
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] = {
|
||||
@@ -218,10 +255,11 @@ elseif checkExists(Exports.OXCoreExport) then
|
||||
brand = v.make
|
||||
}
|
||||
end
|
||||
|
||||
elseif checkExists(Exports.ESXExport) then
|
||||
vehResource = Exports.ESXExport
|
||||
while not MySQL do Wait(1000) 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,
|
||||
@@ -230,29 +268,51 @@ elseif checkExists(Exports.ESXExport) then
|
||||
name = v.name,
|
||||
}
|
||||
end
|
||||
|
||||
elseif checkExists(Exports.RSGExport) then
|
||||
vehResource = Exports.RSGExport
|
||||
end,
|
||||
},
|
||||
{ script = Exports.RSGExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
|
||||
end,
|
||||
},
|
||||
{ script = Exports.VorpExport,
|
||||
cacheVehicle = function()
|
||||
cache.Vehicles = { ["unkown"] = {} }
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
for i = 1, #vehicleFunc do
|
||||
local data = vehicleFunc[i]
|
||||
if checkExists(data.script) then
|
||||
waitStarted(data.script) -- Wait for detected script to start fully
|
||||
data.cacheVehicle() -- run tablized function for core
|
||||
vehResource = data.script -- Grab script name to announce later
|
||||
endTimer("Vehicles") -- end timer
|
||||
break -- break loop so it doesn't keep checking
|
||||
end
|
||||
end
|
||||
endTimer("Vehicles")
|
||||
|
||||
---------------------
|
||||
----- Load Jobs -----
|
||||
---------------------
|
||||
-- Jobs loading based on framework
|
||||
if checkExists(Exports.QBXExport) then
|
||||
jobResource = Exports.QBXExport
|
||||
|
||||
local jobFunc = {
|
||||
|
||||
{ script = Exports.QBXExport,
|
||||
cacheJob = function()
|
||||
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
|
||||
|
||||
elseif checkExists(Exports.OXCoreExport) then
|
||||
jobResource = Exports.OXCoreExport
|
||||
while not MySQL do Wait(1000) end
|
||||
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 = {}
|
||||
@@ -267,10 +327,11 @@ elseif checkExists(Exports.OXCoreExport) then
|
||||
}
|
||||
end
|
||||
cache.Gangs = cache.Jobs
|
||||
|
||||
elseif checkExists(Exports.ESXExport) then
|
||||
jobResource = Exports.ESXExport
|
||||
ESX = exports[Exports.ESXExport]:getSharedObject()
|
||||
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)
|
||||
@@ -301,13 +362,33 @@ elseif checkExists(Exports.ESXExport) then
|
||||
::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,
|
||||
},
|
||||
}
|
||||
|
||||
elseif checkExists(Exports.RSGExport) then
|
||||
jobResource = Exports.RSGExport
|
||||
cache.Jobs, cache.Gangs = exports[Exports.RSGExport]:GetCoreObject().Shared.Jobs, exports[Exports.RSGExport]:GetCoreObject().Shared.Gangs
|
||||
|
||||
for i = 1, #jobFunc do
|
||||
local data = jobFunc[i]
|
||||
if checkExists(data.script) then
|
||||
waitStarted(data.script) -- Wait for detected script to start fully
|
||||
data.cacheJob() -- run tablized function for core
|
||||
jobResource = data.script -- Grab script name to announce later
|
||||
endTimer("Jobs") -- end timer
|
||||
break -- break loop so it doesn't keep checking
|
||||
end
|
||||
end
|
||||
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',
|
||||
@@ -25,3 +24,5 @@ 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,12 +1,22 @@
|
||||
function parseVersion(version)
|
||||
local function readBoolMeta(key, default)
|
||||
local val = GetResourceMetadata("jim_bridge", key, 0)
|
||||
if not val then return default end
|
||||
val = tostring(val):lower()
|
||||
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
|
||||
end
|
||||
|
||||
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
|
||||
|
||||
if not SUPPRESS_UPDATES then
|
||||
function parseVersion(version)
|
||||
local parts = {}
|
||||
for num in version:gmatch("%d+") do
|
||||
table.insert(parts, tonumber(num))
|
||||
end
|
||||
return parts
|
||||
end
|
||||
end
|
||||
|
||||
function compareVersions(current, newest)
|
||||
function compareVersions(current, newest)
|
||||
local currentParts = parseVersion(current)
|
||||
local newestParts = parseVersion(newest)
|
||||
for i = 1, math.max(#currentParts, #newestParts) do
|
||||
@@ -16,20 +26,20 @@ function compareVersions(current, newest)
|
||||
elseif c > n then return 1 end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function capitalize(str)
|
||||
function capitalize(str)
|
||||
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
|
||||
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()
|
||||
function CheckVersion()
|
||||
if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then
|
||||
CreateThread(function()
|
||||
Wait(4000)
|
||||
@@ -95,6 +105,7 @@ function CheckVersion()
|
||||
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,44 +10,9 @@
|
||||
• esx (using ESX.UI.Menu)
|
||||
]]
|
||||
|
||||
--- Opens a menu using the configured menu system.
|
||||
---
|
||||
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
|
||||
---
|
||||
---@param Menu table A table containing the menu options to display.
|
||||
--- Each menu item can include:
|
||||
--- - header (`string`): The text to display for the menu item.
|
||||
--- - txt (`string`, optional): Additional text or description.
|
||||
--- - icon (`string`, optional): Icon to display with the menu item.
|
||||
--- - onSelect (`function`, optional): Function to execute when the menu item is selected.
|
||||
--- - arrow (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
|
||||
--- - params (`table`, optional): Additional parameters, such as events and arguments.
|
||||
--- - isMenuHeader (`boolean`, optional): Marks the item as a header.
|
||||
--- - disabled (`boolean`, optional): Disables the menu item if `true`.
|
||||
---
|
||||
---@param data table A table containing configuration data for the menu.
|
||||
--- - header (`string`): The header/title of the menu.
|
||||
--- - headertxt (`string`, optional): Additional header text.
|
||||
--- - onBack (`function`, optional): Function to call when the "Return" option is selected.
|
||||
--- - onExit (`function`, optional): Function to call when the menu is exited.
|
||||
--- - onSelected (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
|
||||
--- - canClose (`boolean`, optional): Whether the menu can be closed by the user.
|
||||
---
|
||||
---@usage
|
||||
--- ```lua
|
||||
--- openMenu({
|
||||
--- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
|
||||
--- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
|
||||
--- }, {
|
||||
--- header = "Main Menu",
|
||||
--- headertxt = "Select an option",
|
||||
--- onBack = function() print("Return selected") end,
|
||||
--- onExit = function() print("Menu closed") end,
|
||||
--- canClose = true,
|
||||
--- })
|
||||
--- ```
|
||||
function openMenu(Menu, data)
|
||||
if Config.System.Menu == "ox" then
|
||||
local contextFunc = {
|
||||
["ox"] =
|
||||
function(Menu, data)
|
||||
local index = nil
|
||||
if data.onBack and not data.onSelected then
|
||||
table.insert(Menu, 1, {
|
||||
@@ -110,8 +75,10 @@ function openMenu(Menu, data)
|
||||
else
|
||||
lib.showContext(menuID)
|
||||
end
|
||||
end,
|
||||
|
||||
elseif Config.System.Menu == "qb" then
|
||||
["qb"] =
|
||||
function(Menu, data)
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
@@ -151,8 +118,10 @@ function openMenu(Menu, data)
|
||||
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
|
||||
end
|
||||
exports[QBMenuExport]:openMenu(Menu)
|
||||
end,
|
||||
|
||||
elseif Config.System.Menu == "gta" then
|
||||
["gta"] =
|
||||
function(Menu, data)
|
||||
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
|
||||
titleColor = { 222, 255, 255 },
|
||||
maxOptionCountOnScreen = 15,
|
||||
@@ -206,8 +175,10 @@ function openMenu(Menu, data)
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end,
|
||||
|
||||
elseif Config.System.Menu == "esx" then
|
||||
["esx"] =
|
||||
function(Menu, data)
|
||||
for k in pairs(Menu) do
|
||||
Menu[k].label = Menu[k].header
|
||||
Menu[k].name = "button"..k
|
||||
@@ -245,8 +216,10 @@ function openMenu(Menu, data)
|
||||
function(data, menu)
|
||||
menu.close()
|
||||
end)
|
||||
end,
|
||||
|
||||
elseif Config.System.Menu == "lation" then
|
||||
["lation"] =
|
||||
function(Menu, data)
|
||||
if data.onBack then
|
||||
table.insert(Menu, 1, {
|
||||
icon = "fas fa-circle-arrow-left",
|
||||
@@ -290,12 +263,63 @@ function openMenu(Menu, data)
|
||||
|
||||
-- Show menu
|
||||
exports.lation_ui:showMenu('menu')
|
||||
end,
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
--- Opens a menu using the configured menu system.
|
||||
---
|
||||
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
|
||||
---
|
||||
---@param Menu table A table containing the menu options to display.
|
||||
--- Each menu item can include:
|
||||
--- - header (`string`): The text to display for the menu item.
|
||||
--- - txt (`string`, optional): Additional text or description.
|
||||
--- - icon (`string`, optional): Icon to display with the menu item.
|
||||
--- - onSelect (`function`, optional): Function to execute when the menu item is selected.
|
||||
--- - arrow (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
|
||||
--- - params (`table`, optional): Additional parameters, such as events and arguments.
|
||||
--- - isMenuHeader (`boolean`, optional): Marks the item as a header.
|
||||
--- - disabled (`boolean`, optional): Disables the menu item if `true`.
|
||||
---
|
||||
---@param data table A table containing configuration data for the menu.
|
||||
--- - header (`string`): The header/title of the menu.
|
||||
--- - headertxt (`string`, optional): Additional header text.
|
||||
--- - onBack (`function`, optional): Function to call when the "Return" option is selected.
|
||||
--- - onExit (`function`, optional): Function to call when the menu is exited.
|
||||
--- - onSelected (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
|
||||
--- - canClose (`boolean`, optional): Whether the menu can be closed by the user.
|
||||
---
|
||||
---@usage
|
||||
--- ```lua
|
||||
--- openMenu({
|
||||
--- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
|
||||
--- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
|
||||
--- }, {
|
||||
--- header = "Main Menu",
|
||||
--- headertxt = "Select an option",
|
||||
--- onBack = function() print("Return selected") end,
|
||||
--- onExit = function() print("Menu closed") end,
|
||||
--- canClose = true,
|
||||
--- })
|
||||
--- ```
|
||||
function openMenu(Menu, data)
|
||||
contextFunc[Config.System.Menu](Menu, data)
|
||||
end
|
||||
|
||||
-- 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,7 +4,8 @@ local storedPID = nil
|
||||
|
||||
local progressFunc = {
|
||||
ox = {
|
||||
start = function(data)
|
||||
start =
|
||||
function(data)
|
||||
local options = {
|
||||
duration = debugMode and 1000 or data.time,
|
||||
label = data.label,
|
||||
@@ -51,17 +52,20 @@ local progressFunc = {
|
||||
if exports[OXLibExport]:progressBar(options) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
print("^1progressBar was not successful")
|
||||
end
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
stop =
|
||||
function()
|
||||
exports[OXLibExport]:cancelProgress()
|
||||
end,
|
||||
},
|
||||
|
||||
qb = {
|
||||
start = function(data)
|
||||
start =
|
||||
function(data)
|
||||
local p = promise.new()
|
||||
Core.Functions.Progressbar("progbar",
|
||||
data.label,
|
||||
debugMode and 1000 or data.time,
|
||||
@@ -80,19 +84,20 @@ local progressFunc = {
|
||||
task = data.task
|
||||
},
|
||||
{}, {},
|
||||
function()
|
||||
return true
|
||||
end, function()
|
||||
return false
|
||||
end, data.icon)
|
||||
function() p:resolve(true) end,
|
||||
function() p:resolve(false) end,
|
||||
data.icon)
|
||||
return Citizen.Await(p)
|
||||
end,
|
||||
stop = function()
|
||||
stop =
|
||||
function()
|
||||
TriggerEvent("progressbar:client:cancel")
|
||||
end,
|
||||
},
|
||||
|
||||
qs = {
|
||||
start = function(data)
|
||||
start =
|
||||
function(data)
|
||||
if exports['qs-interface']:ProgressBar({
|
||||
duration = debugMode and 1000 or data.time,
|
||||
label = data.label,
|
||||
@@ -112,14 +117,17 @@ local progressFunc = {
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
stop =
|
||||
function()
|
||||
--??
|
||||
TriggerEvent("progressbar:client:cancel")
|
||||
end,
|
||||
},
|
||||
|
||||
esx = {
|
||||
start = function(data)
|
||||
start =
|
||||
function(data)
|
||||
local p = promise.new()
|
||||
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
|
||||
FreezePlayer = true,
|
||||
animation = {
|
||||
@@ -128,20 +136,24 @@ local progressFunc = {
|
||||
scenario = data.task,
|
||||
},
|
||||
onFinish = function()
|
||||
return true
|
||||
p:resolve(true)
|
||||
end,
|
||||
onCancel = function()
|
||||
p:resolve(false)
|
||||
return false
|
||||
end
|
||||
})
|
||||
return Citizen.Await(p)
|
||||
end,
|
||||
stop = function()
|
||||
stop =
|
||||
function()
|
||||
ESX.CancelProgressbar()
|
||||
end,
|
||||
},
|
||||
|
||||
lation = {
|
||||
start = function(data)
|
||||
start =
|
||||
function(data)
|
||||
if exports.lation_ui:progressBar({
|
||||
label = data.label,
|
||||
description = nil,
|
||||
@@ -170,13 +182,15 @@ local progressFunc = {
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
stop =
|
||||
function()
|
||||
exports.lation_ui:cancelProgress()
|
||||
end,
|
||||
},
|
||||
|
||||
red = {
|
||||
start = function(data)
|
||||
start =
|
||||
function(data)
|
||||
if exports.jim_bridge:redProgressBar({
|
||||
label = data.label,
|
||||
time = debugMode and 1000 or data.time,
|
||||
@@ -192,14 +206,15 @@ local progressFunc = {
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
stop =
|
||||
function()
|
||||
exports.jim_bridge:stopProgressBar()
|
||||
end,
|
||||
},
|
||||
|
||||
gta = {
|
||||
start = function(data)
|
||||
|
||||
start =
|
||||
function(data)
|
||||
if exports.jim_bridge:gtaProgressBar({
|
||||
label = data.label,
|
||||
time = debugMode and 1000 or data.time,
|
||||
@@ -215,7 +230,8 @@ local progressFunc = {
|
||||
return false
|
||||
end
|
||||
end,
|
||||
stop = function()
|
||||
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)
|
||||
|
||||
-- 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)
|
||||
|
||||
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)
|
||||
|
||||
-- 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
|
||||
-- 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
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
end
|
||||
-- ===== Ownership + indexes =====
|
||||
-- TextTargets: key -> target data (coords/entity/models/options/etc.)
|
||||
local TextTargets = {}
|
||||
-- targetEntities kept for parity (not strictly required)
|
||||
local targetEntities = {}
|
||||
|
||||
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]
|
||||
-- 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
|
||||
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
|
||||
TargetRegistry.byKey[key] = owner
|
||||
TargetRegistry.byResource[owner] = TargetRegistry.byResource[owner] or {}
|
||||
TargetRegistry.byResource[owner][key] = true
|
||||
end
|
||||
|
||||
local function removeTargetKey(key, reason)
|
||||
local owner = TargetRegistry.byKey[key]
|
||||
if TextTargets[key] then
|
||||
TextTargets[key] = nil
|
||||
-- print("^6Bridge^7:^5 Target^7: ^2Removed target '%s'%s", key, reason and (" ("..reason..")") or "")
|
||||
end
|
||||
TextTargets[entity] = {
|
||||
coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z),
|
||||
buttontext = tempText,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
text = table.concat(tempText, "\n")
|
||||
}
|
||||
if owner then
|
||||
if TargetRegistry.byResource[owner] then
|
||||
TargetRegistry.byResource[owner][key] = nil
|
||||
end
|
||||
TargetRegistry.byKey[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
AddEventHandler("onResourceStop", function(res)
|
||||
local owned = TargetRegistry.byResource[res]
|
||||
if not owned then return end
|
||||
local cnt = 0
|
||||
for key in pairs(owned) do
|
||||
removeTargetKey(key, "resource stopped: "..res)
|
||||
cnt = cnt + 1
|
||||
end
|
||||
TargetRegistry.byResource[res] = nil
|
||||
-- print("^6Bridge^7:^5 Target^7: ^2Cleared "..cnt.." target(s) from '"..res.."'")
|
||||
end)
|
||||
|
||||
-- ===== Helpers =====
|
||||
local function vecKey(v)
|
||||
-- stable rounded coord string for entity dedupe when name isn't provided
|
||||
return ("%.3f,%.3f,%.3f"):format(v.x, v.y, v.z)
|
||||
end
|
||||
|
||||
local function bakeButtons(opts)
|
||||
local tempText = {}
|
||||
for i = 1, #opts do
|
||||
opts[i].key = KEY_TABLE[i]
|
||||
tempText[#tempText + 1] =
|
||||
(" ~b~[~w~%s~b~] ~w~%s"):format(Keys[opts[i].key] or ("K"..opts[i].key), opts[i].label or ("Option "..i))
|
||||
end
|
||||
return tempText
|
||||
end
|
||||
|
||||
-- Update cached text blob
|
||||
local function updateCachedText(target)
|
||||
target.text = table.concat(target.buttontext, "\n")
|
||||
end
|
||||
|
||||
-- ===== Public API: Create targets =====
|
||||
-- ENTITY: createEntityTarget(entity, opts, dist, nameOpt?)
|
||||
function createEntityTarget(entity, opts, dist, name)
|
||||
startTargetLoop()
|
||||
|
||||
if not entity or entity == 0 then return end
|
||||
targetEntities[#targetEntities + 1] = entity
|
||||
|
||||
local owner = getOwnerResource()
|
||||
local coords = GetEntityCoords(entity)
|
||||
local key = tostring(name) or ("entity@" .. vecKey(coords))
|
||||
|
||||
-- Always overwrite on same key
|
||||
local buttontext = bakeButtons(opts)
|
||||
TextTargets[key] = {
|
||||
_key = key,
|
||||
_type = "entity",
|
||||
_owner = owner,
|
||||
entity = entity,
|
||||
coords = vec3(coords.x, coords.y, coords.z),
|
||||
buttontext = buttontext,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
}
|
||||
updateCachedText(TextTargets[key])
|
||||
registerTarget(owner, key)
|
||||
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ENTITY target '"..key.."' by '"..owner.."' @ "..formatCoord(coords))
|
||||
return key
|
||||
end
|
||||
|
||||
-- ZONE: createZoneTarget(data, opts, dist)
|
||||
-- Expect data[1] = id/name, data[2] = vec3 coords (as in your original)
|
||||
function createZoneTarget(data, opts, dist)
|
||||
startTargetLoop()
|
||||
local existingTarget = nil
|
||||
for _, target in pairs(TextTargets) do
|
||||
if #(target.coords - data[2]) < 0.01 then
|
||||
existingTarget = target
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if existingTarget then
|
||||
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,
|
||||
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,
|
||||
text = table.concat(tempText, "\n")
|
||||
}
|
||||
end
|
||||
return data[1]
|
||||
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 createModelTarget(models, opts, dist)
|
||||
-- MODEL: createModelTarget(models, opts, dist, nameOpt?)
|
||||
function createModelTarget(models, opts, dist, name)
|
||||
startTargetLoop()
|
||||
if type(models) ~= "table" then
|
||||
models = { models }
|
||||
|
||||
local owner = getOwnerResource()
|
||||
if type(models) ~= "table" then models = { models } end
|
||||
|
||||
local key
|
||||
if name then
|
||||
key = tostring(name)
|
||||
else
|
||||
local parts = {}
|
||||
for i, m in ipairs(models) do parts[i] = tostring(m) end
|
||||
key = "model_" .. table.concat(parts, "_")
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
local keyStr = ""
|
||||
for i, m in ipairs(models) do
|
||||
keyStr = keyStr .. tostring(m) .. (i < #models and "_" or "")
|
||||
end
|
||||
local targetKey = "model_" .. keyStr
|
||||
|
||||
TextTargets[targetKey] = {
|
||||
local buttontext = bakeButtons(opts)
|
||||
TextTargets[key] = {
|
||||
_key = key,
|
||||
_type = "model",
|
||||
_owner = owner,
|
||||
models = models,
|
||||
buttontext = tempText,
|
||||
buttontext = buttontext,
|
||||
options = opts,
|
||||
dist = dist,
|
||||
coords = vec3(0, 0, 0),
|
||||
text = table.concat(tempText, "\n")
|
||||
coords = vec3(0, 0, 0), -- will be updated by the refresher
|
||||
}
|
||||
|
||||
return targetKey
|
||||
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 removeEntityTarget(entity)
|
||||
TextTargets[entity] = 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(target)
|
||||
TextTargets[target] = nil
|
||||
function removeZoneTarget(key)
|
||||
if not key then return end
|
||||
removeTargetKey(key, "removeZoneTarget")
|
||||
end
|
||||
|
||||
function removeModelTarget(model)
|
||||
TextTargets[model] = nil
|
||||
-- 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()
|
||||
@@ -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,12 +323,12 @@ 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
|
||||
@@ -271,19 +341,10 @@ function DrawText3D(coord, text, highlight)
|
||||
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