601 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			601 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| eventPrefix = '__PolyZone__:'
 | |
| PolyZone = {}
 | |
| 
 | |
| local defaultColorWalls = {0, 255, 0}
 | |
| local defaultColorOutline = {255, 0, 0}
 | |
| local defaultColorGrid = {255, 255, 255}
 | |
| 
 | |
| -- 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
 | |
| 
 | |
| local function _wn_inner_loop(p0, p1, p2, wn)
 | |
|   local p2y = p2.y
 | |
|   if (p0.y <= p2y) then
 | |
|     if (p1.y > p2y) then
 | |
|       if (_isLeft(p0, p1, p2) > 0) then
 | |
|         return wn + 1
 | |
|       end
 | |
|     end
 | |
|   else
 | |
|     if (p1.y <= p2y) then
 | |
|       if (_isLeft(p0, p1, p2) < 0) then
 | |
|         return wn - 1
 | |
|       end
 | |
|     end
 | |
|   end
 | |
|   return wn
 | |
| 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
 | |
| 
 | |
| -- Winding Number Algorithm - http://geomalgorithms.com/a03-_inclusion.html
 | |
| local function _windingNumber(point, poly)
 | |
|   local wn = 0 -- winding number counter
 | |
| 
 | |
|   -- loop through all edges of the polygon
 | |
|   for i = 1, #poly - 1 do
 | |
|     wn = _wn_inner_loop(poly[i], poly[i + 1], point, wn)
 | |
|   end
 | |
|   -- test last point to first point, completing the polygon
 | |
