-- Grid-based point system Point = {} local ActivePoints = {} local GridCells = {} local LoopStarted = false local insidePoints = {} local data = {} -- 👈 Move this outside the function -- Grid configuration local GRID_SIZE = 500.0 -- Size of each grid cell local CELL_BUFFER = 1 -- Number of adjacent cells to check -- Consider adding these optimizations local ADAPTIVE_WAIT = true -- Adjust wait time based on player speed ---This is an internal function, do not call this externally function Point.GetCellKey(coords) local cellX = math.floor(coords.x / GRID_SIZE) local cellY = math.floor(coords.y / GRID_SIZE) return cellX .. ":" .. cellY end ---This is an internal function, do not call this externally function Point.RegisterInGrid(point) local cellKey = Point.GetCellKey(point.coords) -- Initialize cell if it doesn't exist GridCells[cellKey] = GridCells[cellKey] or { points = {}, count = 0 } -- Add point to cell GridCells[cellKey].points[point.id] = point GridCells[cellKey].count = GridCells[cellKey].count + 1 -- Store cell reference in point point.cellKey = cellKey end ---This is an internal function, do not call this externally function Point.UpdateInGrid(point, oldCellKey) -- Remove from old cell if cell key changed if oldCellKey and oldCellKey ~= point.cellKey then if GridCells[oldCellKey] and GridCells[oldCellKey].points[point.id] then GridCells[oldCellKey].points[point.id] = nil GridCells[oldCellKey].count = GridCells[oldCellKey].count - 1 -- Clean up empty cells if GridCells[oldCellKey].count <= 0 then GridCells[oldCellKey] = nil end end -- Add to new cell Point.RegisterInGrid(point) end end ---Gets nearby cells based on the coords ---@param coords table ---@return table function Point.GetNearbyCells(coords) local cellX = math.floor(coords.x / GRID_SIZE) local cellY = math.floor(coords.y / GRID_SIZE) local nearbyCells = {} -- Get current and adjacent cells for x = cellX - CELL_BUFFER, cellX + CELL_BUFFER do for y = cellY - CELL_BUFFER, cellY + CELL_BUFFER do local key = x .. ":" .. y if GridCells[key] then table.insert(nearbyCells, key) end end end return nearbyCells end ---Returns all points in the same cell as the given point ---This will require you to pass the point object ---@param point table ---@return table function Point.CheckPointsInSameCell(point) local cellKey = point.cellKey if not GridCells[cellKey] then return {} end local nearbyPoints = {} for id, otherPoint in pairs(GridCells[cellKey].points) do if id ~= point.id then local distance = #(point.coords - otherPoint.coords) if distance < (point.distance + otherPoint.distance) then nearbyPoints[id] = otherPoint end end end return nearbyPoints end ---Internal function that starts the loop. Do not call this function directly. function Point.StartLoop() if LoopStarted then return false end LoopStarted = true -- Remove the "local data = {}" line from here CreateThread(function() while LoopStarted do local playerPed = PlayerPedId() while playerPed == -1 do Wait(100) playerPed = PlayerPedId() end local playerCoords = GetEntityCoords(playerPed) local targetsExist = false local playerCellKey = Point.GetCellKey(playerCoords) local nearbyCells = Point.GetNearbyCells(playerCoords) local playerSpeed = GetEntitySpeed(playerPed) local maxWeight = 1000 local waitTime = ADAPTIVE_WAIT and math.max(maxWeight/10, maxWeight - playerSpeed * maxWeight/10) or maxWeight -- Process only points in nearby cells for _, cellKey in ipairs(nearbyCells) do if GridCells[cellKey] then for id, point in pairs(GridCells[cellKey].points) do targetsExist = true -- Update entity position if needed local oldCellKey = point.cellKey if point.isEntity then point.coords = GetEntityCoords(point.target) point.cellKey = Point.GetCellKey(point.coords) Point.UpdateInGrid(point, oldCellKey) end local coords = point.coords and vector3(point.coords.x, point.coords.y, point.coords.z) or vector3(0, 0, 0) local distance = #(playerCoords - coords) --local distance = #(playerCoords - point.coords) -- Check if player entered/exited the point if distance < point.distance then if not point.inside then point.inside = true data[point.id] = data[point.id] or point.args or {} data[point.id] = point.onEnter(point, data[point.id]) insidePoints[point.id] = point end -- Modified main loop exit handler elseif point.inside then point.inside = false data[point.id] = data[point.id] or point.args or {} local result = point.onExit(point, data[point.id]) data[point.id] = result ~= nil and result or data[point.id] -- ← Use consistent fallback point.args = data[point.id] -- ← Update point.args to match data[point.id] insidePoints[point.id] = nil end if point.onNearby then point.onNearby(GridCells[cellKey]?.points, waitTime) end end end end for id, insidepoint in pairs(insidePoints) do local pos = insidepoint.coords and vector3(insidepoint.coords.x, insidepoint.coords.y, insidepoint.coords.z) or vector3(0, 0, 0) local dist = #(playerCoords - pos) if dist > insidepoint.distance then insidepoint.inside = false data[insidepoint.id] = data[insidepoint.id] or insidepoint.args or {} local result = insidepoint.onExit(insidepoint, data[insidepoint.id]) data[insidepoint.id] = result ~= nil and result or data[insidepoint.id] insidepoint.args = data[insidepoint.id] -- ← Keep data in sync insidePoints[insidepoint.id] = nil end end Wait(waitTime) -- Faster updates when moving quickly end end) return true end ---Create a point based on a vector or entityID ---@param id string ---@param target number || vector3 ---@param distance number ---@param _onEnter function ---@param _onExit function ---@param _onNearby function ---@param data self ---@return table function Point.Register(id, target, distance, args, _onEnter, _onExit, _onNearby) local isEntity = type(target) == "number" local coords = isEntity and GetEntityCoords(target) or target local self = {} self.id = id self.target = target -- Store entity ID or Vector3 self.isEntity = isEntity self.coords = coords self.distance = distance self.onEnter = _onEnter or function() end self.onExit = _onExit or function() end self.onNearby = _onNearby or function() end self.inside = false -- Track if player is inside self.args = args or {} ActivePoints[id] = self Point.RegisterInGrid(self) Point.StartLoop() return self end ---Remove an exsisting point by its id ---@param id string function Point.Remove(id) local point = ActivePoints[id] if point then local cellKey = point.cellKey if GridCells[cellKey] and GridCells[cellKey].points[id] then GridCells[cellKey].points[id] = nil GridCells[cellKey].count = GridCells[cellKey].count - 1 if GridCells[cellKey].count <= 0 then GridCells[cellKey] = nil end end ActivePoints[id] = nil end end ---Returnes a point by its id ---@param id string ---@return table function Point.Get(id) return ActivePoints[id] end function Point.UpdateCoords(id, coords) local point = ActivePoints[id] if point then point.coords = coords local oldCellKey = point.cellKey point.cellKey = Point.GetCellKey(coords) Point.UpdateInGrid(point, oldCellKey) end end ---Returns all points ---@return table function Point.GetAll() return ActivePoints end return Point