2029 lines
		
	
	
	
		
			69 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			2029 lines
		
	
	
	
		
			69 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
-- Variables --
 | 
						|
local inHeliCam = false
 | 
						|
local helicopter = {}
 | 
						|
local movementInput = false
 | 
						|
local isMarkersThreadActive = false
 | 
						|
local lastRappelKeyPress = 0
 | 
						|
local pauseMenu = false
 | 
						|
local visionState = 0
 | 
						|
local currentFov = 0.0
 | 
						|
local fov = Config.Camera.Zoom.Max
 | 
						|
local tabletObj = nil
 | 
						|
local cameraAction = false
 | 
						|
local instScaleform = nil
 | 
						|
local submix = false
 | 
						|
local postalsActive = Config.ShowPostalCodes
 | 
						|
local spotlights = {}
 | 
						|
local Units = {} -- Unit conversions, gets dynamically created.
 | 
						|
local markers = {}
 | 
						|
local postals = {}
 | 
						|
local displayPostals = {}
 | 
						|
local markerBlips = {}
 | 
						|
local blipStateBagHandlers = {}
 | 
						|
-- 16 = INPUT_SELECT_NEXT_WEAPON, 17 = INPUT_SELECT_PREV_WEAPON, 75 = INPUT_VEH_EXIT, 80 = INPUT_VEH_CIN_CAM, 
 | 
						|
-- 81 = INPUT_VEH_NEXT_RADIO, 82 = INPUT_VEH_PREV_RADIO, 85 = INPUT_VEH_RADIO_WHEEL, 99 = INPUT_VEH_SELECT_NEXT_WEAPON
 | 
						|
local controlActions = {
 | 
						|
    16, 17, 75, 80, 81, 82, 85, 99
 | 
						|
}
 | 
						|
local sounds = {
 | 
						|
    enter = -1,
 | 
						|
    exit = -1,
 | 
						|
    turn = -1,
 | 
						|
    zoom = -1,
 | 
						|
    bleep = -1,
 | 
						|
    scanLoop = -1,
 | 
						|
    scanSuccess = -1,
 | 
						|
    scanFailure = -1,
 | 
						|
    rappel = -1,
 | 
						|
    thermal = -1,
 | 
						|
    spotlight = -1
 | 
						|
}
 | 
						|
local targetBlip = {
 | 
						|
    display = false,
 | 
						|
    handler = nil
 | 
						|
}
 | 
						|
local cache = {
 | 
						|
    helicopter = {},
 | 
						|
    target = {},
 | 
						|
    camera = {}
 | 
						|
}
 | 
						|
local spotlight = {
 | 
						|
    active = false,
 | 
						|
    isThreadActive = false,
 | 
						|
    cameraLockThread = false,
 | 
						|
    brightness = Config.Spotlight.DefaultBrightness,
 | 
						|
    adjustingBrightness = false,
 | 
						|
    radius = Config.Spotlight.DefaultRadius,
 | 
						|
    adjustingRadius = false
 | 
						|
}
 | 
						|
local cameraLock = {
 | 
						|
    active = false,
 | 
						|
    attempting = false,
 | 
						|
    type = nil,
 | 
						|
    prevType = nil,
 | 
						|
    entity = nil,
 | 
						|
    coords = nil,
 | 
						|
    timeout = 0,
 | 
						|
    progress = 0
 | 
						|
}
 | 
						|
