232 lines
8.7 KiB
Lua
232 lines
8.7 KiB
Lua
|
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
|