mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-17 23:16:03 +01:00
Some people accidentally, mistakenly, and without any malicious intent forget to attribute code from this resource to its source. This change will hopefully assist people, so that they do not make that mistake again.
506 lines
18 KiB
Lua
506 lines
18 KiB
Lua
--[[
|
|
https://github.com/overextended/ox_lib
|
|
|
|
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
|
|
|
Copyright (c) 2025 Linden <https://github.com/thelindat/fivem>
|
|
]]
|
|
|
|
local creatorActive = false
|
|
local controlsActive = false
|
|
local zoneType, step, xCoord, yCoord, zCoord, heading, height, width, length
|
|
local steps = {{0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100}, {0.25, 0.5, 1, 2.5, 5, 15, 30, 45, 60, 90, 180}}
|
|
local points = {}
|
|
local format = 'array'
|
|
local displayModes = {'basic', 'walls', 'axes', 'full'}
|
|
local displayMode = 1
|
|
local minCheck = steps[1][1] / 2
|
|
local lastZone = {}
|
|
local alignMovementWithCamera = false
|
|
local useLastZoneFalsyInputs = {['0'] = true, [''] = true, ['false'] = true, ['nil'] = true}
|
|
|
|
local function firstToUpper(str)
|
|
return (str:gsub('^%l', string.upper))
|
|
end
|
|
|
|
local function updateText()
|
|
local text = {
|
|
('------ Creating %s Zone ------ \n'):format(firstToUpper(zoneType)),
|
|
('Step size [Scroll]: %sm/%s° \n'):format(steps[1][step], steps[2][step]),
|
|
('X coord [A/D]: %s \n'):format(xCoord),
|
|
('Y coord [W/S]: %s \n'):format(yCoord),
|
|
('Z coord [R/F]: %s \n'):format(zCoord),
|
|
}
|
|
|
|
if zoneType == 'poly' then
|
|
text[#text + 1] = ('Height [Shift + Scroll]: %s \n'):format(height)
|
|
text[#text + 1] = ('Cycle display mode [G]: %s \n'):format(firstToUpper(displayModes[displayMode]))
|
|
text[#text + 1] = ('Toggle Axis mode [C]: %s \n'):format(alignMovementWithCamera and 'Camera' or 'Grid')
|
|
text[#text + 1] = 'Create new point - [Space] \n'
|
|
text[#text + 1] = 'Edit last point - [Backspace] \n'
|
|
elseif zoneType == 'box' then
|
|
text[#text + 1] = ('Heading [Q/E]: %s° \n'):format(heading)
|
|
text[#text + 1] = ('Height [Shift + Scroll]: %s \n'):format(height)
|
|
text[#text + 1] = ('Width [Ctrl + Scroll]: %s \n'):format(width)
|
|
text[#text + 1] = ('Length [Alt + Scroll]: %s \n'):format(length)
|
|
text[#text + 1] = ('Cycle display mode [G]: %s \n'):format(firstToUpper(displayModes[displayMode]))
|
|
text[#text + 1] = ('Toggle Axis mode [C]: %s \n'):format(alignMovementWithCamera and 'Camera' or 'Grid')
|
|
text[#text + 1] = 'Recenter - [Space] \n'
|
|
elseif zoneType == 'sphere' then
|
|
text[#text + 1] = ('Size [Shift + Scroll]: %s \n'):format(height)
|
|
text[#text + 1] = ('Toggle Axis mode [C]: %s \n'):format(alignMovementWithCamera and 'Camera' or 'Grid')
|
|
text[#text + 1] = 'Recenter - [Space] \n'
|
|
end
|
|
|
|
text[#text + 1] = 'Toggle controls - [X] \n'
|
|
text[#text + 1] = 'Save - [Enter] \n'
|
|
text[#text + 1] = 'Cancel - [Esc]'
|
|
|
|
lib.showTextUI(table.concat(text))
|
|
end
|
|
|
|
local function round(number)
|
|
return number >= 0 and math.floor(number + 0.5) or math.ceil(number - 0.5)
|
|
end
|
|
|
|
local function closeCreator(cancel)
|
|
if not cancel then
|
|
if zoneType == 'poly' then
|
|
points[#points + 1] = vec(xCoord, yCoord)
|
|
end
|
|
|
|
---@type string[]?
|
|
local input = lib.inputDialog(('Name your %s Zone'):format(firstToUpper(zoneType)), {
|
|
{ type = 'input', label = 'Name', placeholder = 'none' },
|
|
{ type = 'select', label = 'Format', default = format, options = {
|
|
{ value = 'function', label = 'Function' },
|
|
{ value = 'array', label = 'Array' },
|
|
{ value = 'target', label = 'Target'},
|
|
}}
|
|
})
|
|
|
|
if not input then return end
|
|
|
|
format = input[2]
|
|
|
|
TriggerServerEvent('ox_lib:saveZone', {
|
|
zoneType = zoneType,
|
|
name = input[1] or 'none',
|
|
format = format,
|
|
xCoord = xCoord,
|
|
yCoord = yCoord,
|
|
zCoord = zCoord,
|
|
heading = heading,
|
|
height = height,
|
|
width = width,
|
|
length = length,
|
|
points = points
|
|
})
|
|
|
|
lastZone[zoneType] = {
|
|
zoneType = zoneType,
|
|
heading = heading,
|
|
height = height,
|
|
width = width,
|
|
length = length,
|
|
}
|
|
end
|
|
|
|
creatorActive = false
|
|
controlsActive = false
|
|
lib.hideTextUI()
|
|
zoneType = nil
|
|
end
|
|
|
|
local function drawRectangle(rec)
|
|
DrawPoly(rec[1].x, rec[1].y, rec[1].z, rec[2].x, rec[2].y, rec[2].z, rec[3].x, rec[3].y, rec[3].z, 255, 42, 24, 100)
|
|
DrawPoly(rec[2].x, rec[2].y, rec[2].z, rec[1].x, rec[1].y, rec[1].z, rec[3].x, rec[3].y, rec[3].z, 255, 42, 24, 100)
|
|
DrawPoly(rec[1].x, rec[1].y, rec[1].z, rec[4].x, rec[4].y, rec[4].z, rec[3].x, rec[3].y, rec[3].z, 255, 42, 24, 100)
|
|
DrawPoly(rec[4].x, rec[4].y, rec[4].z, rec[1].x, rec[1].y, rec[1].z, rec[3].x, rec[3].y, rec[3].z, 255, 42, 24, 100)
|
|
end
|
|
|
|
local function drawLines()
|
|
local thickness = vec(0, 0, height / 2)
|
|
local activeA, activeB = vec(xCoord, yCoord, zCoord) + thickness, vec(xCoord, yCoord, zCoord) - thickness
|
|
|
|
if zoneType == 'poly' then
|
|
DrawLine(activeA.x, activeA.y, activeA.z, activeB.x, activeB.y, activeB.z, 255, 42, 24, 225)
|
|
end
|
|
|
|
for i = 1, #points do
|
|
points[i] = vec(points[i].x, points[i].y, zCoord)
|
|
local a = points[i] + thickness
|
|
local b = points[i] - thickness
|
|
local c = (points[i + 1] and vec(points[i + 1].x, points[i + 1].y, zCoord) or points[1]) + thickness
|
|
local d = (points[i + 1] and vec(points[i + 1].x, points[i + 1].y, zCoord) or points[1]) - thickness
|
|
local e = points[i]
|
|
local f = (points[i + 1] and vec(points[i + 1].x, points[i + 1].y, zCoord) or points[1])
|
|
|
|
if i == #points and zoneType == 'poly' then
|
|
DrawLine(a.x, a.y, a.z, b.x, b.y, b.z, 255, 42, 24, 225)
|
|
DrawLine(activeA.x, activeA.y, activeA.z, c.x, c.y, c.z, 255, 42, 24, 225)
|
|
DrawLine(activeB.x, activeB.y, activeB.z, d.x, d.y, d.z, 255, 42, 24, 225)
|
|
DrawLine(a.x, a.y, a.z, activeA.x, activeA.y, activeA.z, 255, 42, 24, 225)
|
|
DrawLine(b.x, b.y, b.z, activeB.x, activeB.y, activeB.z, 255, 42, 24, 225)
|
|
DrawLine(xCoord, yCoord, zCoord, f.x, f.y, f.z, 255, 42, 24, 225)
|
|
DrawLine(e.x, e.y, e.z, xCoord, yCoord, zCoord, 255, 42, 24, 225)
|
|
else
|
|
DrawLine(a.x, a.y, a.z, b.x, b.y, b.z, 255, 42, 24, 225)
|
|
DrawLine(a.x, a.y, a.z, c.x, c.y, c.z, 255, 42, 24, 225)
|
|
DrawLine(b.x, b.y, b.z, d.x, d.y, d.z, 255, 42, 24, 225)
|
|
DrawLine(e.x, e.y, e.z, f.x, f.y, f.z, 255, 42, 24, 225)
|
|
end
|
|
|
|
if displayMode == 2 or displayMode == 4 then
|
|
if i == #points and zoneType == 'poly' then
|
|
drawRectangle({a, b, activeB, activeA})
|
|
drawRectangle({activeA, activeB, d, c})
|
|
else
|
|
drawRectangle({a, b, d, c})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function getRelativePos(origin, point, theta)
|
|
if theta == 0.0 then return point end
|
|
local p = point - origin
|
|
local pX, pY = p.x, p.y
|
|
theta = math.rad(theta)
|
|
local cosTheta = math.cos(theta)
|
|
local sinTheta = math.sin(theta)
|
|
local x = math.floor(((pX * cosTheta - pY * sinTheta) + origin.x) * 100 + 0.0) / 100
|
|
local y = math.floor(((pX * sinTheta + pY * cosTheta) + origin.y) * 100 + 0.0) / 100
|
|
return x, y
|
|
end
|
|
|
|
local isFivem = cache.game == 'fivem'
|
|
local controls = {
|
|
['INPUT_LOOK_LR'] = isFivem and 1 or 0xA987235F,
|
|
['INPUT_LOOK_UD'] = isFivem and 2 or 0xD2047988,
|
|
['INPUT_MP_TEXT_CHAT_ALL'] = isFivem and 245 or 0x9720FCEE
|
|
}
|
|
|
|
local function startCreator(arg, useLast)
|
|
creatorActive = true
|
|
controlsActive = true
|
|
zoneType = arg
|
|
|
|
step = 5
|
|
local coords = GetEntityCoords(cache.ped)
|
|
xCoord = round(coords.x) + 0.0
|
|
yCoord = round(coords.y) + 0.0
|
|
zCoord = round(coords.z) + 0.0
|
|
heading = useLast and lastZone[zoneType].heading or 0.0
|
|
height = useLast and lastZone[zoneType].height or 4.0
|
|
width = useLast and lastZone[zoneType].width or 4.0
|
|
length = useLast and lastZone[zoneType].length or 4.0
|
|
points = {}
|
|
|
|
updateText()
|
|
|
|
while creatorActive do
|
|
Wait(0)
|
|
|
|
if IsDisabledControlJustReleased(0, 73) then -- x
|
|
controlsActive = not controlsActive
|
|
end
|
|
|
|
if displayMode == 3 or displayMode == 4 then
|
|
if alignMovementWithCamera then
|
|
local rightX, rightY = getRelativePos(vec2(xCoord, yCoord), vec2(xCoord + 2, yCoord), GetGameplayCamRot(2).z)
|
|
local forwardX, forwardY = getRelativePos(vec2(xCoord, yCoord), vec2(xCoord, yCoord + 2), GetGameplayCamRot(2).z)
|
|
|
|
DrawLine(xCoord, yCoord, zCoord, rightX, rightY or 0, zCoord, 0, 255, 0, 225)
|
|
DrawLine(xCoord, yCoord, zCoord, forwardX, forwardY or 0, zCoord, 0, 255, 0, 225)
|
|
end
|
|
|
|
DrawLine(xCoord, yCoord, zCoord, xCoord + 2, yCoord, zCoord, 0, 0, 255, 225)
|
|
DrawLine(xCoord, yCoord, zCoord, xCoord, yCoord + 2, zCoord, 0, 0, 255, 225)
|
|
DrawLine(xCoord, yCoord, zCoord, xCoord, yCoord, zCoord + 2, 0, 0, 255, 225)
|
|
end
|
|
|
|
if zoneType == 'poly' then
|
|
drawLines()
|
|
elseif zoneType == 'box' then
|
|
local rad = math.rad(-heading)
|
|
local sinH = math.sin(rad)
|
|
local cosH = math.cos(rad)
|
|
local center = vec2(xCoord, yCoord)
|
|
---@type vector2[]
|
|
points = {
|
|
center + vec2((width * cosH + length * sinH), (length * cosH - width * sinH)) / 2,
|
|
center + vec2(-(width * cosH - length * sinH), (length * cosH + width * sinH)) / 2,
|
|
center + vec2(-(width * cosH + length * sinH), -(length * cosH - width * sinH)) / 2,
|
|
center + vec2((width * cosH - length * sinH), -(length * cosH + width * sinH)) / 2,
|
|
}
|
|
|
|
drawLines()
|
|
elseif zoneType == 'sphere' then
|
|
---@diagnostic disable-next-line: param-type-mismatch
|
|
DrawMarker(28, xCoord, yCoord, zCoord, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, height, height, height, 255, 42, 24, 100, false, false, 0, false, false, false, false)
|
|
end
|
|
|
|
if controlsActive then
|
|
DisableAllControlActions(0)
|
|
EnableControlAction(0, controls['INPUT_LOOK_LR'], true)
|
|
EnableControlAction(0, controls['INPUT_LOOK_UD'], true)
|
|
EnableControlAction(0, controls['INPUT_MP_TEXT_CHAT_ALL'], true)
|
|
|
|
local change = false
|
|
local lStep = steps[1][step]
|
|
local rStep = steps[2][step]
|
|
|
|
if IsDisabledControlJustReleased(0, 17) then -- scroll up
|
|
if IsDisabledControlPressed(0, 21) then -- shift held down
|
|
change = true
|
|
height += lStep
|
|
elseif IsDisabledControlPressed(0, 36) then -- ctrl held down
|
|
change = true
|
|
width += lStep
|
|
elseif IsDisabledControlPressed(0, 19) then -- alt held down
|
|
change = true
|
|
length += lStep
|
|
elseif step < 11 then
|
|
change = true
|
|
step += 1
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 16) then -- scroll down
|
|
if IsDisabledControlPressed(0, 21) then -- shift held down
|
|
change = true
|
|
|
|
if height - lStep > lStep then
|
|
height -= lStep
|
|
elseif height - lStep > 0 then
|
|
height = lStep
|
|
end
|
|
elseif IsDisabledControlPressed(0, 36) then -- ctrl held down
|
|
change = true
|
|
|
|
if width - lStep > lStep then
|
|
width -= lStep
|
|
elseif width - lStep > 0 then
|
|
width = lStep
|
|
end
|
|
elseif IsDisabledControlPressed(0, 19) then -- alt held down
|
|
change = true
|
|
|
|
if length - lStep > lStep then
|
|
length -= lStep
|
|
elseif length - lStep > 0 then
|
|
length = lStep
|
|
end
|
|
elseif step > 1 then
|
|
change = true
|
|
step -= 1
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 32) then -- w
|
|
change = true
|
|
|
|
if alignMovementWithCamera then
|
|
local newX, newY = getRelativePos(vec2(xCoord, yCoord), vec2(xCoord, yCoord + lStep), GetGameplayCamRot(2).z)
|
|
|
|
if math.abs(newX) < minCheck then
|
|
newX = 0.0
|
|
end
|
|
|
|
if math.abs(newY or 0) < minCheck then
|
|
newY = 0.0
|
|
end
|
|
|
|
xCoord = newX
|
|
yCoord = newY
|
|
else
|
|
local newValue = yCoord + lStep
|
|
|
|
if math.abs(newValue) < minCheck then
|
|
newValue = 0.0
|
|
end
|
|
|
|
yCoord = newValue
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 33) then -- s
|
|
change = true
|
|
|
|
if alignMovementWithCamera then
|
|
local newX, newY = getRelativePos(vec2(xCoord, yCoord), vec2(xCoord, yCoord - lStep), GetGameplayCamRot(2).z)
|
|
|
|
if math.abs(newX) < minCheck then
|
|
newX = 0.0
|
|
end
|
|
|
|
if math.abs(newY or 0) < minCheck then
|
|
newY = 0.0
|
|
end
|
|
|
|
xCoord = newX
|
|
yCoord = newY
|
|
else
|
|
local newValue = yCoord - lStep
|
|
|
|
if math.abs(newValue) < minCheck then
|
|
newValue = 0.0
|
|
end
|
|
|
|
yCoord = newValue
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 35) then -- d
|
|
change = true
|
|
|
|
if alignMovementWithCamera then
|
|
local newX, newY = getRelativePos(vec2(xCoord, yCoord), vec2(xCoord + lStep, yCoord), GetGameplayCamRot(2).z)
|
|
|
|
if math.abs(newX) < minCheck then
|
|
newX = 0.0
|
|
end
|
|
|
|
if math.abs(newY or 0) < minCheck then
|
|
newY = 0.0
|
|
end
|
|
|
|
xCoord = newX
|
|
yCoord = newY
|
|
else
|
|
local newValue = xCoord + lStep
|
|
|
|
if math.abs(newValue) < minCheck then
|
|
newValue = 0.0
|
|
end
|
|
|
|
xCoord = newValue
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 34) then -- a
|
|
change = true
|
|
|
|
if alignMovementWithCamera then
|
|
local newX, newY = getRelativePos(vec2(xCoord, yCoord), vec2(xCoord - lStep, yCoord), GetGameplayCamRot(2).z)
|
|
|
|
if math.abs(newX) < minCheck then
|
|
newX = 0.0
|
|
end
|
|
|
|
if math.abs(newY or 0) < minCheck then
|
|
newY = 0.0
|
|
end
|
|
|
|
xCoord = newX
|
|
yCoord = newY
|
|
else
|
|
local newValue = xCoord - lStep
|
|
|
|
if math.abs(newValue) < minCheck then
|
|
newValue = 0.0
|
|
end
|
|
|
|
xCoord = newValue
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 45) then -- r
|
|
change = true
|
|
local newValue = zCoord + lStep
|
|
|
|
if math.abs(newValue) < minCheck then
|
|
newValue = 0.0
|
|
end
|
|
|
|
zCoord = newValue
|
|
elseif IsDisabledControlJustReleased(0, 23) then -- f
|
|
change = true
|
|
local newValue = zCoord - lStep
|
|
|
|
if math.abs(newValue) < minCheck then
|
|
newValue = 0.0
|
|
end
|
|
|
|
zCoord = newValue
|
|
elseif IsDisabledControlJustReleased(0, 38) then -- e
|
|
change = true
|
|
heading -= rStep
|
|
|
|
if heading < 0 then
|
|
heading += 360
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 44) then -- q
|
|
change = true
|
|
heading += rStep
|
|
|
|
if heading >= 360 then
|
|
heading -= 360
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 47) then -- g
|
|
change = true
|
|
|
|
if displayMode == #displayModes then
|
|
displayMode = 1
|
|
else
|
|
displayMode += 1
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 26) then -- c
|
|
change = true
|
|
alignMovementWithCamera = not alignMovementWithCamera
|
|
elseif IsDisabledControlJustReleased(0, 22) then -- space
|
|
change = true
|
|
|
|
if zoneType == 'poly' then
|
|
points[#points + 1] = vec2(xCoord, yCoord)
|
|
end
|
|
|
|
coords = GetEntityCoords(cache.ped)
|
|
xCoord = round(coords.x)
|
|
yCoord = round(coords.y)
|
|
elseif IsDisabledControlJustReleased(0, 201) then -- enter
|
|
closeCreator()
|
|
elseif IsDisabledControlJustReleased(0, 194) then -- backspace
|
|
change = true
|
|
|
|
if zoneType == 'poly' and #points > 0 then
|
|
xCoord = points[#points].x
|
|
yCoord = points[#points].y
|
|
|
|
points[#points] = nil
|
|
end
|
|
elseif IsDisabledControlJustReleased(0, 200) then -- esc
|
|
SetPauseMenuActive(false)
|
|
closeCreator(true)
|
|
end
|
|
|
|
if change then
|
|
updateText()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
RegisterCommand('zone', function(source, args, rawCommand)
|
|
if args[1] ~= 'poly' and args[1] ~= 'box' and args[1] ~= 'sphere' then
|
|
lib.notify({title = 'Invalid zone type', type = 'error'})
|
|
return
|
|
end
|
|
|
|
if creatorActive then
|
|
lib.notify({title = 'Already creating a zone', type = 'error'})
|
|
return
|
|
end
|
|
|
|
local useLast = args[2] and not useLastZoneFalsyInputs[args[2]]
|
|
|
|
if useLast then
|
|
if args[1] == 'poly' then
|
|
lib.notify({title = 'Cannot duplicate a poly zone', type = 'error'})
|
|
useLast = false
|
|
elseif not lastZone[args[1]] then
|
|
lib.notify({title = ('No previous %s zone to duplicate'):format(args[1]), type = 'error'})
|
|
useLast = false
|
|
end
|
|
end
|
|
|
|
startCreator(args[1], useLast)
|
|
end, true)
|
|
|
|
CreateThread(function()
|
|
Wait(1000)
|
|
TriggerEvent('chat:addSuggestion', '/zone', 'Starts creation of the specified zone', {
|
|
{ name = 'zoneType', help = 'poly, box, sphere' },
|
|
{ name = 'useLast', help = 'duplicates the last created zone of the specified type (box and sphere only, optional)' }
|
|
})
|
|
end)
|