mirror of
https://github.com/jimathy/jim_bridge.git
synced 2026-08-17 05:56:02 +01:00
I've separated the built-in custom native GTA Notifications, skillcheck and draw text targets to separate files to be loaded along side `jim_bridge`
This now makes it so they only load once instead of with every single script, and each one can call on it
This change also makes these systems able to be called outside of `jim_bridge`
For example in my test server I'm currently using:
```lua
function QBCore.Functions.Notify(text, texttype, length, icon)
CreateThread(function()
exports.jim_bridge:Notify(nil, text, icon)
end)
end
```
318 lines
11 KiB
Lua
318 lines
11 KiB
Lua
-- 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.
|
|
local Keys = {
|
|
[322] = "ESC", [288] = "F1", [289] = "F2", [170] = "F3", [166] = "F5",
|
|
[167] = "F6", [168] = "F7", [169] = "F8", [56] = "F9", [57] = "F10",
|
|
[243] = "~", [157] = "1", [158] = "2", [160] = "3", [164] = "4", [165] = "5",
|
|
[159] = "6", [161] = "7", [162] = "8", [163] = "9", [84] = "-", [83] = "=",
|
|
[177] = "BACKSPACE", [37] = "TAB",
|
|
[44] = "Q", [32] = "W", [38] = "E", [45] = "R", [245] = "T", [246] = "Y",
|
|
[303] = "U", [199] = "P",
|
|
[39] = "[", [40] = "]", [18] = "ENTER", [137] = "CAPS",
|
|
[34] = "A", [8] = "S", [9] = "D", [23] = "F", [47] = "G",
|
|
[74] = "H", [311] = "K", [182] = "L", [21] = "LEFTSHIFT",
|
|
[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
|
|
|
|
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[entity] = {
|
|
coords = vec3(entityCoords.x, entityCoords.y, entityCoords.z),
|
|
buttontext = tempText,
|
|
options = opts,
|
|
dist = dist,
|
|
text = table.concat(tempText, "\n")
|
|
}
|
|
end
|
|
end
|
|
|
|
function createZoneTarget(data, opts, dist)
|
|
startTargetLoop()
|
|
local existingTarget = nil
|
|
for _, target in pairs(TextTargets) do
|
|
if #(target.coords - data[2]) < 0.01 then
|
|
existingTarget = target
|
|
break
|
|
end
|
|
end
|
|
|
|
if existingTarget then
|
|
for i = 1, #opts do
|
|
local key = KEY_TABLE[#existingTarget.options + i]
|
|
opts[i].key = key
|
|
existingTarget.buttontext[#existingTarget.buttontext + 1] = " ~b~[~w~" .. Keys[key] .. "~b~] ~w~" .. opts[i].label
|
|
existingTarget.options[#existingTarget.options + 1] = opts[i]
|
|
end
|
|
updateCachedText(existingTarget)
|
|
else
|
|
local tempText = {}
|
|
for i = 1, #opts do
|
|
opts[i].key = KEY_TABLE[i]
|
|
tempText[#tempText + 1] = " ~b~[~w~" .. Keys[opts[i].key] .. "~b~] ~w~" .. opts[i].label
|
|
end
|
|
TextTargets[data[1]] = {
|
|
coords = data[2],
|
|
buttontext = tempText,
|
|
options = opts,
|
|
dist = dist,
|
|
text = table.concat(tempText, "\n")
|
|
}
|
|
end
|
|
return data[1]
|
|
end
|
|
|
|
function createModelTarget(models, opts, dist)
|
|
startTargetLoop()
|
|
if type(models) ~= "table" then
|
|
models = { models }
|
|
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] = {
|
|
models = models,
|
|
buttontext = tempText,
|
|
options = opts,
|
|
dist = dist,
|
|
coords = vec3(0, 0, 0),
|
|
text = table.concat(tempText, "\n")
|
|
}
|
|
|
|
return targetKey
|
|
end
|
|
|
|
function removeEntityTarget(entity)
|
|
TextTargets[entity] = nil
|
|
end
|
|
|
|
function removeZoneTarget(target)
|
|
TextTargets[target] = nil
|
|
end
|
|
|
|
function removeModelTarget(model)
|
|
TextTargets[model] = nil
|
|
end
|
|
|
|
exports("createEntityTarget", createEntityTarget)
|
|
exports("createZoneTarget", createZoneTarget)
|
|
exports("createModelTarget", createModelTarget)
|
|
|
|
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
|
|
local fileLoader = assert(load(LoadResourceFile("jim_bridge", ('starter.lua')), ('@@jim_bridge/starter.lua')))
|
|
fileLoader()
|
|
-- Model Entity Refresher
|
|
CreateThread(function()
|
|
while true do
|
|
local pedCoords = GetEntityCoords(PlayerPedId())
|
|
for _, target in pairs(TextTargets) do
|
|
if target.models then
|
|
for _, model in ipairs(target.models) do
|
|
local entity = GetClosestObjectOfType(pedCoords.x, pedCoords.y, pedCoords.z, target.dist, model, false, false, false)
|
|
if entity and entity ~= 0 then
|
|
target.entity = entity
|
|
target.coords = GetEntityCoords(entity)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
Wait(3000) -- Refresh every 3s
|
|
end
|
|
end)
|
|
|
|
-- Main Target Loop
|
|
CreateThread(function()
|
|
while true do
|
|
local ped = PlayerPedId()
|
|
local pedCoords = GetEntityCoords(ped)
|
|
local camCoords = GetGameplayCamCoord()
|
|
local camRot = GetGameplayCamRot(2)
|
|
local camForward = RotationToDirection(camRot)
|
|
|
|
local closestTarget, closestDist, targetEntity = nil, math.huge, nil
|
|
|
|
for _, target in pairs(TextTargets) do
|
|
local coords = target.coords
|
|
if coords then
|
|
local dist = #(pedCoords - coords)
|
|
if dist <= target.dist then
|
|
local normVec = normalizeVector(coords - camCoords)
|
|
local dot = camForward.x * normVec.x + camForward.y * normVec.y + camForward.z * normVec.z
|
|
|
|
if dot > 0.5 and dist < closestDist then
|
|
closestTarget = target
|
|
closestDist = dist
|
|
targetEntity = target.entity
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, target in pairs(TextTargets) do
|
|
if not target.coords then goto continue end
|
|
local dist = #(pedCoords - target.coords)
|
|
if dist > target.dist then goto continue end
|
|
local isClosest = (target == closestTarget)
|
|
|
|
for i, opt in ipairs(target.options) do
|
|
if IsControlJustPressed(0, opt.key) and isClosest then
|
|
if (not target.canInteract or target.canInteract()) and
|
|
(not opt.item or hasItem(opt.item)) and
|
|
(not opt.job or hasJob(opt.job, nil)) then
|
|
if opt.onSelect then opt.onSelect(targetEntity) end
|
|
if opt.action then opt.action(targetEntity) end
|
|
end
|
|
end
|
|
end
|
|
|
|
local baseZ, lineHeight = target.coords.z + 1.0, -0.16
|
|
local lineOffset = 0
|
|
|
|
for i, opt in ipairs(target.options) do
|
|
if (not target.canInteract or target.canInteract()) and
|
|
(not opt.item or hasItem(opt.item)) and
|
|
(not opt.job or hasJob(opt.job, nil)) then
|
|
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + lineHeight * lineOffset), target.buttontext[i], isClosest)
|
|
lineOffset = lineOffset + 1
|
|
end
|
|
end
|
|
|
|
::continue::
|
|
end
|
|
|
|
Wait(1) -- Throttled
|
|
end
|
|
end)
|
|
end
|
|
|
|
|
|
function DrawText3D(coord, text, highlight)
|
|
SetTextScale(0.30, 0.30)
|
|
SetTextFont(0)
|
|
SetTextProportional(1)
|
|
SetTextColour(255, 255, 255, 215)
|
|
SetTextEntry("STRING")
|
|
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)
|
|
SetDrawOrigin(coord.x, coord.y, coord.z, 0)
|
|
DrawText(0.0, 0.0)
|
|
local count, length = GetLineCountAndMaxLength(text)
|
|
|
|
local padding = 0.005
|
|
local heightFactor = (count / 43) + padding
|
|
local weightFactor = (length / 150) + padding
|
|
|
|
local height = (heightFactor / 2) - padding / 1
|
|
local width = (weightFactor / 2) - padding / 1
|
|
|
|
DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
|
|
ClearDrawOrigin()
|
|
end
|
|
|
|
--- Calculates the number of lines and the maximum line length from the given text.
|
|
---
|
|
--- @param text string The text to analyze.
|
|
--- @return number, number The line count and maximum line length.
|
|
---
|
|
--- @usage
|
|
--- ```lua
|
|
--- local count, maxLen = GetLineCountAndMaxLength("Hello World")
|
|
--- ```
|
|
function GetLineCountAndMaxLength(text)
|
|
local lineCount, maxLength = 0, 0
|
|
for line in text:gmatch("[^\n]+") do
|
|
lineCount += 1
|
|
local lineLength = string.len(line)
|
|
if lineLength > maxLength then
|
|
maxLength = lineLength
|
|
end
|
|
end
|
|
if lineCount == 0 then lineCount = 1 end
|
|
return lineCount, maxLength
|
|
end
|
|
|
|
|
|
function RotationToDirection(rot)
|
|
local adjust = math.pi / 180
|
|
return vec3(
|
|
-math.sin(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
|
|
math.cos(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
|
|
math.sin(adjust * rot.x)
|
|
)
|
|
end
|
|
|
|
function normalizeVector(vec)
|
|
local len = math.sqrt(vec.x^2 + vec.y^2 + vec.z^2)
|
|
if len ~= 0 then
|
|
return vec3(vec.x / len, vec.y / len, vec.z / len)
|
|
else
|
|
return vec3(0, 0, 0)
|
|
end
|
|
end
|
|
|
|
-- Helper to update cached text.
|
|
function updateCachedText(target)
|
|
target.text = table.concat(target.buttontext, "\n")
|
|
end |