local camera = {
 | 
						|
    cam = nil,
 | 
						|
    pitch = 0,
 | 
						|
    heading = 0,
 | 
						|
    bearing = 0
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
-- Utils --
 | 
						|
local function DisplayNotification(msg)
 | 
						|
	BeginTextCommandThefeedPost("STRING")
 | 
						|
	AddTextComponentSubstringPlayerName(msg)
 | 
						|
	EndTextCommandThefeedPostTicker(false, false)
 | 
						|
 | 
						|
    -- Comment out above and add custom notification below:
 | 
						|
	--exports.mythic_notify:SendAlert('error', msg)
 | 
						|
end
 | 
						|
 | 
						|
local function DoesHelicopterHaveCamera(model, vehicle)
 | 
						|
    if Config.Helicopters[model] then
 | 
						|
        return true
 | 
						|
    elseif Config.CanUseAnyHelicopter then
 | 
						|
        local class = GetVehicleClass(vehicle)
 | 
						|
        if class == 15 or class == 16 then
 | 
						|
            return true
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
local function GetHeliCameraOffset(model)
 | 
						|
    return (Config.Helicopters[model] and Config.Helicopters[model].offset) or Config.Helicopters.default.offset
 | 
						|
end
 | 
						|
 | 
						|
local function CanPlayerUseCameraFromCurrentSeat(playerPed, heli, model)
 | 
						|
    local policy = Config.PassengerOnly
 | 
						|
    if Config.Helicopters[model] and Config.Helicopters[model].passengerOnly ~= nil then
 | 
						|
        policy = Config.Helicopters[model].passengerOnly
 | 
						|
    end
 | 
						|
 | 
						|
    if policy then
 | 
						|
        -- Checks if we are the pilot of the helicopter
 | 
						|
        if GetPedInVehicleSeat(heli, -1) == playerPed then
 | 
						|
            return false, 'IsPilot'
 | 
						|
        end
 | 
						|
        -- If only rear passangers, then check if we are in the front passanger seat
 | 
						|
        if policy == 2 and GetPedInVehicleSeat(heli, 0) == playerPed then
 | 
						|
            return false, 'NotInRear'
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return true
 | 
						|
end
 | 
						|
 | 
						|
local function SetHelicopterStateBag(bagName, value)
 | 
						|
    -- TODO: move this to LocalPlayer state like spotlight?
 | 
						|
    if NetworkGetEntityOwner(helicopter.entity) == PlayerId() then
 | 
						|
        Entity(helicopter.entity).state:set(bagName, value, true)
 | 
						|
    else
 | 
						|
        TriggerServerEvent('helicam:setStateBag', helicopter.netId, bagName, value)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function SynchroniseSpotlight(data)
 | 
						|
    LocalPlayer.state:set('heliCamSpotlightData', data, true)
 | 
						|
end
 | 
						|
 | 
						|
local function GetCartesianCoords(coord)
 | 
						|
    local degrees = math.floor(coord)
 | 
						|
    local min = (coord - degrees) * 60
 | 
						|
    local minutes = math.floor(min)
 | 
						|
 | 
						|
    local sec = (min - minutes) * 60
 | 
						|
    local secfloor = math.floor(sec)
 | 
						|
    local seconds = string.format("%02d", secfloor)..string.format("%.2f", sec - secfloor):sub(2)
 | 
						|
 | 
						|
    local cartesian = string.format("%02d", degrees).."° "..string.format("%02d", minutes).."' "..seconds..'"'
 | 
						|
    return cartesian
 | 
						|
end
 | 
						|
 | 
						|
local function GetHeadingBetweenCoords(from, to)
 | 
						|
    local dx = to.x - from.x
 | 
						|
    local dy = to.y - from.y
 | 
						|
 | 
						|
    local heading = GetHeadingFromVector_2d(dx, dy)
 | 
						|
	return heading
 | 
						|
end
 | 
						|
 | 
						|
local function RotationToHeading(rotation)
 | 
						|
    local heading = rotation
 | 
						|
    if heading < 0 then
 | 
						|
        heading = heading*-1
 | 
						|
        heading = heading + math.abs(heading - 180.0)*2
 | 
						|
    end
 | 
						|
 | 
						|
    heading = (heading - 360) *-1
 | 
						|
 | 
						|
    return heading
 | 
						|
end
 | 
						|
 | 
						|
local function RotAnglesToVec(rot)
 | 
						|
	local z = math.rad(rot.z)
 | 
						|
	local x = math.rad(rot.x)
 | 
						|
	local num = math.abs(math.cos(x))
 | 
						|
	return vector3(-math.sin(z)*num, math.cos(z)*num, math.sin(x))
 | 
						|
end
 | 
						|
 | 
						|
local function IsTableEmpty(table)
 | 
						|
    for _ in pairs(table) do return false end
 | 
						|
    return true
 | 
						|
end
 | 
						|
 | 
						|
local function ShouldLockOntoCenter(entityType)
 | 
						|
    if entityType == 1 and Config.LockOntoCenter.Peds then
 | 
						|
        return true
 | 
						|
    elseif entityType == 2 and Config.LockOntoCenter.Vehicles then
 | 
						|
        return true
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
local function EnableSubmix()
 | 
						|
    SetAudioSubmixEffectRadioFx(0, 0)
 | 
						|
    SetAudioSubmixEffectParamInt(0, 0, `default`, 1)
 | 
						|
    SetAudioSubmixEffectParamFloat(0, 0, `freq_low`, 625.0)
 | 
						|
    SetAudioSubmixEffectParamFloat(0, 0, `freq_hi`, 8000.0)
 | 
						|
    SetAudioSubmixEffectParamFloat(0, 0, `fudge`, 0.5)
 | 
						|
    SetAudioSubmixEffectParamFloat(0, 0, `rm_mix`, 50.0)
 | 
						|
    submix = true
 | 
						|
end
 | 
						|
 | 
						|
local function DisableSubmix()
 | 
						|
    SetAudioSubmixEffectRadioFx(0, 0)
 | 
						|
    SetAudioSubmixEffectParamInt(0, 0, `enabled`, 0)
 | 
						|
    submix = false
 | 
						|
end
 | 
						|
 | 
						|
local function DrawText3D(coords, text)
 | 
						|
    --Style the text
 | 
						|
    SetTextColour(255, 255, 255, 255)
 | 
						|
    SetTextScale(0.0, 0.35)
 | 
						|
    SetTextFont(4)
 | 
						|
    SetTextOutline()
 | 
						|
 | 
						|
    -- Diplay the text
 | 
						|
    BeginTextCommandDisplayText("STRING")
 | 
						|
    AddTextComponentSubstringPlayerName(text)
 | 
						|
    SetDrawOrigin(coords.x, coords.y, coords.z, 0)
 | 
						|
    EndTextCommandDisplayText(0.0, 0.0)
 | 
						|
    ClearDrawOrigin()
 | 
						|
    DrawRect(coords.x, coords.y, 1.0, 1.0, 230, 230, 230, 255)
 | 
						|
end
 | 
						|
 | 
						|
local function GetOffsetFromCoordsInWorldCoords(position, rotation, offset)
 | 
						|
    local rotX, rotY, rotZ = math.rad(rotation.x), math.rad(rotation.y), math.rad(rotation.z)
 | 
						|
    local matrix = {}
 | 
						|
 | 
						|
    matrix[1] = {}
 | 
						|
    matrix[1][1] = math.cos(rotZ) * math.cos(rotY) - math.sin(rotZ) * math.sin(rotX) * math.sin(rotY)
 | 
						|
    matrix[1][2] = math.cos(rotY) * math.sin(rotZ) + math.cos(rotZ) * math.sin(rotX) * math.sin(rotY)
 | 
						|
    matrix[1][3] = -math.cos(rotX) * math.sin(rotY)
 | 
						|
    matrix[1][4] = 1
 | 
						|
 | 
						|
    matrix[2] = {}
 | 
						|
    matrix[2][1] = -math.cos(rotX) * math.sin(rotZ)
 | 
						|
    matrix[2][2] = math.cos(rotZ) * math.cos(rotX)
 | 
						|
    matrix[2][3] = math.sin(rotX)
 | 
						|
    matrix[2][4] = 1
 | 
						|
 | 
						|
    matrix[3] = {}
 | 
						|
    matrix[3][1] = math.cos(rotZ) * math.sin(rotY) + math.cos(rotY) * math.sin(rotZ) * math.sin(rotX)
 | 
						|
    matrix[3][2] = math.sin(rotZ) * math.sin(rotY) - math.cos(rotZ) * math.cos(rotY) * math.sin(rotX)
 | 
						|
    matrix[3][3] = math.cos(rotX) * math.cos(rotY)
 | 
						|
    matrix[3][4] = 1
 | 
						|
 | 
						|
    matrix[4] = {}
 | 
						|
    matrix[4][1], matrix[4][2], matrix[4][3] = position.x, position.y, position.z
 | 
						|
    matrix[4][4] = 1
 | 
						|
 | 
						|
    local x = offset.x * matrix[1][1] + offset.y * matrix[2][1] + offset.z * matrix[3][1] + matrix[4][1]
 | 
						|
    local y = offset.x * matrix[1][2] + offset.y * matrix[2][2] + offset.z * matrix[3][2] + matrix[4][2]
 | 
						|
    local z = offset.x * matrix[1][3] + offset.y * matrix[2][3] + offset.z * matrix[3][3] + matrix[4][3]
 | 
						|
 | 
						|
    return vector3(x, y, z)
 | 
						|
end
 | 
						|
 | 
						|
-- General Functions --
 | 
						|
local function CanPlayerUseCamera(playerPed)
 | 
						|
    -- Check for jobs
 | 
						|
    local whitelisted, jobMessage = JobCheck()
 | 
						|
    if not whitelisted then
 | 
						|
        return false, jobMessage
 | 
						|
    end
 | 
						|
 | 
						|
    -- If we aren't in any vehicle or we can't use this heli
 | 
						|
    if not DoesHelicopterHaveCamera(helicopter.model, helicopter.entity) then
 | 
						|
        if IsPedInAnyPlane(playerPed) then
 | 
						|
            return false, 'NoCameraPlane'
 | 
						|
        else
 | 
						|
            return false, 'NoCameraHeli'
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    -- Check seat
 | 
						|
    local canUseFromCurrentSeat, seatMessage = CanPlayerUseCameraFromCurrentSeat(playerPed, helicopter.entity, helicopter.model)
 | 
						|
    if not canUseFromCurrentSeat then
 | 
						|
        return false, seatMessage
 | 
						|
    end
 | 
						|
 | 
						|
    -- Check if the camera for this helicopter already is in use
 | 
						|
    local heliEntity = Entity(helicopter.entity)
 | 
						|
    if heliEntity and heliEntity.state.heliCamInUse then
 | 
						|
        return false, 'CameraInUse'
 | 
						|
    end
 | 
						|
 | 
						|
    -- Check if someone already use camera functions such as spotlight and camera lock
 | 
						|
    if spotlights[helicopter.netId] then
 | 
						|
        return false, 'SpotlightInUse'
 | 
						|
    end
 | 
						|
 | 
						|
    return true, nil
 | 
						|
end
 | 
						|
 | 
						|
local function Raycast(startCoords, destination, entity, flag)
 | 
						|
    local rayHandle = StartShapeTestLosProbe(startCoords.x, startCoords.y, startCoords.z, destination.x, destination.y, destination.z, flag or 4294967295, entity, 4) -- 4294967295 = TraceFlags_IntersectEverything
 | 
						|
 | 
						|
    while true do
 | 
						|
        local result, hit, endCoords, surfaceNormal, entityHit = GetShapeTestResult(rayHandle)
 | 
						|
        if result ~= 1 then
 | 
						|
            return hit, endCoords, surfaceNormal, entityHit
 | 
						|
        end
 | 
						|
 | 
						|
		Wait(0)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function RaycastFromHeliCam(flag)
 | 
						|
    local camCoords = GetCamCoord(camera.cam)
 | 
						|
    local camRotation = camera.rotation or GetCamRot(camera.cam, 2)
 | 
						|
    local destination = GetOffsetFromCoordsInWorldCoords(camCoords, camRotation, vector3(0.0, Config.TargetMaxReach, 0.0))
 | 
						|
    local hit, endCoords, _surfaceNormal, entityHit = Raycast(camCoords, destination, helicopter.entity, flag)
 | 
						|
 | 
						|
    return (hit == 1 and true) or false, endCoords, entityHit
 | 
						|
end
 | 
						|
 | 
						|
local function LoadPostalFile(resource, file)
 | 
						|
    local resourceState = GetResourceState(resource)
 | 
						|
    if resourceState ~= "started" then
 | 
						|
        print(string.format("^1ERROR: Postal resource %s was not started! It MUST be started before helicam for the postals to work! (Resource state: %s)^7", resource, resourceState))
 | 
						|
        Config.ShowPostalCodes = false
 | 
						|
    else
 | 
						|
        local jsonFile = LoadResourceFile(resource, file)
 | 
						|
        if jsonFile == nil then
 | 
						|
            print(string.format("^1ERROR: The script was not able to load postals file %s from postals resource %s! Make sure that the file is loaded in the postals resource.^7", file, resource))
 | 
						|
        else
 | 
						|
            postals = json.decode(jsonFile)
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function SetCameraLabel()
 | 
						|
    local label = nil
 | 
						|
    if Config.ForceCameraLabel then
 | 
						|
        label = Config.ForceCameraLabel
 | 
						|
    else
 | 
						|
        local livery = GetVehicleLivery(helicopter.entity)
 | 
						|
        local heliConfig = Config.Helicopters[helicopter.model]
 | 
						|
        if heliConfig and heliConfig.labels then
 | 
						|
            if heliConfig.labels[livery] then
 | 
						|
                label = heliConfig.labels[livery]
 | 
						|
            else
 | 
						|
                label = heliConfig.labels[0]
 | 
						|
            end
 | 
						|
        else
 | 
						|
            label = Config.Helicopters.default.labels[0]
 | 
						|
        end
 | 
						|
    end
 | 
						|
    SendNUIMessage({ action = 'setCameraLabel', label = label })
 | 
						|
end
 | 
						|
 | 
						|
local function CreateHelicopterCamera(heli, offset, rotation, camFov, ease, easeTime)
 | 
						|
    local cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
 | 
						|
    AttachCamToEntity(cam, heli, offset.x, offset.y, offset.z, true)
 | 
						|
    SetCamRot(cam, 0.0, 0.0, rotation, 2)
 | 
						|
    SetCamFov(cam, camFov)
 | 
						|
    RenderScriptCams(true, ease, easeTime, false, false)
 | 
						|
    return cam
 | 
						|
end
 | 
						|
 | 
						|
local function GetHelicopterTimecycle(model)
 | 
						|
    local heliConfig = Config.Helicopters[model]
 | 
						|
    if heliConfig and heliConfig.timecycle ~= nil then
 | 
						|
        return heliConfig.timecycle, heliConfig.timecycleStrength or Config.DefaultCameraTimecycleStrength
 | 
						|
    end
 | 
						|
 | 
						|
    return Config.DefaultCameraTimecycle, Config.DefaultCameraTimecycleStrength
 | 
						|
end
 | 
						|
 | 
						|
local function DeleteTablet()
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    ClearPedSecondaryTask(playerPed)
 | 
						|
    Wait(100)
 | 
						|
    DetachEntity(tabletObj, true, false)
 | 
						|
    DeleteEntity(tabletObj)
 | 
						|
    tabletObj = nil
 | 
						|
end
 | 
						|
 | 
						|
local function CreateTablet()
 | 
						|
    local tablet = Config.Tablet
 | 
						|
 | 
						|
    RequestAnimDict(tablet.anim.dict)
 | 
						|
    while not HasAnimDictLoaded(tablet.anim.dict) do
 | 
						|
        Wait(0)
 | 
						|
    end
 | 
						|
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    local boneIndex = GetPedBoneIndex(playerPed, tablet.bone)
 | 
						|
 | 
						|
    tabletObj = CreateObject(tablet.model, 0.0, 0.0, 0.0, true, true, false)
 | 
						|
    AttachEntityToEntity(tabletObj, playerPed, boneIndex, tablet.offset.x, tablet.offset.y, tablet.offset.z, tablet.rotation.x, tablet.rotation.y, tablet.rotation.z, false, true, false, true, 1, true)
 | 
						|
 | 
						|
    TaskPlayAnim(playerPed, tablet.anim.dict, tablet.anim.name, 2.0, 2.0, -1, 49, 1.0, false, false, false)
 | 
						|
 | 
						|
    CreateThread(function()
 | 
						|
        while inHeliCam do
 | 
						|
            if not IsEntityPlayingAnim(playerPed, tablet.anim.dict, tablet.anim.name, 3) then
 | 
						|
                TaskPlayAnim(playerPed, tablet.anim.dict, tablet.anim.name, 2.0, 2.0, -1, 49, 1.0, false, false, false)
 | 
						|
            end
 | 
						|
            Wait(500)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function GetStreetAndAreaNames(streetHash, coords)
 | 
						|
    local street = GetStreetNameFromHashKey(streetHash)
 | 
						|
    local area = GetLabelText(GetNameOfZone(coords.x, coords.y, coords.z))
 | 
						|
    return street, area
 | 
						|
end
 | 
						|
 | 
						|
local function SetZoomBarLevel()
 | 
						|
    local range = Config.Camera.Zoom.Max - Config.Camera.Zoom.Min
 | 
						|
    local percentage = (fov - Config.Camera.Zoom.Min) / range * 100
 | 
						|
    percentage = (percentage - 100) *-1 -- Flip the values around
 | 
						|
 | 
						|
    SendNUIMessage({action = 'setZoomBarLevel', percentage = percentage})
 | 
						|
end
 | 
						|
 | 
						|
local function HandleZoomInput()
 | 
						|
    if spotlight.adjustingBrightness or spotlight.adjustingRadius then return end
 | 
						|
 | 
						|
    if GetDisabledControlNormal(0, 40) ~= 0.0 then -- Zoom in
 | 
						|
        fov = math.max(fov - Config.Camera.Zoom.Speed, Config.Camera.Zoom.Min)
 | 
						|
        SetZoomBarLevel()
 | 
						|
    elseif GetDisabledControlNormal(0, 41) ~= 0.0 then -- Zoom out
 | 
						|
        fov = math.min(fov + Config.Camera.Zoom.Speed, Config.Camera.Zoom.Max)
 | 
						|
        SetZoomBarLevel()
 | 
						|
    end
 | 
						|
 | 
						|
    currentFov = GetCamFov(camera.cam)
 | 
						|
    if math.abs(fov - currentFov) < 0.1 then
 | 
						|
        fov = currentFov
 | 
						|
    else
 | 
						|
        SetCamFov(camera.cam, currentFov + (fov - currentFov) * 0.05)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function HandleMovementInput()
 | 
						|
    if cameraLock.active then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local axisX = GetDisabledControlNormal(0, 220)
 | 
						|
	local axisY = GetDisabledControlNormal(0, 221)
 | 
						|
 | 
						|
	if axisX ~= 0.0 or axisY ~= 0.0 then
 | 
						|
        local zoomValue = (1.0/(Config.Camera.Zoom.Max-Config.Camera.Zoom.Min))*(fov-Config.Camera.Zoom.Min)
 | 
						|
        local rotation = camera.rotation
 | 
						|
 | 
						|
        local movementSpeed = (IsUsingKeyboard(1) and Config.Camera.MovementSpeed.Keyboard) or Config.Camera.MovementSpeed.Controller
 | 
						|
        local newX = math.max(math.min(Config.Camera.RotationLimits.Up, rotation.x + axisY*-1.0*(movementSpeed)*(zoomValue+0.1)), Config.Camera.RotationLimits.Down)
 | 
						|
        local newZ = rotation.z + axisX*-1.0*(movementSpeed)*(zoomValue+0.1)
 | 
						|
 | 
						|
		SetCamRot(camera.cam, newX, 0.0, newZ, 2)
 | 
						|
        movementInput = true
 | 
						|
    elseif movementInput then
 | 
						|
        movementInput = false
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Sound --
 | 
						|
local function LoadSounds()
 | 
						|
    RequestAmbientAudioBank("POLICE_CHOPPER_CAM", false)
 | 
						|
    Wait(100)
 | 
						|
 | 
						|
    for key, _soundId in pairs(sounds) do
 | 
						|
        sounds[key] = GetSoundId()
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function UnloadSounds()
 | 
						|
    ReleaseAmbientAudioBank()
 | 
						|
    for key, soundId in pairs(sounds) do
 | 
						|
        if not HasSoundFinished(soundId) then
 | 
						|
            StopSound(soundId)
 | 
						|
        end
 | 
						|
        ReleaseSoundId(soundId)
 | 
						|
        sounds[key] = -1
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function EmitSound(soundId, soundName, audioRef, stopIfActive)
 | 
						|
    if HasSoundFinished(soundId) then
 | 
						|
        PlaySoundFrontend(soundId, soundName, audioRef or 0, true)
 | 
						|
    elseif stopIfActive then
 | 
						|
        StopSound(soundId)
 | 
						|
        PlaySoundFrontend(soundId, soundName, audioRef or 0, true)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function SoundThread()
 | 
						|
    CreateThread(function()
 | 
						|
        while inHeliCam do
 | 
						|
            if movementInput and not pauseMenu then
 | 
						|
                EmitSound(sounds.turn, "COP_HELI_CAM_TURN")
 | 
						|
            elseif not HasSoundFinished(sounds.turn) then
 | 
						|
                StopSound(sounds.turn)
 | 
						|
            end
 | 
						|
 | 
						|
            local fovDifference = math.abs(currentFov - fov)
 | 
						|
            if fovDifference > 5.0 and not pauseMenu then
 | 
						|
                EmitSound(sounds.zoom, "COP_HELI_CAM_ZOOM")
 | 
						|
            else
 | 
						|
                if not HasSoundFinished(sounds.zoom) then
 | 
						|
                    StopSound(sounds.zoom)
 | 
						|
                end
 | 
						|
            end
 | 
						|
 | 
						|
            Wait(100)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Vision --
 | 
						|
local function DoesAnyHeliHaveVisionOverwrite()
 | 
						|
    for _hash, data in pairs(Config.Helicopters) do
 | 
						|
        if data.nightvision or data.thermalvision then
 | 
						|
            return true
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
local function CanHelicopterUseCameraVision(model, type)
 | 
						|
    local configAllowed = (type == "nightvision" and Config.AllowNightVision) or Config.AllowThermal
 | 
						|
    if (configAllowed and (Config.Helicopters[model] and Config.Helicopters[model][type] ~= false)) or (Config.Helicopters[model] and Config.Helicopters[model][type]) then
 | 
						|
        return true
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
local function DisableVision()
 | 
						|
    SendNUIMessage({action = 'setVisionState', state = "HDEO"})
 | 
						|
    SetSeethrough(false)
 | 
						|
    SetNightvision(false)
 | 
						|
    visionState = 0
 | 
						|
end
 | 
						|
 | 
						|
local function EnableThermal()
 | 
						|
    -- Reset the seethrough values
 | 
						|
    SeethroughReset()
 | 
						|
 | 
						|
    -- Big thanks goes to BrD for making the black & white thermal posible!
 | 
						|
    if Config.ThermalOptions.CustomColours then
 | 
						|
        -- Some of these are relative to eachother
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleHot.red", Config.ThermalOptions.Colours.VisibleHot.R)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleHot.green", Config.ThermalOptions.Colours.VisibleHot.G)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleHot.blue", Config.ThermalOptions.Colours.VisibleHot.B)
 | 
						|
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleWarm.red", Config.ThermalOptions.Colours.VisibleWarm.R)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleWarm.green", Config.ThermalOptions.Colours.VisibleWarm.G)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleWarm.blue", Config.ThermalOptions.Colours.VisibleWarm.B)
 | 
						|
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleBase.red", Config.ThermalOptions.Colours.VisibleBase.R)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleBase.green", Config.ThermalOptions.Colours.VisibleBase.G)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorVisibleBase.blue", Config.ThermalOptions.Colours.VisibleBase.B)
 | 
						|
 | 
						|
        -- Colour of the far of fade as well as the sky
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorFar.red", Config.ThermalOptions.Colours.Far.R)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorFar.green", Config.ThermalOptions.Colours.Far.G)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorFar.blue", Config.ThermalOptions.Colours.Far.B)
 | 
						|
 | 
						|
        -- Colour of the ground
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorNear.red", Config.ThermalOptions.Colours.Near.R)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorNear.green", Config.ThermalOptions.Colours.Near.G)
 | 
						|
        SetVisualSettingFloat("seeThrough.ColorNear.blue", Config.ThermalOptions.Colours.Near.B)
 | 
						|
    end
 | 
						|
 | 
						|
    -- Max amount of thickness we can see trough (not in m or ft, unsure how it's calculated by the game.)
 | 
						|
    SeethroughSetMaxThickness(Config.ThermalOptions.MaxThickness)
 | 
						|
 | 
						|
    -- Set the amount of noise
 | 
						|
    SeethroughSetNoiseAmountMin(Config.ThermalOptions.MinNoise)
 | 
						|
    SeethroughSetNoiseAmountMax(Config.ThermalOptions.MaxNoise)
 | 
						|
 | 
						|
    -- Set how far we can see
 | 
						|
    SeethroughSetFadeStartDistance(Config.ThermalOptions.FadeStart)
 | 
						|
    SeethroughSetFadeEndDistance(Config.ThermalOptions.FadeEnd)
 | 
						|
 | 
						|
    -- Enable the seetrough effect (thermal vision)
 | 
						|
    SetSeethrough(true)
 | 
						|
end
 | 
						|
 | 
						|
local function CycleVision()
 | 
						|
    if visionState == 0 then
 | 
						|
        visionState = 1
 | 
						|
        if CanHelicopterUseCameraVision(helicopter.model, "nightvision") then
 | 
						|
            local hour = GetClockHours()
 | 
						|
            if Config.AllowNightVisionDuringDay or (hour > 20 or hour < 6) then
 | 
						|
                EmitSound(sounds.thermal, "THERMAL_VISION_GOGGLES_ON_MASTER", 0, true)
 | 
						|
                SendNUIMessage({action = 'setVisionState', state = "HDNV"})
 | 
						|
                SetNightvision(true)
 | 
						|
                return
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if visionState == 1 and CanHelicopterUseCameraVision(helicopter.model, "thermalvision") then
 | 
						|
        SendNUIMessage({action = 'setVisionState', state = "HDIR"})
 | 
						|
        SetNightvision(false)
 | 
						|
        EnableThermal()
 | 
						|
        visionState = 2
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    DisableVision()
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Camera Lock --
 | 
						|
local function LockCamera(coords, entity)
 | 
						|
    cameraLock.active = true
 | 
						|
    movementInput = false
 | 
						|
    local entityType = GetEntityType(entity)
 | 
						|
    if entity and entityType ~= 0 then
 | 
						|
        local offset = vector3(0.0, 0.0, 0.0)
 | 
						|
        if not ShouldLockOntoCenter(entityType) then
 | 
						|
            offset = GetOffsetFromEntityGivenWorldCoords(entity, coords.x, coords.y, coords.z)
 | 
						|
        end
 | 
						|
        PointCamAtEntity(camera.cam, entity, offset.x, offset.y, offset.z, true)
 | 
						|
        cameraLock.prevType = cameraLock.type
 | 
						|
        cameraLock.type = "entity"
 | 
						|
        cameraLock.entity = entity
 | 
						|
        cameraLock.coords = nil
 | 
						|
    else
 | 
						|
        -- We have to re-create the camera due to bug with the PointCamAtCoord/PointCamAtEntity functions regarding offsets.
 | 
						|
        if cameraLock.prevType == "entity" then
 | 
						|
            local offset = GetHeliCameraOffset(helicopter.model)
 | 
						|
            local rotation = GetEntityRotation(helicopter.entity, 5).z
 | 
						|
            local cam = CreateHelicopterCamera(helicopter.entity, offset, rotation, GetCamFov(camera.cam), false, 0)
 | 
						|
            DestroyCam(camera.cam, true)
 | 
						|
            camera.cam = cam
 | 
						|
        end
 | 
						|
 | 
						|
        PointCamAtCoord(camera.cam, coords.x, coords.y, coords.z)
 | 
						|
        cameraLock.prevType = cameraLock.type
 | 
						|
        cameraLock.type = "coords"
 | 
						|
        cameraLock.entity = nil
 | 
						|
        cameraLock.coords = coords
 | 
						|
    end
 | 
						|
 | 
						|
    local lockType = nil
 | 
						|
    if cameraLock.type == "coords" then
 | 
						|
        lockType = "GROUND"
 | 
						|
    elseif entityType == 2 then
 | 
						|
        lockType = "VEHICLE"
 | 
						|
    elseif entityType == 1 then
 | 
						|
        if GetPedType(cameraLock.entity) == 28 then
 | 
						|
            lockType = "ANIMAL"
 | 
						|
        else
 | 
						|
            lockType = "PERSON"
 | 
						|
        end
 | 
						|
    else
 | 
						|
        lockType = "UNKNOWN"
 | 
						|
    end
 | 
						|
    SendNUIMessage({ action = 'setCameraLockState', state = true, type = lockType })
 | 
						|
end
 | 
						|
 | 
						|
local function AttemptLockScanning(targetEntity)
 | 
						|
    local count = 1
 | 
						|
 | 
						|
    cameraLock.attempting = true
 | 
						|
    SendNUIMessage({ action = 'startLockScanning' })
 | 
						|
 | 
						|
    while true do
 | 
						|
        Wait(200)
 | 
						|
        local hit, hitCoords, hitEntity = RaycastFromHeliCam()
 | 
						|
        if hit and hitEntity == targetEntity then
 | 
						|
            count += 1
 | 
						|
        else
 | 
						|
            count -= 1
 | 
						|
        end
 | 
						|
 | 
						|
        if not inHeliCam then
 | 
						|
            return false
 | 
						|
        end
 | 
						|
 | 
						|
        if Config.PlaySounds then
 | 
						|
            EmitSound(sounds.scanLoop, "COP_HELI_CAM_SCAN_PED_LOOP")
 | 
						|
        end
 | 
						|
 | 
						|
        SendNUIMessage({ action = 'updateLockScanning', value = count })
 | 
						|
 | 
						|
        if count >= 11 then
 | 
						|
            if Config.PlaySounds then
 | 
						|
                if not HasSoundFinished(sounds.scanLoop) then
 | 
						|
                    StopSound(sounds.scanLoop)
 | 
						|
                end
 | 
						|
                PlaySoundFrontend(sounds.scanSuccess, "COP_HELI_CAM_SCAN_PED_SUCCESS", 0, true)
 | 
						|
            end
 | 
						|
            return true, hitCoords, hitEntity
 | 
						|
        end
 | 
						|
 | 
						|
        if count <= -1 or not cameraLock.attempting then
 | 
						|
            if Config.PlaySounds then
 | 
						|
                if not HasSoundFinished(sounds.scanLoop) then
 | 
						|
                    StopSound(sounds.scanLoop)
 | 
						|
                end
 | 
						|
                PlaySoundFrontend(sounds.scanFailure, "COP_HELI_CAM_SCAN_PED_FAILURE", 0, true)
 | 
						|
            end
 | 
						|
            return false
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function AttemptCameraLock()
 | 
						|
    local hit, hitCoords, hitEntity = RaycastFromHeliCam()
 | 
						|
    if hit then
 | 
						|
        if GetEntityType(hitEntity) ~= 0 then
 | 
						|
            if Config.InstantCameraLock then
 | 
						|
                LockCamera(hitCoords, hitEntity)
 | 
						|
            else
 | 
						|
                local success, coords, entity = AttemptLockScanning(hitEntity)
 | 
						|
                if success then
 | 
						|
                    LockCamera(coords, entity)
 | 
						|
                end
 | 
						|
                cameraLock.attempting = false
 | 
						|
                SendNUIMessage({ action = 'lockScanningFinished' })
 | 
						|
            end
 | 
						|
        elseif Config.AllowCameraLockOnGround then
 | 
						|
            LockCamera(hitCoords, hitEntity)
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function StopCameraLock()
 | 
						|
    if camera.cam and cameraLock.active then
 | 
						|
        StopCamPointing(camera.cam)
 | 
						|
    end
 | 
						|
    SendNUIMessage({ action = 'setCameraLockState', state = false, type = "NONE" })
 | 
						|
 | 
						|
    -- Reset variables
 | 
						|
    cameraLock.active = false
 | 
						|
    cameraLock.prevType = cameraLock.type
 | 
						|
    cameraLock.type = nil
 | 
						|
    cameraLock.entity = nil
 | 
						|
    cameraLock.coords = nil
 | 
						|
    cameraLock.timeout = 0
 | 
						|
    cameraLock.progress = 0
 | 
						|
end
 | 
						|
 | 
						|
local function CheckCameraLock(hit, hitCoords, hitEntity)
 | 
						|
    if cameraLock.active then
 | 
						|
        local timeout = false
 | 
						|
        if not hit then
 | 
						|
            timeout = true
 | 
						|
        elseif cameraLock.type == "coords" then
 | 
						|
            local distToTarget = #(hitCoords-cameraLock.coords)
 | 
						|
            if distToTarget > 1.0 then
 | 
						|
                timeout = true
 | 
						|
            end
 | 
						|
        elseif cameraLock.type == "entity" then
 | 
						|
            if hitEntity ~= cameraLock.entity and not HasEntityClearLosToEntity(helicopter.entity, cameraLock.entity, 4294967295) then
 | 
						|
                timeout = true
 | 
						|
            end
 | 
						|
        end
 | 
						|
 | 
						|
        if timeout then
 | 
						|
            cameraLock.timeout += 1
 | 
						|
            if Config.PlaySounds then
 | 
						|
                EmitSound(sounds.bleep, "COP_HELI_CAM_BLEEP_TOO_FAR")
 | 
						|
            end
 | 
						|
 | 
						|
            if cameraLock.timeout >= Config.CameraLockBreakTicks then
 | 
						|
                StopCameraLock()
 | 
						|
            end
 | 
						|
        elseif cameraLock.timeout > 0 then
 | 
						|
            cameraLock.timeout -= 1
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Rappel --
 | 
						|
local function AttemptRappel(wasKeyPress)
 | 
						|
    if not helicopter.entity then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local heliConfig = Config.Helicopters[helicopter.model]
 | 
						|
    if not DoesVehicleAllowRappel(helicopter.entity) or (heliConfig and heliConfig.disableRappelling) then
 | 
						|
        DisplayNotification(Config.Localisation.Notification.CannotRappelFromHeli)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    local isInCorrectSeat = GetPedInVehicleSeat(helicopter.entity, 1) == playerPed or GetPedInVehicleSeat(helicopter.entity, 2) == playerPed
 | 
						|
    if not isInCorrectSeat then
 | 
						|
        DisplayNotification(Config.Localisation.Notification.CannotRappelFromSeat)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local coords = GetEntityCoords(helicopter.entity)
 | 
						|
    local foundHeight, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, false)
 | 
						|
    if not foundHeight or coords.z - groundZ > Config.MaxRappellingHight then
 | 
						|
        DisplayNotification(Config.Localisation.Notification.ToHighToRappel)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if wasKeyPress and (lastRappelKeyPress == 0 or (lastRappelKeyPress + Config.RappellingTimeout < GetGameTimer())) then
 | 
						|
        lastRappelKeyPress = GetGameTimer()
 | 
						|
        DisplayNotification(Config.Localisation.Notification.ConfirmRappel)
 | 
						|
    else
 | 
						|
        lastRappelKeyPress = 0
 | 
						|
        EmitSound(sounds.rappel, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET")
 | 
						|
        DisplayNotification(Config.Localisation.Notification.Rappelling)
 | 
						|
 | 
						|
        TaskRappelFromHeli(playerPed, 1)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Instructions --
 | 
						|
local function AddInInstructionsControl(scaleform, index, control, text)
 | 
						|
    BeginScaleformMovieMethod(scaleform, "SET_DATA_SLOT")
 | 
						|
    ScaleformMovieMethodAddParamInt(index)
 | 
						|
    ScaleformMovieMethodAddParamPlayerNameString(control)
 | 
						|
    BeginTextCommandScaleformString("STRING")
 | 
						|
	AddTextComponentSubstringPlayerName(text)
 | 
						|
	EndTextCommandScaleformString()
 | 
						|
    EndScaleformMovieMethod()
 | 
						|
end
 | 
						|
 | 
						|
local function SetupInstructionsScaleform()
 | 
						|
    instScaleform = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS")
 | 
						|
    while not HasScaleformMovieLoaded(instScaleform) do
 | 
						|
        Wait(10)
 | 
						|
    end
 | 
						|
 | 
						|
    BeginScaleformMovieMethod(instScaleform, "CLEAR_ALL")
 | 
						|
    EndScaleformMovieMethod()
 | 
						|
 | 
						|
    BeginScaleformMovieMethod(instScaleform, "SET_CLEAR_SPACE")
 | 
						|
    ScaleformMovieMethodAddParamInt(200)
 | 
						|
    EndScaleformMovieMethod()
 | 
						|
 | 
						|
    -- Add Controls
 | 
						|
    for index, button in pairs(Config.InstructionButtons) do
 | 
						|
        AddInInstructionsControl(instScaleform, index, button.control, button.label)
 | 
						|
    end
 | 
						|
 | 
						|
    -- Background colour
 | 
						|
    BeginScaleformMovieMethod(instScaleform, "SET_BACKGROUND_COLOUR")
 | 
						|
    ScaleformMovieMethodAddParamInt(0) -- Red
 | 
						|
    ScaleformMovieMethodAddParamInt(0) -- Green
 | 
						|
    ScaleformMovieMethodAddParamInt(0) -- Blue
 | 
						|
    ScaleformMovieMethodAddParamInt(80) -- Alpha
 | 
						|
    EndScaleformMovieMethod()
 | 
						|
 | 
						|
    BeginScaleformMovieMethod(instScaleform, "SET_BACKGROUND")
 | 
						|
    EndScaleformMovieMethod()
 | 
						|
 | 
						|
    BeginScaleformMovieMethod(instScaleform, "DRAW_INSTRUCTIONAL_BUTTONS")
 | 
						|
    EndScaleformMovieMethod()
 | 
						|
end
 | 
						|
 | 
						|
local function InstructionsThread()
 | 
						|
    CreateThread(function()
 | 
						|
        while inHeliCam do
 | 
						|
            DrawScaleformMovieFullscreen(instScaleform, 255, 255, 255, 255, 0)
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Spotlight --
 | 
						|
local function DoesAnyHeliHaveSpotlightOverwrite()
 | 
						|
    for _hash, data in pairs(Config.Helicopters) do
 | 
						|
        if data.spotlight then
 | 
						|
            return true
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
local function CanHelicopterUseSpotlight(model)
 | 
						|
    if (Config.AllowSpotlight and (Config.Helicopters[model] and Config.Helicopters[model].spotlight ~= false)) or (Config.Helicopters[model] and Config.Helicopters[model].spotlight) then
 | 
						|
        return true
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
local function AdjustSpolightBrightness()
 | 
						|
    CreateThread(function()
 | 
						|
        while spotlight.adjustingBrightness do
 | 
						|
            local newBrightness = spotlight.brightness
 | 
						|
 | 
						|
            if GetDisabledControlNormal(0, 40) ~= 0.0 then -- Scroll up
 | 
						|
                newBrightness = math.min(newBrightness + Config.Spotlight.BrightnessIncrements, Config.Spotlight.MaxBrightness)
 | 
						|
            elseif GetDisabledControlNormal(0, 41) ~= 0.0 then -- Scroll down
 | 
						|
                newBrightness = math.max(newBrightness - Config.Spotlight.BrightnessIncrements, Config.Spotlight.MinBrightness)
 | 
						|
            end
 | 
						|
 | 
						|
            spotlight.brightness = newBrightness
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function AdjustSpolightRadius()
 | 
						|
    CreateThread(function()
 | 
						|
        while spotlight.adjustingRadius do
 | 
						|
            local newRadius = spotlight.radius
 | 
						|
 | 
						|
            if GetDisabledControlNormal(0, 40) ~= 0.0 then -- Scroll up
 | 
						|
                newRadius = math.min(newRadius + Config.Spotlight.RadiusIncrements, Config.Spotlight.MaxRadius)
 | 
						|
            elseif GetDisabledControlNormal(0, 41) ~= 0.0 then -- Scroll down
 | 
						|
                newRadius = math.max(newRadius - Config.Spotlight.RadiusIncrements, Config.Spotlight.MinRadius)
 | 
						|
            end
 | 
						|
 | 
						|
            spotlight.radius = newRadius
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function SpotlightThread()
 | 
						|
    local direction = nil
 | 
						|
    local position = nil
 | 
						|
    local netId = helicopter.netId
 | 
						|
 | 
						|
    -- Reset spotlight brightness/radius
 | 
						|
    spotlight.brightness = Config.Spotlight.DefaultBrightness
 | 
						|
    spotlight.radius = Config.Spotlight.DefaultRadius
 | 
						|
 | 
						|
    CreateThread(function()
 | 
						|
        while spotlight.active do
 | 
						|
            local rotation = camera.rotation or GetCamRot(camera.cam, 2)
 | 
						|
            direction = RotAnglesToVec(rotation)
 | 
						|
            local camCoords = GetCamCoord(camera.cam)
 | 
						|
            position = camCoords + direction
 | 
						|
 | 
						|
            DrawSpotLightWithShadow(position.x, position.y, position.z, direction.x, direction.y, direction.z, Config.Spotlight.Colour.R, Config.Spotlight.Colour.G, Config.Spotlight.Colour.B, Config.Spotlight.MaxDistance, spotlight.brightness, Config.Spotlight.Roundness, spotlight.radius, Config.Spotlight.Falloff, 0)
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
 | 
						|
    CreateThread(function()
 | 
						|
        while spotlight.active do
 | 
						|
            SynchroniseSpotlight({ position = position, direction = direction, brightness = spotlight.brightness, radius = spotlight.radius, helicopter = netId })
 | 
						|
            Wait(25)
 | 
						|
        end
 | 
						|
        SynchroniseSpotlight({ position = false, helicopter = netId })
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function SpotlightCameraLockCheck()
 | 
						|
    CreateThread(function()
 | 
						|
        spotlight.cameraLockThread = true
 | 
						|
        while spotlight.active and not inHeliCam do
 | 
						|
            local hit, hitCoords, hitEntity = RaycastFromHeliCam()
 | 
						|
            CheckCameraLock(hit, hitCoords, hitEntity)
 | 
						|
            Wait(250)
 | 
						|
        end
 | 
						|
        spotlight.cameraLockThread = false
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function ToggleSpotlight()
 | 
						|
    if not inHeliCam or pauseMenu then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    -- Check if our helicopter can use a spotlight
 | 
						|
    if not CanHelicopterUseSpotlight(helicopter.model) then
 | 
						|
        DisplayNotification(Config.Localisation.Notification.NoSpotlight)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    -- Emit toggle sound
 | 
						|
    EmitSound(sounds.spotlight, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET")
 | 
						|
 | 
						|
    spotlight.active = not spotlight.active
 | 
						|
    if spotlight.active then
 | 
						|
        if not Config.MaxAmountOfSpotlights then
 | 
						|
            SpotlightThread()
 | 
						|
            return
 | 
						|
        end
 | 
						|
 | 
						|
        if GlobalState.heliSpotlightsActive >= Config.MaxAmountOfSpotlights then
 | 
						|
            spotlight.active = false
 | 
						|
            DisplayNotification(Config.Localisation.Notification.SpotlightGlobalLimit)
 | 
						|
            return
 | 
						|
        end
 | 
						|
 | 
						|
        TriggerServerEvent('helicam:toggleSpotlight', true)
 | 
						|
        SpotlightThread()
 | 
						|
    elseif Config.MaxAmountOfSpotlights then
 | 
						|
        TriggerServerEvent('helicam:toggleSpotlight', false)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Spotlights that are not controled by us
 | 
						|
local function ForeignSpotlightThread()
 | 
						|
    CreateThread(function()
 | 
						|
        spotlight.isThreadActive = true
 | 
						|
        while not IsTableEmpty(spotlights) do
 | 
						|
            for _netId, data in pairs(spotlights) do
 | 
						|
                DrawSpotLightWithShadow(data.position.x, data.position.y, data.position.z, data.direction.x, data.direction.y, data.direction.z, Config.Spotlight.Colour.R, Config.Spotlight.Colour.G, Config.Spotlight.Colour.B, Config.Spotlight.MaxDistance, data.brightness+0.0, Config.Spotlight.Roundness, data.radius+0.0, Config.Spotlight.Falloff, 0)
 | 
						|
            end
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
        spotlight.isThreadActive = false
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
AddStateBagChangeHandler('heliCamSpotlightData', nil, function(bagName, key, data, _unused, replicated)
 | 
						|
    -- Ignore this if we are the camera operator
 | 
						|
    if not data or (data.helicopter == helicopter.netId and camera.cam) then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    -- Turn off spotlight
 | 
						|
    if not data.position then
 | 
						|
        spotlights[data.helicopter] = nil
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    spotlights[data.helicopter] = data
 | 
						|
 | 
						|
    if not spotlight.isThreadActive then
 | 
						|
        ForeignSpotlightThread()
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
 | 
						|
-- Blips --
 | 
						|
local function ToggleMarker()
 | 
						|
    local markerIndex = nil
 | 
						|
    local hit, hitCoords = RaycastFromHeliCam()
 | 
						|
    if not hit then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local adjustedCoords = vector3(hitCoords.x, hitCoords.y, hitCoords.z + 0.25)
 | 
						|
    for index, coords in pairs(markers) do
 | 
						|
        local dist = #(coords - adjustedCoords)
 | 
						|
        if dist < Config.Marker.Circle.Scale + 0.5 then
 | 
						|
            markerIndex = index
 | 
						|
            break
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if markerIndex then
 | 
						|
        -- Remove Marker from table
 | 
						|
        table.remove(markers, markerIndex)
 | 
						|
    else
 | 
						|
        -- If already max markers, remove the marker we created the longest ago
 | 
						|
        if #markers >= Config.Marker.MaxAmount then
 | 
						|
            table.remove(markers, 1)
 | 
						|
        end
 | 
						|
 | 
						|
        -- Add marker
 | 
						|
        markers[#markers+1] = adjustedCoords
 | 
						|
    end
 | 
						|
 | 
						|
    SetHelicopterStateBag("heliCamMarkers", markers)
 | 
						|
end
 | 
						|
 | 
						|
local function CreateMarkerBlip(coords, number)
 | 
						|
    local blip = AddBlipForCoord(coords.x, coords.y, coords.z)
 | 
						|
 | 
						|
	SetBlipSprite(blip, Config.Marker.Blip.Sprite)
 | 
						|
	SetBlipScale(blip, Config.Marker.Blip.Scale)
 | 
						|
	SetBlipColour(blip, Config.Marker.Blip.Colour)
 | 
						|
 | 
						|
    if Config.Marker.Blip.Number then
 | 
						|
	    ShowNumberOnBlip(blip, number)
 | 
						|
    end
 | 
						|
 | 
						|
    -- Set blip name
 | 
						|
	BeginTextCommandSetBlipName("STRING")
 | 
						|
	AddTextComponentSubstringPlayerName(Config.Localisation.Blip.Marker)
 | 
						|
	EndTextCommandSetBlipName(blip)
 | 
						|
 | 
						|
	return blip
 | 
						|
end
 | 
						|
 | 
						|
local function UpdateMarkerBlips()
 | 
						|
    if markers == nil then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if #markers < #markerBlips then
 | 
						|
        for index, data in pairs(markerBlips) do
 | 
						|
            if index > #markers then
 | 
						|
                RemoveBlip(data.handler)
 | 
						|
                markerBlips[index] = nil
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    for index, coords in pairs(markers) do
 | 
						|
        if not markerBlips[index] then
 | 
						|
            markerBlips[index] = {}
 | 
						|
            markerBlips[index].handler = CreateMarkerBlip(coords, index)
 | 
						|
            markerBlips[index].coords = coords
 | 
						|
        elseif markerBlips[index].coords ~= coords then
 | 
						|
            SetBlipCoords(markerBlips[index].handler, coords.x, coords.y, coords.z)
 | 
						|
            ShowNumberOnBlip(markerBlips[index].handler, index)
 | 
						|
            markerBlips[index].coords = coords
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function MarkersThread()
 | 
						|
    CreateThread(function()
 | 
						|
        isMarkersThreadActive = true
 | 
						|
 | 
						|
        while true do
 | 
						|
            if markers == nil or #markers == 0 then
 | 
						|
                break
 | 
						|
            end
 | 
						|
 | 
						|
            local heliCoords = cache.helicopter.coords or GetEntityCoords(helicopter.entity)
 | 
						|
 | 
						|
            for index, coords in pairs(markers) do
 | 
						|
                if #(heliCoords - coords) < Config.Marker.MaxDrawDistance then
 | 
						|
                    DrawMarker(Config.Marker.Circle.Type, coords.x, coords.y, coords.z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Config.Marker.Circle.Scale, Config.Marker.Circle.Scale, Config.Marker.Circle.Scale, Config.Marker.Circle.Colour.R, Config.Marker.Circle.Colour.G, Config.Marker.Circle.Colour.B, Config.Marker.Circle.Colour.A, false, true, 2, false, nil, nil, false)
 | 
						|
                    if Config.Marker.Number.Display then
 | 
						|
                        DrawMarker(index+10, coords.x, coords.y, coords.z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Config.Marker.Number.Scale, Config.Marker.Number.Scale, Config.Marker.Number.Scale, Config.Marker.Number.Colour.R, Config.Marker.Number.Colour.G, Config.Marker.Number.Colour.B, Config.Marker.Number.Colour.A, false, true, 2, false, nil, nil, false)
 | 
						|
                    end
 | 
						|
                end
 | 
						|
            end
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
 | 
						|
        isMarkersThreadActive = false
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function HandleTargetBlip(display)
 | 
						|
    if display then
 | 
						|
        local coords = display
 | 
						|
        if DoesBlipExist(targetBlip.handler) then
 | 
						|
            SetBlipCoords(targetBlip.handler, coords.x, coords.y, coords.z)
 | 
						|
        else
 | 
						|
            -- Create the blip
 | 
						|
            targetBlip.handler = AddBlipForCoord(coords.x, coords.y, coords.z)
 | 
						|
            SetBlipSprite(targetBlip.handler, Config.TargetBlip.Sprite)
 | 
						|
            SetBlipColour(targetBlip.handler, Config.TargetBlip.Colour)
 | 
						|
            BeginTextCommandSetBlipName("STRING")
 | 
						|
            AddTextComponentSubstringPlayerName(Config.Localisation.Blip.Target)
 | 
						|
            EndTextCommandSetBlipName(targetBlip.handler)
 | 
						|
        end
 | 
						|
 | 
						|
        if not targetBlip.display then
 | 
						|
            SetBlipDisplay(targetBlip.handler, 2)
 | 
						|
            targetBlip.display = true
 | 
						|
        end
 | 
						|
    elseif display == false then
 | 
						|
        if targetBlip.display then
 | 
						|
            SetBlipDisplay(targetBlip.handler, 0)
 | 
						|
            targetBlip.display = false
 | 
						|
        end
 | 
						|
    else
 | 
						|
        RemoveBlip(targetBlip.handler)
 | 
						|
        targetBlip.handler = nil
 | 
						|
        targetBlip.display = false
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function FetchAndApplyBlipStateBags(heliEntity)
 | 
						|
    local heli = Entity(heliEntity)
 | 
						|
    if heli then
 | 
						|
        if heli.state.heliCamTargetBlip then
 | 
						|
            HandleTargetBlip(heli.state.heliCamTargetBlip)
 | 
						|
        end
 | 
						|
 | 
						|
        if heli.state.heliCamMarkers then
 | 
						|
            markers = heli.state.heliCamMarkers
 | 
						|
            UpdateMarkerBlips()
 | 
						|
            MarkersThread()
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function RegisterBlipStateBags()
 | 
						|
    if Config.TargetBlip.Display then
 | 
						|
        blipStateBagHandlers.heliCamTargetBlip = AddStateBagChangeHandler('heliCamTargetBlip', nil, function(bagName, key, value, _unused, replicated)
 | 
						|
            local entity = GetEntityFromStateBagName(bagName)
 | 
						|
            local vehicle = helicopter.entity or GetVehiclePedIsIn(PlayerPedId(), false)
 | 
						|
            if entity == vehicle then
 | 
						|
                HandleTargetBlip(value)
 | 
						|
            end
 | 
						|
        end)
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.AllowMarkers then
 | 
						|
        blipStateBagHandlers.heliCamMarkers = AddStateBagChangeHandler('heliCamMarkers', nil, function(bagName, key, value, _unused, replicated)
 | 
						|
            local entity = GetEntityFromStateBagName(bagName)
 | 
						|
            local vehicle = helicopter.entity or GetVehiclePedIsIn(PlayerPedId(), false)
 | 
						|
            if entity == vehicle then
 | 
						|
                markers = value
 | 
						|
                UpdateMarkerBlips()
 | 
						|
                if not isMarkersThreadActive then
 | 
						|
                    MarkersThread()
 | 
						|
                end
 | 
						|
            end
 | 
						|
        end)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function UnregisterBlipStateBags()
 | 
						|
    for _key, cookie in pairs(blipStateBagHandlers) do
 | 
						|
        RemoveStateBagChangeHandler(cookie)
 | 
						|
    end
 | 
						|
    blipStateBagHandlers = {}
 | 
						|
end
 | 
						|
 | 
						|
-- Threads & Main Functions --
 | 
						|
local function MinimapHeadingThread()
 | 
						|
    CreateThread(function()
 | 
						|
        while inHeliCam do
 | 
						|
            SetGameplayCamRelativeHeading(camera.heading * -1)
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function ExitHeliCamera()
 | 
						|
    inHeliCam = false
 | 
						|
    cameraAction = true
 | 
						|
    camera.rotation = nil
 | 
						|
 | 
						|
    -- Set camera avalible for others to use
 | 
						|
    TriggerServerEvent('helicam:leaveCamera', helicopter.netId)
 | 
						|
 | 
						|
    -- Trigger event for other scripts to use
 | 
						|
    TriggerEvent('helicam:leftCamera', helicopter.netId)
 | 
						|
 | 
						|
    -- Close NUI
 | 
						|
    SendNUIMessage({ action = 'close' })
 | 
						|
 | 
						|
    -- Disable night-/therma-vision if enabled
 | 
						|
    if visionState ~= 0 then
 | 
						|
        DisableVision()
 | 
						|
    end
 | 
						|
 | 
						|
    -- Remove tablet object
 | 
						|
    if tabletObj then
 | 
						|
        DeleteTablet()
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.InstructionButtons then
 | 
						|
        SetScaleformMovieAsNoLongerNeeded(instScaleform)
 | 
						|
    end
 | 
						|
 | 
						|
    if not submix and Config.NoSubmixInCamera then
 | 
						|
        EnableSubmix()
 | 
						|
    end
 | 
						|
 | 
						|
    local timecycleName, _timecycleStrength = GetHelicopterTimecycle(helicopter.model)
 | 
						|
    if timecycleName then
 | 
						|
        ClearTimecycleModifier()
 | 
						|
    end
 | 
						|
 | 
						|
    -- Reset gameplay camera, stop rendering and remove helicopter camera
 | 
						|
    SetGameplayCamRelativeHeading(0)
 | 
						|
    RenderScriptCams(false, Config.CameraTransition, Config.CameraTransitionTime, false, false)
 | 
						|
    if Config.CameraTransition then
 | 
						|
        Wait(Config.CameraTransitionTime)
 | 
						|
    end
 | 
						|
 | 
						|
    if not spotlight.active then
 | 
						|
        StopCameraLock()
 | 
						|
        DestroyCam(camera.cam, false)
 | 
						|
        camera.cam = nil
 | 
						|
    elseif not spotlight.cameraLockThread then
 | 
						|
        SpotlightCameraLockCheck()
 | 
						|
    end
 | 
						|
 | 
						|
    -- Unload sounds
 | 
						|
    if Config.PlaySounds then
 | 
						|
        UnloadSounds()
 | 
						|
 | 
						|
        -- Exit sound
 | 
						|
        EmitSound(sounds.exit, "BACK", "HUD_FRONTEND_DEFAULT_SOUNDSET")
 | 
						|
    end
 | 
						|
 | 
						|
    -- Toggle radar on again
 | 
						|
    if Config.HideMinimap then
 | 
						|
        DisplayRadar(true)
 | 
						|
    end
 | 
						|
 | 
						|
    -- Reset varaibles
 | 
						|
    cameraAction = false
 | 
						|
end
 | 
						|
 | 
						|
local function OnLeftHelicopter()
 | 
						|
    markers = {}
 | 
						|
    HandleTargetBlip(nil)
 | 
						|
    UpdateMarkerBlips()
 | 
						|
    UnregisterBlipStateBags()
 | 
						|
 | 
						|
    if inHeliCam then
 | 
						|
        ExitHeliCamera()
 | 
						|
    end
 | 
						|
 | 
						|
    -- Reset camera variables
 | 
						|
    StopCameraLock()
 | 
						|
 | 
						|
    -- Destroy camera
 | 
						|
    DestroyCam(camera.cam, false)
 | 
						|
    camera.cam = nil
 | 
						|
 | 
						|
    -- Removes audio submix if it was enabled
 | 
						|
    if submix then
 | 
						|
        DisableSubmix()
 | 
						|
    end
 | 
						|
 | 
						|
    -- Remove spotlight
 | 
						|
    if spotlight.active then
 | 
						|
        spotlight.active = false
 | 
						|
    end
 | 
						|
 | 
						|
    helicopter = {}
 | 
						|
end
 | 
						|
 | 
						|
local function InHelicopterThread()
 | 
						|
    CreateThread(function()
 | 
						|
        local heli = GetVehiclePedIsIn(PlayerPedId(), false)
 | 
						|
        while heli == helicopter.entity do
 | 
						|
            Wait(250)
 | 
						|
            heli = GetVehiclePedIsIn(PlayerPedId(), false)
 | 
						|
        end
 | 
						|
 | 
						|
        OnLeftHelicopter()
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function OnEnteredVehicle(vehicle)
 | 
						|
    local model = GetEntityModel(vehicle)
 | 
						|
    local hasCamera = DoesHelicopterHaveCamera(model, vehicle)
 | 
						|
    if hasCamera then
 | 
						|
        CreateThread(function()
 | 
						|
            if helicopter.entity then
 | 
						|
                while helicopter.entity ~= nil and helicopter.entity ~= 0 do
 | 
						|
                    Wait(0)
 | 
						|
                end
 | 
						|
                Wait(100)
 | 
						|
            end
 | 
						|
 | 
						|
            helicopter.entity = vehicle
 | 
						|
            helicopter.netId = VehToNet(vehicle)
 | 
						|
            helicopter.model = model
 | 
						|
 | 
						|
            RegisterBlipStateBags()
 | 
						|
            FetchAndApplyBlipStateBags(vehicle)
 | 
						|
            InHelicopterThread()
 | 
						|
        end)
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.UseSubmix then
 | 
						|
        EnableSubmix()
 | 
						|
        if not hasCamera then
 | 
						|
            CreateThread(function()
 | 
						|
                local playerPed = PlayerPedId()
 | 
						|
                while IsPedInAnyHeli(playerPed) or IsPedInAnyPlane(playerPed) do
 | 
						|
                    Wait(250)
 | 
						|
                end
 | 
						|
 | 
						|
                -- Removes audio submix if it was enabled
 | 
						|
                if submix then
 | 
						|
                    DisableSubmix()
 | 
						|
                end
 | 
						|
            end)
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function CollectAndSendData()
 | 
						|
    local info = {}
 | 
						|
    local data = {}
 | 
						|
    data.target = {}
 | 
						|
    data.helicopter = {}
 | 
						|
 | 
						|
    data.helicopter.speed = GetEntitySpeed(helicopter.entity)
 | 
						|
    data.helicopter.coords = GetEntityCoords(helicopter.entity)
 | 
						|
    data.helicopter.heading = GetEntityHeading(helicopter.entity)
 | 
						|
    data.target.numberplate = false
 | 
						|
 | 
						|
    if Config.TimeFormat == 1 then
 | 
						|
        local hour = GetClockHours()
 | 
						|
        local minute = GetClockMinutes()
 | 
						|
 | 
						|
        info.time = ("%.2d"):format((hour == 0) and 12 or hour) .. ":" .. ("%.2d"):format(minute)
 | 
						|
    end
 | 
						|
 | 
						|
    local setData = {}
 | 
						|
    if data.helicopter.speed ~= cache.helicopter.speed then
 | 
						|
        setData['hi-speed'] = string.format("%.0f", data.helicopter.speed * Units.Speed.Conversion)
 | 
						|
        cache.helicopter.speed = data.helicopter.speed
 | 
						|
    end
 | 
						|
    if data.helicopter.coords ~= cache.helicopter.coords then
 | 
						|
        setData['hi-altitude'] = string.format("%.0f", data.helicopter.coords.z * Units.Altitude.Conversion)
 | 
						|
        cache.helicopter.coords = data.helicopter.coords
 | 
						|
    end
 | 
						|
    if data.helicopter.heading ~= cache.helicopter.heading then
 | 
						|
        setData['hi-heading'] = string.format("%.0f", data.helicopter.heading)
 | 
						|
        cache.helicopter.heading = data.helicopter.heading
 | 
						|
    end
 | 
						|
    if camera.pitch ~= cache.camera.pitch then
 | 
						|
        setData['camera-pitch'] = string.format("%.0f", (camera.pitch - 90) * -1).."°"
 | 
						|
        cache.camera.pitch = camera.pitch
 | 
						|
    end
 | 
						|
    if camera.heading ~= cache.camera.heading then
 | 
						|
        setData['camera-heading'] = string.format("%.0f", camera.heading).."°"
 | 
						|
        cache.camera.heading = camera.heading
 | 
						|
    end
 | 
						|
    if camera.bearing ~= cache.camera.bearing then
 | 
						|
        setData['bearing-text'] = string.format("%.0f", camera.bearing).."°T"
 | 
						|
        cache.camera.bearing = camera.bearing
 | 
						|
    end
 | 
						|
 | 
						|
    local hit, hitCoords, hitEntity = RaycastFromHeliCam()
 | 
						|
    if cameraLock.active then
 | 
						|
        CheckCameraLock(hit, hitCoords, hitEntity)
 | 
						|
        hitEntity = cameraLock.entity
 | 
						|
    end
 | 
						|
 | 
						|
    if hit then
 | 
						|
        data.target.elevation = string.format("%.0f", hitCoords.z * Units.TargetElevation.Conversion)
 | 
						|
        if data.target.elevation ~= cache.target.elevation then
 | 
						|
            setData['ta-elevation'] = data.target.elevation
 | 
						|
            cache.target.elevation = data.target.elevation
 | 
						|
        end
 | 
						|
 | 
						|
        if (hitEntity and GetEntityType(hitEntity) ~= 0) or cameraLock.active then
 | 
						|
            data.target.heading = GetEntityHeading(hitEntity)
 | 
						|
            data.target.speed = string.format("%.0f", GetEntitySpeed(hitEntity) * Units.TargetSpeed.Conversion)
 | 
						|
 | 
						|
            if IsEntityAVehicle(hitEntity) and ((Config.OnlyShowPlateIfLocked and cameraLock.active) or not Config.OnlyShowPlateIfLocked) then
 | 
						|
                local success, plate = GetVehicleNumberPlate(hitEntity, GetEntityRotation(hitEntity), camera.rotation or GetCamRot(camera.cam, 2))
 | 
						|
                if success then
 | 
						|
                    data.target.numberplate = plate
 | 
						|
                end
 | 
						|
            end
 | 
						|
 | 
						|
            data.target.heading = string.format("%.0f", data.target.heading)
 | 
						|
        elseif movementInput and cache.target.position ~= nil then
 | 
						|
            data.target.speed = string.format("%.0f", #(cache.target.position - hitCoords) * Units.TargetSpeed.Conversion)
 | 
						|
            data.target.heading = string.format("%.0f", GetHeadingBetweenCoords(cache.target.position, hitCoords))
 | 
						|
        end
 | 
						|
 | 
						|
        if hitCoords ~= cache.target.position then
 | 
						|
            SetHelicopterStateBag('heliCamTargetBlip', hitCoords)
 | 
						|
            cache.target.position = hitCoords
 | 
						|
        end
 | 
						|
        if data.target.speed ~= cache.target.speed then
 | 
						|
            cache.target.speed = data.target.speed
 | 
						|
            if data.target.speed == nil then data.target.speed = "---" end
 | 
						|
            setData['ta-speed'] = data.target.speed
 | 
						|
        end
 | 
						|
        if data.target.heading ~= cache.target.heading then
 | 
						|
            cache.target.heading = data.target.heading
 | 
						|
            if data.target.heading == nil then data.target.heading = "---" end
 | 
						|
            setData['ta-heading'] = data.target.heading
 | 
						|
        end
 | 
						|
 | 
						|
        data.target.distance = #(hitCoords - data.helicopter.coords)
 | 
						|
        if data.target.distance ~= cache.target.distance then
 | 
						|
            local decimals = (Units.TargetDistance.Type == "MI" and "%.2f") or "%.0f"
 | 
						|
            setData['ta-distance'] = string.format(decimals, data.target.distance * Units.TargetDistance.Conversion)
 | 
						|
            cache.target.distance = data.target.distance
 | 
						|
        end
 | 
						|
    else
 | 
						|
        if cache.target.speed ~= "---" then
 | 
						|
            setData['ta-speed'] = "---"
 | 
						|
            cache.target.speed = "---"
 | 
						|
        end
 | 
						|
        if cache.target.heading ~= "---" then
 | 
						|
            setData['ta-heading'] = "---"
 | 
						|
            cache.target.heading = "---"
 | 
						|
        end
 | 
						|
        if cache.target.elevation ~= "---" then
 | 
						|
            setData['ta-elevation'] = "---"
 | 
						|
            cache.target.elevation = "---"
 | 
						|
        end
 | 
						|
        if cache.target.distance ~= "---" then
 | 
						|
            setData['ta-distance'] = "---"
 | 
						|
            cache.target.distance = "---"
 | 
						|
        end
 | 
						|
 | 
						|
        local heli = Entity(helicopter.entity)
 | 
						|
        if not heli or (heli and heli.state.heliCamTargetBlip) then
 | 
						|
            SetHelicopterStateBag('heliCamTargetBlip', false)
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    -- Send number plate if it's different from last time we send it to the NUI
 | 
						|
    if cache.target.numberplate ~= (data.target.numberplate or false) then
 | 
						|
        info.numberplate = data.target.numberplate
 | 
						|
        cache.target.numberplate = data.target.numberplate
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.ShowLatitudeLongitude then
 | 
						|
        local latitude = GetCartesianCoords(data.helicopter.coords.x)
 | 
						|
        local longitude = GetCartesianCoords(data.helicopter.coords.y)
 | 
						|
        if data.helicopter.latitude ~= latitude then
 | 
						|
            setData['hi-latitude'] = latitude
 | 
						|
            cache.helicopter.latitude = latitude
 | 
						|
        end
 | 
						|
        if data.helicopter.longitude ~= longitude then
 | 
						|
            setData['hi-longitude'] = longitude
 | 
						|
            cache.helicopter.longitude = longitude
 | 
						|
        end
 | 
						|
 | 
						|
        if hit then
 | 
						|
            local targetLatitude = GetCartesianCoords(hitCoords.x)
 | 
						|
            local targetLongitude = GetCartesianCoords(hitCoords.y)
 | 
						|
            if data.helicopter.latitude ~= targetLatitude then
 | 
						|
                setData['ta-latitude'] = targetLatitude
 | 
						|
                cache.target.latitude = targetLatitude
 | 
						|
            end
 | 
						|
            if data.helicopter.longitude ~= targetLongitude then
 | 
						|
                setData['ta-longitude'] = targetLongitude
 | 
						|
                cache.target.longitude = targetLongitude
 | 
						|
            end
 | 
						|
        else
 | 
						|
            if data.helicopter.latitude ~= "---" then
 | 
						|
                setData['ta-latitude'] = "---"
 | 
						|
                cache.target.latitude = "---"
 | 
						|
            end
 | 
						|
            if data.helicopter.longitude ~= "---" then
 | 
						|
                setData['ta-longitude'] = "---"
 | 
						|
                cache.target.longitude = "---"
 | 
						|
            end
 | 
						|
        end
 | 
						|
    else
 | 
						|
        -- Street name and area
 | 
						|
        local streetHash, _crossingHash = GetStreetNameAtCoord(data.helicopter.coords.x, data.helicopter.coords.y, data.helicopter.coords.z)
 | 
						|
        if streetHash ~= cache.helicopter.street then
 | 
						|
            local street, area = GetStreetAndAreaNames(streetHash, data.helicopter.coords)
 | 
						|
            setData['hi-street'] = street.." - "..area
 | 
						|
            cache.helicopter.street = streetHash
 | 
						|
        end
 | 
						|
 | 
						|
        if hit then
 | 
						|
            local targetStreetHash, _targetCrossingHash = GetStreetNameAtCoord(hitCoords.x, hitCoords.y, hitCoords.z)
 | 
						|
            if targetStreetHash ~= cache.target.street then
 | 
						|
                local targetStreet, targetArea = GetStreetAndAreaNames(targetStreetHash, hitCoords)
 | 
						|
                setData['ta-street'] = targetStreet.." - "..targetArea
 | 
						|
                cache.target.street = targetStreetHash
 | 
						|
            end
 | 
						|
        elseif cache.target.street ~= "---" then
 | 
						|
            setData['ta-street'] = "---"
 | 
						|
            cache.target.street = "---"
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if not IsTableEmpty(info) or not IsTableEmpty(setData) then
 | 
						|
        SendNUIMessage({
 | 
						|
            action = 'updateData',
 | 
						|
            info = info,
 | 
						|
            set = setData
 | 
						|
        })
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local function UpdateUIHeadingPitchAndBearing()
 | 
						|
    local rotation = GetCamRot(camera.cam, 2)
 | 
						|
    local bearing = string.format("%.0f", RotationToHeading(rotation.z))
 | 
						|
    local pitch = (rotation.x * -1) + 90.0
 | 
						|
    local heading = (rotation.z * -1) + GetEntityHeading(helicopter.entity)
 | 
						|
    if heading > 360 then
 | 
						|
        heading = heading - 360
 | 
						|
    end
 | 
						|
 | 
						|
    if math.abs(camera.pitch - pitch) > 0.1 or math.abs(camera.heading - heading) > 0.1 then
 | 
						|
        SendNUIMessage({
 | 
						|
            action = 'updateDataFrame',
 | 
						|
            pitch = pitch,
 | 
						|
            heading = heading,
 | 
						|
            bearing = bearing
 | 
						|
        })
 | 
						|
 | 
						|
        camera.pitch = pitch
 | 
						|
        camera.heading = heading
 | 
						|
        camera.bearing = bearing
 | 
						|
    end
 | 
						|
 | 
						|
    camera.rotation = rotation
 | 
						|
end
 | 
						|
 | 
						|
local function PrimaryThread()
 | 
						|
    UpdateUIHeadingPitchAndBearing()
 | 
						|
 | 
						|
    CreateThread(function()
 | 
						|
        while inHeliCam do
 | 
						|
            if not pauseMenu then
 | 
						|
                -- Camera Heading, Pitch and Bearing
 | 
						|
                UpdateUIHeadingPitchAndBearing()
 | 
						|
 | 
						|
                -- Handle inputs
 | 
						|
                HandleZoomInput()
 | 
						|
                HandleMovementInput()
 | 
						|
 | 
						|
                -- Disable game inputs
 | 
						|
                for _index, control in pairs(controlActions) do
 | 
						|
                    DisableControlAction(0, control, true)
 | 
						|
                end
 | 
						|
            else
 | 
						|
                Wait(100)
 | 
						|
            end
 | 
						|
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function DisplayPostalLoop()
 | 
						|
    CreateThread(function()
 | 
						|
        while inHeliCam and postalsActive do
 | 
						|
            for _index, data in pairs(displayPostals) do
 | 
						|
                DrawText3D(data.coords, data.code)
 | 
						|
            end
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
 | 
						|
        displayPostals = {}
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function PostalLoop()
 | 
						|
    CreateThread(function()
 | 
						|
        while inHeliCam and postalsActive do
 | 
						|
            local coords = cache.target.position and cache.target.position.xy or cache.helicopter.coords.xy
 | 
						|
            local inDistance = {}
 | 
						|
 | 
						|
            for _index, data in pairs(postals) do
 | 
						|
                local postalCoords = data.coords and data.coords.xy or vector2(data.x, data.y)
 | 
						|
                local dist = #(coords - postalCoords)
 | 
						|
                if dist < 500.0 then
 | 
						|
                    if data.coords then
 | 
						|
                        inDistance[#inDistance+1] = { coords = data.coords, code = data.code, dist = dist }
 | 
						|
                    else
 | 
						|
                        local success, groundZ = GetGroundZFor_3dCoord(data.x, data.y, cache.helicopter.coords.z, false)
 | 
						|
                        if success then
 | 
						|
                            data.coords = vector3(data.x, data.y, groundZ) -- Cache's the z coord
 | 
						|
                            data.x = nil
 | 
						|
                            data.y = nil
 | 
						|
                            inDistance[#inDistance+1] = { coords = data.coords, code = data.code, dist = dist }
 | 
						|
                        end
 | 
						|
                    end
 | 
						|
                end
 | 
						|
            end
 | 
						|
 | 
						|
            table.sort(inDistance, function(p1, p2) return p1.dist < p2.dist end)
 | 
						|
 | 
						|
            displayPostals = {}
 | 
						|
            for i = 1, 50 do
 | 
						|
                displayPostals[i] = inDistance[i]
 | 
						|
            end
 | 
						|
 | 
						|
            Wait(500)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
 | 
						|
    DisplayPostalLoop()
 | 
						|
end
 | 
						|
 | 
						|
local function SecondaryThread()
 | 
						|
    CollectAndSendData()
 | 
						|
    SendNUIMessage({ action = 'open' })
 | 
						|
 | 
						|
    CreateThread(function()
 | 
						|
        while true do
 | 
						|
            if not inHeliCam then
 | 
						|
                return
 | 
						|
            end
 | 
						|
 | 
						|
            if IsEntityDead(PlayerPedId()) then
 | 
						|
                ExitHeliCamera()
 | 
						|
                return
 | 
						|
            end
 | 
						|
 | 
						|
            if IsPauseMenuActive() then
 | 
						|
                if not pauseMenu then
 | 
						|
                    pauseMenu = true
 | 
						|
                    SendNUIMessage({ action = 'close' })
 | 
						|
                end
 | 
						|
            else
 | 
						|
                CollectAndSendData()
 | 
						|
 | 
						|
                -- Sets camera depth of field
 | 
						|
                local dist = type(cache.target.distance) == "number" and cache.target.distance or 500.0
 | 
						|
                SetCamDofFocusDistanceBias(camera.cam, dist)
 | 
						|
 | 
						|
                if pauseMenu then
 | 
						|
                    pauseMenu = false
 | 
						|
                    SendNUIMessage({ action = 'open' })
 | 
						|
                end
 | 
						|
            end
 | 
						|
 | 
						|
            Wait(250)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function UseHeliCamera()
 | 
						|
    if helicopter.model == nil or helicopter.entity == nil then
 | 
						|
        print("^1ERROR: helicopter model or entity was nil, this is fatal and will cause issues!^7")
 | 
						|
    end
 | 
						|
 | 
						|
    cameraAction = true
 | 
						|
    inHeliCam = true
 | 
						|
    SetCameraLabel()
 | 
						|
 | 
						|
    if Config.PlaySounds then
 | 
						|
        LoadSounds()  -- Loads the sounds used when in the camera
 | 
						|
 | 
						|
        if Config.PlayCameraMovementSounds then
 | 
						|
            SoundThread() -- Handles sounds, runs every 100ms
 | 
						|
        end
 | 
						|
 | 
						|
        -- Enter sound
 | 
						|
        EmitSound(sounds.enter, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET")
 | 
						|
    end
 | 
						|
 | 
						|
    if not camera.cam then
 | 
						|
        fov = Config.Camera.Zoom.Max
 | 
						|
        local offset = GetHeliCameraOffset(helicopter.model)
 | 
						|
        local rotation = GetEntityRotation(helicopter.entity, 5).z
 | 
						|
        camera.cam = CreateHelicopterCamera(helicopter.entity, offset, rotation, 50.0, Config.CameraTransition, Config.CameraTransitionTime)
 | 
						|
        if Config.CameraTransition then
 | 
						|
            Wait(Config.CameraTransitionTime)
 | 
						|
        end
 | 
						|
    else
 | 
						|
        RenderScriptCams(true, Config.CameraTransition, Config.CameraTransitionTime, false, false)
 | 
						|
    end
 | 
						|
 | 
						|
    local timecycleName, timecycleStrength = GetHelicopterTimecycle(helicopter.model)
 | 
						|
    if timecycleName then
 | 
						|
        SetTimecycleModifier(timecycleName)
 | 
						|
        SetTimecycleModifierStrength(timecycleStrength)
 | 
						|
    end
 | 
						|
 | 
						|
    -- Trigger event for other scripts to use
 | 
						|
    TriggerEvent('helicam:enteredCamera', helicopter.netId)
 | 
						|
 | 
						|
    -- Reset the zoom bar
 | 
						|
    SetZoomBarLevel()
 | 
						|
 | 
						|
    if Config.ShowInstructions then
 | 
						|
        SetupInstructionsScaleform()
 | 
						|
        InstructionsThread()
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.HideMinimap then
 | 
						|
        DisplayRadar(false)
 | 
						|
    else
 | 
						|
        MinimapHeadingThread()
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.UseSubmix and Config.NoSubmixInCamera then
 | 
						|
        DisableSubmix()
 | 
						|
    end
 | 
						|
 | 
						|
    PrimaryThread()   -- Handles controls and other stuff thats needs to be run every frame
 | 
						|
    SecondaryThread() -- Handles "everything else", runs once every 250ms to save resources
 | 
						|
 | 
						|
    if Config.ShowPostalCodes then
 | 
						|
        PostalLoop()
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.UseAnimProp then
 | 
						|
        CreateTablet()
 | 
						|
    end
 | 
						|
    cameraAction = false
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Events --
 | 
						|
RegisterNetEvent('helicam:enterCamera')
 | 
						|
AddEventHandler('helicam:enterCamera', function(state)
 | 
						|
    if state then
 | 
						|
        UseHeliCamera()
 | 
						|
    else
 | 
						|
        DisplayNotification(Config.Localisation.Notification.CameraInUse)
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
AddEventHandler('gameEventTriggered', function(event, args)
 | 
						|
    if event == "CEventNetworkPlayerEnteredVehicle" then
 | 
						|
        if args[1] == PlayerId() then
 | 
						|
            OnEnteredVehicle(args[2])
 | 
						|
        end
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
 | 
						|
-- Commands & Key Mapping --
 | 
						|
RegisterKeyMapping('helicam', Config.Localisation.KeyMapping.ToggleCam, Config.Keybinds.ToggleCam.Type, Config.Keybinds.ToggleCam.Key)
 | 
						|
RegisterCommand('helicam', function()
 | 
						|
    if cameraAction or pauseMenu then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if not inHeliCam then
 | 
						|
        local playerPed = PlayerPedId()
 | 
						|
        if IsPedInAnyHeli(playerPed) or IsPedInAnyPlane(playerPed) then
 | 
						|
            local canUseCamera, message = CanPlayerUseCamera(playerPed)
 | 
						|
            if canUseCamera then
 | 
						|
                TriggerServerEvent('helicam:enterCamera', helicopter.netId)
 | 
						|
            elseif message then
 | 
						|
                DisplayNotification(Config.Localisation.Notification[message])
 | 
						|
            end
 | 
						|
        end
 | 
						|
    else
 | 
						|
        ExitHeliCamera()
 | 
						|
    end
 | 
						|
end, false)
 | 
						|
 | 
						|
if Config.AllowCameraLock then
 | 
						|
    RegisterKeyMapping('+helicam_lock', Config.Localisation.KeyMapping.AttemptLock, Config.Keybinds.AttemptLock.Type, Config.Keybinds.AttemptLock.Key)
 | 
						|
    RegisterCommand('+helicam_lock', function()
 | 
						|
        if not inHeliCam or pauseMenu then
 | 
						|
            return
 | 
						|
        end
 | 
						|
 | 
						|
        if not cameraLock.active then
 | 
						|
            AttemptCameraLock()
 | 
						|
        else
 | 
						|
            StopCameraLock()
 | 
						|
        end
 | 
						|
    end, false)
 | 
						|
 | 
						|
    RegisterCommand('-helicam_lock', function()
 | 
						|
        cameraLock.attempting = false
 | 
						|
    end, false)
 | 
						|
end
 | 
						|
 | 
						|
if Config.AllowNightVision or Config.AllowThermal or DoesAnyHeliHaveVisionOverwrite() then
 | 
						|
    RegisterKeyMapping('helicam_cycle_vision', Config.Localisation.KeyMapping.CycleVision, Config.Keybinds.CycleVision.Type, Config.Keybinds.CycleVision.Key)
 | 
						|
    RegisterCommand('helicam_cycle_vision', function()
 | 
						|
        if inHeliCam and not pauseMenu then
 | 
						|
            CycleVision()
 | 
						|
        end
 | 
						|
    end, false)
 | 
						|
end
 | 
						|
 | 
						|
if Config.AllowMarkers then
 | 
						|
    RegisterKeyMapping('helicam_toggle_marker', Config.Localisation.KeyMapping.ToggleMarker, Config.Keybinds.ToggleMarker.Type, Config.Keybinds.ToggleMarker.Key)
 | 
						|
    RegisterCommand('helicam_toggle_marker', function()
 | 
						|
        if inHeliCam and not pauseMenu then
 | 
						|
            ToggleMarker()
 | 
						|
        end
 | 
						|
    end, false)
 | 
						|
end
 | 
						|
 | 
						|
if Config.AllowRappelling then
 | 
						|
    RegisterKeyMapping('+rappel', Config.Localisation.KeyMapping.Rappel, Config.Keybinds.Rappel.Type, Config.Keybinds.Rappel.Key)
 | 
						|
    RegisterCommand('rappel', function()
 | 
						|
        AttemptRappel(false)
 | 
						|
    end, false)
 | 
						|
    RegisterCommand('+rappel', function()
 | 
						|
        AttemptRappel(true)
 | 
						|
    end, false)
 | 
						|
    RegisterCommand('-rappel', function()
 | 
						|
        -- This is just a place holder to prevent "unknown command" messages in chat
 | 
						|
    end, false)
 | 
						|
end
 | 
						|
 | 
						|
if Config.AllowSpotlight or DoesAnyHeliHaveSpotlightOverwrite() then
 | 
						|
    -- Toggle spotlight
 | 
						|
    RegisterKeyMapping('helispotlight', Config.Localisation.KeyMapping.Spotlight, Config.Keybinds.Spotlight.Type, Config.Keybinds.Spotlight.Key)
 | 
						|
    RegisterCommand('helispotlight', function()
 | 
						|
        ToggleSpotlight()
 | 
						|
    end, false)
 | 
						|
 | 
						|
    -- Adjusting spoltight brightness
 | 
						|
    RegisterKeyMapping('+adjust_heli_spotlight_brightness', Config.Localisation.KeyMapping.SpotlightBrightness, Config.Keybinds.SpotlightBrightness.Type, Config.Keybinds.SpotlightBrightness.Key)
 | 
						|
    RegisterCommand('+adjust_heli_spotlight_brightness', function()
 | 
						|
        if inHeliCam and spotlight.active and not pauseMenu then
 | 
						|
            if spotlight.adjustingBrightness then return end
 | 
						|
            spotlight.adjustingBrightness = true
 | 
						|
            AdjustSpolightBrightness()
 | 
						|
        end
 | 
						|
    end, false)
 | 
						|
 | 
						|
    RegisterCommand('-adjust_heli_spotlight_brightness', function()
 | 
						|
        spotlight.adjustingBrightness = false
 | 
						|
    end, false)
 | 
						|
 | 
						|
    -- Adjusting spoltight size/radius
 | 
						|
    RegisterKeyMapping('+adjust_heli_spotlight_radius', Config.Localisation.KeyMapping.SpotlightRadius, Config.Keybinds.SpotlightRadius.Type, Config.Keybinds.SpotlightRadius.Key)
 | 
						|
    RegisterCommand('+adjust_heli_spotlight_radius', function()
 | 
						|
        if inHeliCam and spotlight.active and not pauseMenu then
 | 
						|
            if spotlight.adjustingRadius then return end
 | 
						|
            spotlight.adjustingRadius = true
 | 
						|
            AdjustSpolightRadius()
 | 
						|
        end
 | 
						|
    end, false)
 | 
						|
 | 
						|
    RegisterCommand('-adjust_heli_spotlight_radius', function()
 | 
						|
        spotlight.adjustingRadius = false
 | 
						|
    end, false)
 | 
						|
end
 | 
						|
 | 
						|
if Config.ShowPostalCodes then
 | 
						|
    RegisterKeyMapping('helipostals', Config.Localisation.KeyMapping.Postals, Config.Keybinds.Postals.Type, Config.Keybinds.Postals.Key)
 | 
						|
    RegisterCommand('helipostals', function()
 | 
						|
        if inHeliCam then
 | 
						|
            postalsActive = not postalsActive
 | 
						|
            if postalsActive then
 | 
						|
                PostalLoop()
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end, false)
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Init --
 | 
						|
CreateThread(function()
 | 
						|
    local conversions = {
 | 
						|
        -- Speed
 | 
						|
        KTS = 1.943844, -- Knots per hour
 | 
						|
        MPH = 2.236936, -- Miles per hour
 | 
						|
        KMH = 3.6,      -- Kilometers per hour
 | 
						|
        MPS = 1.0,      -- Meters per second
 | 
						|
        FPS = 3.280840, -- Feet per second
 | 
						|
 | 
						|
        -- Distance
 | 
						|
        FT = 3.2808399, -- Feet
 | 
						|
        M = 1.0,        -- Meters
 | 
						|
        MI = 0.00062137 -- Miles
 | 
						|
    }
 | 
						|
 | 
						|
    for index, unit in pairs(Config.Units) do
 | 
						|
        Units[index] = {
 | 
						|
            Type = unit or 'M',
 | 
						|
            Conversion = conversions[unit] or conversions['M']
 | 
						|
        }
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.DisablePoliceScanner then
 | 
						|
        SetAudioFlag('PoliceScannerDisabled', true)
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.DisableFlightMusic then
 | 
						|
        SetAudioFlag("DisableFlightMusic", true)
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.AddChatSuggestions then
 | 
						|
        TriggerEvent('chat:addSuggestion', '/helicam', Config.Localisation.ChatSuggestions.ToggleCamera)
 | 
						|
        TriggerEvent('chat:addSuggestion', '/rappel', Config.Localisation.ChatSuggestions.Rappel)
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.ShowPostalCodes then
 | 
						|
        LoadPostalFile(Config.PostalResource, Config.PostalFile)
 | 
						|
    end
 | 
						|
 | 
						|
    if type(Config.DefaultCameraTimecycleStrength) ~= "number" then
 | 
						|
        print(string.format("^1ERROR: Config.DefaultCameraTimecycleStrength is invalid, it needs to be a number! Current type: %s, current value: %s.^7", type(Config.DefaultCameraTimecycleStrength), Config.CameraTimecycleStrength))
 | 
						|
        Config.DefaultCameraTimecycleStrength = 0.5
 | 
						|
    end
 | 
						|
 | 
						|
    Wait(2500)
 | 
						|
 | 
						|
    SendNUIMessage({
 | 
						|
        action = 'setConfigData',
 | 
						|
        set = {
 | 
						|
            ['hi-speed-unit'] = Config.Units.Speed,
 | 
						|
            ['hi-altitude-unit'] = Config.Units.Altitude,
 | 
						|
            ['ta-speed-unit'] = Config.Units.TargetSpeed,
 | 
						|
            ['ta-elevation-unit'] = Config.Units.TargetElevation,
 | 
						|
            ['ta-distance-unit'] = Config.Units.TargetDistance
 | 
						|
        },
 | 
						|
        showLatitudeLongitude = Config.ShowLatitudeLongitude,
 | 
						|
        showLicensePlate = Config.ShowLicensePlate,
 | 
						|
        timeFormat = Config.TimeFormat,
 | 
						|
        dateFormat = Config.DateFormat,
 | 
						|
        hideMinimap = Config.HideMinimap,
 | 
						|
        showInstructions = Config.ShowInstructions,
 | 
						|
        zoomBarOffset = Config.ZoomBarOffset
 | 
						|
    })
 | 
						|
end)
 | 
						|
 | 
						|
 | 
						|
-- Exports --
 | 
						|
local function InHelicam()
 | 
						|
    return inHeliCam
 | 
						|
end
 | 
						|
exports('InHelicam', InHelicam)
 | 
						|
 | 
						|
 | 
						|
-- Debugging Fix (if you restart the script while in a helicopter)
 | 
						|
local currentResourceName = GetCurrentResourceName()
 | 
						|
AddEventHandler('onResourceStart', function(resourceName)
 | 
						|
    if currentResourceName ~= resourceName then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
 | 
						|
    if vehicle ~= 0 then
 | 
						|
        OnEnteredVehicle(vehicle)
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
AddEventHandler('onResourceStop', function(resourceName)
 | 
						|
    if currentResourceName ~= resourceName then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if DoesEntityExist(tabletObj) then
 | 
						|
        ClearPedSecondaryTask(PlayerPedId())
 | 
						|
        DetachEntity(tabletObj, true, false)
 | 
						|
        DeleteEntity(tabletObj)
 | 
						|
        tabletObj = nil
 | 
						|
    end
 | 
						|
 | 
						|
    if inHeliCam then
 | 
						|
        local heli = Entity(helicopter.entity)
 | 
						|
        if heli and heli.state.heliCamInUse then
 | 
						|
            SetHelicopterStateBag('heliCamInUse', false)
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if submix then
 | 
						|
        DisableSubmix()
 | 
						|
    end
 | 
						|
end)
 |