Laser = {} local ShapeTestRay = StartShapeTestRay or StartExpensiveSynchronousShapeTestLosProbe local function RayCast(origin, destination, flags) local ray = ShapeTestRay(origin.x, origin.y, origin.z, destination.x, destination.y, destination.z, flags, nil, 0) return GetShapeTestResult(ray) end local function randomFloat(lower, greater) return lower + math.random() * (greater - lower); end local function drawLaser(origin, destination, r, g, b, a) DrawLine(origin, destination, r, g, b, a) end -- Calculates the linearly interpreted point along the line from "fromPoint" to "toPoint" -- as a percentage between deltaTime and travelTimeBetweenTargets local function calculateCurrentPoint(fromPoint, toPoint, deltaTime, travelTimeBetweenTargets) local desiredDirection = toPoint - fromPoint local desiredDirectionDist = #desiredDirection local percentOfTravelTime = deltaTime / (travelTimeBetweenTargets * 1000) local distance = math.min(desiredDirectionDist * percentOfTravelTime, desiredDirectionDist) return fromPoint + (norm(desiredDirection) * distance) end local function getNextToIndex(fromIndex, targetPointCount, randomTargetSelection) local toIndex = fromIndex if randomTargetSelection then while toIndex == fromIndex do toIndex = math.random(1, targetPointCount) end else toIndex = (fromIndex % targetPointCount) + 1 end return toIndex end function Laser.new(originPoint, targetPoints, options) local self = {} options = options or {} assert(options.color == nil or #options.color == 4, "Laser-farven skal være af 4 værdier {r, g, b, a}") self.name = options.name local visible = true local moving = true local active = false local r, g, b, a = 255, 0, 0, 255 if options.color then r, g, b, a = table.unpack(options.color) end local extensionEnabled = true if options.extensionEnabled ~= nil then extensionEnabled = options.extensionEnabled end local randomTargetSelection = true if options.randomTargetSelection ~= nil then randomTargetSelection = options.randomTargetSelection end local maxDistance = options.maxDistance or 20.0 local travelTimeBetweenTargets = options.travelTimeBetweenTargets or {} local minTravelTimeBetweenTargets = travelTimeBetweenTargets[1] or 1.0 local maxTravelTimeBetweenTargets = travelTimeBetweenTargets[2] or 1.0 local waitTimeAtTargets = options.waitTimeAtTargets or {} local minWaitTimeAtTargets = waitTimeAtTargets ~= nil and waitTimeAtTargets[1] or 0.0 local maxWaitTimeAtTargets = waitTimeAtTargets ~= nil and waitTimeAtTargets[2] or 0.0 local onPlayerHitCb, playerBeingHit = nil, false function self.getActive() return active end function self.setActive(toggle) if active == toggle then return end active = toggle if active then if type(originPoint) == "vector3" then self._startLaser() elseif type(originPoint) == "table" then self._startMultiOriginLaser() end end end function self.getVisible() return visible end function self.setVisible(toggle) if visible == toggle then return end visible = toggle end function self.getMoving() return moving end function self.setMoving(toggle) if moving == toggle then return end moving = toggle end function self.getColor() return r, g, b, a end function self.setColor(_r, _g, _b, _a) if type(_r) ~= "number" or type(_g) ~= "number" or type(_b) ~= "number" or type(_a) ~= "number" then error("(r, g, b, a) must all be integers " .. string.format("{r = %s, g = %s, b = %s, a = %s}", _r, _g, _b, _a)) end r, g, b, a = _r, _g, _b, _a end function self.onPlayerHit(cb) onPlayerHitCb = cb playerBeingHit = false end function self.clearOnPlayerHit() onPlayerHitCb = nil playerBeingHit = false end function self._onPlayerHitTest(origin, destination) local _, hit, hitPos, _, hitEntity = RayCast(origin, destination, 12) local newPlayerBeingHit = hit and hitEntity == PlayerPedId() if newPlayerBeingHit ~= playerBeingHit then playerBeingHit = newPlayerBeingHit onPlayerHitCb(playerBeingHit, hitPos) end end function self._startLaser() if #targetPoints == 1 then Citizen.CreateThread(function () local direction = norm(targetPoints[1] - originPoint) local destination = originPoint + direction * maxDistance while active do if visible then drawLaser(originPoint, destination, r, g, b, a) if onPlayerHitCb then self._onPlayerHitTest(originPoint, destination) end end Wait(0) end end) else Citizen.CreateThread(function () local deltaTime = 0 local fromIndex = 1 local toIndex = 2 if randomTargetSelection then fromIndex = math.random(1, #targetPoints) toIndex = getNextToIndex(fromIndex, #targetPoints, randomTargetSelection) end local waiting = false local waitTime = 0 local currentTravelTime = randomFloat(minTravelTimeBetweenTargets, maxTravelTimeBetweenTargets) while active do local fromPoint = targetPoints[fromIndex] local toPoint = targetPoints[toIndex] local currentPoint = calculateCurrentPoint(fromPoint, toPoint, deltaTime, currentTravelTime) local currentDirection = norm(currentPoint - originPoint) if visible then local destination = currentPoint if extensionEnabled then destination = originPoint + currentDirection * maxDistance end drawLaser(originPoint, destination, r, g, b, a) if onPlayerHitCb then self._onPlayerHitTest(originPoint, destination) end end if moving and not waiting then if #(toPoint - currentPoint) < 0.001 then deltaTime = 0 fromIndex = toIndex toIndex = getNextToIndex(fromIndex, #targetPoints, randomTargetSelection) currentTravelTime = randomFloat(minTravelTimeBetweenTargets, maxTravelTimeBetweenTargets) if minWaitTimeAtTargets > 0.0 or maxWaitTimeAtTargets > 0.0 then waiting = true waitTime = randomFloat(minWaitTimeAtTargets, maxWaitTimeAtTargets) * 1000 end end deltaTime = deltaTime + (GetFrameTime() * 1000) elseif waiting then waitTime = waitTime - (GetFrameTime() * 1000) if waitTime <= 0.0 then waiting = false end end Wait(0) end end) end end function self._startMultiOriginLaser() assert(#originPoint == #targetPoints, "Multi-origin laser must have same number of origin and target points") assert(#originPoint > 1 and #targetPoints > 1, "Multi-origin laser must have more than one origin and target points") Citizen.CreateThread(function () local deltaTime = 0 local fromIndex = 1 local toIndex = 2 local step = 1 local waiting = false local waitTime = 0 local currentTravelTime = randomFloat(minTravelTimeBetweenTargets, maxTravelTimeBetweenTargets) while active do local fromTargetPoint = targetPoints[fromIndex] local toTargetPoint = targetPoints[toIndex] local currentTargetPoint = calculateCurrentPoint(fromTargetPoint, toTargetPoint, deltaTime, currentTravelTime) local fromOriginPoint = originPoint[fromIndex] local toOriginPoint = originPoint[toIndex] local currentOriginPoint = calculateCurrentPoint(fromOriginPoint, toOriginPoint, deltaTime, currentTravelTime) if visible then drawLaser(currentOriginPoint, currentTargetPoint, r, g, b, a) if onPlayerHitCb then self._onPlayerHitTest(currentOriginPoint, currentTargetPoint) end end if moving and not waiting then if #(currentTargetPoint - toTargetPoint) < 0.001 then deltaTime = 0 if toIndex == 1 or toIndex == #originPoint then step = step * -1 fromIndex = toIndex toIndex = fromIndex + step else fromIndex = fromIndex + step toIndex = toIndex + step end currentTravelTime = randomFloat(minTravelTimeBetweenTargets, maxTravelTimeBetweenTargets) if minWaitTimeAtTargets > 0.0 or maxWaitTimeAtTargets > 0.0 then waiting = true waitTime = randomFloat(minWaitTimeAtTargets, maxWaitTimeAtTargets) * 1000 end end deltaTime = deltaTime + (GetFrameTime() * 1000) elseif waiting then waitTime = waitTime - (GetFrameTime() * 1000) if waitTime <= 0.0 then waiting = false end end Wait(0) end end) end return self end