1859 lines
71 KiB
Lua
1859 lines
71 KiB
Lua
|
--[[---------------------------------------------------------------------------------------
|
||
|
|
||
|
Wraith ARS 2X
|
||
|
Created by WolfKnight
|
||
|
|
||
|
For discussions, information on future updates, and more, join
|
||
|
my Discord: https://discord.gg/fD4e6WD
|
||
|
|
||
|
MIT License
|
||
|
|
||
|
Copyright (c) 2020-2021 WolfKnight
|
||
|
|
||
|
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.
|
||
|
|
||
|
---------------------------------------------------------------------------------------]]--
|
||
|
|
||
|
-- Cache some of the main Lua functions and libraries
|
||
|
local next = next
|
||
|
local dot = dot
|
||
|
local table = table
|
||
|
local type = type
|
||
|
local tostring = tostring
|
||
|
local math = math
|
||
|
local pairs = pairs
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
UI loading and key binds registering
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
local function RegisterKeyBinds()
|
||
|
if ( UTIL:IsResourceNameValid() ) then
|
||
|
UTIL:Log( "Registering radar commands and key binds." )
|
||
|
|
||
|
-- Opens the remote control
|
||
|
RegisterCommand( "radar_remote", function()
|
||
|
if ( not RADAR:GetKeyLockState() ) then
|
||
|
RADAR:OpenRemote()
|
||
|
end
|
||
|
end )
|
||
|
RegisterKeyMapping( "radar_remote", "Open Remote Control", "keyboard", CONFIG.keyDefaults.remote_control )
|
||
|
|
||
|
-- Locks speed from front antenna
|
||
|
RegisterCommand( "radar_fr_ant", function()
|
||
|
if ( not RADAR:GetKeyLockState() and PLY:CanControlRadar() ) then
|
||
|
RADAR:LockAntennaSpeed( "front", nil )
|
||
|
|
||
|
SYNC:LockAntennaSpeed( "front", RADAR:GetAntennaDataPacket( "front" ) )
|
||
|
end
|
||
|
end )
|
||
|
RegisterKeyMapping( "radar_fr_ant", "Front Antenna Lock/Unlock", "keyboard", CONFIG.keyDefaults.front_lock )
|
||
|
|
||
|
-- Locks speed from rear antenna
|
||
|
RegisterCommand( "radar_bk_ant", function()
|
||
|
if ( not RADAR:GetKeyLockState() and PLY:CanControlRadar() ) then
|
||
|
RADAR:LockAntennaSpeed( "rear", nil )
|
||
|
|
||
|
SYNC:LockAntennaSpeed( "rear", RADAR:GetAntennaDataPacket( "rear" ) )
|
||
|
end
|
||
|
end )
|
||
|
RegisterKeyMapping( "radar_bk_ant", "Rear Antenna Lock/Unlock", "keyboard", CONFIG.keyDefaults.rear_lock )
|
||
|
|
||
|
-- Locks front plate reader
|
||
|
RegisterCommand( "radar_fr_cam", function()
|
||
|
if ( not RADAR:GetKeyLockState() and PLY:CanControlRadar() ) then
|
||
|
READER:LockCam( "front", true, false )
|
||
|
|
||
|
SYNC:LockReaderCam( "front", READER:GetCameraDataPacket( "front" ) )
|
||
|
end
|
||
|
end )
|
||
|
RegisterKeyMapping( "radar_fr_cam", "Front Plate Reader Lock/Unlock", "keyboard", CONFIG.keyDefaults.plate_front_lock )
|
||
|
|
||
|
-- Locks rear plate reader
|
||
|
RegisterCommand( "radar_bk_cam", function()
|
||
|
if ( not RADAR:GetKeyLockState() and PLY:CanControlRadar() ) then
|
||
|
READER:LockCam( "rear", true, false )
|
||
|
|
||
|
SYNC:LockReaderCam( "rear", READER:GetCameraDataPacket( "rear" ) )
|
||
|
end
|
||
|
end )
|
||
|
RegisterKeyMapping( "radar_bk_cam", "Rear Plate Reader Lock/Unlock", "keyboard", CONFIG.keyDefaults.plate_rear_lock )
|
||
|
|
||
|
-- Toggles the key lock state
|
||
|
RegisterCommand( "radar_key_lock", function()
|
||
|
RADAR:ToggleKeyLock()
|
||
|
end )
|
||
|
RegisterKeyMapping( "radar_key_lock", "Toggle Keybind Lock", "keyboard", CONFIG.keyDefaults.key_lock )
|
||
|
|
||
|
-- Deletes all of the KVPs
|
||
|
RegisterCommand( "reset_radar_data", function()
|
||
|
DeleteResourceKvp( "wk_wars2x_ui_data" )
|
||
|
DeleteResourceKvp( "wk_wars2x_om_data" )
|
||
|
DeleteResourceKvp( "wk_wars2x_new_user" )
|
||
|
|
||
|
UTIL:Notify( "Radar data deleted, please immediately restart your game without opening the radar's remote." )
|
||
|
end, false )
|
||
|
TriggerEvent( "chat:addSuggestion", "/reset_radar_data", "Resets the KVP data stored for the wk_wars2x resource." )
|
||
|
else
|
||
|
UTIL:Log( "ERROR: Resource name is not wk_wars2x. Key binds will not be registered for compatibility reasons. Contact the server owner and ask them to change the resource name back to wk_wars2x" )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function LoadUISettings()
|
||
|
UTIL:Log( "Attempting to load saved UI settings data." )
|
||
|
|
||
|
-- Try and get the saved UI data
|
||
|
local uiData = GetResourceKvpString( "wk_wars2x_ui_data" )
|
||
|
|
||
|
-- If the data exists, then we send it off!
|
||
|
if ( uiData ~= nil ) then
|
||
|
SendNUIMessage( { _type = "loadUiSettings", data = json.decode( uiData ) } )
|
||
|
|
||
|
UTIL:Log( "Saved UI settings data loaded!" )
|
||
|
-- If the data doesn't exist, then we send the defaults
|
||
|
else
|
||
|
SendNUIMessage( { _type = "setUiDefaults", data = CONFIG.uiDefaults } )
|
||
|
|
||
|
UTIL:Log( "Could not find any saved UI settings data." )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar variables
|
||
|
|
||
|
NOTE - This is not a config, do not touch anything unless you know what
|
||
|
you are actually doing.
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
RADAR = {}
|
||
|
RADAR.vars =
|
||
|
{
|
||
|
-- Whether or not the radar's UI is visible
|
||
|
displayed = false,
|
||
|
|
||
|
-- The radar's power, the system simulates the radar unit powering up when the user clicks the
|
||
|
-- power button on the interface
|
||
|
power = false,
|
||
|
poweringUp = false,
|
||
|
|
||
|
-- Whether or not the radar should be hidden, e.g. the display is active but the player then steps
|
||
|
-- out of their vehicle
|
||
|
hidden = false,
|
||
|
|
||
|
-- These are the settings that are used in the operator menu
|
||
|
settings = {
|
||
|
-- Should the system calculate and display faster targets
|
||
|
["fastDisplay"] = CONFIG.menuDefaults["fastDisplay"],
|
||
|
|
||
|
-- Sensitivity for each radar mode, this changes how far the antennas will detect vehicles
|
||
|
["same"] = CONFIG.menuDefaults["same"],
|
||
|
["opp"] = CONFIG.menuDefaults["opp"],
|
||
|
|
||
|
-- The volume of the audible beep
|
||
|
["beep"] = CONFIG.menuDefaults["beep"],
|
||
|
|
||
|
-- The volume of the verbal lock confirmation
|
||
|
["voice"] = CONFIG.menuDefaults["voice"],
|
||
|
|
||
|
-- The volume of the plate reader audio
|
||
|
["plateAudio"] = CONFIG.menuDefaults["plateAudio"],
|
||
|
|
||
|
-- The speed unit used in conversions
|
||
|
["speedType"] = CONFIG.menuDefaults["speedType"],
|
||
|
|
||
|
-- The state of automatic speed locking
|
||
|
["fastLock"] = CONFIG.menuDefaults["fastLock"],
|
||
|
|
||
|
-- The speed limit for automatic speed locking
|
||
|
["fastLimit"] = CONFIG.menuDefaults["fastLimit"]
|
||
|
},
|
||
|
|
||
|
-- These 3 variables are for the in-radar menu that can be accessed through the remote control, the menuOptions table
|
||
|
-- stores all of the information about each of the settings the user can change
|
||
|
menuActive = false,
|
||
|
currentOptionIndex = 1,
|
||
|
menuOptions = {
|
||
|
{ displayText = { "¦¦¦", "FAS" }, optionsText = { "On¦", "Off" }, options = { true, false }, optionIndex = -1, settingText = "fastDisplay" },
|
||
|
{ displayText = { "¦SL", "SEn" }, optionsText = { "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = -1, settingText = "same" },
|
||
|
{ displayText = { "¦OP", "SEn" }, optionsText = { "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = -1, settingText = "opp" },
|
||
|
{ displayText = { "bEE", "P¦¦" }, optionsText = { "Off", "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = -1, settingText = "beep" },
|
||
|
{ displayText = { "VOI", "CE¦" }, optionsText = { "Off", "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = -1, settingText = "voice" },
|
||
|
{ displayText = { "PLt", "AUd" }, optionsText = { "Off", "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = -1, settingText = "plateAudio" },
|
||
|
{ displayText = { "Uni", "tS¦" }, optionsText = { "USA", "INT" }, options = { "mph", "kmh" }, optionIndex = -1, settingText = "speedType" }
|
||
|
},
|
||
|
|
||
|
-- Player's vehicle speed, mainly used in the dynamic thread wait update
|
||
|
patrolSpeed = 0,
|
||
|
|
||
|
-- Antennas, this table contains all of the data needed for operation of the front and rear antennas
|
||
|
antennas = {
|
||
|
-- Variables for the front antenna
|
||
|
[ "front" ] = {
|
||
|
xmit = false, -- Whether the antenna is transmitting or in hold
|
||
|
mode = 0, -- Current antenna mode, 0 = none, 1 = same, 2 = opp, 3 = same and opp
|
||
|
speed = 0, -- Speed of the vehicle caught by the front antenna
|
||
|
dir = nil, -- Direction the caught vehicle is going, 0 = towards, 1 = away
|
||
|
fastSpeed = 0, -- Speed of the fastest vehicle caught by the front antenna
|
||
|
fastDir = nil, -- Direction the fastest vehicle is going
|
||
|
speedLocked = false, -- A speed has been locked for this antenna
|
||
|
lockedSpeed = nil, -- The locked speed
|
||
|
lockedDir = nil, -- The direction of the vehicle that was locked
|
||
|
lockedType = nil -- The locked type, 1 = strongest, 2 = fastest
|
||
|
},
|
||
|
|
||
|
-- Variables for the rear antenna
|
||
|
[ "rear" ] = {
|
||
|
xmit = false, -- Whether the antenna is transmitting or in hold
|
||
|
mode = 0, -- Current antenna mode, 0 = none, 1 = same, 2 = opp, 3 = same and opp
|
||
|
speed = 0, -- Speed of the vehicle caught by the front antenna
|
||
|
dir = nil, -- Direction the caught vehicle is going, 0 = towards, 1 = away
|
||
|
fastSpeed = 0, -- Speed of the fastest vehicle caught by the front antenna
|
||
|
fastDir = nil, -- Direction the fastest vehicle is going
|
||
|
speedLocked = false, -- A speed has been locked for this antenna
|
||
|
lockedSpeed = nil, -- The locked speed
|
||
|
lockedDir = nil, -- The direction of the vehicle that was locked
|
||
|
lockedType = nil -- The locked type, 1 = strongest, 2 = fastest
|
||
|
}
|
||
|
},
|
||
|
|
||
|
-- The maximum distance that the radar system's ray traces can go, changing this will change the max
|
||
|
-- distance in-game, but I wouldn't really put it more than 500.0
|
||
|
maxCheckDist = 350.0,
|
||
|
|
||
|
-- Cached dynamic vehicle sphere sizes, automatically populated when the system is running
|
||
|
sphereSizes = {},
|
||
|
|
||
|
-- Table to store tables for hit entities of captured vehicles
|
||
|
capturedVehicles = {},
|
||
|
|
||
|
-- Table to store the valid vehicle models
|
||
|
validVehicles = {},
|
||
|
|
||
|
-- The current vehicle data for display
|
||
|
activeVehicles = {},
|
||
|
|
||
|
-- Vehicle pool, automatically populated when the system is running, holds all of the current
|
||
|
-- vehicle IDs for the player using entity enumeration (see cl_utils.lua)
|
||
|
vehiclePool = {},
|
||
|
|
||
|
-- Ray trace state, this is used so the radar system doesn't initiate another set of ray traces until
|
||
|
-- the current set has finished
|
||
|
rayTraceState = 0,
|
||
|
|
||
|
-- Number of ray traces, automatically cached when the system first runs
|
||
|
numberOfRays = 0,
|
||
|
|
||
|
-- The wait time for the ray trace system, this changes dynamically based on if the player's vehicle is stationary
|
||
|
-- or not
|
||
|
threadWaitTime = 500,
|
||
|
|
||
|
-- Key lock, when true, prevents any of the radar's key events from working, like the ELS key lock
|
||
|
keyLock = false
|
||
|
}
|
||
|
|
||
|
-- Speed conversion values
|
||
|
RADAR.speedConversions = { ["mph"] = 2.236936, ["kmh"] = 3.6 }
|
||
|
|
||
|
-- These vectors are used in the custom ray tracing system
|
||
|
RADAR.rayTraces = {
|
||
|
{ startVec = { x = 0.0 }, endVec = { x = 0.0, y = 0.0 }, rayType = "same" },
|
||
|
{ startVec = { x = -5.0 }, endVec = { x = -5.0, y = 0.0 }, rayType = "same" },
|
||
|
{ startVec = { x = 5.0 }, endVec = { x = 5.0, y = 0.0 }, rayType = "same" },
|
||
|
{ startVec = { x = -10.0 }, endVec = { x = -10.0, y = 0.0 }, rayType = "opp" },
|
||
|
{ startVec = { x = -17.0 }, endVec = { x = -17.0, y = 0.0 }, rayType = "opp" }
|
||
|
}
|
||
|
|
||
|
-- Each of these are used for sorting the captured vehicle data, the 'strongest' filter is used for the main
|
||
|
-- target window of each antenna, whereas the 'fastest' filter is used for the fast target window of each antenna
|
||
|
RADAR.sorting = {
|
||
|
strongest = function( a, b ) return a.size > b.size end,
|
||
|
fastest = function( a, b ) return a.speed > b.speed end
|
||
|
}
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar essentials functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Returns if the radar's power is on or off
|
||
|
function RADAR:IsPowerOn()
|
||
|
return self.vars.power
|
||
|
end
|
||
|
|
||
|
-- Returns if the radar system is powering up, the powering up stage only takes 2 seconds
|
||
|
function RADAR:IsPoweringUp()
|
||
|
return self.vars.poweringUp
|
||
|
end
|
||
|
|
||
|
-- Allows the powering up state variable to be set
|
||
|
function RADAR:SetPoweringUpState( state )
|
||
|
self.vars.poweringUp = state
|
||
|
end
|
||
|
|
||
|
-- Toggles the radar power
|
||
|
function RADAR:SetPowerState( state, instantOverride )
|
||
|
local currentState = self:IsPowerOn()
|
||
|
|
||
|
-- Only power up if the system is not already powering up
|
||
|
if ( not self:IsPoweringUp() and currentState ~= state ) then
|
||
|
-- Toggle the power variable
|
||
|
self.vars.power = state
|
||
|
|
||
|
-- Send the NUI message to toggle the power
|
||
|
SendNUIMessage( { _type = "radarPower", state = state, override = instantOverride, fast = self:IsFastDisplayEnabled() } )
|
||
|
|
||
|
-- Power is now turned on
|
||
|
if ( self:IsPowerOn() ) then
|
||
|
-- Also make sure the operator menu is inactive
|
||
|
self:SetMenuState( false )
|
||
|
|
||
|
-- Only do the power up simulation if allowed
|
||
|
if ( not instantOverride ) then
|
||
|
-- Tell the system the radar is 'powering up'
|
||
|
self:SetPoweringUpState( true )
|
||
|
|
||
|
-- Set a 2 second countdown
|
||
|
Citizen.SetTimeout( 2000, function()
|
||
|
-- Tell the system the radar has 'powered up'
|
||
|
self:SetPoweringUpState( false )
|
||
|
|
||
|
-- Let the UI side know the system has loaded
|
||
|
SendNUIMessage( { _type = "poweredUp", fast = self:IsFastDisplayEnabled() } )
|
||
|
end )
|
||
|
end
|
||
|
else
|
||
|
-- If the system is being turned off, then we reset the antennas
|
||
|
self:ResetAntenna( "front" )
|
||
|
self:ResetAntenna( "rear" )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Toggles the display state of the radar system
|
||
|
function RADAR:ToggleDisplayState()
|
||
|
-- Toggle the display variable
|
||
|
self.vars.displayed = not self.vars.displayed
|
||
|
|
||
|
-- Send the toggle message to the NUI side
|
||
|
SendNUIMessage( { _type = "setRadarDisplayState", state = self:GetDisplayState() } )
|
||
|
end
|
||
|
|
||
|
-- Gets the display state
|
||
|
function RADAR:GetDisplayState()
|
||
|
return self.vars.displayed
|
||
|
end
|
||
|
|
||
|
-- Return the state of the fastDisplay setting, short hand direct way to check if the fast system is enabled
|
||
|
function RADAR:IsFastDisplayEnabled()
|
||
|
return self:GetSettingValue( "fastDisplay" )
|
||
|
end
|
||
|
|
||
|
-- Returns if either of the antennas are transmitting
|
||
|
function RADAR:IsEitherAntennaOn()
|
||
|
return self:IsAntennaTransmitting( "front" ) or self:IsAntennaTransmitting( "rear" )
|
||
|
end
|
||
|
|
||
|
-- Sends an update to the NUI side with the current state of the antennas and if the fast system is enabled
|
||
|
function RADAR:SendSettingUpdate()
|
||
|
-- Create a table to store the setting information for the antennas
|
||
|
local antennas = {}
|
||
|
|
||
|
-- Iterate through each antenna and grab the relevant information
|
||
|
for ant in UTIL:Values( { "front", "rear" } ) do
|
||
|
antennas[ant] = {}
|
||
|
antennas[ant].xmit = self:IsAntennaTransmitting( ant )
|
||
|
antennas[ant].mode = self:GetAntennaMode( ant )
|
||
|
antennas[ant].speedLocked = self:IsAntennaSpeedLocked( ant )
|
||
|
antennas[ant].fast = self:ShouldFastBeDisplayed( ant )
|
||
|
end
|
||
|
|
||
|
-- Send a message to the NUI side with the current state of the antennas
|
||
|
SendNUIMessage( { _type = "settingUpdate", antennaData = antennas } )
|
||
|
end
|
||
|
|
||
|
-- Returns if a main task can be performed
|
||
|
-- A main task such as the ray trace thread should only run if the radar's power is on, the system is not in the
|
||
|
-- process of powering up, and the operator menu is not open
|
||
|
function RADAR:CanPerformMainTask()
|
||
|
return self:IsPowerOn() and not self:IsPoweringUp() and not self:IsMenuOpen()
|
||
|
end
|
||
|
|
||
|
-- Returns/sets what the dynamic thread wait time is
|
||
|
function RADAR:GetThreadWaitTime() return self.vars.threadWaitTime end
|
||
|
function RADAR:SetThreadWaitTime( time ) self.vars.threadWaitTime = time end
|
||
|
|
||
|
-- Returns/sets the radr's display hidden state
|
||
|
function RADAR:GetDisplayHidden() return self.vars.hidden end
|
||
|
function RADAR:SetDisplayHidden( state ) self.vars.hidden = state end
|
||
|
|
||
|
-- Opens the remote only if the pause menu is not open and the player's vehicle state is valid, as the
|
||
|
-- passenger can also open the remote, we check the config variable as well.
|
||
|
function RADAR:OpenRemote()
|
||
|
if ( not IsPauseMenuActive() and PLY:CanViewRadar() ) then
|
||
|
-- Get the remote open state from the other player
|
||
|
local openByOtherPly = SYNC:IsRemoteAlreadyOpen( PLY:GetOtherPed() )
|
||
|
|
||
|
-- Check that the remote can be opened
|
||
|
if ( not openByOtherPly ) then
|
||
|
-- Tell the NUI side to open the remote
|
||
|
SendNUIMessage( { _type = "openRemote" } )
|
||
|
|
||
|
SYNC:SetRemoteOpenState( true )
|
||
|
|
||
|
if ( CONFIG.allow_quick_start_video ) then
|
||
|
-- Display the new user popup if we can
|
||
|
local show = GetResourceKvpInt( "wk_wars2x_new_user" )
|
||
|
|
||
|
if ( show == 0 ) then
|
||
|
SendNUIMessage( { _type = "showNewUser" } )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Bring focus to the NUI side
|
||
|
SetNuiFocus( true, true )
|
||
|
else
|
||
|
UTIL:Notify( "Another player already has the remote open." )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Event to open the remote
|
||
|
RegisterNetEvent( "wk:openRemote" )
|
||
|
AddEventHandler( "wk:openRemote", function()
|
||
|
RADAR:OpenRemote()
|
||
|
end )
|
||
|
|
||
|
-- Returns if the passenger can view the radar too
|
||
|
function RADAR:IsPassengerViewAllowed()
|
||
|
return CONFIG.allow_passenger_view
|
||
|
end
|
||
|
|
||
|
-- Returns if the passenger can control the radar and plate reader, reliant on the passenger being
|
||
|
-- able to view the radar and plate reader too
|
||
|
function RADAR:IsPassengerControlAllowed()
|
||
|
return CONFIG.allow_passenger_view and CONFIG.allow_passenger_control
|
||
|
end
|
||
|
|
||
|
-- Returns if we only auto lock vehicle speeds if said vehicle is a player
|
||
|
function RADAR:OnlyLockFastPlayers()
|
||
|
return CONFIG.only_lock_players
|
||
|
end
|
||
|
|
||
|
-- Returns if the fast limit option should be available for the radar
|
||
|
function RADAR:IsFastLimitAllowed()
|
||
|
return CONFIG.allow_fast_limit
|
||
|
end
|
||
|
|
||
|
-- Only create the functions if the fast limit config option is enabled
|
||
|
if ( RADAR:IsFastLimitAllowed() ) then
|
||
|
-- Adds settings into the radar's variables for when the allow_fast_limit variable is true
|
||
|
function RADAR:CreateFastLimitConfig()
|
||
|
-- Create the options for the menu
|
||
|
local fastOptions =
|
||
|
{
|
||
|
{ displayText = { "FAS", "Loc" }, optionsText = { "On¦", "Off" }, options = { true, false }, optionIndex = 2, settingText = "fastLock" },
|
||
|
{ displayText = { "FAS", "SPd" }, optionsText = {}, options = {}, optionIndex = 12, settingText = "fastLimit" }
|
||
|
}
|
||
|
|
||
|
-- Iterate from 5 to 200 in steps of 5 and insert into the fast limit option
|
||
|
for i = 5, 200, 5 do
|
||
|
local text = UTIL:FormatSpeed( i )
|
||
|
|
||
|
table.insert( fastOptions[2].optionsText, text )
|
||
|
table.insert( fastOptions[2].options, i )
|
||
|
end
|
||
|
|
||
|
-- Add the fast options to the main menu options table
|
||
|
if ( CONFIG.fast_limit_first_in_menu ) then
|
||
|
table.insert( self.vars.menuOptions, 1, fastOptions[2] ) --FasSpd
|
||
|
table.insert( self.vars.menuOptions, 2, fastOptions[1] ) --FasLoc
|
||
|
else
|
||
|
table.insert( self.vars.menuOptions, fastOptions[1] ) --FasLoc
|
||
|
table.insert( self.vars.menuOptions, fastOptions[2] ) --FasSpd
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns the numerical fast limit
|
||
|
function RADAR:GetFastLimit()
|
||
|
return self:GetSettingValue( "fastLimit" )
|
||
|
end
|
||
|
|
||
|
-- Returns if the fast lock menu option is on or off
|
||
|
function RADAR:IsFastLockEnabled()
|
||
|
return self:GetSettingValue( "fastLock" )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Toggles the internal key lock state, which stops any of the radar's key binds from working
|
||
|
function RADAR:ToggleKeyLock()
|
||
|
-- Check the player state is valid
|
||
|
if ( PLY:CanViewRadar() ) then
|
||
|
-- Toggle the key lock variable
|
||
|
self.vars.keyLock = not self.vars.keyLock
|
||
|
|
||
|
-- Tell the NUI side to display the key lock message
|
||
|
SendNUIMessage( { _type = "displayKeyLock", state = self:GetKeyLockState() } )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns the key lock state
|
||
|
function RADAR:GetKeyLockState()
|
||
|
return self.vars.keyLock
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar menu functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Sets the menu state to the given state
|
||
|
function RADAR:SetMenuState( state )
|
||
|
-- Make sure that the radar's power is on
|
||
|
if ( self:IsPowerOn() ) then
|
||
|
-- Set the menuActive variable to the given state
|
||
|
self.vars.menuActive = state
|
||
|
|
||
|
-- If we are opening the menu, make sure the first item is displayed
|
||
|
if ( state ) then
|
||
|
self.vars.currentOptionIndex = 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Closes the operator menu
|
||
|
function RADAR:CloseMenu( playAudio )
|
||
|
-- Set the internal menu state to be closed (false)
|
||
|
RADAR:SetMenuState( false )
|
||
|
|
||
|
-- Send a setting update to the NUI side
|
||
|
RADAR:SendSettingUpdate()
|
||
|
|
||
|
-- Play a menu done beep
|
||
|
if ( playAudio or playAudio == nil ) then
|
||
|
SendNUIMessage( { _type = "audio", name = "done", vol = RADAR:GetSettingValue( "beep" ) } )
|
||
|
end
|
||
|
|
||
|
-- Save the operator menu values
|
||
|
local omData = json.encode( RADAR.vars.settings )
|
||
|
SetResourceKvp( "wk_wars2x_om_data", omData )
|
||
|
|
||
|
-- Send the operator menu to the passenger if allowed
|
||
|
if ( self:IsPassengerViewAllowed() ) then
|
||
|
local updatedOMData = self:GetOMTableData()
|
||
|
SYNC:SendUpdatedOMData( updatedOMData )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns if the operator menu is open
|
||
|
function RADAR:IsMenuOpen()
|
||
|
return self.vars.menuActive
|
||
|
end
|
||
|
|
||
|
-- This function changes the menu index variable so the user can iterate through the options in the operator menu
|
||
|
function RADAR:ChangeMenuIndex()
|
||
|
-- Create a temporary variable of the current menu index plus 1
|
||
|
local temp = self.vars.currentOptionIndex + 1
|
||
|
|
||
|
-- If the temporary value is larger than how many options there are, set it to 1, this way the menu
|
||
|
-- loops back round to the start of the menu
|
||
|
if ( temp > #self.vars.menuOptions ) then
|
||
|
temp = 1
|
||
|
end
|
||
|
|
||
|
-- Set the menu index variable to the temporary value we created
|
||
|
self.vars.currentOptionIndex = temp
|
||
|
|
||
|
-- Call the function to send an update to the NUI side
|
||
|
self:SendMenuUpdate()
|
||
|
end
|
||
|
|
||
|
-- Returns the option table of the current menu index
|
||
|
function RADAR:GetMenuOptionTable()
|
||
|
return self.vars.menuOptions[self.vars.currentOptionIndex]
|
||
|
end
|
||
|
|
||
|
-- Changes the index for an individual option
|
||
|
-- E.g. { "On" "Off" }, index = 2 would be "Off"
|
||
|
function RADAR:SetMenuOptionIndex( index )
|
||
|
self.vars.menuOptions[self.vars.currentOptionIndex].optionIndex = index
|
||
|
end
|
||
|
|
||
|
-- Returns the option value for the current option
|
||
|
function RADAR:GetMenuOptionValue()
|
||
|
local opt = self:GetMenuOptionTable()
|
||
|
local index = opt.optionIndex
|
||
|
|
||
|
return opt.options[index]
|
||
|
end
|
||
|
|
||
|
-- This function is similar to RADAR:ChangeMenuIndex() but allows for iterating forward and backward through options
|
||
|
function RADAR:ChangeMenuOption( dir )
|
||
|
-- Get the option table of the currently selected option
|
||
|
local opt = self:GetMenuOptionTable()
|
||
|
|
||
|
-- Get the current option index of the selected option
|
||
|
local index = opt.optionIndex
|
||
|
|
||
|
-- Cache the size of this setting's options table
|
||
|
local size = #opt.options
|
||
|
|
||
|
-- As the XMIT/HOLD buttons are used for changing the option values, we have to check which button is being pressed
|
||
|
if ( dir == "front" ) then
|
||
|
index = index + 1
|
||
|
if ( index > size ) then index = 1 end
|
||
|
elseif ( dir == "rear" ) then
|
||
|
index = index - 1
|
||
|
if ( index < 1 ) then index = size end
|
||
|
end
|
||
|
|
||
|
-- Update the option's index
|
||
|
self:SetMenuOptionIndex( index )
|
||
|
|
||
|
-- Change the value of the setting in the main RADAR.vars.settings table
|
||
|
self:SetSettingValue( opt.settingText, self:GetMenuOptionValue() )
|
||
|
|
||
|
-- Call the function to send an update to the NUI side
|
||
|
self:SendMenuUpdate()
|
||
|
end
|
||
|
|
||
|
-- Returns what text should be displayed in the boxes for the current option
|
||
|
-- E.g. "¦SL" "SEN"
|
||
|
function RADAR:GetMenuOptionDisplayText()
|
||
|
return self:GetMenuOptionTable().displayText
|
||
|
end
|
||
|
|
||
|
-- Returns the option text of the currently selected setting
|
||
|
function RADAR:GetMenuOptionText()
|
||
|
local opt = self:GetMenuOptionTable()
|
||
|
|
||
|
return opt.optionsText[opt.optionIndex]
|
||
|
end
|
||
|
|
||
|
-- Sends a message to the NUI side with updated information on what should be displayed for the menu
|
||
|
function RADAR:SendMenuUpdate()
|
||
|
SendNUIMessage( { _type = "menu", text = self:GetMenuOptionDisplayText(), option = self:GetMenuOptionText() } )
|
||
|
end
|
||
|
|
||
|
-- Used to set individual settings within RADAR.vars.settings, as all of the settings use string keys, using this
|
||
|
-- function makes updating settings easier
|
||
|
function RADAR:SetSettingValue( setting, value )
|
||
|
-- Make sure that we're not trying to set a nil value for the setting
|
||
|
if ( value ~= nil ) then
|
||
|
-- Set the setting's value
|
||
|
self.vars.settings[setting] = value
|
||
|
|
||
|
-- If the setting that's being updated is same or opp, then we update the end coordinates for the ray tracer
|
||
|
if ( setting == "same" or setting == "opp" ) then
|
||
|
self:UpdateRayEndCoords()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns the value of the given setting
|
||
|
function RADAR:GetSettingValue( setting )
|
||
|
return self.vars.settings[setting]
|
||
|
end
|
||
|
|
||
|
-- Attempts to load the saved operator menu data
|
||
|
function RADAR:LoadOMData()
|
||
|
UTIL:Log( "Attempting to load saved operator menu data." )
|
||
|
|
||
|
-- Try and get the data
|
||
|
local rawData = GetResourceKvpString( "wk_wars2x_om_data" )
|
||
|
|
||
|
-- If the data exists, decode it and replace the operator menu table
|
||
|
if ( rawData ~= nil ) then
|
||
|
local omData = json.decode( rawData )
|
||
|
self.vars.settings = omData
|
||
|
|
||
|
UTIL:Log( "Saved operator menu data loaded!" )
|
||
|
else
|
||
|
UTIL:Log( "Could not find any saved operator menu data." )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Updates the operator menu option indexes, as the default menu values can be changed in the config, we
|
||
|
-- need to update the indexes otherwise the menu will display the wrong values
|
||
|
function RADAR:UpdateOptionIndexes( loadSaved )
|
||
|
if ( loadSaved ) then
|
||
|
self:LoadOMData()
|
||
|
end
|
||
|
|
||
|
-- Iterate through each of the internal settings
|
||
|
for k, v in pairs( self.vars.settings ) do
|
||
|
-- Iterate through all of the menu options
|
||
|
for i, t in pairs( self.vars.menuOptions ) do
|
||
|
-- If the current menu option is the same as the current setting
|
||
|
if ( t.settingText == k ) then
|
||
|
-- Iterate through the option values of the current menu option
|
||
|
for oi, ov in pairs( t.options ) do
|
||
|
-- If the value of the current option set in the config matches the current value of
|
||
|
-- the option value, then we update the option index variable
|
||
|
if ( v == ov ) then
|
||
|
t.optionIndex = oi
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar basics functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Returns the patrol speed value stored
|
||
|
function RADAR:GetPatrolSpeed() return self.vars.patrolSpeed end
|
||
|
|
||
|
-- Returns the current vehicle pool
|
||
|
function RADAR:GetVehiclePool() return self.vars.vehiclePool end
|
||
|
|
||
|
-- Returns the maximum distance a ray trace can go
|
||
|
function RADAR:GetMaxCheckDist() return self.vars.maxCheckDist end
|
||
|
|
||
|
-- Returns the table sorting function 'strongest'
|
||
|
function RADAR:GetStrongestSortFunc() return self.sorting.strongest end
|
||
|
|
||
|
-- Returns the table sorting function 'fastest'
|
||
|
function RADAR:GetFastestSortFunc() return self.sorting.fastest end
|
||
|
|
||
|
-- Sets the patrol speed to a formatted version of the given number
|
||
|
function RADAR:SetPatrolSpeed( speed )
|
||
|
if ( type( speed ) == "number" ) then
|
||
|
self.vars.patrolSpeed = self:GetVehSpeedConverted( speed )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Sets the vehicle pool to the given value if it's a table
|
||
|
function RADAR:SetVehiclePool( pool )
|
||
|
if ( type( pool ) == "table" ) then
|
||
|
self.vars.vehiclePool = pool
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar ray trace functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Returns what the current ray trace state is
|
||
|
function RADAR:GetRayTraceState() return self.vars.rayTraceState end
|
||
|
|
||
|
-- Caches the number of ray traces in RADAR.rayTraces
|
||
|
function RADAR:CacheNumRays() self.vars.numberOfRays = #self.rayTraces end
|
||
|
|
||
|
-- Returns the number of ray traces the system has
|
||
|
function RADAR:GetNumOfRays() return self.vars.numberOfRays end
|
||
|
|
||
|
-- Increases the system's ray trace state ny 1
|
||
|
function RADAR:IncreaseRayTraceState() self.vars.rayTraceState = self.vars.rayTraceState + 1 end
|
||
|
|
||
|
-- Resets the ray trace state to 0
|
||
|
function RADAR:ResetRayTraceState() self.vars.rayTraceState = 0 end
|
||
|
|
||
|
-- This function is used to determine if a sphere intersect is in front or behind the player's vehicle, the
|
||
|
-- sphere intersect calculation has a 'tProj' value that is a line from the centre of the sphere that goes onto
|
||
|
-- the line being traced. This value will either be positive or negative and can be used to work out the
|
||
|
-- relative position of a point.
|
||
|
function RADAR:GetIntersectedVehIsFrontOrRear( t )
|
||
|
if ( t > 8.0 ) then
|
||
|
return 1 -- vehicle is in front
|
||
|
elseif ( t < -8.0 ) then
|
||
|
return -1 -- vehicle is behind
|
||
|
end
|
||
|
|
||
|
return 0 -- vehicle is next to self
|
||
|
end
|
||
|
|
||
|
-- This function is used to check if a line going from point A to B intersects with a given sphere, it's used in
|
||
|
-- the radar system to check if the patrol vehicle can detect any vehicles. As the default ray trace system in GTA
|
||
|
-- cannot detect vehicles beyond 40~ units, my system acts as a replacement that allows the detection of vehicles
|
||
|
-- much further away (400+ units). Also, as my system uses sphere intersections, each sphere can have a different
|
||
|
-- radius, which means that larger vehicles can have larger spheres, and smaller vehicles can have smaller spheres.
|
||
|
function RADAR:GetLineHitsSphereAndDir( c, radius, rs, re )
|
||
|
-- Take the vector3's and turn them into vector2's, this way all of the calculations below are for an
|
||
|
-- infinite cylinder rather than a sphere, which also means that vehicles can be detected even when on
|
||
|
-- an incline!
|
||
|
local rayStart = vector2( rs.x, rs.y )
|
||
|
local rayEnd = vector2( re.x, re.y )
|
||
|
local centre = vector2( c.x, c.y )
|
||
|
|
||
|
-- First we get the normalised ray, this way we then know the direction the ray is going
|
||
|
local rayNorm = norm( rayEnd - rayStart )
|
||
|
|
||
|
-- Then we calculate the ray from the start point to the centre position of the sphere
|
||
|
local rayToCentre = centre - rayStart
|
||
|
|
||
|
-- Now that we have the ray to the centre of the sphere, and the normalised ray direction, we
|
||
|
-- can calculate the shortest point from the centre of the sphere onto the ray itself. This
|
||
|
-- would then give us the opposite side of the right angled triangle. All of the resulting
|
||
|
-- values are also in squared form, as performing square root functions is slower.
|
||
|
local tProj = dot( rayToCentre, rayNorm )
|
||
|
local oppLenSqr = dot( rayToCentre, rayToCentre ) - ( tProj * tProj )
|
||
|
|
||
|
-- Square the radius
|
||
|
local radiusSqr = radius * radius
|
||
|
|
||
|
-- Calculate the distance of the ray trace to make sure we only return valid results if the trace
|
||
|
-- is actually within the distance
|
||
|
local rayDist = #( rayEnd - rayStart )
|
||
|
local distToCentre = #( rayStart - centre ) - ( radius * 2 )
|
||
|
|
||
|
-- Now all we have to do is compare the squared opposite length and the radius squared, this
|
||
|
-- will then tell us if the ray intersects with the sphere.
|
||
|
if ( oppLenSqr < radiusSqr and not ( distToCentre > rayDist ) ) then
|
||
|
return true, self:GetIntersectedVehIsFrontOrRear( tProj )
|
||
|
end
|
||
|
|
||
|
return false, nil
|
||
|
end
|
||
|
|
||
|
-- This function is used to check if the target vehicle is in the same general traffic flow as the player's vehicle
|
||
|
-- is sitting. If the angle is too great, then the radar would have an incorrect return for the speed.
|
||
|
function RADAR:IsVehicleInTraffic( tgtVeh, relPos )
|
||
|
local tgtHdg = GetEntityHeading( tgtVeh )
|
||
|
local plyHdg = GetEntityHeading( PLY.veh )
|
||
|
|
||
|
-- Work out the heading difference, but also take into account extreme opposites (e.g. 5deg and 350deg)
|
||
|
local hdgDiff = math.abs( ( plyHdg - tgtHdg + 180 ) % 360 - 180 )
|
||
|
|
||
|
if ( relPos == 1 and hdgDiff > 45 and hdgDiff < 135 ) then
|
||
|
return false
|
||
|
elseif ( relPos == -1 and hdgDiff > 45 and ( hdgDiff < 135 or hdgDiff > 215 ) ) then
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- This function is the main custom ray trace function, it performs most of the major tasks for checking a vehicle
|
||
|
-- is valid and should be tested. It also makes use of the LOS native to make sure that we can only trace a vehicle
|
||
|
-- if actually nas a direct line of sight with the player's vehicle, this way we don't pick up vehicles behind walls
|
||
|
-- for example. It then creates a dynamic sphere for the vehicle based on the actual model dimensions of it, adds a
|
||
|
-- small bit of realism, as real radars usually return the strongest target speed.
|
||
|
function RADAR:ShootCustomRay( plyVeh, veh, s, e )
|
||
|
-- Get the world coordinates of the target vehicle
|
||
|
local pos = GetEntityCoords( veh )
|
||
|
|
||
|
-- Calculate the distance between the target vehicle and the start point of the ray trace, note how we don't
|
||
|
-- use GetDistanceBetweenCoords or Vdist, the method below still returns the same result with less cpu time
|
||
|
local dist = #( pos - s )
|
||
|
|
||
|
-- We only perform a trace on the target vehicle if it exists, isn't the player's vehicle, and the distance is
|
||
|
-- less than the max distance defined by the system
|
||
|
if ( DoesEntityExist( veh ) and veh ~= plyVeh and dist < self:GetMaxCheckDist() ) then
|
||
|
-- Get the speed of the target vehicle
|
||
|
local entSpeed = GetEntitySpeed( veh )
|
||
|
|
||
|
-- Check that the target vehicle is within the line of sight of the player's vehicle
|
||
|
local visible = HasEntityClearLosToEntity( plyVeh, veh, 15 ) -- 13 seems okay, 15 too (doesn't grab ents through ents)
|
||
|
|
||
|
-- Get the pitch of the player's vehicle
|
||
|
local pitch = GetEntityPitch( plyVeh )
|
||
|
|
||
|
-- Now we check that the target vehicle is moving and is visible
|
||
|
if ( entSpeed > 0.1 and ( pitch > -35 and pitch < 35 ) and visible ) then
|
||
|
-- Get the dynamic radius as well as the size of the target vehicle
|
||
|
local radius, size = self:GetDynamicRadius( veh )
|
||
|
|
||
|
-- Check that the trace line intersects with the target vehicle's sphere
|
||
|
local hit, relPos = self:GetLineHitsSphereAndDir( pos, radius, s, e )
|
||
|
|
||
|
-- Return all of the information if the vehicle was hit and is in the flow of traffic
|
||
|
if ( hit and self:IsVehicleInTraffic( veh, relPos ) ) then
|
||
|
return true, relPos, dist, entSpeed, size
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Return a whole lot of nothing
|
||
|
return false, nil, nil, nil, nil
|
||
|
end
|
||
|
|
||
|
-- This function is used to gather all of the data on vehicles that have been hit by the given trace line, when
|
||
|
-- a vehicle is hit, all of the information about that vehicle is put into a keyless table which is then inserted
|
||
|
-- into a main table. When the loop has finished, the function then returns the table with all of the data.
|
||
|
function RADAR:GetVehsHitByRay( ownVeh, vehs, s, e )
|
||
|
-- Create the table that will be used to store all of the results
|
||
|
local caughtVehs = {}
|
||
|
|
||
|
-- Set the variable to say if there has been data collected
|
||
|
local hasData = false
|
||
|
|
||
|
-- Iterate through all of the vehicles
|
||
|
for _, veh in pairs( vehs ) do
|
||
|
-- Shoot a custom ray trace to see if the vehicle gets hit
|
||
|
local hit, relativePos, distance, speed, size = self:ShootCustomRay( ownVeh, veh, s, e )
|
||
|
|
||
|
-- If the vehicle is hit, then we create a table containing all of the information
|
||
|
if ( hit ) then
|
||
|
-- Create the table to store the data
|
||
|
local vehData = {}
|
||
|
vehData.veh = veh
|
||
|
vehData.relPos = relativePos
|
||
|
vehData.dist = distance
|
||
|
vehData.speed = speed
|
||
|
vehData.size = size
|
||
|
|
||
|
-- Insert the table into the caught vehicles table
|
||
|
table.insert( caughtVehs, vehData )
|
||
|
|
||
|
-- Change the has data variable to true, this way the table will be returned
|
||
|
hasData = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- If the caughtVehs table actually has data, then return it
|
||
|
if ( hasData ) then return caughtVehs end
|
||
|
end
|
||
|
|
||
|
-- This function is used to gather all of the vehicles hit by a given line trace, and then insert it into the
|
||
|
-- internal captured vehicles table.
|
||
|
function RADAR:CreateRayThread( vehs, from, startX, endX, endY, rayType )
|
||
|
-- Get the start and end points for the ray trace based on the given start and end coordinates
|
||
|
local startPoint = GetOffsetFromEntityInWorldCoords( from, startX, 0.0, 0.0 )
|
||
|
local endPoint = GetOffsetFromEntityInWorldCoords( from, endX, endY, 0.0 )
|
||
|
|
||
|
-- Get all of the vehicles hit by the ray
|
||
|
local hitVehs = self:GetVehsHitByRay( from, vehs, startPoint, endPoint )
|
||
|
|
||
|
-- Insert the captured vehicle data and pass the ray type too
|
||
|
self:InsertCapturedVehicleData( hitVehs, rayType )
|
||
|
|
||
|
-- Increase the ray trace state
|
||
|
self:IncreaseRayTraceState()
|
||
|
end
|
||
|
|
||
|
-- This function iterates through each of the traces defined in RADAR.rayTraces and creates a 'thread' for
|
||
|
-- them, passing along all of the vehicle pool data and the player's vehicle
|
||
|
function RADAR:CreateRayThreads( ownVeh, vehicles )
|
||
|
for _, v in pairs( self.rayTraces ) do
|
||
|
self:CreateRayThread( vehicles, ownVeh, v.startVec.x, v.endVec.x, v.endVec.y, v.rayType )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- When the user changes either the same lane or opp lane sensitivity from within the operator menu, this function
|
||
|
-- is then called to update the end coordinates for all of the traces
|
||
|
function RADAR:UpdateRayEndCoords()
|
||
|
for _, v in pairs( self.rayTraces ) do
|
||
|
-- Calculate what the new end coordinate should be
|
||
|
local endY = self:GetSettingValue( v.rayType ) * self:GetMaxCheckDist()
|
||
|
|
||
|
-- Update the end Y coordinate in the traces table
|
||
|
v.endVec.y = endY
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar antenna functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Toggles the state of the given antenna between hold and transmitting, only works if the radar's power is
|
||
|
-- on. Also runs a callback function when present.
|
||
|
function RADAR:ToggleAntenna( ant )
|
||
|
-- Check power is on
|
||
|
if ( self:IsPowerOn() ) then
|
||
|
-- Toggle the given antennas state
|
||
|
self.vars.antennas[ant].xmit = not self.vars.antennas[ant].xmit
|
||
|
|
||
|
-- Update the interface with the new antenna transmit state
|
||
|
SendNUIMessage( { _type = "antennaXmit", ant = ant, on = self:IsAntennaTransmitting( ant ) } )
|
||
|
|
||
|
-- Play some audio specific to the transmit state
|
||
|
SendNUIMessage( { _type = "audio", name = self:IsAntennaTransmitting( ant ) and "xmit_on" or "xmit_off", vol = self:GetSettingValue( "beep" ) } )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns if the given antenna is transmitting
|
||
|
function RADAR:IsAntennaTransmitting( ant ) return self.vars.antennas[ant].xmit end
|
||
|
|
||
|
-- Returns if the given relative position value is for the front or rear antenna
|
||
|
function RADAR:GetAntennaTextFromNum( relPos )
|
||
|
if ( relPos == 1 ) then
|
||
|
return "front"
|
||
|
elseif ( relPos == -1 ) then
|
||
|
return "rear"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns the mode of the given antenna
|
||
|
function RADAR:GetAntennaMode( ant ) return self.vars.antennas[ant].mode end
|
||
|
|
||
|
-- Sets the mode of the given antenna if the mode is valid and the power is on. Also runs a callback function
|
||
|
-- when present.
|
||
|
function RADAR:SetAntennaMode( ant, mode )
|
||
|
-- Check the mode is actually a number, this is needed as the radar system relies on the mode to be
|
||
|
-- a number to work
|
||
|
if ( type( mode ) == "number" ) then
|
||
|
-- Check the mode is in the valid range for modes, and that the power is on
|
||
|
if ( mode >= 0 and mode <= 3 and self:IsPowerOn() ) then
|
||
|
-- Update the mode for the antenna
|
||
|
self.vars.antennas[ant].mode = mode
|
||
|
|
||
|
-- Update the interface with the new mode
|
||
|
SendNUIMessage( { _type = "antennaMode", ant = ant, mode = mode } )
|
||
|
|
||
|
-- Play a beep
|
||
|
SendNUIMessage( { _type = "audio", name = "beep", vol = self:GetSettingValue( "beep" ) } )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns/sets the speed for the given antenna
|
||
|
function RADAR:GetAntennaSpeed( ant ) return self.vars.antennas[ant].speed end
|
||
|
function RADAR:SetAntennaSpeed( ant, speed ) self.vars.antennas[ant].speed = speed end
|
||
|
|
||
|
-- Returns/sets the direction for the given antenna
|
||
|
function RADAR:GetAntennaDir( ant ) return self.vars.antennas[ant].dir end
|
||
|
function RADAR:SetAntennaDir( ant, dir ) self.vars.antennas[ant].dir = dir end
|
||
|
|
||
|
-- Sets the speed and direction in one go
|
||
|
function RADAR:SetAntennaData( ant, speed, dir )
|
||
|
self:SetAntennaSpeed( ant, speed )
|
||
|
self:SetAntennaDir( ant, dir )
|
||
|
end
|
||
|
|
||
|
-- Returns/sets the fast speed for the given antenna
|
||
|
function RADAR:GetAntennaFastSpeed( ant ) return self.vars.antennas[ant].fastSpeed end
|
||
|
function RADAR:SetAntennaFastSpeed( ant, speed ) self.vars.antennas[ant].fastSpeed = speed end
|
||
|
|
||
|
-- Returns/sets the fast direction for the given antenna
|
||
|
function RADAR:GetAntennaFastDir( ant ) return self.vars.antennas[ant].fastDir end
|
||
|
function RADAR:SetAntennaFastDir( ant, dir ) self.vars.antennas[ant].fastDir = dir end
|
||
|
|
||
|
-- Sets the fast speed and direction in one go
|
||
|
function RADAR:SetAntennaFastData( ant, speed, dir )
|
||
|
self:SetAntennaFastSpeed( ant, speed )
|
||
|
self:SetAntennaFastDir( ant, dir )
|
||
|
end
|
||
|
|
||
|
-- Returns if the stored speed for the given antenna is valid
|
||
|
function RADAR:DoesAntennaHaveValidData( ant )
|
||
|
return self:GetAntennaSpeed( ant ) ~= nil
|
||
|
end
|
||
|
|
||
|
-- Returns if the stored fast speed for the given antenna is valid
|
||
|
function RADAR:DoesAntennaHaveValidFastData( ant )
|
||
|
return self:GetAntennaFastSpeed( ant ) ~= nil
|
||
|
end
|
||
|
|
||
|
-- Returns if the fast label should be displayed
|
||
|
function RADAR:ShouldFastBeDisplayed( ant )
|
||
|
if ( self:IsAntennaSpeedLocked( ant ) ) then
|
||
|
return self:GetAntennaLockedType( ant ) == 2
|
||
|
else
|
||
|
return self:IsFastDisplayEnabled()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns if the given antenna has a locked speed
|
||
|
function RADAR:IsAntennaSpeedLocked( ant )
|
||
|
return self.vars.antennas[ant].speedLocked
|
||
|
end
|
||
|
|
||
|
-- Sets the state of speed lock for the given antenna to the given state
|
||
|
function RADAR:SetAntennaSpeedIsLocked( ant, state )
|
||
|
self.vars.antennas[ant].speedLocked = state
|
||
|
end
|
||
|
|
||
|
-- Sets a speed and direction to be locked in for the given antenna
|
||
|
function RADAR:SetAntennaSpeedLock( ant, speed, dir, lockType, playAudio )
|
||
|
-- Check that the passed speed and direction are actually valid
|
||
|
if ( speed ~= nil and dir ~= nil and lockType ~= nil ) then
|
||
|
-- Set the locked speed and direction to the passed values
|
||
|
self.vars.antennas[ant].lockedSpeed = speed
|
||
|
self.vars.antennas[ant].lockedDir = dir
|
||
|
self.vars.antennas[ant].lockedType = lockType
|
||
|
|
||
|
-- Tell the system that a speed has been locked for the given antenna
|
||
|
self:SetAntennaSpeedIsLocked( ant, true )
|
||
|
|
||
|
if ( playAudio ) then
|
||
|
-- Send a message to the NUI side to play the beep sound with the current volume setting
|
||
|
SendNUIMessage( { _type = "audio", name = "beep", vol = self:GetSettingValue( "beep" ) } )
|
||
|
|
||
|
-- Send a message to the NUI side to play the lock audio with the current voice volume setting
|
||
|
SendNUIMessage( { _type = "lockAudio", ant = ant, dir = dir, vol = self:GetSettingValue( "voice" ) } )
|
||
|
end
|
||
|
|
||
|
-- Great Scott!
|
||
|
if ( speed == "¦88" and self:GetSettingValue( "speedType" ) == "mph" ) then
|
||
|
math.randomseed( GetGameTimer() )
|
||
|
|
||
|
local chance = math.random()
|
||
|
|
||
|
-- 15% chance
|
||
|
if ( chance <= 0.15 ) then
|
||
|
SendNUIMessage( { _type = "audio", name = "speed_alert", vol = self:GetSettingValue( "beep" ) } )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns the locked speed for the given antenna
|
||
|
function RADAR:GetAntennaLockedSpeed( ant )
|
||
|
return self.vars.antennas[ant].lockedSpeed
|
||
|
end
|
||
|
|
||
|
-- Returns the locked direction for the given antenna
|
||
|
function RADAR:GetAntennaLockedDir( ant )
|
||
|
return self.vars.antennas[ant].lockedDir
|
||
|
end
|
||
|
|
||
|
-- Returns the lock type for the given antenna
|
||
|
function RADAR:GetAntennaLockedType( ant )
|
||
|
return self.vars.antennas[ant].lockedType
|
||
|
end
|
||
|
|
||
|
-- Resets the speed lock info to do with the given antenna
|
||
|
function RADAR:ResetAntennaSpeedLock( ant )
|
||
|
-- Blank the locked speed and direction
|
||
|
self.vars.antennas[ant].lockedSpeed = nil
|
||
|
self.vars.antennas[ant].lockedDir = nil
|
||
|
self.vars.antennas[ant].lockedType = nil
|
||
|
|
||
|
-- Set the locked state to false
|
||
|
self:SetAntennaSpeedIsLocked( ant, false )
|
||
|
end
|
||
|
|
||
|
-- When the user presses the speed lock key for either antenna, this function is called to get the
|
||
|
-- necessary information from the antenna, and then lock it into the display
|
||
|
function RADAR:LockAntennaSpeed( ant, override, lockRegardless )
|
||
|
-- Only lock a speed if the antenna is on and the UI is displayed
|
||
|
if ( self:IsPowerOn() and ( ( self:GetDisplayState() and not self:GetDisplayHidden() ) or lockRegardless ) and self:IsAntennaTransmitting( ant ) ) then
|
||
|
-- Used to determine whether or not to play the audio and update the display. This is mainly for the passenger
|
||
|
-- control system, as in theory one player could be in the operator menu, and the other player could lock a speed.
|
||
|
local isMenuOpen = self:IsMenuOpen()
|
||
|
|
||
|
-- Check if the antenna doesn't have a locked speed, if it doesn't then we lock in the speed, otherwise we
|
||
|
-- reset the lock
|
||
|
if ( not self:IsAntennaSpeedLocked( ant ) ) then
|
||
|
-- Here we check if the override parameter is valid, if so then we set the radar's speed data to the
|
||
|
-- speed data provided in the override table.
|
||
|
if ( override ~= nil ) then
|
||
|
self:SetAntennaData( ant, override[1], override[2] )
|
||
|
self:SetAntennaFastData( ant, override[3], override[4] )
|
||
|
end
|
||
|
|
||
|
-- This override parameter is used for the passenger control system, as the speeds displayed on the
|
||
|
-- recipients display can't be trusted. When the player who locks the speed triggers the sync, their
|
||
|
-- speed data is collected and sent to the other player so that their speed data is overriden to be the same.
|
||
|
override = override or { nil, nil, nil, nil }
|
||
|
|
||
|
-- Set up a temporary table with 3 nil values, this way if the system isn't able to get a speed or
|
||
|
-- direction, the speed lock function won't work
|
||
|
local data = { nil, nil, nil }
|
||
|
|
||
|
-- As the lock system is based on which speed is displayed, we have to check if there is a speed in the
|
||
|
-- fast box, if there is then we lock in the fast speed, otherwise we lock in the strongest speed
|
||
|
if ( self:IsFastDisplayEnabled() and self:DoesAntennaHaveValidFastData( ant ) ) then
|
||
|
data[1] = self:GetAntennaFastSpeed( ant )
|
||
|
data[2] = self:GetAntennaFastDir( ant )
|
||
|
data[3] = 2
|
||
|
else
|
||
|
data[1] = self:GetAntennaSpeed( ant )
|
||
|
data[2] = self:GetAntennaDir( ant )
|
||
|
data[3] = 1
|
||
|
end
|
||
|
|
||
|
-- Lock in the speed data for the antenna
|
||
|
self:SetAntennaSpeedLock( ant, data[1], data[2], data[3], not isMenuOpen )
|
||
|
else
|
||
|
self:ResetAntennaSpeedLock( ant )
|
||
|
end
|
||
|
|
||
|
if ( not isMenuOpen ) then
|
||
|
-- Send an NUI message to change the lock label, otherwise we'd have to wait until the next main loop
|
||
|
SendNUIMessage( { _type = "antennaLock", ant = ant, state = self:IsAntennaSpeedLocked( ant ) } )
|
||
|
SendNUIMessage( { _type = "antennaFast", ant = ant, state = self:ShouldFastBeDisplayed( ant ) } )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Resets an antenna, used when the system is turned off
|
||
|
function RADAR:ResetAntenna( ant )
|
||
|
-- Overwrite default behaviour, this is because when the system is turned off, the temporary memory is
|
||
|
-- technically reset, as the setter functions require either the radar power to be on or the antenna to
|
||
|
-- be transmitting, this is the only way to reset the values
|
||
|
self.vars.antennas[ant].xmit = false
|
||
|
self.vars.antennas[ant].mode = 0
|
||
|
|
||
|
self:ResetAntennaSpeedLock( ant )
|
||
|
end
|
||
|
|
||
|
-- Returns a table with the given antenna's speed data and directions
|
||
|
function RADAR:GetAntennaDataPacket( ant )
|
||
|
return {
|
||
|
self:GetAntennaSpeed( ant ),
|
||
|
self:GetAntennaDir( ant ),
|
||
|
self:GetAntennaFastSpeed( ant ),
|
||
|
self:GetAntennaFastDir( ant )
|
||
|
}
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar captured vehicle functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Returns the captured vehicles table
|
||
|
function RADAR:GetCapturedVehicles()
|
||
|
return self.vars.capturedVehicles
|
||
|
end
|
||
|
|
||
|
-- Resets the captured vehicles table to an empty table
|
||
|
function RADAR:ResetCapturedVehicles()
|
||
|
self.vars.capturedVehicles = {}
|
||
|
end
|
||
|
|
||
|
-- Takes the vehicle data from RADAR:CreateRayThread() and puts it into the main captured vehicles table, along
|
||
|
-- with the ray type for that vehicle data set (e.g. same or opp)
|
||
|
function RADAR:InsertCapturedVehicleData( t, rt )
|
||
|
-- Make sure the table being passed is valid and not empty
|
||
|
if ( type( t ) == "table" and not UTIL:IsTableEmpty( t ) ) then
|
||
|
-- Iterate through the given table
|
||
|
for _, v in pairs( t ) do
|
||
|
-- Add the ray type to the current row
|
||
|
v.rayType = rt
|
||
|
|
||
|
-- Insert it into the main captured vehicles table
|
||
|
table.insert( self.vars.capturedVehicles, v )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar dynamic sphere radius functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Returns the dynamic sphere data for the given key if there is any
|
||
|
function RADAR:GetDynamicDataValue( key )
|
||
|
return self.vars.sphereSizes[key]
|
||
|
end
|
||
|
|
||
|
-- Returns if dynamic sphere data exists for the given key
|
||
|
function RADAR:DoesDynamicRadiusDataExist( key )
|
||
|
return self:GetDynamicDataValue( key ) ~= nil
|
||
|
end
|
||
|
|
||
|
-- Sets the dynamic sohere data for the given key to the given table
|
||
|
function RADAR:SetDynamicRadiusKey( key, t )
|
||
|
self.vars.sphereSizes[key] = t
|
||
|
end
|
||
|
|
||
|
-- Inserts the given data into the dynamic spheres table, stores the radius and the actual summed up
|
||
|
-- vehicle size. The key is just the model of a vehicle put into string form
|
||
|
function RADAR:InsertDynamicRadiusData( key, radius, actualSize )
|
||
|
-- Check to make sure there is no data for the vehicle
|
||
|
if ( self:GetDynamicDataValue( key ) == nil ) then
|
||
|
-- Create a table to store the data in
|
||
|
local data = {}
|
||
|
|
||
|
-- Put the data into the temporary table
|
||
|
data.radius = radius
|
||
|
data.actualSize = actualSize
|
||
|
|
||
|
-- Set the dynamic sphere data for the vehicle
|
||
|
self:SetDynamicRadiusKey( key, data )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns the dynamic sphere data for the given vehicle
|
||
|
function RADAR:GetRadiusData( key )
|
||
|
return self.vars.sphereSizes[key].radius, self.vars.sphereSizes[key].actualSize
|
||
|
end
|
||
|
|
||
|
-- This function is used to get the dynamic sphere data for a vehicle, if data already exists for the
|
||
|
-- given vehicle, then the system just returns the already made data, otherwise the data gets created
|
||
|
function RADAR:GetDynamicRadius( veh )
|
||
|
-- Get the model of the vehicle
|
||
|
local mdl = GetEntityModel( veh )
|
||
|
|
||
|
-- Create a key based on the model
|
||
|
local key = tostring( mdl )
|
||
|
|
||
|
-- Check to see if data already exists
|
||
|
local dataExists = self:DoesDynamicRadiusDataExist( key )
|
||
|
|
||
|
-- If the data doesn't already exist, then we create it
|
||
|
if ( not dataExists ) then
|
||
|
-- Get the min and max points of the vehicle model
|
||
|
local min, max = GetModelDimensions( mdl )
|
||
|
|
||
|
-- Calculate the size, as the min value is negative
|
||
|
local size = max - min
|
||
|
|
||
|
-- Get a numeric size which composes of the x, y, and z size combined
|
||
|
local numericSize = size.x + size.y + size.z
|
||
|
|
||
|
-- Get a dynamic radius for the given vehicle model that fits into the world of GTA
|
||
|
local dynamicRadius = UTIL:Clamp( ( numericSize * numericSize ) / 12, 5.0, 11.0 )
|
||
|
|
||
|
-- Insert the newly created sphere data into the sphere data table
|
||
|
self:InsertDynamicRadiusData( key, dynamicRadius, numericSize )
|
||
|
|
||
|
-- Return the data
|
||
|
return dynamicRadius, numericSize
|
||
|
end
|
||
|
|
||
|
-- Return the stored data
|
||
|
return self:GetRadiusData( key )
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Radar functions
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Takes a GTA speed and converts it into the type defined by the user in the operator menu
|
||
|
function RADAR:GetVehSpeedConverted( speed )
|
||
|
-- Get the speed unit from the settings
|
||
|
local unit = self:GetSettingValue( "speedType" )
|
||
|
|
||
|
-- Return the coverted speed rounded to a whole number
|
||
|
return UTIL:Round( speed * self.speedConversions[unit], 0 )
|
||
|
end
|
||
|
|
||
|
-- Returns/sets the validity of the given vehicle model
|
||
|
function RADAR:GetVehicleValidity( key ) return self.vars.validVehicles[key] end
|
||
|
function RADAR:SetVehicleValidity( key, validity ) self.vars.validVehicles[key] = validity end
|
||
|
|
||
|
-- Returns if vehicle validity data exists for the given vehicle model
|
||
|
function RADAR:DoesVehicleValidityExist( key )
|
||
|
return self:GetVehicleValidity( key ) ~= nil
|
||
|
end
|
||
|
|
||
|
-- Returns if the given vehicle is valid, as we don't want the radar to detect boats, helicopters, or planes!
|
||
|
function RADAR:IsVehicleValid( veh )
|
||
|
-- Get the model of the vehicle
|
||
|
local mdl = GetEntityModel( veh )
|
||
|
|
||
|
-- Create a key based on the model
|
||
|
local key = tostring( mdl )
|
||
|
|
||
|
-- Check if the vehicle model is valid
|
||
|
local valid = self:GetVehicleValidity( key )
|
||
|
|
||
|
-- If the validity value hasn't been set for the vehicle model, then we do it now
|
||
|
if ( valid == nil ) then
|
||
|
-- If the model is not what we want, then set the validity to false
|
||
|
if ( IsThisModelABoat( mdl ) or IsThisModelAHeli( mdl ) or IsThisModelAPlane( mdl ) ) then
|
||
|
self:SetVehicleValidity( key, false )
|
||
|
return false
|
||
|
else
|
||
|
self:SetVehicleValidity( key, true )
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return valid
|
||
|
end
|
||
|
|
||
|
-- Gathers all of the vehicles in the local area of the player
|
||
|
function RADAR:GetAllVehicles()
|
||
|
-- Create a temporary table
|
||
|
local t = {}
|
||
|
|
||
|
-- Iterate through vehicles
|
||
|
for v in UTIL:EnumerateVehicles() do
|
||
|
if ( self:IsVehicleValid( v ) ) then
|
||
|
-- Insert the vehicle id into the temporary table
|
||
|
table.insert( t, v )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Return the table
|
||
|
return t
|
||
|
end
|
||
|
|
||
|
-- Used to check if an antennas mode fits with a ray type from the ray trace system
|
||
|
function RADAR:CheckVehicleDataFitsMode( ant, rt )
|
||
|
-- Get the current mode value for the given antenna
|
||
|
local mode = self:GetAntennaMode( ant )
|
||
|
|
||
|
-- Check that the given ray type matches up with the antenna's current mode
|
||
|
if ( ( mode == 3 ) or ( mode == 1 and rt == "same" ) or ( mode == 2 and rt == "opp" ) ) then return true end
|
||
|
|
||
|
-- Otherwise, return false as a last resort
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
-- This function is used to filter through the captured vehicles and work out what vehicles should be used for display
|
||
|
-- on the radar interface
|
||
|
function RADAR:GetVehiclesForAntenna()
|
||
|
-- Create the vehs table to store the split up captured vehicle data
|
||
|
local vehs = { ["front"] = {}, ["rear"] = {} }
|
||
|
|
||
|
-- Create the results table to store the vehicle results, the first index is for the 'strongest' vehicle and the
|
||
|
-- second index is for the 'fastest' vehicle
|
||
|
local results = { ["front"] = { nil, nil }, ["rear"] = { nil, nil } }
|
||
|
|
||
|
-- Loop through and split up the vehicles based on front and rear, this is simply because the actual system
|
||
|
-- that gets all of the vehicles hit by the radar only has a relative position of either 1 or -1, which we
|
||
|
-- then convert below into an antenna string!
|
||
|
for ant in UTIL:Values( { "front", "rear" } ) do
|
||
|
-- Check that the antenna is actually transmitting
|
||
|
if ( self:IsAntennaTransmitting( ant ) ) then
|
||
|
-- Iterate through the captured vehicles
|
||
|
for k, v in pairs( self:GetCapturedVehicles() ) do
|
||
|
-- Convert the relative position to antenna text
|
||
|
local antText = self:GetAntennaTextFromNum( v.relPos )
|
||
|
|
||
|
-- Check the current vehicle's relative position is the same as the current antenna
|
||
|
if ( ant == antText ) then
|
||
|
-- Insert the vehicle into the table for the current antenna
|
||
|
table.insert( vehs[ant], v )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- As the radar is based on how the real Stalker DSR 2X works, we now sort the dataset by
|
||
|
-- the 'strongest' (largest) target, this way the first result for the front and rear data
|
||
|
-- will be the one that gets displayed in the target boxes.
|
||
|
table.sort( vehs[ant], self:GetStrongestSortFunc() )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Now that we have all of the vehicles split into front and rear, we can iterate through both sets and get
|
||
|
-- the strongest and fastest vehicle for display
|
||
|
for ant in UTIL:Values( { "front", "rear" } ) do
|
||
|
-- Check that the table for the current antenna is not empty
|
||
|
if ( not UTIL:IsTableEmpty( vehs[ant] ) ) then
|
||
|
-- Get the 'strongest' vehicle for the antenna
|
||
|
for k, v in pairs( vehs[ant] ) do
|
||
|
-- Check if the current vehicle item fits the mode set by the user
|
||
|
if ( self:CheckVehicleDataFitsMode( ant, v.rayType ) ) then
|
||
|
-- Set the result for the current antenna
|
||
|
results[ant][1] = v
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Here we get the vehicle for the fastest section, but only if the user has the fast mode enabled
|
||
|
-- in the operator menu
|
||
|
if ( self:IsFastDisplayEnabled() ) then
|
||
|
-- Get the 'fastest' vehicle for the antenna
|
||
|
table.sort( vehs[ant], self:GetFastestSortFunc() )
|
||
|
|
||
|
-- Create a temporary variable for the first result, reduces line length
|
||
|
local temp = results[ant][1]
|
||
|
|
||
|
-- Iterate through the vehicles for the current antenna
|
||
|
for k, v in pairs( vehs[ant] ) do
|
||
|
-- When we grab a vehicle for the fastest section, as it is like how the real system works, there are a few
|
||
|
-- additional checks that have to be made
|
||
|
if ( self:CheckVehicleDataFitsMode( ant, v.rayType ) and v.veh ~= temp.veh and v.size < temp.size and v.speed > temp.speed + 1.0 ) then
|
||
|
-- Set the result for the current antenna
|
||
|
results[ant][2] = v
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Return the results
|
||
|
return { ["front"] = { results["front"][1], results["front"][2] }, ["rear"] = { results["rear"][1], results["rear"][2] } }
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
NUI callback
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Runs when the "Toggle Display" button is pressed on the remote control
|
||
|
RegisterNUICallback( "toggleRadarDisplay", function( data, cb )
|
||
|
-- Toggle the display state
|
||
|
RADAR:ToggleDisplayState()
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
-- Runs when the user presses the power button on the radar ui
|
||
|
RegisterNUICallback( "togglePower", function( data, cb )
|
||
|
if ( PLY:CanControlRadar() ) then
|
||
|
if ( not RADAR:IsPoweringUp() ) then
|
||
|
-- Toggle the radar's power
|
||
|
RADAR:SetPowerState( not RADAR:IsPowerOn(), false )
|
||
|
|
||
|
SYNC:SendPowerState( RADAR:IsPowerOn() )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
-- Runs when the user presses the ESC or RMB when the remote is open
|
||
|
RegisterNUICallback( "closeRemote", function( data, cb )
|
||
|
-- Remove focus to the NUI side
|
||
|
SetNuiFocus( false, false )
|
||
|
|
||
|
if ( RADAR:IsMenuOpen() ) then
|
||
|
RADAR:CloseMenu( false )
|
||
|
end
|
||
|
|
||
|
SYNC:SetRemoteOpenState( false )
|
||
|
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
-- Runs when the user presses any of the antenna mode buttons on the remote
|
||
|
RegisterNUICallback( "setAntennaMode", function( data, cb )
|
||
|
if ( PLY:CanControlRadar() ) then
|
||
|
-- Only run the codw if the radar has power and is not powering up
|
||
|
if ( RADAR:IsPowerOn() and not RADAR:IsPoweringUp() ) then
|
||
|
-- As the mode buttons are used to exit the menu, we check for that
|
||
|
if ( RADAR:IsMenuOpen() ) then
|
||
|
RADAR:CloseMenu()
|
||
|
else
|
||
|
-- Change the mode for the designated antenna, pass along a callback which contains data from this NUI callback
|
||
|
RADAR:SetAntennaMode( data.value, tonumber( data.mode ) )
|
||
|
|
||
|
-- Sync
|
||
|
SYNC:SendAntennaMode( data.value, tonumber( data.mode ) )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
-- Runs when the user presses either of the XMIT/HOLD buttons on the remote
|
||
|
RegisterNUICallback( "toggleAntenna", function( data, cb )
|
||
|
if ( PLY:CanControlRadar() ) then
|
||
|
-- Only run the codw if the radar has power and is not powering up
|
||
|
if ( RADAR:IsPowerOn() and not RADAR:IsPoweringUp() ) then
|
||
|
-- As the xmit/hold buttons are used to change settings in the menu, we check for that
|
||
|
if ( RADAR:IsMenuOpen() ) then
|
||
|
-- Change the menu option based on which button is pressed
|
||
|
RADAR:ChangeMenuOption( data.value )
|
||
|
|
||
|
-- Play a beep noise
|
||
|
SendNUIMessage( { _type = "audio", name = "beep", vol = RADAR:GetSettingValue( "beep" ) } )
|
||
|
else
|
||
|
-- Toggle the transmit state for the designated antenna, pass along a callback which contains data from this NUI callback
|
||
|
RADAR:ToggleAntenna( data.value )
|
||
|
|
||
|
-- Sync
|
||
|
SYNC:SendAntennaPowerState( RADAR:IsAntennaTransmitting( data.value ), data.value )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
-- Runs when the user presses the menu button on the remote control
|
||
|
RegisterNUICallback( "menu", function( data, cb )
|
||
|
if ( PLY:CanControlRadar() ) then
|
||
|
-- Only run the codw if the radar has power and is not powering up
|
||
|
if ( RADAR:IsPowerOn() and not RADAR:IsPoweringUp() ) then
|
||
|
-- As the menu button is a multipurpose button, we first check to see if the menu is already open
|
||
|
if ( RADAR:IsMenuOpen() ) then
|
||
|
-- As the menu is already open, we then iterate to the next option in the settings list
|
||
|
RADAR:ChangeMenuIndex()
|
||
|
else
|
||
|
-- Set the menu state to open, which will prevent anything else within the radar from working
|
||
|
RADAR:SetMenuState( true )
|
||
|
|
||
|
-- Send an update to the NUI side
|
||
|
RADAR:SendMenuUpdate()
|
||
|
end
|
||
|
|
||
|
-- Play the standard audio beep
|
||
|
SendNUIMessage( { _type = "audio", name = "beep", vol = RADAR:GetSettingValue( "beep" ) } )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
-- Runs when the JavaScript side sends the UI data for saving
|
||
|
RegisterNUICallback( "saveUiData", function( data, cb )
|
||
|
UTIL:Log( "Saving updated UI settings data." )
|
||
|
SetResourceKvp( "wk_wars2x_ui_data", json.encode( data ) )
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
-- Runs when the JavaScript side sends the quick start video has been watched
|
||
|
RegisterNUICallback( "qsvWatched", function( data, cb )
|
||
|
SetResourceKvpInt( "wk_wars2x_new_user", 1 )
|
||
|
cb( "ok" )
|
||
|
end )
|
||
|
|
||
|
|
||
|
--[[----------------------------------------------------------------------------------
|
||
|
Main threads
|
||
|
----------------------------------------------------------------------------------]]--
|
||
|
-- Some people might not like the idea of the resource having a CPU MSEC over 0.10, but due to the functions
|
||
|
-- and the way the whole radar system works, it will use over 0.10 a decent amount. In this function, we
|
||
|
-- dynamically adjust the wait time in the main thread, so that when the player is driving their vehicle and
|
||
|
-- moving, the system doesn't run as fast so as to use less CPU time. When they have their vehicle
|
||
|
-- stationary, the system runs more often, which means that if a situation occurs such as a vehicle flying
|
||
|
-- past them at a high rate of speed, the system will be able to pick it up as it is running faster. Also, as
|
||
|
-- the user is stationary, if the system takes up an additional one or two frames per second, it won't really
|
||
|
-- be noticeable.
|
||
|
function RADAR:RunDynamicThreadWaitCheck()
|
||
|
-- Get the speed of the local players vehicle
|
||
|
local speed = self:GetPatrolSpeed()
|
||
|
|
||
|
-- Check that the vehicle speed is less than 0.1
|
||
|
if ( speed < 0.1 ) then
|
||
|
-- Change the thread wait time to 200 ms, the trace system will now run five times per second
|
||
|
self:SetThreadWaitTime( 200 )
|
||
|
else
|
||
|
-- Change the thread wait time to 500 ms, the trace system will now run two times a second
|
||
|
self:SetThreadWaitTime( 500 )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Create the thread that will run the dynamic thread wait check, this check only runs every two seconds
|
||
|
Citizen.CreateThread( function()
|
||
|
while ( true ) do
|
||
|
-- Run the function
|
||
|
RADAR:RunDynamicThreadWaitCheck()
|
||
|
|
||
|
-- Make the thread wait two seconds
|
||
|
Citizen.Wait( 2000 )
|
||
|
end
|
||
|
end )
|
||
|
|
||
|
-- This function handles the custom ray trace system that is used to gather all of the vehicles hit by
|
||
|
-- the ray traces defined in RADAR.rayTraces.
|
||
|
function RADAR:RunThreads()
|
||
|
-- For the system to even run, the player needs to be sat in the driver's seat of a class 18 vehicle, the
|
||
|
-- radar has to be visible and the power must be on, and either one of the antennas must be enabled.
|
||
|
if ( PLY:CanViewRadar() and self:CanPerformMainTask() and self:IsEitherAntennaOn() ) then
|
||
|
-- Before we create any of the custom ray trace threads, we need to make sure that the ray trace state
|
||
|
-- is at zero, if it is not at zero, then it means the system is still currently tracing
|
||
|
if ( self:GetRayTraceState() == 0 ) then
|
||
|
-- Grab a copy of the vehicle pool
|
||
|
local vehs = self:GetVehiclePool()
|
||
|
|
||
|
-- Reset the main captured vehicles table
|
||
|
self:ResetCapturedVehicles()
|
||
|
|
||
|
-- Here we run the function that creates all of the main ray threads
|
||
|
self:CreateRayThreads( PLY.veh, vehs )
|
||
|
|
||
|
-- Make the thread this function runs in wait the dynamic time defined by the system
|
||
|
Citizen.Wait( self:GetThreadWaitTime() )
|
||
|
|
||
|
-- If the current ray trace state is the same as the total number of rays, then we reset the ray trace
|
||
|
-- state back to 0 so the thread system can run again
|
||
|
elseif ( self:GetRayTraceState() == self:GetNumOfRays() ) then
|
||
|
-- Reset the ray trace state to 0
|
||
|
self:ResetRayTraceState()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Create the main thread that will run the threads function, the function itself is run every frame as the
|
||
|
-- dynamic wait time is ran inside the function
|
||
|
Citizen.CreateThread( function()
|
||
|
while ( true ) do
|
||
|
-- Run the function
|
||
|
RADAR:RunThreads()
|
||
|
|
||
|
-- Make the thread wait 0 ms
|
||
|
Citizen.Wait( 0 )
|
||
|
end
|
||
|
end )
|
||
|
|
||
|
-- This is the main function that runs and handles all information that is sent to the NUI side for display, all
|
||
|
-- speed values are converted on the Lua side into a format that is displayable using the custom font on the NUI side
|
||
|
function RADAR:Main()
|
||
|
-- Only run any of the main code if all of the states are met, player in the driver's seat of a class 18 vehicle, and
|
||
|
-- the system has to be able to perform main tasks
|
||
|
if ( PLY:CanViewRadar() and self:CanPerformMainTask() ) then
|
||
|
-- Create a table that will be used to store all of the data to be sent to the NUI side
|
||
|
local data = {}
|
||
|
|
||
|
-- Get the player's vehicle speed
|
||
|
local entSpeed = GetEntitySpeed( PLY.veh )
|
||
|
|
||
|
-- Set the internal patrol speed to the speed obtained above, this is then used in the dynamic thread wait calculation
|
||
|
self:SetPatrolSpeed( entSpeed )
|
||
|
|
||
|
-- Change what is displayed in the patrol speed box on the radar interface depending on if the players vehicle is
|
||
|
-- stationary or moving
|
||
|
if ( entSpeed == 0 ) then
|
||
|
data.patrolSpeed = "¦[]"
|
||
|
else
|
||
|
data.patrolSpeed = math.floor(entSpeed * 3.6 + 0.5)
|
||
|
end
|
||
|
|
||
|
-- Get the vehicles to be displayed for the antenna, then we take the results from that and send the relevant
|
||
|
-- information to the NUI side
|
||
|
local av = self:GetVehiclesForAntenna()
|
||
|
data.antennas = { ["front"] = nil, ["rear"] = nil }
|
||
|
|
||
|
-- Iterate through the front and rear data and obtain the information to be displayed
|
||
|
for ant in UTIL:Values( { "front", "rear" } ) do
|
||
|
-- Check that the antenna is actually transmitting, no point in running all the checks below if the antenna is off
|
||
|
if ( self:IsAntennaTransmitting( ant ) ) then
|
||
|
-- Create a table for the current antenna to store the information
|
||
|
data.antennas[ant] = {}
|
||
|
|
||
|
-- When the system works out what vehicles to be used, both the "front" and "rear" keys have two items located
|
||
|
-- at index 1 and 2. Index 1 stores the vehicle data for the antenna's 'strongest' vehicle, and index 2 stores
|
||
|
-- the vehicle data for the 'fastest' vehicle. Here we iterate through both the indexes and just run checks to
|
||
|
-- see if it is a particular type (e.g. if i % 2 == 0 then it's the 'fastest' vehicle)
|
||
|
for i = 1, 2 do
|
||
|
-- Create the table to store the speed and direction for this vehicle data
|
||
|
data.antennas[ant][i] = { speed = "¦¦¦", dir = 0 }
|
||
|
|
||
|
-- If the current iteration is the number 2 ('fastest') and there's a speed locked, grab the locked speed
|
||
|
-- and direction
|
||
|
if ( i == 2 and self:IsAntennaSpeedLocked( ant ) ) then
|
||
|
data.antennas[ant][i].speed = self:GetAntennaLockedSpeed( ant )
|
||
|
data.antennas[ant][i].dir = self:GetAntennaLockedDir( ant )
|
||
|
|
||
|
-- Otherwise, continue with getting speed and direction data
|
||
|
else
|
||
|
-- The vehicle data exists for this slot
|
||
|
if ( av[ant][i] ~= nil ) then
|
||
|
-- Here we get the entity speed of the vehicle, the speed for this vehicle would've been obtained
|
||
|
-- and stored in the trace stage, but the speed would've only been obtained and stored once, which
|
||
|
-- means that it woulsn't be the current speed
|
||
|
local vehSpeed = GetEntitySpeed( av[ant][i].veh )
|
||
|
local convertedSpeed = self:GetVehSpeedConverted( vehSpeed )
|
||
|
data.antennas[ant][i].speed = UTIL:FormatSpeed( convertedSpeed )
|
||
|
|
||
|
-- Work out if the vehicle is closing or away
|
||
|
local ownH = UTIL:Round( GetEntityHeading( PLY.veh ), 0 )
|
||
|
local tarH = UTIL:Round( GetEntityHeading( av[ant][i].veh ), 0 )
|
||
|
data.antennas[ant][i].dir = UTIL:GetEntityRelativeDirection( ownH, tarH )
|
||
|
|
||
|
-- Set the internal antenna data as this actual dataset is valid
|
||
|
if ( i % 2 == 0 ) then
|
||
|
self:SetAntennaFastData( ant, data.antennas[ant][i].speed, data.antennas[ant][i].dir )
|
||
|
else
|
||
|
self:SetAntennaData( ant, data.antennas[ant][i].speed, data.antennas[ant][i].dir )
|
||
|
end
|
||
|
|
||
|
-- Lock the speed automatically if the fast limit system is allowed
|
||
|
if ( self:IsFastLimitAllowed() ) then
|
||
|
-- Make sure the speed is larger than the limit, and that there isn't already a locked speed
|
||
|
if ( self:IsFastLockEnabled() and convertedSpeed > self:GetFastLimit() and not self:IsAntennaSpeedLocked( ant ) ) then
|
||
|
if ( ( self:OnlyLockFastPlayers() and UTIL:IsPlayerInVeh( av[ant][i].veh ) ) or not self:OnlyLockFastPlayers() ) then
|
||
|
if ( PLY:IsDriver() ) then
|
||
|
self:LockAntennaSpeed( ant, nil, false )
|
||
|
SYNC:LockAntennaSpeed( ant, RADAR:GetAntennaDataPacket( ant ) )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
-- If the active vehicle is not valid, we reset the internal data
|
||
|
if ( i % 2 == 0 ) then
|
||
|
self:SetAntennaFastData( ant, nil, nil )
|
||
|
else
|
||
|
self:SetAntennaData( ant, nil, nil )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Send the update to the NUI side
|
||
|
SendNUIMessage( { _type = "update", speed = data.patrolSpeed, antennas = data.antennas } )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Main thread
|
||
|
Citizen.CreateThread( function()
|
||
|
-- Remove the NUI focus just in case
|
||
|
SetNuiFocus( false, false )
|
||
|
|
||
|
-- Run the function to cache the number of rays, this way a hard coded number is never needed
|
||
|
RADAR:CacheNumRays()
|
||
|
|
||
|
-- Update the end coordinates for the ray traces based on the config, again, reduced hard coding
|
||
|
RADAR:UpdateRayEndCoords()
|
||
|
|
||
|
-- If the fast limit feature is allowed, create the config in the radar variables
|
||
|
if ( RADAR:IsFastLimitAllowed() ) then
|
||
|
RADAR:CreateFastLimitConfig()
|
||
|
end
|
||
|
|
||
|
-- Register the key binds
|
||
|
RegisterKeyBinds()
|
||
|
|
||
|
-- Wait a short period of time
|
||
|
Citizen.Wait( 1000 )
|
||
|
|
||
|
-- Load the saved UI settings (if available)
|
||
|
LoadUISettings()
|
||
|
|
||
|
-- Set the player's remoteOpen decorator to false
|
||
|
DecorSetBool( PlayerPedId(), "wk_wars2x_sync_remoteOpen", false )
|
||
|
|
||
|
-- Update the operator menu positions
|
||
|
RADAR:UpdateOptionIndexes( true )
|
||
|
|
||
|
-- Run the main radar function
|
||
|
while ( true ) do
|
||
|
RADAR:Main()
|
||
|
|
||
|
Citizen.Wait( 100 )
|
||
|
end
|
||
|
end )
|
||
|
|
||
|
-- This function is pretty much straight from WraithRS, it does the job so I didn't see the point in not
|
||
|
-- using it. Hides the radar UI when certain criteria is met, e.g. in pause menu or stepped out ot the
|
||
|
-- patrol vehicle
|
||
|
function RADAR:RunDisplayValidationCheck()
|
||
|
if ( ( ( PLY.veh == 0 or ( PLY.veh > 0 and not PLY.vehClassValid ) ) and self:GetDisplayState() and not self:GetDisplayHidden() ) or IsPauseMenuActive() and self:GetDisplayState() ) then
|
||
|
self:SetDisplayHidden( true )
|
||
|
SendNUIMessage( { _type = "setRadarDisplayState", state = false } )
|
||
|
elseif ( PLY:CanViewRadar() and self:GetDisplayState() and self:GetDisplayHidden() ) then
|
||
|
self:SetDisplayHidden( false )
|
||
|
SendNUIMessage( { _type = "setRadarDisplayState", state = true } )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Runs the display validation check for the radar
|
||
|
Citizen.CreateThread( function()
|
||
|
Citizen.Wait( 100 )
|
||
|
|
||
|
while ( true ) do
|
||
|
-- Run the check
|
||
|
RADAR:RunDisplayValidationCheck()
|
||
|
|
||
|
-- Wait half a second
|
||
|
Citizen.Wait( 500 )
|
||
|
end
|
||
|
end )
|
||
|
|
||
|
-- Update the vehicle pool every 3 seconds
|
||
|
function RADAR:UpdateVehiclePool()
|
||
|
-- Only update the vehicle pool if we need to
|
||
|
if ( PLY:CanViewRadar() and self:CanPerformMainTask() and self:IsEitherAntennaOn() ) then
|
||
|
-- Get the active vehicle set
|
||
|
local vehs = self:GetAllVehicles()
|
||
|
|
||
|
-- Update the vehicle pool
|
||
|
self:SetVehiclePool( vehs )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Runs the vehicle pool updater
|
||
|
Citizen.CreateThread( function()
|
||
|
while ( true ) do
|
||
|
-- Update the vehicle pool
|
||
|
RADAR:UpdateVehiclePool()
|
||
|
|
||
|
-- Wait 3 seconds
|
||
|
Citizen.Wait( 3000 )
|
||
|
end
|
||
|
end )
|