ed
This commit is contained in:
parent
57069485d5
commit
8d5055a73d
1412 changed files with 5583 additions and 0 deletions
229
resources/[jobs]/[police]/ProLaser4/UTIL/cl_history.lua
Normal file
229
resources/[jobs]/[police]/ProLaser4/UTIL/cl_history.lua
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
local SPEED_LIMITS_RAW = LoadResourceFile(GetCurrentResourceName(), "/speedlimits.json")
|
||||
local speedLimits = json.decode(SPEED_LIMITS_RAW)
|
||||
|
||||
HIST = { }
|
||||
HIST.history = { }
|
||||
HIST.loggedHistory = { }
|
||||
|
||||
local cfg = cfg
|
||||
local savePrefix = 'prolaser4_'
|
||||
local pendingChanges = false
|
||||
local selfTestTimestamp = nil
|
||||
local waitingForServer = false
|
||||
local lastLoggedTarget
|
||||
|
||||
-- local function forward declarations
|
||||
local GetTimeString, PadTime, CorrectHour
|
||||
-- [[COMMANDS]]
|
||||
-- CLEAR SAVED DATA / KVPS
|
||||
RegisterCommand('lidarwipe', function(source, args)
|
||||
DeleteResourceKvp(savePrefix .. 'history')
|
||||
HUD:ShowNotification("~g~Success~s~: wiped local save data. Please restart for changes to take effect.")
|
||||
HUD:ResizeOnScreenDisplay(true)
|
||||
end)
|
||||
TriggerEvent('chat:addSuggestion', '/lidarwipe', 'Deletes all local save data including local history, lidar position and scale.')
|
||||
|
||||
if cfg.logging then
|
||||
-- MANUAL SAVE COMMAND
|
||||
RegisterCommand('lidarupload', function(source, args)
|
||||
lastLoggedTarget = nil
|
||||
TriggerServerEvent('prolaser4:SendLogData', HIST.loggedHistory)
|
||||
HIST.loggedHistory = { }
|
||||
end)
|
||||
TriggerEvent('chat:addSuggestion', '/lidarupload', 'Manually upload lidar event data to server. (debugging purposes)')
|
||||
|
||||
-- RECORDS INTERFACE
|
||||
RegisterCommand('lidarrecords', function(source, args)
|
||||
waitingForServer = true
|
||||
TriggerServerEvent('prolaser4:GetLogData')
|
||||
Wait(5000)
|
||||
if waitingForServer then
|
||||
HUD:ShowNotification("~r~Error~s~: Database timed out, check server console.")
|
||||
TriggerServerEvent('prolaser4:DatabaseTimeout')
|
||||
end
|
||||
end)
|
||||
TriggerEvent('chat:addSuggestion', '/lidarrecords', 'Review lidar records.')
|
||||
|
||||
-- [[EVENTS]]
|
||||
RegisterNetEvent("prolaser4:ReturnLogData")
|
||||
AddEventHandler("prolaser4:ReturnLogData", function(databaseData)
|
||||
waitingForServer = false
|
||||
HUD:SendDatabaseRecords(databaseData)
|
||||
HUD:SetTabletState(true)
|
||||
end)
|
||||
end
|
||||
|
||||
-- [[THREADS]]
|
||||
-- SAVE/LOAD HISTORY THREAD: saves history if it's been changed every 5 minutes.
|
||||
CreateThread(function()
|
||||
Wait(1000)
|
||||
-- load save history
|
||||
local historySaveData = GetResourceKvpString(savePrefix..'history')
|
||||
if historySaveData ~= nil then
|
||||
HIST.history = json.decode(historySaveData)
|
||||
end
|
||||
|
||||
-- save pending changes to kvp
|
||||
while true do
|
||||
Wait(60000)
|
||||
if pendingChanges then
|
||||
-- as the data is being pushed, we don't want to attempt to update loggedData since it's being uploaded and emptied
|
||||
lastLoggedTarget = nil
|
||||
SetResourceKvp(savePrefix .. 'history', json.encode(HIST.history))
|
||||
|
||||
if cfg.logging and #HIST.loggedHistory > 0 then
|
||||
TriggerServerEvent('prolaser4:SendLogData', HIST.loggedHistory)
|
||||
HIST.loggedHistory = { }
|
||||
end
|
||||
pendingChanges = false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- [[FUNCTION]]
|
||||
-- STORE LAST 100 CLOCKS IN DATA TABLE TO SEND TO NUI FOR DISPLAY
|
||||
function HIST:StoreLidarData(target, speed, range, towards)
|
||||
-- format clock data
|
||||
local clockString = string.format("%03.0f mph %03.1f ft", speed, range)
|
||||
if towards then
|
||||
clockString = '-'..clockString
|
||||
else
|
||||
clockString = '+'..clockString
|
||||
end
|
||||
|
||||
-- check is this the same target we just clocked if so update clock time and return
|
||||
if self.history[1] ~= nil and self.history[1].target == target then
|
||||
-- if new record is of higher speed, update to the new speed
|
||||
if self.history[1].speed < speed then
|
||||
self.history[1].clock = clockString
|
||||
self.history[1].time = GetTimeString()
|
||||
end
|
||||
else
|
||||
-- different vehicle, store data in table and add
|
||||
local data = { target = target,
|
||||
speed = speed,
|
||||
time = GetTimeString(),
|
||||
clock = clockString,
|
||||
}
|
||||
-- clear old history items FIFO (first-in-first-out)
|
||||
while #self.history > 99 do
|
||||
table.remove(self.history, 100)
|
||||
end
|
||||
table.insert(self.history, 1, data)
|
||||
end
|
||||
|
||||
-- logging data
|
||||
if cfg.logging then
|
||||
if not cfg.loggingPlayersOnly or IsPedAPlayer(GetPedInVehicleSeat(target, -1)) then
|
||||
if lastLoggedTarget ~= target then
|
||||
local loggedData = { }
|
||||
loggedData['speed'] = speed
|
||||
loggedData['range'] = string.format("%03.1f", range)
|
||||
loggedData['time'] = GetTimeString()
|
||||
local targetPos = GetEntityCoords(target)
|
||||
loggedData['targetX'] = targetPos.x
|
||||
loggedData['targetY'] = targetPos.y
|
||||
loggedData['selfTestTimestamp'] = selfTestTimestamp
|
||||
|
||||
local streetHash1, streetHash2 = GetStreetNameAtCoord(targetPos.x, targetPos.y, targetPos.z, Citizen.ResultAsInteger(), Citizen.ResultAsInteger())
|
||||
local streetName1 = GetStreetNameFromHashKey(streetHash1)
|
||||
local streetName2 = GetStreetNameFromHashKey(streetHash2)
|
||||
if not cfg.loggingOnlySpeeders or speed > speedLimits[streetName1] then
|
||||
if streetName2 == "" then
|
||||
loggedData['street'] = streetName1
|
||||
else
|
||||
loggedData['street'] = string.format("%s / %s", streetName1, streetName2)
|
||||
end
|
||||
lastLoggedTarget = target
|
||||
table.insert(self.loggedHistory, 1, loggedData)
|
||||
pendingChanges = true
|
||||
end
|
||||
else
|
||||
-- Update pending data to reflect higher clock.
|
||||
local loggedData = self.loggedHistory[1]
|
||||
if loggedData ~= nil and loggedData['speed'] ~= nil then
|
||||
if speed > loggedData['speed'] then
|
||||
loggedData['speed'] = speed
|
||||
loggedData['range'] = string.format("%03.1f", range)
|
||||
loggedData['time'] = GetTimeString()
|
||||
local targetPos = GetEntityCoords(target)
|
||||
loggedData['targetX'] = targetPos.x
|
||||
loggedData['targetY'] = targetPos.y
|
||||
loggedData['selfTestTimestamp'] = selfTestTimestamp
|
||||
|
||||
local streetHash1, streetHash2 = GetStreetNameAtCoord(targetPos.x, targetPos.y, targetPos.z, Citizen.ResultAsInteger(), Citizen.ResultAsInteger())
|
||||
local streetName1 = GetStreetNameFromHashKey(streetHash1)
|
||||
local streetName2 = GetStreetNameFromHashKey(streetHash2)
|
||||
if streetName2 == "" then
|
||||
loggedData['street'] = streetName1
|
||||
else
|
||||
loggedData['street'] = string.format("%s / %s", streetName1, streetName2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- [[ GLOBAL FUNCTIONS ]]
|
||||
-- HUD->HIST store self-test datetime for SQL
|
||||
function HIST:SetSelfTestTimestamp()
|
||||
selfTestTimestamp = GetTimeString()
|
||||
end
|
||||
|
||||
-- HIST->HUD return KVP theme save int/enum
|
||||
function HIST:GetTabletTheme()
|
||||
return GetResourceKvpInt(savePrefix..'tablet_theme')
|
||||
end
|
||||
|
||||
-- HUD->HIST send NUI theme back to HIST for storage
|
||||
function HIST:SaveTabletTheme(theme)
|
||||
SetResourceKvpInt(savePrefix .. 'tablet_theme', theme)
|
||||
end
|
||||
|
||||
-- HUD->HIST send NUI OSD style back to HIST for storage
|
||||
function HIST:SaveOsdStyle(data)
|
||||
SetResourceKvp(savePrefix .. 'osd_style', json.encode(data))
|
||||
end
|
||||
|
||||
-- HIST->HUD return KVP for OSD style
|
||||
function HIST:GetOsdStyle()
|
||||
local osdStyle = GetResourceKvpString(savePrefix..'osd_style')
|
||||
if osdStyle ~= nil then
|
||||
return GetResourceKvpString(savePrefix..'osd_style')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- [[ LOCAL FUNCTIONS ]]
|
||||
-- Gets formatted zulu time
|
||||
GetTimeString = function()
|
||||
local year, month, day, hour, minute, second = GetPosixTime()
|
||||
-- for some reason PosixTime returns 1 hour ahead of correct time
|
||||
hour = CorrectHour(hour)
|
||||
-- pad time with leading zero if needed
|
||||
month = PadTime(month)
|
||||
day = PadTime(day)
|
||||
hour = PadTime(hour)
|
||||
minute = PadTime(minute)
|
||||
second = PadTime(second)
|
||||
return string.format("%s/%s/%s %s:%s:%s", month, day, year, hour, minute, second)
|
||||
end
|
||||
|
||||
PadTime = function(time)
|
||||
if time < 10 then
|
||||
time = "0" .. time
|
||||
end
|
||||
return time
|
||||
end
|
||||
|
||||
CorrectHour = function(hour)
|
||||
hour = hour - 1
|
||||
if hour < 1 then
|
||||
hour = 23
|
||||
elseif hour > 23 then
|
||||
hour = 1
|
||||
end
|
||||
return hour
|
||||
end
|
||||
715
resources/[jobs]/[police]/ProLaser4/UTIL/cl_lidar.lua
Normal file
715
resources/[jobs]/[police]/ProLaser4/UTIL/cl_lidar.lua
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
selfTestState = not cfg.performSelfTest
|
||||
|
||||
holdingLidarGun = false
|
||||
local cfg = cfg
|
||||
local lidarGunHash = GetHashKey(cfg.lidarGunHash)
|
||||
local selfTestInProgress = false
|
||||
local tempHidden = false
|
||||
local shown = false
|
||||
local hudMode = false
|
||||
local isAiming = false
|
||||
local isUsingKeyboard = false
|
||||
local inFirstPersonPed = true
|
||||
local fpAimDownSight = false
|
||||
local tpAimDownSight = false
|
||||
local ped, target
|
||||
local playerId = PlayerId()
|
||||
local targetHeading, pedHeading, towards
|
||||
local lastTarget, lastDistance, lastTime
|
||||
local beingShownLidarGun = false
|
||||
local mainThreadRunning = true
|
||||
|
||||
local lidarFOV = (cfg.minFOV+cfg.maxFOV)*0.5
|
||||
local currentLidarFOV
|
||||
local cam, weap, zoomvalue
|
||||
local rightAxisX, rightAxisY, rotation
|
||||
local camInVehicle
|
||||
local inVehicleDeltaCamRot
|
||||
|
||||
local isHistoryActive = false
|
||||
local historyIndex = 0
|
||||
|
||||
local slowScroll = 500
|
||||
local fastScroll = 50
|
||||
local scrollWait = slowScroll
|
||||
local scrollDirection = nil
|
||||
|
||||
local rangeScalar, velocityScalar
|
||||
-- Metric vs Imperial
|
||||
if cfg.useMetric then
|
||||
rangeScalar = 1.0
|
||||
velocityScalar = 3.6
|
||||
else
|
||||
rangeScalar = 3.28084
|
||||
velocityScalar = 2.236936
|
||||
end
|
||||
|
||||
-- local function forward declarations
|
||||
local GetLidarReturn
|
||||
local CheckInputRotation, HandleZoom
|
||||
local PlayButtonPressBeep
|
||||
local IsTriggerControlPressed, IsFpsAimControlPressed, IsFpsUnaimControlPressed, IsTpAimControlPressed, IsTpUnaimControlPressed
|
||||
|
||||
-- lidar jamming from sonoran [plate] = state.
|
||||
local jammedList = { }
|
||||
|
||||
-- TOGGLE LIDAR DISPLAY COMMAND
|
||||
RegisterCommand('lidar', function(source, args)
|
||||
if holdingLidarGun and not hudMode then
|
||||
-- open HUD Display and self-test
|
||||
if shown == true then
|
||||
HUD:SetLidarDisplayState(false)
|
||||
else
|
||||
HUD:SetLidarDisplayState(true)
|
||||
end
|
||||
shown = not shown
|
||||
if not selfTestState and not selfTestInProgress then
|
||||
selfTestInProgress = true
|
||||
selfTestState = HUD:DisplaySelfTest()
|
||||
end
|
||||
end
|
||||
end)
|
||||
RegisterKeyMapping('lidar', 'Toggle Lidar Display', 'keyboard', cfg.toggleMenu)
|
||||
TriggerEvent('chat:addSuggestion', '/lidar', 'Toggle lidar display.')
|
||||
|
||||
-- TOGGLE LIDAR WEAPON COMMAND
|
||||
RegisterCommand('lidarweapon', function(source, args)
|
||||
if HasPedGotWeapon(ped, lidarGunHash) then
|
||||
RemoveWeaponFromPed(ped, lidarGunHash)
|
||||
else
|
||||
GiveWeaponToPed(ped, lidarGunHash, 0, false, false)
|
||||
end
|
||||
end)
|
||||
TriggerEvent('chat:addSuggestion', '/lidarweapon', 'Equip / Remove lidar weapon.')
|
||||
|
||||
-- SHOW LIDAR TO NEAREST PLAYER COMMAND
|
||||
RegisterCommand('lidarshow', function(source, args)
|
||||
if holdingLidarGun and shown then
|
||||
local players = GetActivePlayers()
|
||||
local closestDistance = -1
|
||||
local closestPlayer = -1
|
||||
local playerCoords = GetEntityCoords(ped)
|
||||
|
||||
for i=1,#players do
|
||||
local targetPed = GetPlayerPed(players[i])
|
||||
if targetPed ~= ped then
|
||||
local targetCoords = GetEntityCoords(targetPed)
|
||||
local distance = #(playerCoords - targetCoords)
|
||||
if distance <= 3 and (closestDistance == -1 or distance < closestDistance) then
|
||||
closestPlayer = players[i]
|
||||
closestDistance = distance
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if closestPlayer ~= -1 then
|
||||
HUD:GetCurrentDisplayData(GetPlayerServerId(closestPlayer))
|
||||
end
|
||||
end
|
||||
end)
|
||||
TriggerEvent('chat:addSuggestion', '/lidarshow', 'Show lidar display to nearest player for 5 seconds.')
|
||||
|
||||
-- RESIZE / MOVE OSD
|
||||
RegisterCommand('lidarmove', function(source, args)
|
||||
if holdingLidarGun and shown and not hudMode then
|
||||
if args[1] ~= nil and string.upper(args[1]) == 'TRUE' then
|
||||
HUD:ResizeOnScreenDisplay(true)
|
||||
HUD:ShowNotification("~g~Success~s~: ProLaser4 OSD position and scale reset.")
|
||||
else
|
||||
HUD:ResizeOnScreenDisplay()
|
||||
HUD:DisplayControlHint('moveOSD')
|
||||
end
|
||||
end
|
||||
end)
|
||||
TriggerEvent('chat:addSuggestion', '/lidarmove', 'Move and resize Lidar OSD.', { { name = "reset (opt.)", help = "Optional: resets position and scale of OSD <true/false>." } } );
|
||||
|
||||
--Crash recovery command
|
||||
RegisterCommand('lidarrecovercrash', function()
|
||||
if not mainThreadRunning then
|
||||
local timer = 3000
|
||||
local blocked = false
|
||||
CreateThread(function()
|
||||
while timer > 0 do
|
||||
timer = timer - 1
|
||||
if mainThreadRunning then
|
||||
blocked = true
|
||||
end
|
||||
Wait(1)
|
||||
end
|
||||
end)
|
||||
|
||||
if not blocked then
|
||||
print("^3ProLaser4 Development Log: attempting to recover from a crash... This may not work. Please make a bug report with log file.")
|
||||
HUD:ShowNotification("~r~ProLaser4~w~: ~y~please make a bug report with log file~w~.")
|
||||
HUD:ShowNotification("~r~ProLaser4~w~: attempting to recover from a crash...")
|
||||
CreateThread(MainThread)
|
||||
return
|
||||
end
|
||||
end
|
||||
print("^3ProLaser4 Development: unable to recover, appears to be running. ~y~Please make a bug report with log file~w~.")
|
||||
HUD:ShowNotification("~r~ProLaser4~w~: unable to recover, running. ~y~Please make a bug report with log file~w~.")
|
||||
end)
|
||||
TriggerEvent('chat:addSuggestion', '/lidarrecovercrash', 'Attempts to recover ProLaser4 after the resource crashed.')
|
||||
|
||||
-- /lidarshow event - show lidar display to nearby player.
|
||||
RegisterNetEvent("prolaser4:ReturnDisplayData")
|
||||
AddEventHandler("prolaser4:ReturnDisplayData", function(displayData)
|
||||
if not shown then
|
||||
beingShownLidarGun = false
|
||||
|
||||
HUD:SetSelfTestState(true)
|
||||
if (displayData.onHistory) then
|
||||
HUD:SetHistoryState(true)
|
||||
HUD:SetHistoryData(displayData.counter, { time = displayData.time, clock = displayData.clock } )
|
||||
else
|
||||
HUD:SetHistoryState(false)
|
||||
HUD:SendPeersDisplayData(displayData)
|
||||
end
|
||||
Wait(500)
|
||||
HUD:SetLidarDisplayState(true)
|
||||
|
||||
beingShownLidarGun = true
|
||||
local timer = GetGameTimer() + 8000
|
||||
while GetGameTimer() < timer do
|
||||
-- if displayed again, do not hide return and use new event thread
|
||||
if not beingShownLidarGun then
|
||||
return
|
||||
end
|
||||
Wait(250)
|
||||
end
|
||||
|
||||
HUD:SetLidarDisplayState(false)
|
||||
Wait(500)
|
||||
HUD:SetHistoryState(false)
|
||||
HUD:SetSelfTestState(selfTestState)
|
||||
end
|
||||
end)
|
||||
|
||||
-- MAIN GET VEHICLE TO CLOCKTHREAD & START UP
|
||||
CreateThread(function()
|
||||
CreateThread(MainThread)
|
||||
Wait(2000)
|
||||
-- Initalize lidar state and vars LUA->JS
|
||||
HUD:SetSelfTestState(selfTestState, false)
|
||||
HUD:SendBatteryPercentage()
|
||||
HUD:SendConfigData()
|
||||
|
||||
-- Texture load check & label replacement.
|
||||
AddTextEntry(cfg.lidarNameHashString, "ProLaser 4")
|
||||
RequestStreamedTextureDict(cfg.lidarGunTextureDict)
|
||||
while not HasStreamedTextureDictLoaded(cfg.lidarGunTextureDict) do
|
||||
Wait(100)
|
||||
end
|
||||
|
||||
while true do
|
||||
ped = PlayerPedId()
|
||||
holdingLidarGun = GetSelectedPedWeapon(ped) == lidarGunHash
|
||||
if holdingLidarGun then
|
||||
isInVehicle = IsPedInAnyVehicle(ped, true)
|
||||
isAiming = IsPlayerFreeAiming(playerId)
|
||||
isGtaMenuOpen = IsWarningMessageActive() or IsPauseMenuActive()
|
||||
Wait(100)
|
||||
else
|
||||
Wait(500)
|
||||
end
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
-- REMOVE CONTROLS & HUD MESSAGE
|
||||
CreateThread( function()
|
||||
while true do
|
||||
Wait(1)
|
||||
if holdingLidarGun then
|
||||
HideHudComponentThisFrame(2)
|
||||
if isAiming then
|
||||
if not hudMode then
|
||||
DrawSprite(cfg.lidarGunTextureDict, "lidar_reticle", 0.5, 0.5, 0.005, 0.01, 0.0, 200, 200, 200, 255)
|
||||
else
|
||||
DisableControlAction(0, 26, true) -- INPUT_LOOK_BEHIND
|
||||
end
|
||||
-- if aiming down sight disable change weapon to enable scrolling without HUD wheel opening
|
||||
DisableControlAction(0, 99, true) -- INPUT_VEH_SELECT_NEXT_WEAPON
|
||||
DisableControlAction(0, 16, true) -- INPUT_SELECT_NEXT_WEAPON
|
||||
DisableControlAction(0, 17, true) -- INPUT_SELECT_PREV_WEAPON
|
||||
end
|
||||
DisablePlayerFiring(ped, true) -- Disable Weapon Firing
|
||||
DisableControlAction(0, 24, true) -- Disable Trigger Action
|
||||
DisableControlAction(0, cfg.previousHistory, true)
|
||||
DisableControlAction(0, cfg.nextHistory, true)
|
||||
DisableControlAction(0, 142, true) -- INPUT_MELEE_ATTACK_ALTERNATE
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- ADS HUD Call -> JS
|
||||
CreateThread( function()
|
||||
while true do
|
||||
if holdingLidarGun or hudMode then
|
||||
inFirstPersonPed = not isInVehicle and GetFollowPedCamViewMode() == 4
|
||||
inFirstPersonVeh = isInVehicle and GetFollowVehicleCamViewMode() == 4
|
||||
if not hudMode and fpAimDownSight and (inFirstPersonPed or inFirstPersonVeh) then
|
||||
if not shown then
|
||||
shown = true
|
||||
if not selfTestState and not selfTestInProgress then
|
||||
selfTestInProgress = true
|
||||
HUD:DisplaySelfTest()
|
||||
end
|
||||
end
|
||||
hudMode = true
|
||||
HUD:SetDisplayMode('ADS')
|
||||
DisplayRadar(false)
|
||||
elseif shown and hudMode and not (fpAimDownSight and (inFirstPersonPed or inFirstPersonVeh)) then
|
||||
hudMode = false
|
||||
HUD:SetDisplayMode('DISPLAY')
|
||||
DisplayRadar(true)
|
||||
end
|
||||
Wait(100)
|
||||
else
|
||||
Wait(500)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--LIDAR MAIN THREAD: handle hiding lidar NUI, self-test, ADS aiming, clocking, and control handling.
|
||||
function MainThread()
|
||||
while true do
|
||||
-- Crash recovery variable, resets to true at end of loop.
|
||||
mainThreadRunning = false
|
||||
Wait(1)
|
||||
-- Hide HUD if weapon not selected, keep lidar on
|
||||
if ( ( not holdingLidarGun and not beingShownLidarGun ) or isGtaMenuOpen) and shown and not tempHidden then
|
||||
HUD:SetDisplayMode('DISPLAY')
|
||||
hudMode = false
|
||||
HUD:SetLidarDisplayState(false)
|
||||
tempHidden = true
|
||||
elseif holdingLidarGun and not isGtaMenuOpen and tempHidden then
|
||||
HUD:SetLidarDisplayState(true)
|
||||
tempHidden = false
|
||||
end
|
||||
|
||||
if holdingLidarGun then
|
||||
isUsingKeyboard = IsUsingKeyboard(0)
|
||||
-- toggle ADS if first person and aim, otherwise unADS
|
||||
if IsFpsAimControlPressed() then
|
||||
fpAimDownSight = true
|
||||
SetPlayerForcedAim(playerId, true)
|
||||
elseif IsFpsUnaimControlPressed() then
|
||||
fpAimDownSight = false
|
||||
SetPlayerForcedAim(playerId, false)
|
||||
-- Simulate control just released, if still holding right click disable the control till they unclick to prevent retoggling accidently
|
||||
while IsControlJustPressed(0,25) or IsDisabledControlPressed(0,25) or IsControlPressed(0,177) or IsDisabledControlPressed(0,177) or IsControlJustPressed(0,68) or IsDisabledControlPressed(0,68) do
|
||||
DisableControlAction(0, 25, true) -- INPUT_AIM
|
||||
DisableControlAction(0, 177, true) -- INPUT_CELLPHONE_CANCEL
|
||||
DisableControlAction(0, 68, true) -- INPUT_VEH_AIM
|
||||
Wait(1)
|
||||
end
|
||||
Wait(100)
|
||||
elseif not fpAimDownSight and (inFirstPersonPed or inFirstPersonVeh) and isAiming then
|
||||
fpAimDownSight = true
|
||||
SetPlayerForcedAim(playerId, true)
|
||||
end
|
||||
|
||||
-- toggle ADS if in third person and aim, otherwide unaim
|
||||
if not (inFirstPersonPed or inFirstPersonVeh) then
|
||||
if IsTpAimControlPressed() then
|
||||
tpAimDownSight = true
|
||||
SetPlayerForcedAim(playerId, true)
|
||||
elseif IsTpUnaimControlPressed() then
|
||||
tpAimDownSight = false
|
||||
SetPlayerForcedAim(playerId, false)
|
||||
-- Simulate control just released, if still holding right click disable the control till they unclick to prevent retoggling accidently
|
||||
while IsControlJustPressed(0,25) or IsDisabledControlPressed(0,25) or IsControlPressed(0,177) or IsDisabledControlPressed(0,177) or IsControlJustPressed(0,68) or IsDisabledControlPressed(0,68) do
|
||||
DisableControlAction(0, 25, true) -- INPUT_AIM
|
||||
DisableControlAction(0, 177, true) -- INPUT_CELLPHONE_CANCEL
|
||||
DisableControlAction(0, 68, true) -- INPUT_VEH_AIM
|
||||
Wait(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get target speed and update display
|
||||
if shown and not tempHidden and selfTestState then
|
||||
if IsTriggerControlPressed() then
|
||||
found, target = GetEntityPlayerIsFreeAimingAt(playerId)
|
||||
if IsPedInAnyVehicle(target) then
|
||||
target = GetVehiclePedIsIn(target, false)
|
||||
end
|
||||
speed, range, towards = GetLidarReturn(target, ped)
|
||||
if towards ~= -1 then
|
||||
HUD:SendLidarUpdate(speed, range, towards)
|
||||
HIST:StoreLidarData(target, speed, range, towards)
|
||||
else
|
||||
HUD:ClearLidarDisplay()
|
||||
end
|
||||
Wait(250)
|
||||
-- Hides history if on first, otherwise go to previous history
|
||||
elseif IsDisabledControlPressed(0, cfg.previousHistory) and isUsingKeyboard and #HIST.history > 0 then
|
||||
if isHistoryActive then
|
||||
historyIndex = historyIndex - 1
|
||||
if scrollWait == slowScroll then
|
||||
PlayButtonPressBeep()
|
||||
end
|
||||
if historyIndex > 0 then
|
||||
HUD:SetHistoryData(historyIndex, HIST.history[historyIndex])
|
||||
Wait(scrollWait)
|
||||
else
|
||||
isHistoryActive = false
|
||||
HUD:SetHistoryState(false)
|
||||
end
|
||||
end
|
||||
-- Displays history if not shown, otherwise go to next history page.
|
||||
elseif IsDisabledControlPressed(0, cfg.nextHistory) and isUsingKeyboard and #HIST.history > 0 then
|
||||
isHistoryActive = true
|
||||
HUD:SetHistoryState(isHistoryActive)
|
||||
if historyIndex < #HIST.history then
|
||||
if scrollWait == slowScroll then
|
||||
PlayButtonPressBeep()
|
||||
end
|
||||
historyIndex = historyIndex + 1
|
||||
HUD:SetHistoryData(historyIndex, HIST.history[historyIndex])
|
||||
Wait(scrollWait)
|
||||
end
|
||||
elseif IsDisabledControlJustReleased(0, cfg.changeSight) and isUsingKeyboard and fpAimDownSight then
|
||||
HUD:ChangeSightStyle()
|
||||
end
|
||||
end
|
||||
-- force unaim if no longer holding lidar gun
|
||||
elseif tpAimDownSight or fpAimDownSight then
|
||||
SetPlayerForcedAim(playerId, false)
|
||||
tpAimDownSight = false
|
||||
fpAimDownSight = false
|
||||
else
|
||||
Wait(500)
|
||||
end
|
||||
-- Crash detection: iteration completed successfully
|
||||
mainThreadRunning = true
|
||||
end
|
||||
end
|
||||
|
||||
-- ADVANCED CONTROL HANDLING
|
||||
-- Handles controller vs keyboard events, faster validation checking.
|
||||
IsTriggerControlPressed = function()
|
||||
if not isAiming then
|
||||
return false
|
||||
end
|
||||
|
||||
if isHistoryActive then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Angle Limitation
|
||||
if tpAimDownSight and (GetGameplayCamRelativeHeading() < -131 or GetGameplayCamRelativeHeading() > 178) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- INPUT_ATTACK or INPUT_VEH_HANDBRAKE (LCLICK, SPACEBAR, CONTROLLER RB)
|
||||
-- On foot, LMOUSE and Trigger In vehicle RB
|
||||
if (IsDisabledControlPressed(0, 24) and (not isInVehicle or isUsingKeyboard)) or (IsControlPressed(0, 76) and isInVehicle) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-----------------------------------------
|
||||
IsFpsAimControlPressed = function()
|
||||
if fpAimDownSight then
|
||||
return false
|
||||
end
|
||||
|
||||
if not inFirstPersonPed or not inFirstPersonVeh then
|
||||
return false
|
||||
end
|
||||
|
||||
-- LBUMPER OR LMOUSE IN VEHICLE
|
||||
if IsControlJustPressed(0,68) and isInVehicle then
|
||||
return true
|
||||
end
|
||||
|
||||
-- LTRIGGER OR LMOUSE ON FOOT
|
||||
if IsControlJustPressed(0, 25) and not isInVehicle then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-----------------------------------------
|
||||
IsFpsUnaimControlPressed= function()
|
||||
if not fpAimDownSight then
|
||||
return false
|
||||
end
|
||||
|
||||
if not (inFirstPersonPed or inFirstPersonVeh) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- LBUMPER OR LMOUSE IN VEHICLE
|
||||
if IsControlJustPressed(0,68) and isInVehicle then
|
||||
return true
|
||||
end
|
||||
|
||||
-- LTRIGGER OR LMOUSE ON FOOT
|
||||
if IsControlJustPressed(0, 25) and not isInVehicle then
|
||||
return true
|
||||
end
|
||||
|
||||
-- BACKSPACE, ESC, RMOUSE or V (view-change)
|
||||
if (isUsingKeyboard and (IsControlJustPressed(0,177)) or IsControlJustPressed(0, 0)) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-----------------------------------------
|
||||
IsTpAimControlPressed = function()
|
||||
if tpAimDownSight then
|
||||
return false
|
||||
end
|
||||
-- LBUMPER OR LMOUSE IN VEHICLE
|
||||
if IsControlJustPressed(0,68) and isInVehicle then
|
||||
return true
|
||||
end
|
||||
|
||||
-- LTRIGGER OR LMOUSE ON FOOT
|
||||
if IsControlJustPressed(0, 25) and not isInVehicle then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-----------------------------------------
|
||||
IsTpUnaimControlPressed = function()
|
||||
if not tpAimDownSight then
|
||||
return false
|
||||
end
|
||||
|
||||
-- LBUMPER OR LMOUSE IN VEHICLE
|
||||
if IsControlJustPressed(0,68) and isInVehicle then
|
||||
return true
|
||||
end
|
||||
|
||||
-- LTRIGGER OR LMOUSE ON FOOT
|
||||
if IsControlJustPressed(0, 25) and not isInVehicle then
|
||||
return true
|
||||
end
|
||||
|
||||
-- BACKSPACE, ESC, RMOUSE or V (view-change)
|
||||
if (isUsingKeyboard and (IsControlJustPressed(0,177)) or IsControlJustPressed(0, 0)) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-----------------------------------------
|
||||
|
||||
-- SCROLL SPEED: handles fast scrolling, if holding scroll increase scroll speed.
|
||||
CreateThread(function()
|
||||
Wait(1000)
|
||||
while true do
|
||||
if holdingLidarGun and isHistoryActive then
|
||||
if IsDisabledControlPressed(0, cfg.nextHistory) then
|
||||
local count = 0
|
||||
while IsDisabledControlPressed(0, cfg.nextHistory) do
|
||||
count = count + 1
|
||||
if count > 15 then
|
||||
scrollDirection = 'next'
|
||||
scrollWait = fastScroll
|
||||
break;
|
||||
end
|
||||
Wait(100)
|
||||
end
|
||||
if scrollDirection == 'next' and not IsDisabledControlPressed(0, cfg.nextHistory) then
|
||||
scrollWait = slowScroll
|
||||
end
|
||||
elseif IsDisabledControlPressed(0, cfg.previousHistory) then
|
||||
local count = 0
|
||||
while IsDisabledControlPressed(0, cfg.previousHistory) do
|
||||
count = count + 1
|
||||
if count > 15 then
|
||||
scrollDirection = 'prev'
|
||||
scrollWait = fastScroll
|
||||
break;
|
||||
end
|
||||
Wait(100)
|
||||
end
|
||||
if scrollDirection == 'prev' and not IsDisabledControlPressed(0, cfg.previousHistory) then
|
||||
scrollWait = slowScroll
|
||||
end
|
||||
end
|
||||
else
|
||||
Wait(500)
|
||||
end
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
|
||||
-- AIM DOWNSIGHTS CAM & ZOOM
|
||||
CreateThread(function()
|
||||
while true do
|
||||
if holdingLidarGun then
|
||||
if fpAimDownSight then
|
||||
cam = CreateCam("DEFAULT_SCRIPTED_FLY_CAMERA", true)
|
||||
weap = GetCurrentPedWeaponEntityIndex(ped)
|
||||
if isInVehicle then
|
||||
AttachCamToEntity(cam, weap, -0.018, -0.2, -0.05, true)
|
||||
camInVehicle = true
|
||||
else
|
||||
AttachCamToEntity(cam, weap, 0.0, -0.2, -0.0, true)
|
||||
camInVehicle = false
|
||||
end
|
||||
SetCamRot(cam, GetGameplayCamRot(2), 2)
|
||||
SetCamFov(cam, lidarFOV)
|
||||
RenderScriptCams(true, false, 0, 1, 0)
|
||||
if cfg.displayControls then
|
||||
HUD:DisplayControlHint('fpADS')
|
||||
cfg.displayControls = false
|
||||
end
|
||||
|
||||
while fpAimDownSight and not IsEntityDead(ped) do
|
||||
if ((camInVehicle and not isInVehicle) or (not camInVehicle and isInVehicle)) or not holdingLidarGun then
|
||||
fpAimDownSight = false
|
||||
SetPlayerForcedAim(playerId, false)
|
||||
break
|
||||
end
|
||||
zoomvalue = (1.0/(cfg.maxFOV-cfg.minFOV))*(lidarFOV-cfg.minFOV)
|
||||
CheckInputRotation(cam, zoomvalue)
|
||||
HandleZoom(cam)
|
||||
Wait(1)
|
||||
end
|
||||
RenderScriptCams(false, false, 0, 1, 0)
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
DestroyCam(cam, false)
|
||||
end
|
||||
Wait(1)
|
||||
else
|
||||
Wait(500)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
--FUNCTIONS--
|
||||
-- COSINE ERROR CALULCATIONS AND TOWARDS/AWAY STATE
|
||||
-- SEE: https://copradar.com/chapts/chapt2/ch2d1.html
|
||||
GetLidarReturn = function(target, ped)
|
||||
-- no target found
|
||||
if target == 0 then
|
||||
return 0, 0, -1
|
||||
end
|
||||
|
||||
-- sonoran jammer
|
||||
if cfg.sonoranJammer then
|
||||
if IsEntityAVehicle(target) and next(jammedList) ~= nil then
|
||||
if jammedList[GetVehicleNumberPlateText(target)] then
|
||||
return 0, 0, -1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- towards calculations
|
||||
targetHeading = GetEntityHeading(target)
|
||||
if hudMode then
|
||||
pedHeading = GetCamRot(cam, 2)[3]
|
||||
else
|
||||
pedHeading = GetEntityHeading(ped) + GetGameplayCamRelativeHeading()
|
||||
end
|
||||
towards = false
|
||||
|
||||
diffHeading = math.abs((pedHeading - targetHeading + 180) % 360 - 180)
|
||||
|
||||
if ( diffHeading > 135 ) then
|
||||
towards = true
|
||||
end
|
||||
|
||||
if diffHeading < 160 and diffHeading > 110 or
|
||||
diffHeading > 20 and diffHeading < 70 then
|
||||
if math.random(0, 100) > 15 then
|
||||
return 0, 0, -1
|
||||
end
|
||||
end
|
||||
|
||||
targetPos = GetEntityCoords(target)
|
||||
distance = #(targetPos-GetEntityCoords(ped))
|
||||
if lastDistance ~= 0 and lastTarget == target then
|
||||
-- distance traveled in meters
|
||||
distanceTraveled = lastDistance - distance
|
||||
-- time between last clock and current
|
||||
timeElapsed = (lastTime - GetGameTimer()) / 1000
|
||||
-- distance over time with conversion from neters to miles.
|
||||
speedEstimate = math.abs((distanceTraveled * velocityScalar) / timeElapsed)
|
||||
-- update last values to determine next clock
|
||||
lastDistance, lastTarget, lastTime = distance, target, GetGameTimer()
|
||||
else
|
||||
lastDistance, lastTarget, lastTime = distance, target, GetGameTimer()
|
||||
return 0, 0, -1
|
||||
end
|
||||
|
||||
return speedEstimate, distance, towards
|
||||
end
|
||||
|
||||
-- AIM DOWNSIGHTS PAN
|
||||
CheckInputRotation = function(cam, zoomvalue)
|
||||
rightAxisX = GetDisabledControlNormal(0, 220)
|
||||
rightAxisY = GetDisabledControlNormal(0, 221)
|
||||
rotation = GetCamRot(cam, 2)
|
||||
if rightAxisX ~= 0.0 or rightAxisY ~= 0.0 then
|
||||
if isInVehicle then
|
||||
newZ = rotation.z + rightAxisX*-1.0*(cfg.verticalPanSpeed-zoomvalue*8)
|
||||
newX = math.max(math.min(20.0, rotation.x + rightAxisY*-1.0*(cfg.horizontalPanSpeed-zoomvalue*8)), -20.0) -- Clamping at top (cant see top of heli) and at bottom (doesn't glitch out in -90deg)
|
||||
SetCamRot(cam, newX, 0.0, newZ, 2)
|
||||
SetGameplayCamRelativeRotation(0.0, 0.0, 0.0)
|
||||
-- limit ADS rotation while in vehicle
|
||||
inVehicleDeltaCamRot = (GetCamRot(cam, 2)[3] - GetEntityHeading(ped) + 180) % 360 - 180
|
||||
while inVehicleDeltaCamRot < -75 and inVehicleDeltaCamRot > -130 do
|
||||
newZ = newZ + 0.2
|
||||
SetCamRot(cam, newX, 0.0, newZ, 2)
|
||||
inVehicleDeltaCamRot = (GetCamRot(cam, 2)[3] - GetEntityHeading(ped) + 180) % 360 - 180
|
||||
Wait(1)
|
||||
end
|
||||
while inVehicleDeltaCamRot > 178 or (inVehicleDeltaCamRot > -180 and inVehicleDeltaCamRot < -130) do
|
||||
newZ = newZ - 0.2
|
||||
SetCamRot(cam, newX, 0.0, newZ, 2)
|
||||
inVehicleDeltaCamRot = (GetCamRot(cam, 2)[3] - GetEntityHeading(ped) + 180) % 360 - 180
|
||||
Wait(1)
|
||||
end
|
||||
else
|
||||
newZ = rotation.z + rightAxisX*-1.0*(cfg.verticalPanSpeed-zoomvalue*8)
|
||||
newX = math.max(math.min(40.0, rotation.x + rightAxisY*-1.0*(cfg.horizontalPanSpeed-zoomvalue*8)), -89.5) -- Clamping at top (cant see top of heli) and at bottom (doesn't glitch out in -90deg)
|
||||
SetCamRot(cam, newX, 0.0, newZ, 2)
|
||||
SetGameplayCamRelativeRotation(0.0, newX, newZ)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- AIM DOWNSIGHTS ZOOM
|
||||
HandleZoom = function(cam)
|
||||
if IsDisabledControlPressed(0,15) or IsDisabledControlPressed(0, 99) then -- Scrollup
|
||||
lidarFOV = math.max(lidarFOV - cfg.zoomSpeed, cfg.maxFOV)
|
||||
end
|
||||
if IsDisabledControlPressed(0,334) or IsDisabledControlPressed(0, 16) then
|
||||
lidarFOV = math.min(lidarFOV + cfg.zoomSpeed/6, cfg.minFOV) -- ScrollDown
|
||||
end
|
||||
currentLidarFOV = GetCamFov(cam)
|
||||
if math.abs(lidarFOV-currentLidarFOV) < 0.1 then -- the difference is too small, just set the value directly to avoid unneeded updates to FOV of order 10^-5
|
||||
lidarFOV = currentLidarFOV
|
||||
end
|
||||
SetCamFov(cam, currentLidarFOV + (lidarFOV - currentLidarFOV)*0.03) -- Smoothing of camera zoom
|
||||
end
|
||||
|
||||
-- Play NUI front in audio.
|
||||
PlayButtonPressBeep = function()
|
||||
SendNUIMessage({
|
||||
action = 'PlayButtonPressBeep',
|
||||
file = 'LidarBeep',
|
||||
})
|
||||
end
|
||||
|
||||
--[[SONORAN RADAR / LIDAR JAMMER]]
|
||||
if cfg.sonoranJammer then
|
||||
RegisterNetEvent( "Sonoran:SendJammedListToClient", function (listFromServer)
|
||||
jammedList = listFromServer
|
||||
end)
|
||||
end
|
||||
|
||||
232
resources/[jobs]/[police]/ProLaser4/UTIL/semver.lua
Normal file
232
resources/[jobs]/[police]/ProLaser4/UTIL/semver.lua
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
--[[
|
||||
Checkout semver.lua semantic versioning library for LUA
|
||||
https://github.com/kikito/semver.lua
|
||||
|
||||
Copyright (c) 2011 Enrique García Cota
|
||||
|
||||
MIT LICENSE
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
semver = {
|
||||
_VERSION = '1.2.1',
|
||||
_DESCRIPTION = 'semver for Lua',
|
||||
_URL = 'https://github.com/kikito/semver.lua',
|
||||
_LICENSE = [[
|
||||
MIT LICENSE
|
||||
Copyright (c) 2015 Enrique García Cota
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of tother software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
The above copyright notice and tother permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
}
|
||||
|
||||
local function checkPositiveInteger(number, name)
|
||||
assert(number >= 0, name .. ' must be a valid positive number')
|
||||
assert(math.floor(number) == number, name .. ' must be an integer')
|
||||
end
|
||||
|
||||
local function present(value)
|
||||
return value and value ~= ''
|
||||
end
|
||||
|
||||
-- splitByDot("a.bbc.d") == {"a", "bbc", "d"}
|
||||
local function splitByDot(str)
|
||||
str = str or ""
|
||||
local t, count = {}, 0
|
||||
str:gsub("([^%.]+)", function(c)
|
||||
count = count + 1
|
||||
t[count] = c
|
||||
end)
|
||||
return t
|
||||
end
|
||||
|
||||
local function parsePrereleaseAndBuildWithSign(str)
|
||||
local prereleaseWithSign, buildWithSign = str:match("^(-[^+]+)(+.+)$")
|
||||
if not (prereleaseWithSign and buildWithSign) then
|
||||
prereleaseWithSign = str:match("^(-.+)$")
|
||||
buildWithSign = str:match("^(+.+)$")
|
||||
end
|
||||
assert(prereleaseWithSign or buildWithSign, ("The parameter %q must begin with + or - to denote a prerelease or a build"):format(str))
|
||||
return prereleaseWithSign, buildWithSign
|
||||
end
|
||||
|
||||
local function parsePrerelease(prereleaseWithSign)
|
||||
if prereleaseWithSign then
|
||||
local prerelease = prereleaseWithSign:match("^-(%w[%.%w-]*)$")
|
||||
assert(prerelease, ("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(prereleaseWithSign))
|
||||
return prerelease
|
||||
end
|
||||
end
|
||||
|
||||
local function parseBuild(buildWithSign)
|
||||
if buildWithSign then
|
||||
local build = buildWithSign:match("^%+(%w[%.%w-]*)$")
|
||||
assert(build, ("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(buildWithSign))
|
||||
return build
|
||||
end
|
||||
end
|
||||
|
||||
local function parsePrereleaseAndBuild(str)
|
||||
if not present(str) then return nil, nil end
|
||||
|
||||
local prereleaseWithSign, buildWithSign = parsePrereleaseAndBuildWithSign(str)
|
||||
|
||||
local prerelease = parsePrerelease(prereleaseWithSign)
|
||||
local build = parseBuild(buildWithSign)
|
||||
|
||||
return prerelease, build
|
||||
end
|
||||
|
||||
local function parseVersion(str)
|
||||
local sMajor, sMinor, sPatch, sPrereleaseAndBuild = str:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")
|
||||
assert(type(sMajor) == 'string', ("Could not extract version number(s) from %q"):format(str))
|
||||
local major, minor, patch = tonumber(sMajor), tonumber(sMinor), tonumber(sPatch)
|
||||
local prerelease, build = parsePrereleaseAndBuild(sPrereleaseAndBuild)
|
||||
return major, minor, patch, prerelease, build
|
||||
end
|
||||
|
||||
|
||||
-- return 0 if a == b, -1 if a < b, and 1 if a > b
|
||||
local function compare(a,b)
|
||||
return a == b and 0 or a < b and -1 or 1
|
||||
end
|
||||
|
||||
local function compareIds(myId, otherId)
|
||||
if myId == otherId then return 0
|
||||
elseif not myId then return -1
|
||||
elseif not otherId then return 1
|
||||
end
|
||||
|
||||
local selfNumber, otherNumber = tonumber(myId), tonumber(otherId)
|
||||
|
||||
if selfNumber and otherNumber then -- numerical comparison
|
||||
return compare(selfNumber, otherNumber)
|
||||
-- numericals are always smaller than alphanums
|
||||
elseif selfNumber then
|
||||
return -1
|
||||
elseif otherNumber then
|
||||
return 1
|
||||
else
|
||||
return compare(myId, otherId) -- alphanumerical comparison
|
||||
end
|
||||
end
|
||||
|
||||
local function smallerIdList(myIds, otherIds)
|
||||
local myLength = #myIds
|
||||
local comparison
|
||||
|
||||
for i=1, myLength do
|
||||
comparison = compareIds(myIds[i], otherIds[i])
|
||||
if comparison ~= 0 then
|
||||
return comparison == -1
|
||||
end
|
||||
-- if comparison == 0, continue loop
|
||||
end
|
||||
|
||||
return myLength < #otherIds
|
||||
end
|
||||
|
||||
local function smallerPrerelease(mine, other)
|
||||
if mine == other or not mine then return false
|
||||
elseif not other then return true
|
||||
end
|
||||
|
||||
return smallerIdList(splitByDot(mine), splitByDot(other))
|
||||
end
|
||||
|
||||
local methods = {}
|
||||
|
||||
function methods:nextMajor()
|
||||
return semver(self.major + 1, 0, 0)
|
||||
end
|
||||
function methods:nextMinor()
|
||||
return semver(self.major, self.minor + 1, 0)
|
||||
end
|
||||
function methods:nextPatch()
|
||||
return semver(self.major, self.minor, self.patch + 1)
|
||||
end
|
||||
|
||||
local mt = { __index = methods }
|
||||
function mt:__eq(other)
|
||||
return self.major == other.major and
|
||||
self.minor == other.minor and
|
||||
self.patch == other.patch and
|
||||
self.prerelease == other.prerelease
|
||||
-- notice that build is ignored for precedence in semver 2.0.0
|
||||
end
|
||||
function mt:__lt(other)
|
||||
if self.major ~= other.major then return self.major < other.major end
|
||||
if self.minor ~= other.minor then return self.minor < other.minor end
|
||||
if self.patch ~= other.patch then return self.patch < other.patch end
|
||||
return smallerPrerelease(self.prerelease, other.prerelease)
|
||||
-- notice that build is ignored for precedence in semver 2.0.0
|
||||
end
|
||||
-- This works like the "pessimisstic operator" in Rubygems.
|
||||
-- if a and b are versions, a ^ b means "b is backwards-compatible with a"
|
||||
-- in other words, "it's safe to upgrade from a to b"
|
||||
function mt:__pow(other)
|
||||
if self.major == 0 then
|
||||
return self == other
|
||||
end
|
||||
return self.major == other.major and
|
||||
self.minor <= other.minor
|
||||
end
|
||||
function mt:__tostring()
|
||||
local buffer = { ("%d.%d.%d"):format(self.major, self.minor, self.patch) }
|
||||
if self.prerelease then table.insert(buffer, "-" .. self.prerelease) end
|
||||
if self.build then table.insert(buffer, "+" .. self.build) end
|
||||
return table.concat(buffer)
|
||||
end
|
||||
|
||||
local function new(major, minor, patch, prerelease, build)
|
||||
assert(major, "At least one parameter is needed")
|
||||
|
||||
if type(major) == 'string' then
|
||||
major,minor,patch,prerelease,build = parseVersion(major)
|
||||
end
|
||||
patch = patch or 0
|
||||
minor = minor or 0
|
||||
|
||||
checkPositiveInteger(major, "major")
|
||||
checkPositiveInteger(minor, "minor")
|
||||
checkPositiveInteger(patch, "patch")
|
||||
|
||||
local result = {major=major, minor=minor, patch=patch, prerelease=prerelease, build=build}
|
||||
return setmetatable(result, mt)
|
||||
end
|
||||
|
||||
setmetatable(semver, { __call = function(_, ...) return new(...) end })
|
||||
semver._VERSION= semver(semver._VERSION)
|
||||
|
||||
return semver
|
||||
209
resources/[jobs]/[police]/ProLaser4/UTIL/sv_lidar.lua
Normal file
209
resources/[jobs]/[police]/ProLaser4/UTIL/sv_lidar.lua
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
local cfg = cfg
|
||||
|
||||
-- ShowLidar, repeater event to nearest player to show lidar to.
|
||||
RegisterServerEvent('prolaser4:SendDisplayData')
|
||||
AddEventHandler('prolaser4:SendDisplayData', function(target, data)
|
||||
TriggerClientEvent('prolaser4:ReturnDisplayData', target, data)
|
||||
end)
|
||||
|
||||
-- Database timeout event from client->server for server console log.
|
||||
RegisterServerEvent('prolaser4:DatabaseTimeout')
|
||||
AddEventHandler('prolaser4:DatabaseTimeout', function()
|
||||
print(string.format('^8[ERROR]: ^3Database timed out for %s after 5 seconds. Lidar records tablet unavailable.\n\t\t1) Ensure your database is online.\n\t\t2) restart oxmysql.\n\t\t3) restart ProLaser4.^7', GetPlayerName(source)))
|
||||
end)
|
||||
|
||||
function DebugPrint(text)
|
||||
if cfg.serverDebugging then
|
||||
print(text)
|
||||
end
|
||||
end
|
||||
|
||||
--[[--------------- ADVANCED LOGGING --------------]]
|
||||
if cfg.logging and MySQL ~= nil then
|
||||
local isInsertActive = false
|
||||
LOGGED_EVENTS = { }
|
||||
TEMP_LOGGED_EVENTS = { }
|
||||
|
||||
---------------- QUERIES ----------------
|
||||
local insertQuery = [[
|
||||
INSERT INTO prolaser4
|
||||
(timestamp, speed, distance, targetX, targetY, player, street, selfTestTimestamp)
|
||||
VALUES
|
||||
(STR_TO_DATE(?, "%m/%d/%Y %H:%i:%s"), ?, ?, ?, ?, ?, ?, STR_TO_DATE(?, "%m/%d/%Y %H:%i:%s"))
|
||||
]]
|
||||
local selectQueryRaw = [[
|
||||
SELECT
|
||||
rid,
|
||||
DATE_FORMAT(timestamp, "%c/%d/%y %H:%i") AS timestamp,
|
||||
speed,
|
||||
distance as 'range',
|
||||
targetX,
|
||||
targetY,
|
||||
player,
|
||||
street,
|
||||
DATE_FORMAT(selfTestTimestamp, "%m/%d/%Y %H:%i") AS selfTestTimestamp
|
||||
FROM prolaser4
|
||||
ORDER BY timestamp
|
||||
LIMIT
|
||||
]]
|
||||
local selectQuery = string.format("%s %s", selectQueryRaw, cfg.loggingSelectLimit)
|
||||
local countQuery = 'SELECT COUNT(*) FROM prolaser4'
|
||||
local cleanupQuery = 'DELETE FROM prolaser4 WHERE timestamp < DATE_SUB(NOW(), INTERVAL ? DAY);'
|
||||
-----------------------------------------
|
||||
-- Debugging Command
|
||||
RegisterCommand('lidarsqlupdate', function(source, args)
|
||||
-- check if from server console
|
||||
if source == 0 then
|
||||
DebugPrint('^3[INFO]: Manually inserting records to SQL.^7')
|
||||
InsertRecordsToSQL()
|
||||
else
|
||||
DebugPrint(string.format('^3[INFO]: Attempted to manually insert records but got source %s.^7', source))
|
||||
TriggerClientEvent('chat:addMessage', source, { args = { '^1Error', 'This command can only be executed from the console.' } })
|
||||
end
|
||||
end)
|
||||
|
||||
-----------------------------------------
|
||||
-- Main thread, every restart remove old records if needed, insert records every X minutes as defined by cfg.loggingInsertInterval.
|
||||
CreateThread(function()
|
||||
local insertWait = cfg.loggingInsertInterval * 60000
|
||||
if cfg.loggingCleanUpInterval ~= -1 then
|
||||
CleanUpRecordsFromSQL()
|
||||
end
|
||||
while true do
|
||||
InsertRecordsToSQL()
|
||||
Wait(insertWait)
|
||||
end
|
||||
end)
|
||||
|
||||
---------------- SETTER / INSERT ----------------
|
||||
-- Shared event handler colate all lidar data from all players for SQL submission.
|
||||
RegisterServerEvent('prolaser4:SendLogData')
|
||||
AddEventHandler('prolaser4:SendLogData', function(logData)
|
||||
local playerName = GetPlayerName(source)
|
||||
if not isInsertActive then
|
||||
for i, entry in ipairs(logData) do
|
||||
entry.player = playerName
|
||||
table.insert(LOGGED_EVENTS, entry)
|
||||
end
|
||||
else
|
||||
-- since the insertion is active, inserting now may result in lost data, store temporarily.
|
||||
for i, entry in ipairs(logData) do
|
||||
entry.player = playerName
|
||||
table.insert(TEMP_LOGGED_EVENTS, entry)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Inserts records to SQL table
|
||||
function InsertRecordsToSQL()
|
||||
if not isInsertActive then
|
||||
if #LOGGED_EVENTS > 0 then
|
||||
DebugPrint(string.format('^3[INFO]: Started inserting %s records.^7', #LOGGED_EVENTS))
|
||||
isInsertActive = true
|
||||
-- Execute the insert statement for each entry
|
||||
for _, entry in ipairs(LOGGED_EVENTS) do
|
||||
MySQL.insert(insertQuery, {entry.time, entry.speed, entry.range, entry.targetX, entry.targetY, entry.player, entry.street, entry.selfTestTimestamp}, function(returnData) end)
|
||||
end
|
||||
-- Remove processed records
|
||||
LOGGED_EVENTS = {}
|
||||
isInsertActive = false
|
||||
-- Copy over temp entries to be processed next run
|
||||
for _, entry in ipairs(TEMP_LOGGED_EVENTS) do
|
||||
table.insert(LOGGED_EVENTS, entry)
|
||||
end
|
||||
-- Remove copied over values.
|
||||
TEMP_LOGGED_EVENTS = {}
|
||||
DebugPrint('^3[INFO]: Finished inserting records.^7')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---------------- GETTER / SELECT ----------------
|
||||
-- C->S request all record data
|
||||
RegisterNetEvent('prolaser4:GetLogData')
|
||||
AddEventHandler('prolaser4:GetLogData', function()
|
||||
SelectRecordsFromSQL(source)
|
||||
end)
|
||||
|
||||
-- Get all record data and return to client
|
||||
function SelectRecordsFromSQL(source)
|
||||
DebugPrint(string.format('^3[INFO]: Getting records for %s.^7', GetPlayerName(source)))
|
||||
MySQL.query(selectQuery, {}, function(result)
|
||||
DebugPrint(string.format('^3[INFO]: Returned %s from select query.^7', #result))
|
||||
if result then
|
||||
TriggerClientEvent('prolaser4:ReturnLogData', source, result)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
------------------ AUTO CLEANUP -----------------
|
||||
-- Remove old records after X days old.
|
||||
function CleanUpRecordsFromSQL()
|
||||
DebugPrint('^3[INFO]: Removing old records.^7');
|
||||
MySQL.query(cleanupQuery, {cfg.loggingCleanUpInterval}, function(returnData)
|
||||
DebugPrint(string.format('^3[INFO]: Removed %s records (older than %s days)^7', returnData.affectedRows, cfg.loggingCleanUpInterval));
|
||||
end)
|
||||
end
|
||||
|
||||
------------------ RECORD COUNT -----------------
|
||||
function GetRecordCount()
|
||||
local recordCount = '^8FAILED TO RETRIEVE ^7'
|
||||
MySQL.query(countQuery, {}, function(returnData)
|
||||
if returnData and returnData[1] and returnData[1]['COUNT(*)'] then
|
||||
recordCount = returnData[1]['COUNT(*)']
|
||||
end
|
||||
end)
|
||||
Wait(500)
|
||||
return recordCount
|
||||
end
|
||||
end
|
||||
|
||||
--[[------------ STARTUP / VERSION CHECKING -----------]]
|
||||
CreateThread( function()
|
||||
local currentVersion = semver(GetResourceMetadata(GetCurrentResourceName(), 'version', 0))
|
||||
local repoVersion = semver('0.0.0')
|
||||
local recordCount = 0
|
||||
|
||||
-- Get prolaser4 version from github
|
||||
PerformHttpRequest('https://raw.githubusercontent.com/TrevorBarns/ProLaser4/main/version', function(err, responseText, headers)
|
||||
if responseText ~= nil and responseText ~= '' then
|
||||
repoVersion = semver(responseText:gsub('\n', ''))
|
||||
end
|
||||
end)
|
||||
|
||||
if cfg.logging then
|
||||
if MySQL == nil then
|
||||
print('^3[WARNING]: logging enabled, but oxmysql not found. Did you uncomment the oxmysql\n\t\t lines in fxmanifest.lua?\n\n\t\t Remember, changes to fxmanifest are only loaded after running `refresh`, then `restart`.^7')
|
||||
recordCount = '^8NO CONNECTION^7'
|
||||
else
|
||||
recordCount = GetRecordCount()
|
||||
end
|
||||
end
|
||||
|
||||
Wait(1000)
|
||||
print('\n\t^7 _______________________________________________________')
|
||||
print('\t|^8 ____ __ __ __ ^7|')
|
||||
print('\t|^8 / __ \\_________ / / ____ ________ _____/ // / ^7|')
|
||||
print('\t|^8 / /_/ / ___/ __ \\/ / / __ `/ ___/ _ \\/ ___/ // /_ ^7|')
|
||||
print('\t|^8 / ____/ / / /_/ / /___/ /_/ (__ ) __/ / /__ __/ ^7|')
|
||||
print('\t|^8 /_/ /_/ \\____/_____/\\__,_/____/\\___/_/ /_/ ^7|')
|
||||
print('\t^7|_______________________________________________________|')
|
||||
print(('\t|\t INSTALLED: %-26s|'):format(currentVersion))
|
||||
print(('\t|\t LATEST: %-26s|'):format(repoVersion))
|
||||
if cfg.logging then
|
||||
if type(recordCount) == 'number' then
|
||||
print(('\t|\t RECORD COUNT: %-26s|'):format(recordCount))
|
||||
else
|
||||
print(('\t|\t RECORD COUNT: %-30s|'):format(recordCount))
|
||||
end
|
||||
end
|
||||
if currentVersion < repoVersion then
|
||||
print('\t^7|_______________________________________________________|')
|
||||
print('\t|\t ^8STABLE UPDATE AVAILABLE ^7|')
|
||||
print('\t|^8 DOWNLOAD AT: ^7|')
|
||||
print('\t|^5 github.com/TrevorBarns/ProLaser4/releases ^7|')
|
||||
end
|
||||
print('\t^7|_______________________________________________________|')
|
||||
print('\t^7| Updates, Support, Feedback: ^5discord.gg/PXQ4T8wB9 ^7|')
|
||||
print('\t^7|_______________________________________________________|\n\n')
|
||||
end)
|
||||
Loading…
Add table
Add a link
Reference in a new issue