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