diff --git a/.gitignore b/.gitignore index 3e3cb03..6017dae 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -saves/*.json \ No newline at end of file +saves/*.json +.vscode/settings.json diff --git a/LICENSE b/LICENSE index 0307540..b3e153b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 WolfKnight +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 diff --git a/README.md b/README.md index a9e603a..ded345a 100644 --- a/README.md +++ b/README.md @@ -25,23 +25,39 @@ Although these can be viewed ingame through the operator manual, the default key All of the configuration for the Wraith ARS 2X is done inside the `config.lua` file, below is a copy of the configuration file. All of the options have comments to describe what they do, along with the available options you can set. You have the ability to change the key binds for the large and small key set, the default operator menu options, and the default UI element scale and safezone. ```lua -- Radar fast limit locking --- When enabled, the player will be able to define a fast limit within the radar's menu, when a vehicle +-- When enabled, the player will be able to define a fast limit within the radar's menu, when a vehicle -- exceeds the fast limit, it will be locked into the fast box. Default setting is disabled to maintain realism -CONFIG.allow_fast_limit = false +CONFIG.allow_fast_limit = true + +-- Radar only lock players with auto fast locking +-- When enabled, the radar will only automatically lock a speed if the caught vehicle has a real player in it. +CONFIG.only_lock_players = false -- In-game first time quick start video --- When enabled, the player will be asked if they'd like to view the quick start video the first time they --- open the remote. -CONFIG.allow_quick_start_video = true +-- When enabled, the player will be asked if they'd like to view the quick start video the first time they +-- open the remote. +CONFIG.allow_quick_start_video = true + +-- Allow passenger view +-- When enabled, the front seat passenger will be able to view the radar and plate reader from their end. +CONFIG.allow_passenger_view = true + +-- Allow passenger control +-- Dependent on CONFIG.allow_passenger_view. When enabled, the front seat passenger will be able to open the +-- radar remote and control the radar and plate reader for themself and the driver. +CONFIG.allow_passenger_control = true + +-- Set this to true if you use Sonoran CAD with the WraithV2 plugin +CONFIG.use_sonorancad = false -- Sets the defaults of all keybinds -- These keybinds can be changed by each person in their GTA Settings->Keybinds->FiveM CONFIG.keyDefaults = { - -- Remote control key + -- Remote control key remote_control = "f5", - -- Radar key lock key + -- Radar key lock key key_lock = "l", -- Radar front antenna lock/unlock Key @@ -58,33 +74,41 @@ CONFIG.keyDefaults = } -- Here you can change the default values for the operator menu, do note, if any of these values are not --- one of the options listed, the script will not work. -CONFIG.menuDefaults = +-- one of the options listed, the script will not work. +CONFIG.menuDefaults = { -- Should the system calculate and display faster targets -- Options: true or false - ["fastDisplay"] = true, + ["fastDisplay"] = true, -- Sensitivity for each radar mode, this changes how far the antennas will detect vehicles -- Options: 0.2, 0.4, 0.6, 0.8, 1.0 - ["same"] = 0.6, - ["opp"] = 0.6, + ["same"] = 0.6, + ["opp"] = 0.6, - -- The volume of the audible beep - -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 + -- The volume of the audible beep + -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ["beep"] = 0.6, - - -- The volume of the verbal lock confirmation - -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 + + -- The volume of the verbal lock confirmation + -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ["voice"] = 0.6, - - -- The volume of the plate reader audio - -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 - ["plateAudio"] = 0.6, + + -- The volume of the plate reader audio + -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 + ["plateAudio"] = 0.6, -- The speed unit used in conversions - -- Options: mph or kmh - ["speedType"] = "mph" + -- Options: mph or kmh + ["speedType"] = "mph", + + -- The state for automatic speed locking. This requires CONFIG.allow_fast_limit to be true. + -- Options: true or false + ["fastLock"] = false, + + -- The speed limit required for automatic speed locking. This requires CONFIG.allow_fast_limit to be true. + -- Options: 0 to 200 + ["fastLimit"] = 60 } -- Here you can change the default scale of the UI elements, as well as the safezone size @@ -94,14 +118,14 @@ CONFIG.uiDefaults = -- Options: 0.25 - 2.5 scale = { - radar = 1.0, - remote = 1.0, - plateReader = 1.0 - }, + radar = 0.75, + remote = 0.75, + plateReader = 0.75 + }, -- The safezone size, must be a multiple of 5. -- Options: 0 - 100 - safezone = 20 + safezone = 20 } ``` @@ -109,4 +133,4 @@ CONFIG.uiDefaults = If there is an improvement that you think should be made, open a pull request with your modified code, I will then review your request and either accept/deny it. Code in a pull request should be well formatted and commented, it will make it much easier for others to read and understand. In the event that you want to suggest something, but don't know how to code, open an issue with the enhancement tag and then fully describe your suggestion. ## Reporting issues/bugs -Open an issue if you encounter any problems with the resource, if applicable, try to include detailed information on the issue and how to reproduce it. This will make it much easier to find and fix. +Open an issue if you encounter any problems with the resource, if applicable, try to include detailed information on the issue and how to reproduce it. This will make it much easier to find and fix. \ No newline at end of file diff --git a/cl_plate_reader.lua b/cl_plate_reader.lua index aa817de..87b43ac 100644 --- a/cl_plate_reader.lua +++ b/cl_plate_reader.lua @@ -2,13 +2,13 @@ Wraith ARS 2X Created by WolfKnight - - For discussions, information on future updates, and more, join - my Discord: https://discord.gg/fD4e6WD - + + For discussions, information on future updates, and more, join + my Discord: https://discord.gg/fD4e6WD + MIT License - Copyright (c) 2020 WolfKnight + 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 @@ -36,163 +36,203 @@ READER = {} Plate reader variables NOTE - This is not a config, do not touch anything unless you know what - you are actually doing. + you are actually doing. ----------------------------------------------------------------------------------]]-- -READER.vars = +READER.vars = { - -- Whether or not the plate reader's UI is visible + -- Whether or not the plate reader's UI is visible displayed = false, -- Whether or not the plate reader should be hidden, e.g. the display is active but the player then steps -- out of their vehicle hidden = false, - -- The BOLO plate - boloPlate = "", - - -- Cameras, this table contains all of the data needed for operation of the front and rear plate reader + -- The BOLO plate + boloPlate = "", + + -- Cameras, this table contains all of the data needed for operation of the front and rear plate reader cams = { -- Variables for the front camera ["front"] = { plate = "", -- The current plate caught by the reader index = "", -- The index of the current plate - locked = false -- If the reader is locked - }, + locked = false -- If the reader is locked + }, -- Variables for the rear camera ["rear"] = { plate = "", -- The current plate caught by the reader index = "", -- The index of the current plate - locked = false -- If the reader is locked + locked = false -- If the reader is locked } } } + +--[[---------------------------------------------------------------------------------- + Plate reader functions +----------------------------------------------------------------------------------]]-- -- Gets the display state function READER:GetDisplayState() return self.vars.displayed -end +end -- Toggles the display state of the plate reader system function READER:ToggleDisplayState() - -- Toggle the display variable - self.vars.displayed = not self.vars.displayed + -- Toggle the display variable + self.vars.displayed = not self.vars.displayed - -- Send the toggle message to the NUI side + -- Send the toggle message to the NUI side SendNUIMessage( { _type = "setReaderDisplayState", state = self:GetDisplayState() } ) -end - --- Sets the display's hidden state to the given state -function READER:SetDisplayHidden( state ) - self.vars.hidden = state -end - --- Returns if the display is hidden -function READER:GetDisplayHidden() - return self.vars.hidden end --- Returns the stored plate for the given reader -function READER:GetPlate( cam ) - return self.vars.cams[cam].plate -end +-- Getter and setter for the display hidden state +function READER:GetDisplayHidden() return self.vars.hidden end +function READER:SetDisplayHidden( state ) self.vars.hidden = state end --- Sets the plate for the given reader to the given plate -function READER:SetPlate( cam, plate ) - self.vars.cams[cam].plate = plate -end +-- Getter and setter for the given camera's plate +function READER:GetPlate( cam ) return self.vars.cams[cam].plate end +function READER:SetPlate( cam, plate ) self.vars.cams[cam].plate = plate end --- Returns the stored plate index for the given reader -function READER:GetIndex( cam ) - return self.vars.cams[cam].index -end - --- Sets the plate index for the given reader to the given index -function READER:SetIndex( cam, index ) - self.vars.cams[cam].index = index -end +-- Getter and setter for the given camera's plate display index +function READER:GetIndex( cam ) return self.vars.cams[cam].index end +function READER:SetIndex( cam, index ) self.vars.cams[cam].index = index end -- Returns the bolo plate function READER:GetBoloPlate() - return self.vars.boloPlate -end + if ( self.vars.boloPlate ~= nil ) then + return self.vars.boloPlate + end +end --- Sets the bolo plate to the given plate +-- Sets the bolo plate to the given plate function READER:SetBoloPlate( plate ) self.vars.boloPlate = plate - UTIL:Notify( "BOLO plate set to: " .. plate ) -end + UTIL:Notify( "BOLO plate set to: ~b~" .. plate ) +end + +-- Clears the BOLO plate +function READER:ClearBoloPlate() + self.vars.boloPlate = nil + UTIL:Notify( "~b~BOLO plate cleared!" ) +end -- Returns if the given reader is locked -function READER:GetCamLocked( cam ) - return self.vars.cams[cam].locked -end +function READER:GetCamLocked( cam ) return self.vars.cams[cam].locked end -- Locks the given reader -function READER:LockCam( cam, playBeep, isBolo ) +function READER:LockCam( cam, playBeep, isBolo, override ) -- Check that plate readers can actually be locked - if ( PLY:VehicleStateValid() and self:CanPerformMainTask() ) then - -- Toggle the lock state + if ( PLY:VehicleStateValid() and self:CanPerformMainTask() and self:GetPlate( cam ) ~= "" ) then + -- Toggle the lock state self.vars.cams[cam].locked = not self.vars.cams[cam].locked - -- Tell the NUI side to show/hide the lock icon - SendNUIMessage( { _type = "lockPlate", cam = cam, state = self:GetCamLocked( cam ), isBolo = isBolo } ) + -- Play a beep + if ( self:GetCamLocked( cam ) ) then + -- Here we check if the override parameter is valid, if so then we set the reader's plate data to the + -- plate data provided in the override table. + if ( override ~= nil ) then + self:SetPlate( cam, override[1] ) + self:SetIndex( cam, override[2] ) - -- Play a beep - if ( self:GetCamLocked( cam ) ) then - if ( playBeep ) then + self:ForceNUIUpdate( false ) + end + + if ( playBeep ) then SendNUIMessage( { _type = "audio", name = "beep", vol = RADAR:GetSettingValue( "plateAudio" ) } ) - end + end - if ( isBolo ) then + if ( isBolo ) then SendNUIMessage( { _type = "audio", name = "plate_hit", vol = RADAR:GetSettingValue( "plateAudio" ) } ) - end - + end + -- Trigger an event so developers can hook into the scanner every time a plate is locked TriggerServerEvent( "wk:onPlateLocked", cam, self:GetPlate( cam ), self:GetIndex( cam ) ) - end - end -end + end + + -- Tell the NUI side to show/hide the lock icon + SendNUIMessage( { _type = "lockPlate", cam = cam, state = self:GetCamLocked( cam ), isBolo = isBolo } ) + end +end -- Returns if the plate reader system can perform tasks function READER:CanPerformMainTask() return self.vars.displayed and not self.vars.hidden -end +end --- Returns if the given relative position value is for front or rear +-- Returns if the given relative position value is for front or rear function READER:GetCamFromNum( relPos ) - if ( relPos == 1 ) then + if ( relPos == 1 ) then return "front" - elseif ( relPos == -1 ) then + elseif ( relPos == -1 ) then return "rear" - end -end + end +end + +-- Forces an NUI update, used by the passenger control system +function READER:ForceNUIUpdate( lock ) + for cam in UTIL:Values( { "front", "rear" } ) do + local plate = self:GetPlate( cam ) + local index = self:GetIndex( cam ) + + if ( plate ~= "" and index ~= "" ) then + SendNUIMessage( { _type = "changePlate", cam = cam, plate = plate, index = index } ) + + if ( lock ) then + SendNUIMessage( { _type = "lockPlate", cam = cam, state = self:GetCamLocked( cam ), isBolo = false } ) + end + end + end +end + +-- Returns a table with both antenna's speed data and directions +function READER:GetCameraDataPacket( cam ) + return { + self:GetPlate( cam ), + self:GetIndex( cam ) + } +end RegisterNetEvent( "wk:togglePlateLock" ) AddEventHandler( "wk:togglePlateLock", function( cam, beep, bolo ) READER:LockCam( cam, beep, bolo ) end ) --- Runs when the "Toggle Display" button is pressed on the plate reder box + +--[[---------------------------------------------------------------------------------- + Plate reader NUI callbacks +----------------------------------------------------------------------------------]]-- +-- Runs when the "Toggle Display" button is pressed on the plate reder box RegisterNUICallback( "togglePlateReaderDisplay", function( data, cb ) - -- Toggle the display state + -- Toggle the display state READER:ToggleDisplayState() - cb('ok') + cb( "ok" ) end ) -- Runs when the "Set BOLO Plate" button is pressed on the plate reader box RegisterNUICallback( "setBoloPlate", function( plate, cb ) - -- Set the BOLO plate + -- Set the BOLO plate READER:SetBoloPlate( plate ) - cb('ok') + cb( "ok" ) end ) +-- Runs when the "Clear BOLO Plate" button is pressed on the plate reader box +RegisterNUICallback( "clearBoloPlate", function( plate, cb ) + -- Clear the BOLO plate + READER:ClearBoloPlate() + cb( "ok" ) +end ) + + +--[[---------------------------------------------------------------------------------- + Plate reader threads +----------------------------------------------------------------------------------]]-- -- This is the main function that runs and scans all vehicles in front and behind the patrol vehicle function READER:Main() - -- Check that the system can actually run - if ( PLY:VehicleStateValid() and self:CanPerformMainTask() ) then + -- Check that the system can actually run + if ( PLY:VehicleStateValid() and self:CanPerformMainTask() ) then -- Loop through front (1) and rear (-1) - for i = 1, -1, -2 do + for i = 1, -1, -2 do -- Get the world position of the player's vehicle local pos = GetEntityCoords( PLY.veh ) @@ -202,14 +242,14 @@ function READER:Main() -- Get the end position 50m in front/behind the player's vehicle local offset = GetOffsetFromEntityInWorldCoords( PLY.veh, -2.5, ( 50.0 * i ), 0.0 ) - -- Run the ray trace to get a vehicle + -- Run the ray trace to get a vehicle local veh = UTIL:GetVehicleInDirection( PLY.veh, start, offset ) -- Get the plate reader text for front/rear local cam = self:GetCamFromNum( i ) - + -- Only proceed to read a plate if the hit entity is a valid vehicle and the current camera isn't locked - if ( DoesEntityExist( veh ) and IsEntityAVehicle( veh ) and not self:GetCamLocked( cam ) ) then + if ( DoesEntityExist( veh ) and IsEntityAVehicle( veh ) and not self:GetCamLocked( cam ) ) then -- Get the heading of the player's vehicle and the hit vehicle local ownH = UTIL:Round( GetEntityHeading( PLY.veh ), 0 ) local tarH = UTIL:Round( GetEntityHeading( veh ), 0 ) @@ -218,16 +258,16 @@ function READER:Main() local dir = UTIL:GetEntityRelativeDirection( ownH, tarH ) -- Only run the rest of the plate check code if we can see the front or rear of the vehicle - if ( dir > 0 ) then - -- Get the licence plate text from the vehicle + if ( dir > 0 ) then + -- Get the licence plate text from the vehicle local plate = GetVehicleNumberPlateText( veh ) - -- Get the licence plate index from the vehicle + -- Get the licence plate index from the vehicle local index = GetVehicleNumberPlateTextIndex( veh ) -- Only update the stored plate if it's different, otherwise we'd keep sending a NUI message to update the displayed - -- plate and image even though they're the same - if ( self:GetPlate( cam ) ~= plate ) then + -- plate and image even though they're the same + if ( self:GetPlate( cam ) ~= plate ) then -- Set the plate for the current reader self:SetPlate( cam, plate ) @@ -235,21 +275,26 @@ function READER:Main() self:SetIndex( cam, index ) -- Automatically lock the plate if the scanned plate matches the BOLO - if ( plate == self:GetBoloPlate() ) then + if ( plate == self:GetBoloPlate() ) then self:LockCam( cam, false, true ) - end + + SYNC:LockReaderCam( cam, READER:GetCameraDataPacket( cam ) ) + end -- Send the plate information to the NUI side to update the UI SendNUIMessage( { _type = "changePlate", cam = cam, plate = plate, index = index } ) - -- Trigger the event so developers can hook into the scanner every time a plate is scanned - TriggerServerEvent( "wk:onPlateScanned", cam, plate, index ) - end - end - end - end - end -end + -- If we use Sonoran CAD, reduce the plate events to just player's vehicle, otherwise life as normal + if ( ( CONFIG.use_sonorancad and ( UTIL:IsPlayerInVeh( veh ) or IsVehiclePreviouslyOwnedByPlayer( veh ) ) and GetVehicleClass( veh ) ~= 18 ) or not CONFIG.use_sonorancad ) then + -- Trigger the event so developers can hook into the scanner every time a plate is scanned + TriggerServerEvent( "wk:onPlateScanned", cam, plate, index ) + end + end + end + end + end + end +end -- Main thread Citizen.CreateThread( function() @@ -259,31 +304,31 @@ Citizen.CreateThread( function() -- Wait half a second Citizen.Wait( 500 ) - end + 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 +-- 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 READER: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 ) + self:SetDisplayHidden( true ) SendNUIMessage( { _type = "setReaderDisplayState", state = false } ) - elseif ( PLY.veh > 0 and PLY.vehClassValid and PLY.inDriverSeat and self:GetDisplayState() and self:GetDisplayHidden() ) then - self:SetDisplayHidden( false ) + elseif ( PLY:CanViewRadar() and self:GetDisplayState() and self:GetDisplayHidden() ) then + self:SetDisplayHidden( false ) SendNUIMessage( { _type = "setReaderDisplayState", state = true } ) - end + end end -- Runs the display validation check for the radar -Citizen.CreateThread( function() +Citizen.CreateThread( function() Citizen.Wait( 100 ) - while ( true ) do - -- Run the check + while ( true ) do + -- Run the check READER:RunDisplayValidationCheck() - -- Wait half a second + -- Wait half a second Citizen.Wait( 500 ) - end + end end ) \ No newline at end of file diff --git a/cl_player.lua b/cl_player.lua new file mode 100644 index 0000000..5d0ba4a --- /dev/null +++ b/cl_player.lua @@ -0,0 +1,136 @@ +--[[--------------------------------------------------------------------------------------- + + 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. + +---------------------------------------------------------------------------------------]]-- + +--[[---------------------------------------------------------------------------------- + Player info variables +----------------------------------------------------------------------------------]]-- +PLY = +{ + ped = PlayerPedId(), + veh = nil, + inDriverSeat = false, + inPassengerSeat = false, + vehClassValid = false +} + +-- Returns if the current vehicle fits the validity requirements for the radar to work +function PLY:VehicleStateValid() + return DoesEntityExist( self.veh ) and self.veh > 0 and self.vehClassValid +end + +-- Used to check if the player is in a position where the radar should be allowed operation +function PLY:IsDriver() + return self:VehicleStateValid() and self.inDriverSeat +end + +-- Returns if the player is in the front passenger seat of an emergency vehicle +function PLY:IsPassenger() + return self:VehicleStateValid() and self.inPassengerSeat +end + +-- Returns if the player can view the radar, ensures their vehicle state is valid and that they are a driver or +-- a passenger (where valid) +function PLY:CanViewRadar() + return self:IsDriver() or ( self:IsPassenger() and RADAR:IsPassengerViewAllowed() ) +end + +-- Returns if the player is allowed to control the radar from the passenger seat +function PLY:CanControlRadar() + return self:IsDriver() or ( self:IsPassenger() and RADAR:IsPassengerControlAllowed() ) +end + +-- Returns the ped in the opposite seat to the player, e.g. if we're the passenger, then return the driver +function PLY:GetOtherPed() + if ( self:IsDriver() ) then + return GetPedInVehicleSeat( PLY.veh, 0 ) + elseif ( self:IsPassenger() ) then + return GetPedInVehicleSeat( PLY.veh, -1 ) + end + + return nil +end + +-- Returns the server ID of the player in the opposite seat (driver/passenger) +function PLY:GetOtherPedServerId() + local otherPed = self:GetOtherPed() + + if ( otherPed ~= nil and otherPed ~= 0 and IsPedAPlayer( otherPed ) ) then + local otherPly = GetPlayerServerId( NetworkGetPlayerIndexFromPed( otherPed ) ) + + return otherPly + end + + return nil +end + +-- The main purpose of this thread is to update the information about the local player, including their +-- ped id, the vehicle id (if they're in one), whether they're in a driver seat, and if the vehicle's class +-- is valid or not +Citizen.CreateThread( function() + while ( true ) do + PLY.ped = PlayerPedId() + PLY.veh = GetVehiclePedIsIn( PLY.ped, false ) + PLY.inDriverSeat = GetPedInVehicleSeat( PLY.veh, -1 ) == PLY.ped + PLY.inPassengerSeat = GetPedInVehicleSeat( PLY.veh, 0 ) == PLY.ped + PLY.vehClassValid = GetVehicleClass( PLY.veh ) == 18 + + Citizen.Wait( 500 ) + end +end ) + +-- This thread is used to check when the player is entering a vehicle and then triggers the sync system +Citizen.CreateThread( function() + while ( true ) do + -- The sync trigger should only start when the player is getting into a vehicle + if ( IsPedGettingIntoAVehicle( PLY.ped ) and RADAR:IsPassengerViewAllowed() ) then + -- Get the vehicle the player is entering + local vehEntering = GetVehiclePedIsEntering( PLY.ped ) + + -- Only proceed if the vehicle the player is entering is an emergency vehicle + if ( GetVehicleClass( vehEntering ) == 18 ) then + -- Wait two seconds, this gives enough time for the player to get sat in the seat + Citizen.Wait( 2000 ) + + -- Get the vehicle the player is now in + local veh = GetVehiclePedIsIn( PLY.ped, false ) + + -- Trigger the main sync data function if the vehicle the player is now in is the same as the one they + -- began entering + if ( veh == vehEntering ) then + SYNC:SyncDataOnEnter() + end + end + end + + Citizen.Wait( 500 ) + end +end ) \ No newline at end of file diff --git a/cl_radar.lua b/cl_radar.lua index f943078..1c51439 100644 --- a/cl_radar.lua +++ b/cl_radar.lua @@ -2,13 +2,13 @@ Wraith ARS 2X Created by WolfKnight - - For discussions, information on future updates, and more, join - my Discord: https://discord.gg/fD4e6WD - + + For discussions, information on future updates, and more, join + my Discord: https://discord.gg/fD4e6WD + MIT License - Copyright (c) 2020 WolfKnight + 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 @@ -30,90 +30,140 @@ ---------------------------------------------------------------------------------------]]-- --- Cache some of the main Lua functions and libraries -local next = next -local dot = dot -local table = table +-- 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 - ---[[---------------------------------------------------------------------------------- - Resource Rename Fix - for those muppets who rename the resource and - complain that the NUI aspect doesn't work! -----------------------------------------------------------------------------------]]-- -Citizen.SetTimeout( 1000, function() - -- Get the name of the resource, for example the default name is 'wk_wars2x' - local name = string.lower( GetCurrentResourceName() ) - - -- Print a little message in the client's console - UTIL:Log( "Sending resource name (" .. name .. ") to JavaScript side." ) - - -- Send a message through the NUI system to the JavaScript file to give the name of the resource - SendNUIMessage( { _type = "updatePathName", pathName = name } ) -end ) +local math = math +local pairs = pairs --[[---------------------------------------------------------------------------------- - UI loading trigger + Key bind registering ----------------------------------------------------------------------------------]]-- -local spawned = false +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 + + +--[[---------------------------------------------------------------------------------- + UI loading and key binds trigger +----------------------------------------------------------------------------------]]-- +local spawned = false + +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 -- Runs every time the player spawns, but the additional check means it only runs the first time -- the player spawns AddEventHandler( "playerSpawned", function() - if ( not spawned ) then - 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 + if ( not spawned ) then + RegisterKeyBinds() + LoadUISettings() spawned = true - end + end end ) +-- Loads the UI settings when the resource gets restarted, this way active users don't have the +-- default settings applied +AddEventHandler( "onResourceStart", function( resourceName ) + if ( GetCurrentResourceName() == resourceName ) then + Citizen.CreateThread( function() + Citizen.Wait( 1000 ) ---[[---------------------------------------------------------------------------------- - Player info variables -----------------------------------------------------------------------------------]]-- -PLY = -{ - ped = PlayerPedId(), - veh = nil, - inDriverSeat = false, - vehClassValid = false -} + RegisterKeyBinds() + LoadUISettings() --- Used to check if the player is in a position where the radar should be allowed operation -function PLY:VehicleStateValid() - return DoesEntityExist( self.veh ) and self.veh > 0 and self.inDriverSeat and self.vehClassValid -end - --- The main purpose of this thread is to update the information about the local player, including their --- ped id, the vehicle id (if they're in one), whether they're in a driver seat, and if the vehicle's class --- is valid or not -Citizen.CreateThread( function() - while ( true ) do - PLY.ped = PlayerPedId() - PLY.veh = GetVehiclePedIsIn( PLY.ped, false ) - PLY.inDriverSeat = GetPedInVehicleSeat( PLY.veh, -1 ) == PLY.ped - PLY.vehClassValid = GetVehicleClass( PLY.veh ) == 18 - - Citizen.Wait( 500 ) - end + DecorSetBool( PlayerPedId(), "wk_wars2x_sync_remoteOpen", false ) + end ) + end end ) @@ -121,49 +171,55 @@ end ) Radar variables NOTE - This is not a config, do not touch anything unless you know what - you are actually doing. + you are actually doing. ----------------------------------------------------------------------------------]]-- RADAR = {} -RADAR.vars = +RADAR.vars = { - -- Whether or not the radar's UI is visible + -- 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 + -- 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, + 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 + -- These are the settings that are used in the operator menu settings = { -- Should the system calculate and display faster targets - ["fastDisplay"] = CONFIG.menuDefaults["fastDisplay"], + ["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"], + ["same"] = CONFIG.menuDefaults["same"], + ["opp"] = CONFIG.menuDefaults["opp"], - -- The volume of the audible beep + -- The volume of the audible beep ["beep"] = CONFIG.menuDefaults["beep"], - - -- The volume of the verbal lock confirmation + + -- The volume of the verbal lock confirmation ["voice"] = CONFIG.menuDefaults["voice"], - - -- The volume of the plate reader audio - ["plateAudio"] = CONFIG.menuDefaults["plateAudio"], + + -- The volume of the plate reader audio + ["plateAudio"] = CONFIG.menuDefaults["plateAudio"], -- The speed unit used in conversions - ["speedType"] = CONFIG.menuDefaults["speedType"] + ["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, + -- 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" }, @@ -174,30 +230,30 @@ RADAR.vars = { 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 + -- 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, this table contains all of the data needed for operation of the front and rear antennas antennas = { - -- Variables for the front antenna + -- 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 + 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 + 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 + 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 @@ -206,51 +262,47 @@ RADAR.vars = 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 = {}, + -- Cached dynamic vehicle sphere sizes, automatically populated when the system is running + sphereSizes = {}, - -- Table to store tables for hit entities of captured vehicles + -- Table to store tables for hit entities of captured vehicles capturedVehicles = {}, - -- Table for temp id storage to stop unnecessary trace checks - -- needs to be redone - -- tempVehicleIDs = {}, - -- Table to store the valid vehicle models - validVehicles = {}, + validVehicles = {}, - -- The current vehicle data for display + -- 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 = {}, + -- 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 + -- 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 + -- 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 + -- 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 + 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 +-- 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" }, @@ -259,171 +311,158 @@ RADAR.rayTraces = { { 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 +-- 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, + strongest = function( a, b ) return a.size > b.size end, fastest = function( a, b ) return a.speed > b.speed end } --[[---------------------------------------------------------------------------------- - Radar essentials functions + Radar essentials functions ----------------------------------------------------------------------------------]]-- -- Returns if the radar's power is on or off function RADAR:IsPowerOn() - return self.vars.power -end + 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 +end --- Allows the powering up state variable to be set +-- Allows the powering up state variable to be set function RADAR:SetPoweringUpState( state ) - self.vars.poweringUp = state -end + self.vars.poweringUp = state +end -- Toggles the radar power -function RADAR:TogglePower() - -- Toggle the power variable - self.vars.power = not self.vars.power - - -- Send the NUI message to toggle the power - SendNUIMessage( { _type = "radarPower", state = self:IsPowerOn() } ) +function RADAR:SetPowerState( state, instantOverride ) + local currentState = self:IsPowerOn() - -- Power is now turned on - if ( self:IsPowerOn() ) then - -- Also make sure the operator menu is inactive - self:SetMenuState( false ) - - -- Tell the system the radar is 'powering up' - self:SetPoweringUpState( true ) + -- 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 - -- Set a 2 second countdown - Citizen.SetTimeout( 2000, function() - -- Tell the system the radar has 'powered up' - self:SetPoweringUpState( false ) + -- Send the NUI message to toggle the power + SendNUIMessage( { _type = "radarPower", state = state, override = instantOverride, fast = self:IsFastDisplayEnabled() } ) - -- Let the UI side know the system has loaded - SendNUIMessage( { _type = "poweredUp" } ) - end ) - else - -- If the system is being turned off, then we reset the antennas - self:ResetAntenna( "front" ) - self:ResetAntenna( "rear" ) + -- 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 + -- Toggle the display variable + self.vars.displayed = not self.vars.displayed - -- Send the toggle message to the NUI side + -- Send the toggle message to the NUI side SendNUIMessage( { _type = "setRadarDisplayState", state = self:GetDisplayState() } ) -end +end -- Gets the display state function RADAR:GetDisplayState() return self.vars.displayed -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 -- Return the state of the fastDisplay setting, short hand direct way to check if the fast system is enabled function RADAR:IsFastDisplayEnabled() - return self.vars.settings["fastDisplay"] -end + return self:GetSettingValue( "fastDisplay" ) +end --- Returns if either of the antennas are transmitting +-- Returns if either of the antennas are transmitting function RADAR:IsEitherAntennaOn() return self:IsAntennaTransmitting( "front" ) or self:IsAntennaTransmitting( "rear" ) -end +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 + -- 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 + 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 what the dynamic thread wait time is -function RADAR:GetThreadWaitTime() - return self.vars.threadWaitTime -end - --- Sets the dynamic thread wait time to the given value -function RADAR:SetThreadWaitTime( time ) - self.vars.threadWaitTime = time -end - --- Sets the display's hidden state to the given state -function RADAR:SetDisplayHidden( state ) - self.vars.hidden = state -end - --- Returns if the display is hidden -function RADAR:GetDisplayHidden() - return self.vars.hidden end --- Opens the remote only if the pause menu is not open and the player's vehicle state is valid +-- 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:VehicleStateValid() ) then - -- Tell the NUI side to open the remote - SendNUIMessage( { _type = "openRemote" } ) + if ( not IsPauseMenuActive() and PLY:CanViewRadar() ) then + -- Get the remote open state from the other player + local openByOtherPly = SYNC:IsRemoteAlreadyOpen( PLY:GetOtherPed() ) - if ( CONFIG.allow_quick_start_video ) then - -- Display the new user popup if we can - local show = GetResourceKvpInt( "wk_wars2x_new_user" ) + -- Check that the remote can be opened + if ( not openByOtherPly ) then + -- Tell the NUI side to open the remote + SendNUIMessage( { _type = "openRemote" } ) - if ( show == 0 ) then - SendNUIMessage( { _type = "showNewUser" } ) - end - end + SYNC:SetRemoteOpenState( true ) - -- Bring focus to the NUI side - SetNuiFocus( true, 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 +end -- Event to open the remote RegisterNetEvent( "wk:openRemote" ) @@ -431,6 +470,22 @@ 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 @@ -438,338 +493,360 @@ 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 + -- 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 = + -- 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 + -- 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 + end - -- Create the settings with the default options - self:SetSettingValue( "fastLock", false ) - self:SetSettingValue( "fastLimit", 60 ) - - -- Add the fast options to the main menu options table + -- Add the fast options to the main menu options table table.insert( self.vars.menuOptions, fastOptions[1] ) table.insert( self.vars.menuOptions, fastOptions[2] ) - end + end - -- Returns the numerical fast limit + -- Returns the numerical fast limit function RADAR:GetFastLimit() - return self.vars.settings["fastLimit"] - end + return self:GetSettingValue( "fastLimit" ) + end -- Returns if the fast lock menu option is on or off function RADAR:IsFastLockEnabled() - return self.vars.settings["fastLock"] - end -end + 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:VehicleStateValid() ) then - -- Toggle the key lock variable + 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 +end --- Returns the key lock state +-- Returns the key lock state function RADAR:GetKeyLockState() return self.vars.keyLock -end +end --[[---------------------------------------------------------------------------------- - Radar menu functions + 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 + -- 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 + if ( state ) then self.vars.currentOptionIndex = 1 end end -end +end --- Returns if the operator menu is open +-- 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 +end --- This function changes the menu index variable so the user can iterate through the options in the operator menu +-- 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 + -- 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 +end -- Returns the option table of the current menu index function RADAR:GetMenuOptionTable() return self.vars.menuOptions[self.vars.currentOptionIndex] -end +end --- Changes the index for an individual option +-- 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 +end --- Returns the option value for the current option +-- Returns the option value for the current option function RADAR:GetMenuOptionValue() local opt = self:GetMenuOptionTable() local index = opt.optionIndex return opt.options[index] -end +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 + -- Get the option table of the currently selected option local opt = self:GetMenuOptionTable() - -- Get the current option index of the selected option + -- Get the current option index of the selected option local index = opt.optionIndex - -- Cache the size of this setting's options table + -- 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 + if ( dir == "front" ) then index = index + 1 - if ( index > size ) then index = 1 end - elseif ( dir == "rear" ) then + if ( index > size ) then index = 1 end + elseif ( dir == "rear" ) then index = index - 1 - if ( index < 1 ) then index = size end + if ( index < 1 ) then index = size end end - -- Update the option's index + -- Update the option's index self:SetMenuOptionIndex( index ) - -- Change the value of the setting in the main RADAR.vars.settings table + -- 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 +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 +end --- Returns the option text of the currently selected setting +-- Returns the option text of the currently selected setting function RADAR:GetMenuOptionText() local opt = self:GetMenuOptionTable() return opt.optionsText[opt.optionIndex] -end +end --- Sends a message to the NUI side with updated information on what should be displayed for the menu +-- 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 +end --- Attempts to load the saved operator menu data +-- 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 + -- 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 + else UTIL:Log( "Could not find any saved operator menu data." ) - end -end + end +end --- Updates the operator menu option indexes, as the default menu values can be changed in the config, we +-- 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() - self:LoadOMData() +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 + 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 + 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 ( 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 + if ( v == ov ) then t.optionIndex = oi - end - end - end - end - end -end + end + end + end + end + end +end --[[---------------------------------------------------------------------------------- - Radar basics functions + Radar basics functions ----------------------------------------------------------------------------------]]-- -- Returns the patrol speed value stored -function RADAR:GetPatrolSpeed() - return self.vars.patrolSpeed -end +function RADAR:GetPatrolSpeed() return self.vars.patrolSpeed end --- Returns the current vehicle pool -function RADAR:GetVehiclePool() - return self.vars.vehiclePool -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 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 +function RADAR:GetStrongestSortFunc() return self.sorting.strongest end -- Returns the table sorting function 'fastest' -function RADAR:GetFastestSortFunc() - return self.sorting.fastest -end +function RADAR:GetFastestSortFunc() return self.sorting.fastest end --- Sets the patrol speed to a formatted version of the given number +-- Sets the patrol speed to a formatted version of the given number function RADAR:SetPatrolSpeed( speed ) - if ( type( speed ) == "number" ) then + 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 + if ( type( pool ) == "table" ) then + self.vars.vehiclePool = pool end -end +end --[[---------------------------------------------------------------------------------- - Radar ray trace functions + Radar ray trace functions ----------------------------------------------------------------------------------]]-- -- Returns what the current ray trace state is -function RADAR:GetRayTraceState() - return self.vars.rayTraceState -end +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 +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 +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 +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 +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 +-- 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 + if ( t > 8.0 ) then + return 1 -- vehicle is in front + elseif ( t < -8.0 ) then return -1 -- vehicle is behind - end + end return 0 -- vehicle is next to self -end +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. +-- 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 + -- 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! + -- 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 + -- 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. + -- 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 + 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 + -- 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 + if ( oppLenSqr < radiusSqr and not ( distToCentre > rayDist ) ) then return true, self:GetIntersectedVehIsFrontOrRear( tProj ) end - return false, nil -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. @@ -792,30 +869,30 @@ 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. +-- 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 + -- 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 + -- 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 + -- 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 + -- 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 ) @@ -823,55 +900,55 @@ function RADAR:ShootCustomRay( plyVeh, veh, s, e ) 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 + if ( hit and self:IsVehicleInTraffic( veh, relPos ) ) then return true, relPos, dist, entSpeed, size - end + end end - end + end -- Return a whole lot of nothing return false, nil, nil, nil, nil -end +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. +-- 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 + local hasData = false -- Iterate through all of the vehicles - for _, veh in pairs( vehs ) do + 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 + -- 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.veh = veh vehData.relPos = relativePos vehData.dist = distance vehData.speed = speed vehData.size = size - -- Insert the table into the caught vehicles table + -- 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 + hasData = true + end + end -- If the caughtVehs table actually has data, then return it if ( hasData ) then return caughtVehs end -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. +-- 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 ) @@ -880,133 +957,111 @@ function RADAR:CreateRayThread( vehs, from, startX, endX, endY, rayType ) -- 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 + -- Insert the captured vehicle data and pass the ray type too self:InsertCapturedVehicleData( hitVehs, rayType ) - -- Increase the ray trace state + -- Increase the ray trace state self:IncreaseRayTraceState() -end +end --- This function iterates through each of the traces defined in RADAR.rayTraces and creates a 'thread' for +-- 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 + for _, v in pairs( self.rayTraces ) do self:CreateRayThread( vehicles, ownVeh, v.startVec.x, v.endVec.x, v.endVec.y, v.rayType ) - end -end + 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 + 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 + + -- Update the end Y coordinate in the traces table v.endVec.y = endY - end -end + end +end --[[---------------------------------------------------------------------------------- - Radar antenna functions + Radar antenna functions ----------------------------------------------------------------------------------]]-- --- Toggles the state of the given antenna between hold and transmitting, only works if the radar's power is +-- 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, cb ) - -- Check power is on - if ( self:IsPowerOn() ) then +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 + self.vars.antennas[ant].xmit = not self.vars.antennas[ant].xmit - -- Run the callback function if there is one - if ( cb ) then cb() end - end -end + -- 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 +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 + if ( relPos == 1 ) then return "front" - elseif ( relPos == -1 ) then + elseif ( relPos == -1 ) then return "rear" - end -end + end +end -- Returns the mode of the given antenna -function RADAR:GetAntennaMode( ant ) - return self.vars.antennas[ant].mode -end +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 +-- 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, cb ) - -- 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 +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 + if ( mode >= 0 and mode <= 3 and self:IsPowerOn() ) then -- Update the mode for the antenna - self.vars.antennas[ant].mode = mode + self.vars.antennas[ant].mode = mode - -- Run the callback function if there is one - if ( cb ) then cb() end - end - end -end + -- Update the interface with the new mode + SendNUIMessage( { _type = "antennaMode", ant = ant, mode = mode } ) --- Returns the speed stored for the given antenna -function RADAR:GetAntennaSpeed( ant ) - return self.vars.antennas[ant].speed -end + -- Play a beep + SendNUIMessage( { _type = "audio", name = "beep", vol = self:GetSettingValue( "beep" ) } ) + end + end +end --- Sets the speed of the given antenna to the given speed -function RADAR:SetAntennaSpeed( ant, speed ) - self.vars.antennas[ant].speed = speed -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 the direction value stored for the given antenna -function RADAR:GetAntennaDir( ant ) - return self.vars.antennas[ant].dir -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 direction value of the given antenna to the given direction -function RADAR:SetAntennaDir( ant, dir ) - self.vars.antennas[ant].dir = dir -end - --- Sets the fast speed and direction in one go +-- Sets the speed and direction in one go function RADAR:SetAntennaData( ant, speed, dir ) self:SetAntennaSpeed( ant, speed ) self:SetAntennaDir( ant, dir ) end --- Returns the fast speed stored for the given antenna -function RADAR:GetAntennaFastSpeed( ant ) - return self.vars.antennas[ant].fastSpeed -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 --- Sets the fast speed of the given antenna to the given speed -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 --- Returns the direction value for the fast box stored for the given antenna -function RADAR:GetAntennaFastDir( ant ) - return self.vars.antennas[ant].fastDir -end - --- Sets the direction value of the given antenna's fast box to the given direction -function RADAR:SetAntennaFastDir( ant, dir ) - self.vars.antennas[ant].fastDir = dir -end - --- Sets the fast speed and direction in one go +-- Sets the fast speed and direction in one go function RADAR:SetAntennaFastData( ant, speed, dir ) self:SetAntennaFastSpeed( ant, speed ) self:SetAntennaFastDir( ant, dir ) @@ -1014,148 +1069,178 @@ end -- Returns if the stored speed for the given antenna is valid function RADAR:DoesAntennaHaveValidData( ant ) - return self:GetAntennaSpeed( ant ) ~= nil -end + 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 + return self:GetAntennaFastSpeed( ant ) ~= nil +end --- Returns if the fast label should be displayed +-- Returns if the fast label should be displayed function RADAR:ShouldFastBeDisplayed( ant ) - if ( self:IsAntennaSpeedLocked( ant ) ) then - return self:GetAntennaLockedType( ant ) == 2 - else + if ( self:IsAntennaSpeedLocked( ant ) ) then + return self:GetAntennaLockedType( ant ) == 2 + else return self:IsFastDisplayEnabled() end -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 +-- 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 +end --- Sets a speed and direction to be locked in for the given antenna -function RADAR:SetAntennaSpeedLock( ant, speed, dir, lockType ) +-- 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 + 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].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 ) - -- 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" ) } ) - + 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 + if ( speed == "¦88" and self:GetSettingValue( "speedType" ) == "mph" ) then math.randomseed( GetGameTimer() ) local chance = math.random() - + -- 15% chance - if ( chance <= 0.15 ) then + if ( chance <= 0.15 ) then SendNUIMessage( { _type = "audio", name = "speed_alert", vol = self:GetSettingValue( "beep" ) } ) - end - end + end + end end -end +end -- Returns the locked speed for the given antenna function RADAR:GetAntennaLockedSpeed( ant ) return self.vars.antennas[ant].lockedSpeed -end +end -- Returns the locked direction for the given antenna function RADAR:GetAntennaLockedDir( ant ) return self.vars.antennas[ant].lockedDir -end +end -- Returns the lock type for the given antenna function RADAR:GetAntennaLockedType( ant ) - return self.vars.antennas[ant].lockedType -end + 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].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 +-- 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 ) +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() and self:IsAntennaTransmitting( ant ) ) then - -- 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 + 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 + -- 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 + -- 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 ) + 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 ) + 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] ) - else + self:SetAntennaSpeedLock( ant, data[1], data[2], data[3], not isMenuOpen ) + else self:ResetAntennaSpeedLock( ant ) - end + end - -- 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 + 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 + -- 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].xmit = false self.vars.antennas[ant].mode = 0 self:ResetAntennaSpeedLock( ant ) -end +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 + Radar captured vehicle functions ----------------------------------------------------------------------------------]]-- --- Returns the captured vehicles table +-- Returns the captured vehicles table function RADAR:GetCapturedVehicles() return self.vars.capturedVehicles end --- Resets the captured vehicles table to an empty table +-- Resets the captured vehicles table to an empty table function RADAR:ResetCapturedVehicles() self.vars.capturedVehicles = {} end @@ -1164,82 +1249,82 @@ end -- 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 + 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 + v.rayType = rt + + -- Insert it into the main captured vehicles table table.insert( self.vars.capturedVehicles, v ) end - end -end + end +end --[[---------------------------------------------------------------------------------- - Radar dynamic sphere radius functions + 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 +-- 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 +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 +-- 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 + 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 + -- 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 + end +end --- Returns the dynamic sphere data for the given vehicle +-- 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 +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 +-- 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 + -- Get the model of the vehicle local mdl = GetEntityModel( veh ) - - -- Create a key based on the model + + -- 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 + + -- 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 - + + -- 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 - + 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 ) @@ -1248,7 +1333,7 @@ function RADAR:GetDynamicRadius( veh ) -- Return the data return dynamicRadius, numericSize - end + end -- Return the stored data return self:GetRadiusData( key ) @@ -1256,57 +1341,51 @@ end --[[---------------------------------------------------------------------------------- - Radar functions + Radar functions ----------------------------------------------------------------------------------]]-- --- Takes a GTA speed and converts it into the type defined by the user in the operator menu +-- 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 the coverted speed rounded to a whole number return UTIL:Round( speed * self.speedConversions[unit], 0 ) -end +end --- Returns the validity of the given vehicle model -function RADAR:GetVehicleValidity( key ) - return self.vars.validVehicles[key] -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 --- Sets the validity for the given vehicle model -function RADAR:SetVehicleValidity( key, validity ) - self.vars.validVehicles[key] = validity -end - --- Returns if vehicle validity data exists for the given vehicle model +-- Returns if vehicle validity data exists for the given vehicle model function RADAR:DoesVehicleValidityExist( key ) - return self:GetVehicleValidity( key ) ~= nil -end + 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 + -- Get the model of the vehicle local mdl = GetEntityModel( veh ) - - -- Create a key based on the model + + -- 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 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 + 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 false + else + self:SetVehicleValidity( key, true ) + return true + end + end - return valid -end + return valid +end -- Gathers all of the vehicles in the local area of the player function RADAR:GetAllVehicles() @@ -1315,26 +1394,26 @@ function RADAR:GetAllVehicles() -- Iterate through vehicles for v in UTIL:EnumerateVehicles() do - if ( self:IsVehicleValid( v ) ) then - -- Insert the vehicle id into the temporary table + if ( self:IsVehicleValid( v ) ) then + -- Insert the vehicle id into the temporary table table.insert( t, v ) - end - end + end + end - -- Return the table + -- Return the table return t -end +end --- Used to check if an antennas mode fits with a ray type from the ray trace system +-- 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 + 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 + return false end -- This function is used to filter through the captured vehicles and work out what vehicles should be used for display @@ -1342,297 +1421,303 @@ end 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 + -- 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 + for ant in UTIL:Values( { "front", "rear" } ) do -- Check that the antenna is actually transmitting - if ( self:IsAntennaTransmitting( ant ) ) then + if ( self:IsAntennaTransmitting( ant ) ) then -- Iterate through the captured vehicles - for k, v in pairs( self:GetCapturedVehicles() ) do + 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 + -- 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 + 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. + -- will be the one that gets displayed in the target boxes. table.sort( vehs[ant], self:GetStrongestSortFunc() ) end - end + end - -- Now that we have all of the vehicles split into front and rear, we can iterate through both sets and get + -- 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 + 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 + -- 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 + if ( self:CheckVehicleDataFitsMode( ant, v.rayType ) ) then + -- Set the result for the current antenna results[ant][1] = v break - end - end + 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 + -- 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 + 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 + -- 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 - end + end end - -- Return the results + -- Return the results return { ["front"] = { results["front"][1], results["front"][2] }, ["rear"] = { results["rear"][1], results["rear"][2] } } -end +end --[[---------------------------------------------------------------------------------- NUI callback ----------------------------------------------------------------------------------]]-- --- Runs when the "Toggle Display" button is pressed on the remote control +-- Runs when the "Toggle Display" button is pressed on the remote control RegisterNUICallback( "toggleRadarDisplay", function( data, cb ) - -- Toggle the display state + -- Toggle the display state RADAR:ToggleDisplayState() - cb('ok') + cb( "ok" ) end ) --- Runs when the user presses the power button on the radar ui +-- Runs when the user presses the power button on the radar ui RegisterNUICallback( "togglePower", function( data, cb ) - -- Toggle the radar's power - RADAR:TogglePower() - cb('ok') + 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 +-- 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 + -- Remove focus to the NUI side SetNuiFocus( false, false ) - cb('ok') + + 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 ) - -- 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 - -- 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 - SendNUIMessage( { _type = "audio", name = "done", vol = RADAR:GetSettingValue( "beep" ) } ) +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 ) ) - -- Save the operator menu values - local omData = json.encode( RADAR.vars.settings ) - SetResourceKvp( "wk_wars2x_om_data", omData ) - 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 ), function() - -- Update the interface with the new mode - SendNUIMessage( { _type = "antennaMode", ant = data.value, mode = tonumber( data.mode ) } ) - - -- Play a beep - SendNUIMessage( { _type = "audio", name = "beep", vol = RADAR:GetSettingValue( "beep" ) } ) - end ) - end + -- Sync + SYNC:SendAntennaMode( data.value, tonumber( data.mode ) ) + end + end end - cb('ok') + + cb( "ok" ) end ) --- Runs when the user presses either of the XMIT/HOLD buttons on the remote -RegisterNUICallback( "toggleAntenna", function( data, cb ) - -- 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, function() - -- Update the interface with the new antenna transmit state - SendNUIMessage( { _type = "antennaXmit", ant = data.value, on = RADAR:IsAntennaTransmitting( data.value ) } ) - - -- Play some audio specific to the transmit state - SendNUIMessage( { _type = "audio", name = RADAR:IsAntennaTransmitting( data.value ) and "xmit_on" or "xmit_off", vol = RADAR:GetSettingValue( "beep" ) } ) - end ) - 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') + + cb( "ok" ) end ) -- Runs when the user presses the menu button on the remote control RegisterNUICallback( "menu", function( data, cb ) - -- 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 + 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 ) - -- Play the standard audio beep - SendNUIMessage( { _type = "audio", name = "beep", vol = RADAR:GetSettingValue( "beep" ) } ) + -- 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') + + cb( "ok" ) end ) --- Runs when the JavaScript side sends the UI data for saving +-- 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') + 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') + cb( "ok" ) end ) --[[---------------------------------------------------------------------------------- - Main threads + Main threads ----------------------------------------------------------------------------------]]-- --- Some people might not like the idea of the resource having a CPU MSEC over 0.10, but due to the functions +-- 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 +-- 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 +-- 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 + 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 + else -- Change the thread wait time to 500 ms, the trace system will now run two times a second self:SetThreadWaitTime( 500 ) - end -end + 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 + while ( true ) do -- Run the function RADAR:RunDynamicThreadWaitCheck() -- Make the thread wait two seconds Citizen.Wait( 2000 ) - end + 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. +-- 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 + -- 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:VehicleStateValid() 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 + 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 + 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 + + -- 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 + elseif ( self:GetRayTraceState() == self:GetNumOfRays() ) then -- Reset the ray trace state to 0 self:ResetRayTraceState() end - end -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 + while ( true ) do -- Run the function RADAR:RunThreads() -- Make the thread wait 0 ms Citizen.Wait( 0 ) - end + end end ) --- This is the main function that runs and handles all information that is sent to the NUI side for display, all +-- 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:VehicleStateValid() and self:CanPerformMainTask() ) then + -- 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 = {} + 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 + -- 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 + if ( entSpeed == 0 ) then data.patrolSpeed = "¦[]" - else + else local speed = self:GetVehSpeedConverted( entSpeed ) data.patrolSpeed = UTIL:FormatSpeed( speed ) - end + 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 @@ -1640,73 +1725,78 @@ function RADAR:Main() 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 + 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 + -- 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 + -- 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 + -- 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 + 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 + -- 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 + 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 + + -- 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 ) + data.antennas[ant][i].speed = UTIL:FormatSpeed( convertedSpeed ) - -- Work out if the vehicle is closing or away + -- 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 + -- 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 + 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 + 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 - self:LockAntennaSpeed( ant ) - end - end - else + 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 + if ( i % 2 == 0 ) then self:SetAntennaFastData( ant, nil, nil ) - else + else self:SetAntennaData( ant, nil, nil ) end - end - end - end - end - end + end + end + end + end + end -- Send the update to the NUI side SendNUIMessage( { _type = "update", speed = data.patrolSpeed, antennas = data.antennas } ) - end -end + end +end -- Main thread Citizen.CreateThread( function() @@ -1715,19 +1805,19 @@ Citizen.CreateThread( function() -- 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() - - -- Update the operator menu positions - RADAR:UpdateOptionIndexes() -- If the fast limit feature is allowed, create the config in the radar variables - if ( RADAR:IsFastLimitAllowed() ) then + if ( RADAR:IsFastLimitAllowed() ) then RADAR:CreateFastLimitConfig() - end + end - -- Run the main radar function + -- Update the operator menu positions + RADAR:UpdateOptionIndexes( true ) + + -- Run the main radar function while ( true ) do RADAR:Main() @@ -1735,108 +1825,51 @@ Citizen.CreateThread( function() 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 +-- 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 ) + self:SetDisplayHidden( true ) SendNUIMessage( { _type = "setRadarDisplayState", state = false } ) - elseif ( PLY.veh > 0 and PLY.vehClassValid and PLY.inDriverSeat and self:GetDisplayState() and self:GetDisplayHidden() ) then - self:SetDisplayHidden( false ) + elseif ( PLY:CanViewRadar() and self:GetDisplayState() and self:GetDisplayHidden() ) then + self:SetDisplayHidden( false ) SendNUIMessage( { _type = "setRadarDisplayState", state = true } ) - end + end end -- Runs the display validation check for the radar -Citizen.CreateThread( function() +Citizen.CreateThread( function() Citizen.Wait( 100 ) - while ( true ) do - -- Run the check + while ( true ) do + -- Run the check RADAR:RunDisplayValidationCheck() - -- Wait half a second + -- Wait half a second Citizen.Wait( 500 ) - end + end end ) -- Update the vehicle pool every 3 seconds function RADAR:UpdateVehiclePool() - -- Only update the vehicle pool if we need to - if ( PLY:VehicleStateValid() and self:CanPerformMainTask() and self:IsEitherAntennaOn() ) then + -- 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 + + -- Update the vehicle pool self:SetVehiclePool( vehs ) - end -end + end +end -- Runs the vehicle pool updater -Citizen.CreateThread( function() +Citizen.CreateThread( function() while ( true ) do - -- Update the vehicle pool + -- Update the vehicle pool RADAR:UpdateVehiclePool() -- Wait 3 seconds Citizen.Wait( 3000 ) - end -end ) - -Citizen.CreateThread( function() - Citizen.Wait( 3000 ) - - -- 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() ) then - RADAR:LockAntennaSpeed( "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() ) then - RADAR:LockAntennaSpeed( "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() ) then - READER:LockCam( "front", true, false ) - 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() ) then - READER:LockCam( "rear", true, false ) - 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" ) - end, false ) + end end ) \ No newline at end of file diff --git a/cl_sync.lua b/cl_sync.lua new file mode 100644 index 0000000..e4aac8b --- /dev/null +++ b/cl_sync.lua @@ -0,0 +1,437 @@ +--[[--------------------------------------------------------------------------------------- + + 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. + +---------------------------------------------------------------------------------------]]-- + +-- Register the decorator used to tell if the other player has the remote open +DecorRegister( "wk_wars2x_sync_remoteOpen", 2 ) + +-- Takes the given backup functions and restores the data +local function RestoreData( obj, getFunc, setFunc, setBackupFunc, key ) + if ( key ~= nil ) then + local data = getFunc( obj, key ) + + if ( data ~= nil ) then + setFunc( obj, key, data ) + setBackupFunc( obj, key, nil ) + end + else + local data = getFunc( obj ) + + if ( data ~= nil ) then + setFunc( obj, data ) + setBackupFunc( obj, nil ) + end + end +end + + +--[[---------------------------------------------------------------------------------- + Plate reader sync variables and functions +----------------------------------------------------------------------------------]]-- +-- Declares a table that is used to backup the player's plate reader data +READER.backupData = +{ + cams = { + ["front"] = nil, + ["rear"] = nil + } +} + +-- Returns a table with the front and rear plate reader data +function READER:GetReaderDataForSync() + return { + ["front"] = self.vars.cams["front"], + ["rear"] = self.vars.cams["rear"] + } +end + +-- Sets the internal plate reader data for the given camera +function READER:SetReaderCamData( cam, data ) + if ( type( data ) == "table" ) then + self.vars.cams[cam] = data + end +end + +-- Getter and setter for the backup plate reader data +function READER:GetBackupReaderData( cam ) return self.backupData.cams[cam] end +function READER:SetBackupReaderData( cam, data ) self.backupData.cams[cam] = data end + +-- Returns if there is any backup data for the plate reader +function READER:IsThereBackupData() + return self:GetBackupReaderData( "front" ) ~= nil or self:GetBackupReaderData( "rear" ) ~= nil +end + +-- Backs up the player's plate reader data +function READER:BackupData() + -- Get the player's data + local data = self:GetReaderDataForSync() + + -- Iterate through the front and rear camera + for cam in UTIL:Values( { "front", "rear" } ) do + -- Check that there isn't already backup data, then if not, back up the player's data + if ( self:GetBackupReaderData( cam ) == nil ) then + self:SetBackupReaderData( cam, data[cam] ) + end + end +end + +-- Replaces the internal plate reader data with the data provided +function READER:LoadDataFromDriver( data ) + -- Backup the local data first + self:BackupData() + + -- As a precaution, give the system 50ms before it replaces the local data with the data from the driver + Citizen.SetTimeout( 50, function() + -- Set the camera data + for cam in UTIL:Values( { "front", "rear" } ) do + self:SetReaderCamData( cam, data[cam] ) + end + + -- Force the NUI side to update the plate reader display with the new data + self:ForceNUIUpdate( true ) + end ) +end + +-- Restores the backed up plate reader data +function READER:RestoreFromBackup() + -- Iterate through the cameras and restore their backups + for cam in UTIL:Values( { "front", "rear" } ) do + RestoreData( READER, READER.GetBackupReaderData, READER.SetReaderCamData, READER.SetBackupReaderData, cam ) + end + + -- Force the NUI side to update the plate reader display with the restored data + self:ForceNUIUpdate( true ) +end + + +--[[---------------------------------------------------------------------------------- + Radar sync variables and functions +----------------------------------------------------------------------------------]]-- +-- Declares a table that is used to backup the player's radar data +RADAR.backupData = { + power = nil, + om = nil, + antennas = { + ["front"] = nil, + ["rear"] = nil + } +} + +-- Returns a table with the power state, operator meny, front and rear radar data +function RADAR:GetRadarDataForSync() + return { + power = self.vars.power, + om = self.vars.settings, + ["front"] = self.vars.antennas["front"], + ["rear"] = self.vars.antennas["rear"] + } +end + +-- Returns the radar's internal operator menu settings table +function RADAR:GetOMTableData() return self.vars.settings end + +-- Sets the operator menu settings table within the radar's main variables table +function RADAR:SetOMTableData( data ) + if ( type( data ) == "table" ) then + self.vars.settings = data + self:UpdateOptionIndexes( false ) + end +end + +-- Sets the antenna settings table for the given antenna within the radar's main variables table +function RADAR:SetAntennaTableData( ant, data ) + if ( type( data ) == "table" ) then + self.vars.antennas[ant] = data + end +end + +-- Getter and setter for the backup radar power state +function RADAR:GetBackupPowerState() return self.backupData.power end +function RADAR:SetBackupPowerState( state ) self.backupData.power = state end + +-- Getter and setter for the backup radar operator menu data +function RADAR:GetBackupOMData() return self.backupData.om end +function RADAR:SetBackupOMData( data ) self.backupData.om = data end + +-- Getter and setter for the backup radar antennas data +function RADAR:GetBackupAntennaData( ant ) return self.backupData.antennas[ant] end +function RADAR:SetBackupAntennaData( ant, data ) self.backupData.antennas[ant] = data end + +-- Retuns if there is any backup radar data +function RADAR:IsThereBackupData() + return self:GetBackupOMData() ~= nil or self:GetBackupAntennaData( "front" ) ~= nil or self:GetBackupAntennaData( "rear" ) ~= nil +end + +-- Used when the player becomes a passenger in another vehicle. The local data is backed up to make way for the data +-- provided by the driver. When the player becomes the driver again, the local data is restored. +function RADAR:BackupData() + local data = self:GetRadarDataForSync() + + -- Backup the power state + if ( self:GetBackupPowerState() == nil ) then + self:SetBackupPowerState( data.power ) + end + + -- Backup operator menu data + if ( self:GetBackupOMData() == nil ) then + self:SetBackupOMData( data.om ) + end + + -- Only backup the radar data if the player has the power on. There's no point backing up the data as it'll just + -- get reset when they turn the power on anyway + if ( data.power ) then + -- Backup front and rear antenna data + for ant in UTIL:Values( { "front", "rear" } ) do + if ( self:GetBackupAntennaData( ant ) == nil ) then + self:SetBackupAntennaData( ant, data[ant] ) + end + end + end +end + +-- Backs up the local radar data and then replaces it with the data provided by the driver +function RADAR:LoadDataFromDriver( data ) + -- Backup the local data first + self:BackupData() + + -- As a precaution, give the system 50ms before it replaces the local data with the data from the driver + Citizen.SetTimeout( 50, function() + -- Set the operator menu settings + self:SetOMTableData( data.om ) + + -- Set the antenna data + for ant in UTIL:Values( { "front", "rear" } ) do + self:SetAntennaTableData( ant, data[ant] ) + end + + -- Set the power state + self:SetPowerState( data.power, true ) + + -- Update the display + if ( data.power ) then + self:SendSettingUpdate() + end + end ) +end + +-- Restores the local player's operator menu and antenna data +function RADAR:RestoreFromBackup() + -- Restore the operator menu data + RestoreData( RADAR, RADAR.GetBackupOMData, RADAR.SetOMTableData, RADAR.SetBackupOMData ) + + -- Iterate through the antennas and restore their backups + for ant in UTIL:Values( { "front", "rear" } ) do + RestoreData( RADAR, RADAR.GetBackupAntennaData, RADAR.SetAntennaTableData, RADAR.SetBackupAntennaData, ant ) + end + + -- Get the power state + local pwrState = self:GetBackupPowerState() + + -- Restore the power state + if ( pwrState ~= nil ) then + self:SetPowerState( pwrState, true ) + self:SetBackupPowerState( nil ) + end + + -- Update the display + if ( pwrState ) then + Citizen.SetTimeout( 50, function() + self:SendSettingUpdate() + end ) + end +end + + +--[[---------------------------------------------------------------------------------- + Sync variables +----------------------------------------------------------------------------------]]-- +SYNC = {} + + +--[[---------------------------------------------------------------------------------- + Sync functions +----------------------------------------------------------------------------------]]-- +-- Returns if the given player has the remote open +function SYNC:IsRemoteAlreadyOpen( ply ) + if ( not RADAR:IsPassengerViewAllowed() ) then + return false + else + return DecorGetBool( ply, "wk_wars2x_sync_remoteOpen" ) + end +end + +-- Sets the remote open decor for the local player to the given state +function SYNC:SetRemoteOpenState( state ) + if ( RADAR:IsPassengerViewAllowed() ) then + DecorSetBool( PLY.ped, "wk_wars2x_sync_remoteOpen", state ) + end +end + +-- Used to get the other ped (driver/passenger) in a vehicle and calls the given callback. This function will only work +-- if the player can control the radar, it also ensures that the other ped (if found) exists and is a player. The other +-- player's server ID is passed to the given callback as an argument. +function SYNC:SyncData( cb ) + if ( PLY:CanControlRadar() ) then + local otherPly = PLY:GetOtherPedServerId() + + if ( otherPly ~= nil ) then + cb( otherPly ) + end + end +end + +-- Sends the radar's power state to the other player (driver/passenger) +function SYNC:SendPowerState( state ) + self:SyncData( function( ply ) + TriggerServerEvent( "wk_wars2x_sync:sendPowerState", ply, state ) + end ) +end + +-- Sends the power state for the given antenna to the other player (driver/passenger) +function SYNC:SendAntennaPowerState( state, ant ) + self:SyncData( function( ply ) + TriggerServerEvent( "wk_wars2x_sync:sendAntennaPowerState", ply, state, ant ) + end ) +end + +-- Sends the mode for the given antenna to the other player (driver/passenger) +function SYNC:SendAntennaMode( ant, mode ) + self:SyncData( function( ply ) + TriggerServerEvent( "wk_wars2x_sync:sendAntennaMode", ply, ant, mode ) + end ) +end + +-- Sends a lock/unlock state, as well as the current player's displayed data to the other player (driver/passenger) +function SYNC:LockAntennaSpeed( ant, data ) + self:SyncData( function( ply ) + TriggerServerEvent( "wk_wars2x_sync:sendLockAntennaSpeed", ply, ant, data ) + end ) +end + +-- Sends the given operator menu table data to the other player +function SYNC:SendUpdatedOMData( data ) + self:SyncData( function( ply ) + TriggerServerEvent( "wk_wars2x_sync:sendUpdatedOMData", ply, data ) + end ) +end + +-- Sends the plate reader lock event with the data from the reader that was locked +function SYNC:LockReaderCam( cam, data ) + self:SyncData( function( ply ) + TriggerServerEvent( "wk_wars2x_sync:sendLockCameraPlate", ply, cam, data ) + end ) +end + +-- Requests radar data from the driver if the player has just entered a valid vehicle as a front seat passenger +function SYNC:SyncDataOnEnter() + -- Make sure passenger view is allowed, also, using PLY:IsPassenger() already checks that the player's + -- vehicle meets the requirements of what the radar requires. This way we don't have to do additional + -- checks manually. + if ( PLY:IsPassenger() ) then + local driver = PLY:GetOtherPedServerId() + + -- Only trigger the event if there is actually a driver + if ( driver ~= nil ) then + TriggerServerEvent( "wk_wars2x_sync:requestRadarData", driver ) + end + elseif ( PLY:IsDriver() ) then + if ( RADAR:IsThereBackupData() ) then + -- Restore the local data + RADAR:RestoreFromBackup() + READER:RestoreFromBackup() + end + end +end + + +--[[---------------------------------------------------------------------------------- + Sync client events +----------------------------------------------------------------------------------]]-- +-- Event for receiving the radar powet state +RegisterNetEvent( "wk_wars2x_sync:receivePowerState" ) +AddEventHandler( "wk_wars2x_sync:receivePowerState", function( state ) + -- Set the radar's power + RADAR:SetPowerState( state, false ) +end ) + +-- Event for receiving a power state for the given antenna +RegisterNetEvent( "wk_wars2x_sync:receiveAntennaPowerState" ) +AddEventHandler( "wk_wars2x_sync:receiveAntennaPowerState", function( state, antenna ) + -- Get the current local antenna power state + local power = RADAR:IsAntennaTransmitting( antenna ) + + -- If the local power state is not the same as the given state, toggle the antenna's power + if ( power ~= state ) then + RADAR:ToggleAntenna( antenna ) + end +end ) + +-- Event for receiving a mode for the given antenna +RegisterNetEvent( "wk_wars2x_sync:receiveAntennaMode" ) +AddEventHandler( "wk_wars2x_sync:receiveAntennaMode", function( antenna, mode ) + RADAR:SetAntennaMode( antenna, mode ) +end ) + +-- Event for receiving a lock state and speed data for the given antenna +RegisterNetEvent( "wk_wars2x_sync:receiveLockAntennaSpeed" ) +AddEventHandler( "wk_wars2x_sync:receiveLockAntennaSpeed", function( antenna, data ) + RADAR:LockAntennaSpeed( antenna, data, true ) +end ) + +RegisterNetEvent( "wk_wars2x_sync:receiveLockCameraPlate" ) +AddEventHandler( "wk_wars2x_sync:receiveLockCameraPlate", function( camera, data ) + READER:LockCam( camera, true, false, data ) +end ) + +-- Event for gathering the radar data and sending it to another player +RegisterNetEvent( "wk_wars2x_sync:getRadarDataFromDriver" ) +AddEventHandler( "wk_wars2x_sync:getRadarDataFromDriver", function( playerFor ) + local radarData = RADAR:GetRadarDataForSync() + local readerData = READER:GetReaderDataForSync() + + TriggerServerEvent( "wk_wars2x_sync:sendRadarDataForPassenger", playerFor, { radarData, readerData } ) +end ) + +-- Event for receiving radar data from another player +RegisterNetEvent( "wk_wars2x_sync:receiveRadarData" ) +AddEventHandler( "wk_wars2x_sync:receiveRadarData", function( data ) + RADAR:LoadDataFromDriver( data[1] ) + READER:LoadDataFromDriver( data[2] ) +end ) + +-- Event for receiving updated operator menu data from another player +RegisterNetEvent( "wk_wars2x_sync:receiveUpdatedOMData" ) +AddEventHandler( "wk_wars2x_sync:receiveUpdatedOMData", function( data ) + if ( PLY:IsDriver() or ( PLY:IsPassenger() and RADAR:IsThereBackupData() ) ) then + RADAR:SetOMTableData( data ) + RADAR:SendSettingUpdate() + end +end ) \ No newline at end of file diff --git a/cl_utils.lua b/cl_utils.lua index 08c77c1..5cec538 100644 --- a/cl_utils.lua +++ b/cl_utils.lua @@ -2,13 +2,13 @@ Wraith ARS 2X Created by WolfKnight - - For discussions, information on future updates, and more, join - my Discord: https://discord.gg/fD4e6WD - + + For discussions, information on future updates, and more, join + my Discord: https://discord.gg/fD4e6WD + MIT License - Copyright (c) 2020 WolfKnight + 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 @@ -35,49 +35,49 @@ UTIL = {} -- Returns a number to a set number of decimal places function UTIL:Round( num, numDecimalPlaces ) return tonumber( string.format( "%." .. ( numDecimalPlaces or 0 ) .. "f", num ) ) -end +end --- The custom font used for the digital displays have the ¦ symbol as an empty character, this function --- takes a speed and returns a formatted speed that can be displayed on the radar +-- The custom font used for the digital displays have the ¦ symbol as an empty character, this function +-- takes a speed and returns a formatted speed that can be displayed on the radar function UTIL:FormatSpeed( speed ) - -- Return "Err" (Error) if the given speed is outside the 0-999 range - if ( speed < 0 or speed > 999 ) then return "Err" end + -- Return "Err" (Error) if the given speed is outside the 0-999 range + if ( speed < 0 or speed > 999 ) then return "Err" end - -- Convert the speed to a string + -- Convert the speed to a string local text = tostring( speed ) local pipes = "" -- Create a string of pipes (¦) for the number of spaces - for i = 1, 3 - string.len( text ) do + for i = 1, 3 - string.len( text ) do pipes = pipes .. "¦" - end - + end + -- Return the formatted speed return pipes .. text -end +end --- Returns a clamped numerical value based on the given parameters +-- Returns a clamped numerical value based on the given parameters function UTIL:Clamp( val, min, max ) -- Return the min value if the given value is less than the min - if ( val < min ) then - return min + if ( val < min ) then + return min -- Return the max value if the given value is larger than the max - elseif ( val > max ) then - return max - end + elseif ( val > max ) then + return max + end -- Return the given value if it's between the min and max - return val -end + return val +end -- Returns if the given table is empty, includes numerical and non-numerical key values function UTIL:IsTableEmpty( t ) - local c = 0 + local c = 0 - for _ in pairs( t ) do c = c + 1 end + for _ in pairs( t ) do c = c + 1 end return c == 0 -end +end -- Credit to Deltanic for this function function UTIL:Values( xs ) @@ -96,31 +96,46 @@ function UTIL:GetVehicleInDirection( entFrom, coordFrom, coordTo ) return vehicle end --- Returns if a target vehicle is coming towards or going away from the patrol vehicle, it has a range --- so if a vehicle is sideways compared to the patrol vehicle, the directional arrows won't light up +-- Returns if a target vehicle is coming towards or going away from the patrol vehicle, it has a range +-- so if a vehicle is sideways compared to the patrol vehicle, the directional arrows won't light up function UTIL:GetEntityRelativeDirection( myAng, tarAng ) local angleDiff = math.abs( ( myAng - tarAng + 180 ) % 360 - 180 ) - if ( angleDiff < 45 ) then + if ( angleDiff < 45 ) then return 1 - elseif ( angleDiff > 135 ) then + elseif ( angleDiff > 135 ) then return 2 - end + end return 0 end --- Your everyday GTA notification function +-- Returns if there is a player in the given vehicle +function UTIL:IsPlayerInVeh( veh ) + for i = -1, GetVehicleMaxNumberOfPassengers( veh ) + 1, 1 do + local ped = GetPedInVehicleSeat( veh, i ) + + if ( DoesEntityExist( ped ) ) then + if ( IsPedAPlayer( ped ) ) then return true end + end + end + + return false +end + +-- Your everyday GTA notification function function UTIL:Notify( text ) SetNotificationTextEntry( "STRING" ) AddTextComponentSubstringPlayerName( text ) DrawNotification( false, true ) end +-- Prints the given message to the client console function UTIL:Log( msg ) print( "[Wraith ARS 2X]: " .. msg ) -end +end +-- Used to draw text to the screen, helpful for debugging issues function UTIL:DrawDebugText( x, y, scale, centre, text ) SetTextFont( 4 ) SetTextProportional( 0 ) @@ -136,6 +151,11 @@ function UTIL:DrawDebugText( x, y, scale, centre, text ) DrawText( x, y ) end +-- Returns if the current resource name is valid +function UTIL:IsResourceNameValid() + return GetCurrentResourceName() == "wk_wars2x" +end + --[[The MIT License (MIT) Copyright (c) 2017 IllidanS4 diff --git a/config.lua b/config.lua index 1c9d75f..be4a634 100644 --- a/config.lua +++ b/config.lua @@ -2,13 +2,13 @@ Wraith ARS 2X Created by WolfKnight - - For discussions, information on future updates, and more, join - my Discord: https://discord.gg/fD4e6WD - + + For discussions, information on future updates, and more, join + my Discord: https://discord.gg/fD4e6WD + MIT License - Copyright (c) 2020 WolfKnight + 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 @@ -34,23 +34,39 @@ CONFIG = {} -- Radar fast limit locking --- When enabled, the player will be able to define a fast limit within the radar's menu, when a vehicle +-- When enabled, the player will be able to define a fast limit within the radar's menu, when a vehicle -- exceeds the fast limit, it will be locked into the fast box. Default setting is disabled to maintain realism -CONFIG.allow_fast_limit = false +CONFIG.allow_fast_limit = true + +-- Radar only lock players with auto fast locking +-- When enabled, the radar will only automatically lock a speed if the caught vehicle has a real player in it. +CONFIG.only_lock_players = false -- In-game first time quick start video --- When enabled, the player will be asked if they'd like to view the quick start video the first time they --- open the remote. -CONFIG.allow_quick_start_video = true +-- When enabled, the player will be asked if they'd like to view the quick start video the first time they +-- open the remote. +CONFIG.allow_quick_start_video = true + +-- Allow passenger view +-- When enabled, the front seat passenger will be able to view the radar and plate reader from their end. +CONFIG.allow_passenger_view = false + +-- Allow passenger control +-- Dependent on CONFIG.allow_passenger_view. When enabled, the front seat passenger will be able to open the +-- radar remote and control the radar and plate reader for themself and the driver. +CONFIG.allow_passenger_control = false + +-- Set this to true if you use Sonoran CAD with the WraithV2 plugin +CONFIG.use_sonorancad = false -- Sets the defaults of all keybinds -- These keybinds can be changed by each person in their GTA Settings->Keybinds->FiveM CONFIG.keyDefaults = { - -- Remote control key + -- Remote control key remote_control = "f5", - -- Radar key lock key + -- Radar key lock key key_lock = "l", -- Radar front antenna lock/unlock Key @@ -67,33 +83,41 @@ CONFIG.keyDefaults = } -- Here you can change the default values for the operator menu, do note, if any of these values are not --- one of the options listed, the script will not work. -CONFIG.menuDefaults = +-- one of the options listed, the script will not work. +CONFIG.menuDefaults = { -- Should the system calculate and display faster targets -- Options: true or false - ["fastDisplay"] = true, + ["fastDisplay"] = true, -- Sensitivity for each radar mode, this changes how far the antennas will detect vehicles -- Options: 0.2, 0.4, 0.6, 0.8, 1.0 - ["same"] = 0.6, - ["opp"] = 0.6, + ["same"] = 0.6, + ["opp"] = 0.6, - -- The volume of the audible beep - -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 + -- The volume of the audible beep + -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ["beep"] = 0.6, - - -- The volume of the verbal lock confirmation - -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 + + -- The volume of the verbal lock confirmation + -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ["voice"] = 0.6, - - -- The volume of the plate reader audio - -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 - ["plateAudio"] = 0.6, + + -- The volume of the plate reader audio + -- Options: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 + ["plateAudio"] = 0.6, -- The speed unit used in conversions - -- Options: mph or kmh - ["speedType"] = "mph" + -- Options: mph or kmh + ["speedType"] = "mph", + + -- The state for automatic speed locking. This requires CONFIG.allow_fast_limit to be true. + -- Options: true or false + ["fastLock"] = false, + + -- The speed limit required for automatic speed locking. This requires CONFIG.allow_fast_limit to be true. + -- Options: 0 to 200 + ["fastLimit"] = 60 } -- Here you can change the default scale of the UI elements, as well as the safezone size @@ -103,12 +127,12 @@ CONFIG.uiDefaults = -- Options: 0.25 - 2.5 scale = { - radar = 1.0, - remote = 1.0, - plateReader = 1.0 - }, + radar = 0.75, + remote = 0.75, + plateReader = 0.75 + }, -- The safezone size, must be a multiple of 5. -- Options: 0 - 100 - safezone = 20 + safezone = 20 } \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index dd7d014..6ede8a1 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -2,13 +2,13 @@ Wraith ARS 2X Created by WolfKnight - - For discussions, information on future updates, and more, join - my Discord: https://discord.gg/fD4e6WD - + + For discussions, information on future updates, and more, join + my Discord: https://discord.gg/fD4e6WD + MIT License - Copyright (c) 2020 WolfKnight + 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 @@ -31,20 +31,19 @@ ---------------------------------------------------------------------------------------]]-- -- Define the FX Server version and game type -fx_version "bodacious" +fx_version "cerulean" game "gta5" -- Define the resource metadata name "Wraith ARS 2X" description "Police radar and plate reader system for FiveM" author "WolfKnight" -version "1.2.4" +version "1.3.0" -- Include the files files { - "nui/radar.html", - "nui/radar.css", - "nui/jquery-3.4.1.min.js", + "nui/radar.html", + "nui/radar.css", "nui/radar.js", "nui/images/*.png", "nui/images/plates/*.png", @@ -59,10 +58,13 @@ ui_page "nui/radar.html" -- Run the server scripts server_script "sv_version_check.lua" server_script "sv_exports.lua" +server_script "sv_sync.lua" server_export "TogglePlateLock" -- Run the client scripts client_script "config.lua" client_script "cl_utils.lua" +client_script "cl_player.lua" client_script "cl_radar.lua" -client_script "cl_plate_reader.lua" \ No newline at end of file +client_script "cl_plate_reader.lua" +client_script "cl_sync.lua" \ No newline at end of file diff --git a/nui/jquery-3.4.1.min.js b/nui/jquery-3.4.1.min.js deleted file mode 100644 index a1c07fd..0000000 --- a/nui/jquery-3.4.1.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0 + + @@ -298,7 +300,7 @@ -

Radar key lock

+

Radar key binds

@@ -317,7 +319,7 @@
- + \ No newline at end of file diff --git a/nui/radar.js b/nui/radar.js index f1210cf..e906f12 100644 --- a/nui/radar.js +++ b/nui/radar.js @@ -8,7 +8,7 @@ MIT License - Copyright (c) 2020 WolfKnight + 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 @@ -33,7 +33,6 @@ /*------------------------------------------------------------------------------------ Variables ------------------------------------------------------------------------------------*/ -var resourceName; var uiEdited = false; // All of the audio file names @@ -90,6 +89,7 @@ const elements = plateReaderBox: $( "#plateReaderBox" ), boloText: $( "#boloText" ), setBoloBtn: $( "#setBoloPlate" ), + clearBoloBtn: $( "#clearBoloPlate" ), closePrBtn: $( "#closePlateReaderSettings" ), openHelp: $( "#helpBtn" ), @@ -494,7 +494,7 @@ function poweringUp() } // Simulates the 'fully powered' state of the radar unit -function poweredUp() +function poweredUp( fastDisplay ) { // Completely clear everything clearEverything(); @@ -505,14 +505,14 @@ function poweredUp() // Even though the clearEverything() function is called above, we run this so the fast window // displays 'HLd' setAntennaXmit( ant, false ); - setAntennaFastMode( ant, true ); + setAntennaFastMode( ant, fastDisplay ); } } // Runs the startup process or clears everything, the Lua side calls for the full powered up state -function radarPower( state ) +function radarPower( state, override, fastDisplay ) { - state ? poweringUp() : clearEverything(); + state ? ( override ? poweredUp( fastDisplay ) : poweringUp() ) : clearEverything(); } @@ -605,17 +605,27 @@ function menu( optionText, option ) elements.patrolSpeed.html( option ); } +var keyLockTimeout; + // Makes the key lock label fade in then fade out after 2 seconds function displayKeyLock( state ) { + let sl = elements.keyLock.stateLabel; + // Set the state label text to enabled or disabled - elements.keyLock.stateLabel.html( state ? "enabled" : "disabled" ); + sl.html( state ? "blocked" : "enabled" ); + + // Change the colour of the altered text + state ? sl.addClass( "red" ).removeClass( "green" ) : sl.addClass( "green" ).removeClass( "red" ); // Fade in the label elements.keyLock.label.fadeIn(); + // Clear the timeout if it already exists + clearTimeout( keyLockTimeout ); + // Make the label fade out after 2 seconds - setTimeout( function() { + keyLockTimeout = setTimeout( function() { elements.keyLock.label.fadeOut(); }, 2000 ); } @@ -629,7 +639,7 @@ $.ajaxSetup({ // This function is used to send data back through to the LUA side function sendData( name, data ) { - $.post( "https://" + resourceName + "/" + name, JSON.stringify( data ), function( datab ) { + $.post( "https://wk_wars2x/" + name, JSON.stringify( data ), function( datab ) { if ( datab != "ok" ) { console.log( datab ); } @@ -767,6 +777,11 @@ elements.setBoloBtn.click( function() { } } ) +// Sets the on click function for the clear BOLO button +elements.clearBoloBtn.click( function() { + sendData( "clearBoloPlate", null ); +} ) + // Checks what the user is typing into the plate box function checkPlateInput( event ) { @@ -1077,9 +1092,6 @@ window.addEventListener( "message", function( event ) { switch ( type ) { // System events - case "updatePathName": - resourceName = item.pathName - break; case "loadUiSettings": loadUiSettings( item.data, true ); break; @@ -1102,10 +1114,10 @@ window.addEventListener( "message", function( event ) { setEleVisible( elements.radar, item.state ); break; case "radarPower": - radarPower( item.state ); + radarPower( item.state, item.override, item.fast ); break; case "poweredUp": - poweredUp(); + poweredUp( item.fast ); break; case "update": updateDisplays( item.speed, item.antennas ); diff --git a/sv_sync.lua b/sv_sync.lua new file mode 100644 index 0000000..961b2a6 --- /dev/null +++ b/sv_sync.lua @@ -0,0 +1,78 @@ +--[[--------------------------------------------------------------------------------------- + + 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. + +---------------------------------------------------------------------------------------]]-- + +--[[---------------------------------------------------------------------------------- + Sync server events +----------------------------------------------------------------------------------]]-- +RegisterNetEvent( "wk_wars2x_sync:sendPowerState" ) +AddEventHandler( "wk_wars2x_sync:sendPowerState", function( target, state ) + TriggerClientEvent( "wk_wars2x_sync:receivePowerState", target, state ) +end ) + +RegisterNetEvent( "wk_wars2x_sync:sendAntennaPowerState" ) +AddEventHandler( "wk_wars2x_sync:sendAntennaPowerState", function( target, state, ant ) + TriggerClientEvent( "wk_wars2x_sync:receiveAntennaPowerState", target, state, ant ) +end ) + +RegisterNetEvent( "wk_wars2x_sync:sendAntennaMode" ) +AddEventHandler( "wk_wars2x_sync:sendAntennaMode", function( target, ant, mode ) + TriggerClientEvent( "wk_wars2x_sync:receiveAntennaMode", target, ant, mode ) +end ) + +RegisterNetEvent( "wk_wars2x_sync:sendLockAntennaSpeed" ) +AddEventHandler( "wk_wars2x_sync:sendLockAntennaSpeed", function( target, ant, data ) + TriggerClientEvent( "wk_wars2x_sync:receiveLockAntennaSpeed", target, ant, data ) +end ) + +RegisterNetEvent( "wk_wars2x_sync:sendLockCameraPlate" ) +AddEventHandler( "wk_wars2x_sync:sendLockCameraPlate", function( target, cam, data ) + TriggerClientEvent( "wk_wars2x_sync:receiveLockCameraPlate", target, cam, data ) +end ) + + +--[[---------------------------------------------------------------------------------- + Radar data sync server events +----------------------------------------------------------------------------------]]-- +RegisterNetEvent( "wk_wars2x_sync:requestRadarData" ) +AddEventHandler( "wk_wars2x_sync:requestRadarData", function( target ) + TriggerClientEvent( "wk_wars2x_sync:getRadarDataFromDriver", target, source ) +end ) + +RegisterNetEvent( "wk_wars2x_sync:sendRadarDataForPassenger" ) +AddEventHandler( "wk_wars2x_sync:sendRadarDataForPassenger", function( playerFor, data ) + TriggerClientEvent( "wk_wars2x_sync:receiveRadarData", playerFor, data ) +end ) + +RegisterNetEvent( "wk_wars2x_sync:sendUpdatedOMData" ) +AddEventHandler( "wk_wars2x_sync:sendUpdatedOMData", function( playerFor, data ) + TriggerClientEvent( "wk_wars2x_sync:receiveUpdatedOMData", playerFor, data ) +end ) \ No newline at end of file diff --git a/sv_version_check.lua b/sv_version_check.lua index b6db618..030d9cf 100644 --- a/sv_version_check.lua +++ b/sv_version_check.lua @@ -2,13 +2,13 @@ Wraith ARS 2X Created by WolfKnight - - For discussions, information on future updates, and more, join - my Discord: https://discord.gg/fD4e6WD - + + For discussions, information on future updates, and more, join + my Discord: https://discord.gg/fD4e6WD + MIT License - Copyright (c) 2020 WolfKnight + 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 @@ -31,48 +31,52 @@ ---------------------------------------------------------------------------------------]]-- -- Branding! -local label = +local label = [[ // - || __ __ _ _ _ _____ _____ ___ __ __ - || \ \ / / (_) | | | /\ | __ \ / ____| |__ \\ \ / / - || \ \ /\ / / __ __ _ _| |_| |__ / \ | |__) | (___ ) |\ V / - || \ \/ \/ / '__/ _` | | __| '_ \ / /\ \ | _ / \___ \ / / > < - || \ /\ /| | | (_| | | |_| | | | / ____ \| | \ \ ____) | / /_ / . \ - || \/ \/ |_| \__,_|_|\__|_| |_| /_/ \_\_| \_\_____/ |____/_/ \_\ - || - || Created by WolfKnight + || __ __ _ _ _ _ ___ ___ _____ __ + || \ \ / / _ __ _(_) |_| |_ /_\ | _ \/ __| |_ ) \/ / + || \ \/\/ / '_/ _` | | _| ' \ / _ \| /\__ \ / / > < + || \_/\_/|_| \__,_|_|\__|_||_| /_/ \_\_|_\|___/ /___/_/\_\ + || + || Created by WolfKnight ||]] -- Returns the current version set in fxmanifest.lua function GetCurrentVersion() return GetResourceMetadata( GetCurrentResourceName(), "version" ) -end +end -- Grabs the latest version number from the web GitHub PerformHttpRequest( "https://wolfknight98.github.io/wk_wars2x_web/version.txt", function( err, text, headers ) - -- Wait to reduce spam + -- Wait to reduce spam Citizen.Wait( 2000 ) -- Print the branding! print( label ) - -- Get the current resource version + -- Get the current resource version local curVer = GetCurrentVersion() - - if ( text ~= nil ) then - -- Print out the current and latest version - print( " || Current version: " .. curVer ) + + print( " || Current version: " .. curVer ) + + if ( text ~= nil ) then + -- Print latest version print( " || Latest recommended version: " .. text .."\n ||" ) - + -- If the versions are different, print it out if ( text ~= curVer ) then print( " || ^1Your Wraith ARS 2X version is outdated, visit the FiveM forum post to get the latest version.\n^0 \\\\\n" ) else print( " || ^2Wraith ARS 2X is up to date!\n^0 ||\n \\\\\n" ) end - else + else -- In case the version can not be requested, print out an error message - print( " || ^1There was an error getting the latest version information, if the issue persists contact WolfKnight#8586 on Discord.\n^0 ||\n \\\\\n" ) - end + print( " || ^1There was an error getting the latest version information.\n^0 ||\n \\\\\n" ) + end + + -- Warn the console if the resource has been renamed, as this will cause issues with the resource's functionality. + if ( GetCurrentResourceName() ~= "wk_wars2x" ) then + print( "^1ERROR: Resource name is not wk_wars2x, expect there to be issues with the resource. To ensure there are no issues, please leave the resource name as wk_wars2x^0\n\n" ) + end end ) \ No newline at end of file