|   wn = _wn_inner_loop(poly[#poly], poly[1], point, wn)
 | |
| 
 | |
|   -- the point is outside only when this winding number wn===0, otherwise it's inside
 | |
|   return wn ~= 0
 | |
| end
 | |
| 
 | |
| -- Detects intersection between two lines
 | |
| local function _isIntersecting(a, b, c, d)
 | |
|   -- Store calculations in local variables for performance
 | |
|   local ax_minus_cx = a.x - c.x
 | |
|   local bx_minus_ax = b.x - a.x
 | |
|   local dx_minus_cx = d.x - c.x
 | |
|   local ay_minus_cy = a.y - c.y
 | |
|   local by_minus_ay = b.y - a.y
 | |
|   local dy_minus_cy = d.y - c.y
 | |
|   local denominator = ((bx_minus_ax) * (dy_minus_cy)) - ((by_minus_ay) * (dx_minus_cx))
 | |
|   local numerator1 = ((ay_minus_cy) * (dx_minus_cx)) - ((ax_minus_cx) * (dy_minus_cy))
 | |
|   local numerator2 = ((ay_minus_cy) * (bx_minus_ax)) - ((ax_minus_cx) * (by_minus_ay))
 | |
| 
 | |
|   -- Detect coincident lines
 | |
|   if denominator == 0 then return numerator1 == 0 and numerator2 == 0 end
 | |
| 
 | |
|   local r = numerator1 / denominator
 | |
|   local s = numerator2 / denominator
 | |
| 
 | |
|   return (r >= 0 and r <= 1) and (s >= 0 and s <= 1)
 | |
| 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
 | |
| 
 | |
| 
 | |
| -- Debug drawing functions
 | |
| function _drawWall(p1, p2, minZ, maxZ, r, g, b, a)
 | |
|   local bottomLeft = vector3(p1.x, p1.y, minZ)
 | |
|   local topLeft = vector3(p1.x, p1.y, maxZ)
 | |
|   local bottomRight = vector3(p2.x, p2.y, minZ)
 | |
|   local topRight = vector3(p2.x, p2.y, maxZ)
 | |
| 
 | |
|   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(forceDraw)
 | |
|   if not forceDraw and not self.debugPoly and not self.debugGrid then return end
 | |
|   
 | |
|   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
 | |
| 
 | |
|   local points = self.points
 | |
|   for i=1, #points do
 | |
|     local point = self:TransformPoint(points[i])
 | |
|     DrawLine(point.x, point.y, minZ, point.x, point.y, maxZ, oR, oG, oB, 164)
 | |
| 
 | |
|     if i < #points then
 | |
|       local p2 = self:TransformPoint(points[i+1])
 | |
|       DrawLine(point.x, point.y, maxZ, p2.x, p2.y, maxZ, oR, oG, oB, 184)
 | |
|       _drawWall(point, p2, minZ, maxZ, wR, wG, wB, 48)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   if #points > 2 then
 | |
|     local firstPoint = self:TransformPoint(points[1])
 | |
|     local lastPoint = self:TransformPoint(points[#points])
 | |
|     DrawLine(firstPoint.x, firstPoint.y, maxZ, lastPoint.x, lastPoint.y, maxZ, oR, oG, oB, 184)
 | |
|     _drawWall(firstPoint, lastPoint, minZ, maxZ, wR, wG, wB, 48)
 | |
|   end
 | |
| end
 | |
| 
 | |
| function PolyZone.drawPoly(poly, forceDraw)
 | |
|   PolyZone.draw(poly, forceDraw)
 | |
| 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
 | |
| 
 | |
|   local lines = poly.lines
 | |
|   local color = poly.debugColors.grid or defaultColorGrid
 | |
|   local r, g, b = color[1], color[2], color[3]
 | |
|   for i=1, #lines do
 | |
|     local line = lines[i]
 | |
|     local min = line.min
 | |
|     local max = line.max
 | |
|     DrawLine(min.x + 0.0, min.y + 0.0, maxZ + 0.0, max.x + 0.0, max.y + 0.0, maxZ + 0.0, r, g, b, 196)
 | |
|   end
 | |
| end
 | |
| 
 | |
| 
 | |
| local function _pointInPoly(point, poly)
 | |
|   local x = point.x
 | |
|   local y = point.y
 | |
|   local min = poly.min
 | |
|   local minX = min.x
 | |
|   local minY = min.y
 | |
|   local max = poly.max
 | |
| 
 | |
|   -- Checks if point is within the polygon's bounding box
 | |
|   if x < minX or
 | |
|      x > max.x or
 | |
|      y < minY or
 | |
|      y > max.y then
 | |
|       return false
 | |
|   end
 | |
| 
 | |
|   -- Checks if point is within the polygon's height bounds
 | |
|   local minZ = poly.minZ
 | |
|   local maxZ = poly.maxZ
 | |
|   local z = point.z
 | |
|   if (minZ and z < minZ) or (maxZ and z > maxZ) then
 | |
|     return false
 | |
|   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
 | |
| 
 | |
|   return _windingNumber(point, poly.points)
 | |
| end
 | |
| 
 | |
| 
 | |
| -- Grid creation functions
 | |
| -- Calculates the points of the rectangle that make up the grid cell at grid position (cellX, cellY)
 | |
| local function _calculateGridCellPoints(cellX, cellY, poly)
 | |
|   local gridCellWidth = poly.gridCellWidth
 | |
|   local gridCellHeight = poly.gridCellHeight
 | |
|   local min = poly.min
 | |
|   -- min added to initial point, in order to shift the grid cells to the poly's starting position
 | |
|   local x = cellX * gridCellWidth + min.x
 | |
|   local y = cellY * gridCellHeight + min.y
 | |
|   return {
 | |
|     vector2(x, y),
 | |
|     vector2(x + gridCellWidth, y),
 | |
|     vector2(x + gridCellWidth, y + gridCellHeight),
 | |
|     vector2(x, y + gridCellHeight),
 | |
|     vector2(x, y)
 | |
|   }
 | |
| end
 | |
| 
 | |
| 
 | |
| function _isGridCellInsidePoly(cellX, cellY, poly)
 | |
|   gridCellPoints = _calculateGridCellPoints(cellX, cellY, poly)
 | |
|   local polyPoints = {table.unpack(poly.points)}
 | |
|   -- Connect the polygon to its starting point
 | |
|   polyPoints[#polyPoints + 1] = polyPoints[1]
 | |
| 
 | |
|   -- 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
 | |
| 
 | |
| 
 | |
| local function _calculateLinesForDrawingGrid(poly)
 | |
|   local lines = {}
 | |
|   for x, tbl in pairs(poly.gridXPoints) do
 | |
|     local yValues = {}
 | |
|     -- Turn dict/set of values into array
 | |
|     for y, _ in pairs(tbl) do yValues[#yValues + 1] = y end
 | |
|     if #yValues >= 2 then
 | |
|       table.sort(yValues)
 | |
|       local minY = yValues[1]
 | |
|       local lastY = yValues[1]
 | |
|       for i=1, #yValues do
 | |
|         local y = yValues[i]
 | |
|         -- Checks for breaks in the grid. If the distance between the last value and the current one
 | |
|         -- is greater than the size of a grid cell, that means the line between them must go outside the polygon.
 | |
|         -- Therefore, a line must be created between minY and the lastY, and a new line started at the current y
 | |
|         if y - lastY > poly.gridCellHeight + 0.01 then
 | |
|           lines[#lines+1] = {min=vector2(x, minY), max=vector2(x, lastY)}
 | |
|           minY = y
 | |
|         elseif i == #yValues then
 | |
|           -- If at the last point, create a line between minY and the last point
 | |
|           lines[#lines+1] = {min=vector2(x, minY), max=vector2(x, y)}
 | |
|         end
 | |
|         lastY = y
 | |
|       end
 | |
|     end
 | |
|   end
 | |
|   -- Setting nil to allow the GC to clear it out of memory, since we no longer need this
 | |
|   poly.gridXPoints = nil
 | |
| 
 | |
|   -- Same as above, but for gridYPoints instead of gridXPoints
 | |
|   for y, tbl in pairs(poly.gridYPoints) do
 | |
|     local xValues = {}
 | |
|     for x, _ in pairs(tbl) do xValues[#xValues + 1] = x end
 | |
|     if #xValues >= 2 then
 | |
|       table.sort(xValues)
 | |
|       local minX = xValues[1]
 | |
|       local lastX = xValues[1]
 | |
|       for i=1, #xValues do
 | |
|         local x = xValues[i]
 | |
|         if x - lastX > poly.gridCellWidth + 0.01 then
 | |
|           lines[#lines+1] = {min=vector2(minX, y), max=vector2(lastX, y)}
 | |
|           minX = x
 | |
|         elseif i == #xValues then
 | |
|           lines[#lines+1] = {min=vector2(minX, y), max=vector2(x, y)}
 | |
|         end
 | |
|         lastX = x
 | |
|       end
 | |
|     end
 | |
|   end
 | |
|   poly.gridYPoints = nil
 | |
|   return lines
 | |
| 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
 | |
| 
 | |
|   poly.boundingRadius = math.sqrt(poly.size.y * poly.size.y + poly.size.x * poly.size.x) / 2
 | |
| 
 | |
|   if poly.useGrid and not poly.lazyGrid then
 | |
|     if options.debugGrid then
 | |
|       poly.gridXPoints = {}
 | |
|       poly.gridYPoints = {}
 | |
|       poly.lines = {}
 | |
|     end
 | |
|     _createGrid(poly, options)
 | |
|   elseif poly.useGrid then
 | |
|     local isInside = {}
 | |
|     for y=1, poly.gridDivisions do
 | |
|       isInside[y] = {}
 | |
|     end
 | |
|     poly.grid = isInside
 | |
|     poly.gridCellWidth = poly.size.x / poly.gridDivisions
 | |
|     poly.gridCellHeight = poly.size.y / poly.gridDivisions
 | |
|   end
 | |
| end
 | |
| 
 | |
| 
 | |
| local function _initDebug(poly, options)
 | |
|   if options.debugBlip then poly:addDebugBlip() end
 | |
|   local debugEnabled = options.debugPoly or options.debugGrid
 | |
|   if not debugEnabled then
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   Citizen.CreateThread(function()
 | |
|     while not poly.destroyed do
 | |
|       poly:draw(false)
 | |
|       if options.debugGrid and poly.lines then
 | |
|         _drawGrid(poly)
 | |
|       end
 | |
|       Citizen.Wait(0)
 | |
|     end
 | |
|   end)
 | |
| end
 | |
| 
 | |
| function PolyZone:new(points, options)
 | |
|   if not points then
 | |
|     print("[PolyZone] Error: Passed nil points table to PolyZone:Create() {name=" .. options.name .. "}")
 | |
|     return
 | |
|   end
 | |
|   if #points < 3 then
 | |
|     print("[PolyZone] Warning: Passed points table with less than 3 points to PolyZone:Create() {name=" .. options.name .. "}")
 | |
|   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:Create(points, options)
 | |
|   local poly = PolyZone:new(points, options)
 | |
|   _initDebug(poly, options)
 | |
|   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
 | |
| 
 | |
|   return _pointInPoly(point, self)
 | |
| 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
 | |
| 
 | |
| function PolyZone:onPointInOut(getPointCb, onPointInOutCb, waitInMS)
 | |
|   -- Localize the waitInMS value for performance reasons (default of 500 ms)
 | |
|   local _waitInMS = 500
 | |
|   if waitInMS ~= nil then _waitInMS = waitInMS 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:onPlayerInOut(onPointInOutCb, waitInMS)
 | |
|   self:onPointInOut(PolyZone.getPlayerPosition, onPointInOutCb, waitInMS)
 | |
| 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
 | 
