Fivem Code
Fivem Code
PolyZone = {}
-- Utility functions
local abs = math.abs
local function _isLeft(p0, p1, p2)
local p0x = p0.x
local p0y = p0.y
return ((p1.x - p0x) * (p2.y - p0y)) - ((p2.x - p0x) * (p1.y - p0y))
end
function addBlip(pos)
local blip = AddBlipForCoord(pos.x, pos.y, 0.0)
SetBlipColour(blip, 7)
SetBlipDisplay(blip, 8)
SetBlipScale(blip, 1.0)
SetBlipAsShortRange(blip, true)
return blip
end
function clearTbl(tbl)
-- Only works with contiguous (array-like) tables
if tbl == nil then return end
for i=1, #tbl do
tbl[i] = nil
end
return tbl
end
function copyTbl(tbl)
-- Only a shallow copy, and only works with contiguous (array-like) tables
if tbl == nil then return end
local ret = {}
for i=1, #tbl do
ret[i] = tbl[i]
end
return ret
end
-- the point is outside only when this winding number wn===0, otherwise it's
inside
return wn ~= 0
end
-- https://rosettacode.org/wiki/Shoelace_formula_for_polygonal_area#Lua
local function _calculatePolygonArea(points)
local function det2(i,j)
return points[i].x*points[j].y-points[j].x*points[i].y
end
local sum = #points>2 and det2(#points,1) or 0
for i=1,#points-1 do sum = sum + det2(i,i+1)end
return abs(0.5 * sum)
end
DrawPoly(bottomLeft,topLeft,bottomRight,r,g,b,a)
DrawPoly(topLeft,topRight,bottomRight,r,g,b,a)
DrawPoly(bottomRight,topRight,topLeft,r,g,b,a)
DrawPoly(bottomRight,topLeft,bottomLeft,r,g,b,a)
end
function PolyZone:TransformPoint(point)
-- No point transform necessary for regular PolyZones, unlike zones like Entity
Zones, whose points can be rotated and offset
return point
end
function PolyZone:draw()
local zDrawDist = 45.0
local oColor = self.debugColors.outline or defaultColorOutline
local oR, oG, oB = oColor[1], oColor[2], oColor[3]
local wColor = self.debugColors.walls or defaultColorWalls
local wR, wG, wB = wColor[1], wColor[2], wColor[3]
local plyPed = PlayerPedId()
local plyPos = GetEntityCoords(plyPed)
local minZ = self.minZ or plyPos.z - zDrawDist
local maxZ = self.maxZ or plyPos.z + zDrawDist
function PolyZone.drawPoly(poly)
PolyZone.draw(poly)
end
-- Debug drawing all grid cells that are completly within the polygon
local function _drawGrid(poly)
local minZ = poly.minZ
local maxZ = poly.maxZ
if not minZ or not maxZ then
local plyPed = PlayerPedId()
local plyPos = GetEntityCoords(plyPed)
minZ = plyPos.z - 46.0
maxZ = plyPos.z - 45.0
end
-- Returns true if the grid cell associated with the point is entirely inside the
poly
local grid = poly.grid
if grid then
local gridDivisions = poly.gridDivisions
local size = poly.size
local gridPosX = x - minX
local gridPosY = y - minY
local gridCellX = (gridPosX * gridDivisions) // size.x
local gridCellY = (gridPosY * gridDivisions) // size.y
local gridCellValue = grid[gridCellY + 1][gridCellX + 1]
if gridCellValue == nil and poly.lazyGrid then
gridCellValue = _isGridCellInsidePoly(gridCellX, gridCellY, poly)
grid[gridCellY + 1][gridCellX + 1] = gridCellValue
end
if gridCellValue then return true end
end
-- If none of the points of the grid cell are in the polygon, the grid cell can't
be in it
local isOnePointInPoly = false
for i=1, #gridCellPoints - 1 do
local cellPoint = gridCellPoints[i]
local x = cellPoint.x
local y = cellPoint.y
if _windingNumber(cellPoint, poly.points) then
isOnePointInPoly = true
-- If we are drawing the grid (poly.lines ~= nil), we need to go through all
the points,
-- and therefore can't break out of the loop early
if poly.lines then
if not poly.gridXPoints[x] then poly.gridXPoints[x] = {} end
if not poly.gridYPoints[y] then poly.gridYPoints[y] = {} end
poly.gridXPoints[x][y] = true
poly.gridYPoints[y][x] = true
else break end
end
end
if isOnePointInPoly == false then
return false
end
-- If any of the grid cell's lines intersects with any of the polygon's lines
-- then the grid cell is not completely within the poly
for i=1, #gridCellPoints - 1 do
local gridCellP1 = gridCellPoints[i]
local gridCellP2 = gridCellPoints[i+1]
for j=1, #polyPoints - 1 do
if _isIntersecting(gridCellP1, gridCellP2, polyPoints[j], polyPoints[j+1])
then
return false
end
end
end
return true
end
-- Calculate for each grid cell whether it is entirely inside the polygon, and
store if true
local function _createGrid(poly, options)
poly.gridArea = 0.0
poly.gridCellWidth = poly.size.x / poly.gridDivisions
poly.gridCellHeight = poly.size.y / poly.gridDivisions
Citizen.CreateThread(function()
-- Calculate all grid cells that are entirely inside the polygon
local isInside = {}
local gridCellArea = poly.gridCellWidth * poly.gridCellHeight
for y=1, poly.gridDivisions do
Citizen.Wait(0)
isInside[y] = {}
for x=1, poly.gridDivisions do
if _isGridCellInsidePoly(x-1, y-1, poly) then
poly.gridArea = poly.gridArea + gridCellArea
isInside[y][x] = true
end
end
end
poly.grid = isInside
poly.gridCoverage = poly.gridArea / poly.area
-- A lot of memory is used by this pre-calc. Force a gc collect after to clear
it out
collectgarbage("collect")
if options.debugGrid then
local coverage = string.format("%.2f", poly.gridCoverage * 100)
print("[PolyZone] Debug: Grid Coverage at " .. coverage .. "% with " ..
poly.gridDivisions
.. " divisions. Optimal coverage for memory usage and startup time is 80-
90%")
Citizen.CreateThread(function()
poly.lines = _calculateLinesForDrawingGrid(poly)
-- A lot of memory is used by this pre-calc. Force a gc collect after to
clear it out
collectgarbage("collect")
end)
end
end)
end
-- Initialization functions
local function _calculatePoly(poly, options)
if not poly.min or not poly.max or not poly.size or not poly.center or not
poly.area then
local minX, minY = math.maxinteger, math.maxinteger
local maxX, maxY = math.mininteger, math.mininteger
for _, p in ipairs(poly.points) do
minX = math.min(minX, p.x)
minY = math.min(minY, p.y)
maxX = math.max(maxX, p.x)
maxY = math.max(maxY, p.y)
end
poly.min = vector2(minX, minY)
poly.max = vector2(maxX, maxY)
poly.size = poly.max - poly.min
poly.center = (poly.max + poly.min) / 2
poly.area = _calculatePolygonArea(poly.points)
end
Citizen.CreateThread(function()
while not poly.destroyed do
poly:draw()
if options.debugGrid and poly.lines then
_drawGrid(poly)
end
Citizen.Wait(0)
end
end)
end
options = options or {}
local useGrid = options.useGrid
if useGrid == nil then useGrid = true end
local lazyGrid = options.lazyGrid
if lazyGrid == nil then lazyGrid = true end
local poly = {
name = tostring(options.name) or nil,
points = points,
center = options.center,
size = options.size,
max = options.max,
min = options.min,
area = options.area,
minZ = tonumber(options.minZ) or nil,
maxZ = tonumber(options.maxZ) or nil,
useGrid = useGrid,
lazyGrid = lazyGrid,
gridDivisions = tonumber(options.gridDivisions) or 30,
debugColors = options.debugColors or {},
debugPoly = options.debugPoly or false,
debugGrid = options.debugGrid or false,
data = options.data or {},
isPolyZone = true,
}
if poly.debugGrid then poly.lazyGrid = false end
_calculatePoly(poly, options)
setmetatable(poly, self)
self.__index = self
return poly
end
function PolyZone:isPointInside(point)
if self.destroyed then
print("[PolyZone] Warning: Called isPointInside on destroyed zone {name=" ..
self.name .. "}")
return false
end
function PolyZone:destroy()
self.destroyed = true
if self.debugPoly or self.debugGrid then
print("[PolyZone] Debug: Destroying zone {name=" .. self.name .. "}")
end
end
-- Helper functions
function PolyZone.getPlayerPosition()
return GetEntityCoords(PlayerPedId())
end
HeadBone = 0x796e;
function PolyZone.getPlayerHeadPosition()
return GetPedBoneCoords(PlayerPedId(), HeadBone);
end
function PolyZone.ensureMetatable(zone)
if zone.isComboZone then
setmetatable(zone, ComboZone)
elseif zone.isEntityZone then
setmetatable(zone, EntityZone)
elseif zone.isBoxZone then
setmetatable(zone, BoxZone)
elseif zone.isCircleZone then
setmetatable(zone, CircleZone)
elseif zone.isPolyZone then
setmetatable(zone, PolyZone)
end
end
Citizen.CreateThread(function()
local isInside = false
while not self.destroyed do
if not self.paused then
local point = getPointCb()
local newIsInside = self:isPointInside(point)
if newIsInside ~= isInside then
onPointInOutCb(newIsInside, point)
isInside = newIsInside
end
end
Citizen.Wait(_waitInMS)
end
end)
end
function PolyZone:addEvent(eventName)
if self.events == nil then self.events = {} end
local internalEventName = eventPrefix .. eventName
RegisterNetEvent(internalEventName)
self.events[eventName] = AddEventHandler(internalEventName, function (...)
if self:isPointInside(PolyZone.getPlayerPosition()) then
TriggerEvent(eventName, ...)
end
end)
end
function PolyZone:removeEvent(eventName)
if self.events and self.events[eventName] then
RemoveEventHandler(self.events[eventName])
self.events[eventName] = nil
end
end
function PolyZone:addDebugBlip()
return addBlip(self.center or self:getBoundingBoxCenter())
end
function PolyZone:setPaused(paused)
self.paused = paused
end
function PolyZone:isPaused()
return self.paused
end
function PolyZone:getBoundingBoxMin()
return self.min
end
function PolyZone:getBoundingBoxMax()
return self.max
end
function PolyZone:getBoundingBoxSize()
return self.size
end
function PolyZone:getBoundingBoxCenter()
return self.center
end
4
0
5
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
93
96
97
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
6
6
111
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
31
1
31
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
93
106
0
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
94
103
101
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
111
8
0
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
5
5
111
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
<Item>
<indices content="char_array">
51
0
52
156
0
0
</indices>
<liveries>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
<Item value="false"/>
</liveries>
</Item>
</colors>
<kits>
<Item>0_suv_modkit</Item>
</kits>
<windowsWithExposedEdges/>
<plateProbabilities>
<Probabilities>
<Item>
<Name>Standard White</Name>
<Value value="20"/>
</Item>
<Item>
<Name>Blue Plate</Name>
<Value value="30"/>
</Item>
<Item>
<Name>White Plate 2</Name>
<Value value="50"/>
</Item>
</Probabilities>
</plateProbabilities>
<lightSettings value="1"/>
<sirenSettings value="0"/>
</Item>
</variationData>
</CVehicleModelInfoVariation>