-- CSAR Script for DCS Ciribob - 2015
-- Version 20230831.01
-- DCS 1.5 Compatible - Needs Mist 4.0.55 or higher!
--
-- 4 Options:
--      0 - No Limit - NO Aircraft disabling or pilot lives
--      1 - Disable Aircraft when its down - Timeout to reenable aircraft
--      2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft
--      3 - Pilot Life Limit - No Aircraft Disabling 



csar = {}
csar.Version = "2024.07.11.01-VEAF"
csar.Id = "CSAR"

-- SETTINGS FOR MISSION DESIGNER vvvvvvvvvvvvvvvvvv
--Enable CSar Options -HELICOPTERS
--enableAllslots and Use prefix will work for Helicopter 

csar.enableAllslots = false  -- Doesn't require to set the Unit name check Aircraft Type and Limit below
-- All slot / Limit settings
csar.aircraftType = {} -- Type and limit
csar.aircraftType["SA342Mistral"] = 2
csar.aircraftType["SA342Minigun"] = 2
csar.aircraftType["SA342L"] = 2
csar.aircraftType["SA342M"] = 2
csar.aircraftType["UH-1H"] = 8
csar.aircraftType["Mi-8MT"] = 16
csar.aircraftType["Mi-24P"] = 8

-- Prefix Settings - Only For helicopters
csar.useprefix    = true  -- Use the Prefixed defined below, Requires Unit have the Prefix defined below 
csar.csarPrefix = { "helicargo", "MEDEVAC"}

-- Fixed Unit Name.
--Enable Csar Options for the units with the names in the list below                  
csar.csarFixedUnits =  {
    "helicargo1",
    "helicargo2",
    "helicargo3",
    "helicargo4",
    "helicargo5",
    "helicargo6",
    "helicargo7",
    "helicargo8",
    "helicargo9",
    "helicargo10",

    "helicargo11",
    "helicargo12",
    "helicargo13",
    "helicargo14",
    "helicargo15",
    "helicargo16",
    "helicargo17",
    "helicargo18",
    "helicargo19",
    "helicargo20",

    "helicargo21",
    "helicargo22",
    "helicargo23",
    "helicargo24",
    "helicargo25",

    "MEDEVAC #1",
    "MEDEVAC #2",
    "MEDEVAC #3",
    "MEDEVAC #4",
    "MEDEVAC #5",
    "MEDEVAC #6",
    "MEDEVAC #7",
    "MEDEVAC #8",
    "MEDEVAC #9",
    "MEDEVAC #10",
    "MEDEVAC #11",
    "MEDEVAC #12",
    "MEDEVAC #13",
    "MEDEVAC #14",
    "MEDEVAC #15",
    "MEDEVAC #16",

    "MEDEVAC RED #1",
    "MEDEVAC RED #2",
    "MEDEVAC RED #3",
    "MEDEVAC RED #4",
    "MEDEVAC RED #5",
    "MEDEVAC RED #6",
    "MEDEVAC RED #7",
    "MEDEVAC RED #8",
    "MEDEVAC RED #9",
    "MEDEVAC RED #10",
    "MEDEVAC RED #11",
    "MEDEVAC RED #12",
    "MEDEVAC RED #13",
    "MEDEVAC RED #14",
    "MEDEVAC RED #15",
    "MEDEVAC RED #16",
    "MEDEVAC RED #17",
    "MEDEVAC RED #18",
    "MEDEVAC RED #19",
    "MEDEVAC RED #20",
    "MEDEVAC RED #21",

    "MEDEVAC BLUE #1",
    "MEDEVAC BLUE #2",
    "MEDEVAC BLUE #3",
    "MEDEVAC BLUE #4",
    "MEDEVAC BLUE #5",
    "MEDEVAC BLUE #6",
    "MEDEVAC BLUE #7",
    "MEDEVAC BLUE #8",
    "MEDEVAC BLUE #9",
    "MEDEVAC BLUE #10",
    "MEDEVAC BLUE #11",
    "MEDEVAC BLUE #12",
    "MEDEVAC BLUE #13",
    "MEDEVAC BLUE #14",
    "MEDEVAC BLUE #15",
    "MEDEVAC BLUE #16",
    "MEDEVAC BLUE #17",
    "MEDEVAC BLUE #18",
    "MEDEVAC BLUE #19",
    "MEDEVAC BLUE #20",
    "MEDEVAC BLUE #21",
} -- List of all the MEDEVAC _UNIT NAMES_ (the line where it says "Pilot" in the ME)!

csar.autosmoke = false -- Automatically Smoke when CSAR helicopter is at 5 km

csar.bluemash = {
    "BlueMASH #1",
    "BlueMASH #2",
    "BlueMASH #3",
    "BlueMASH #4",
    "BlueMASH #5",
    "BlueMASH #6",
    "BlueMASH #7",
    "BlueMASH #8",
    "BlueMASH #9",
    "BlueMASH #10"
} -- The unit that serves as MASH for the blue side

csar.redmash = {
    "RedMASH #1",
    "RedMASH #2",
    "RedMASH #3",
    "RedMASH #4",
    "RedMASH #5",
    "RedMASH #6",
    "RedMASH #7",
    "RedMASH #8",
    "RedMASH #9",
    "RedMASH #10"
} -- The unit that serves as MASH for the red side


csar.csarMode = 0

--      0 - No Limit - NO Aircraft disabling
--      1 - Disable Aircraft when its down - Timeout to reenable aircraft
--      2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft
--      3 - Pilot Life Limit - No Aircraft Disabling -- timeout to reset lives?

csar.maxLives = 8 -- Maximum pilot lives

csar.countCSARCrash = false -- If you set to true, pilot lives count for CSAR and CSAR aircraft will count.

csar.csarOncrash = true -- If set to true, will generate a csar when crash as well.

csar.allowDownedPilotCAcontrol = true -- Set to false if you don't want to allow control by Combined arms



csar.reenableIfCSARCrashes = true -- If a CSAR heli crashes, the pilots are counted as rescued anyway. Set to false to Stop this

-- - I recommend you leave the option on below IF USING MODE 1 otherwise the
-- aircraft will be disabled for the duration of the mission
csar.disableAircraftTimeout = true -- Allow aircraft to be used after 20 minutes if the pilot isnt rescued
csar.disableTimeoutTime = 20 -- Time in minutes for TIMEOUT

csar.destructionHeight = 150 -- height in meters an aircraft will be destroyed at if the aircraft is disabled

csar.enableForAI = true -- set to false to disable AI units from being rescued.

csar.enableForRED = true -- enable for red side

csar.enableForBLUE = true -- enable for blue side

csar.enableSlotBlocking = true -- if set to true, you need to put the csarSlotBlockGameGUI.lua
-- in C:/Users/<YOUR USERNAME>/DCS/Scripts for 1.5 or C:/Users/<YOUR USERNAME>/DCS.openalpha/Scripts for 2.0
-- For missions using FLAGS and this script, the CSAR flags will NOT interfere with your mission :)

csar.bluesmokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue
csar.redsmokecolor = 1 -- Color of smokemarker for red side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue

csar.requestdelay = 2 -- Time in seconds before the survivors will request Medevac

csar.coordtype = 3 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates.
csar.coordaccuracy = 1 -- Precision of the reported coordinates, see MIST-docs at http://wiki.hoggit.us/view/GetMGRSString
-- only applies to _non_ bullseye coords

csar.immortalcrew = true -- Set to true to make wounded crew immortal
csar.invisiblecrew = true -- Set to true to make wounded crew insvisible

csar.messageTime = 30 -- Time to show the intial wounded message for in seconds

csar.weight = 100

csar.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to csar.extractDistance METERS 

csar.loadDistance = 60 -- configure distance for pilot to get in helicopter in meters.
csar.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter
csar.loadtimemax = 135

csar.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK!

csar.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase

csar.landedStatus = {} -- tracks status of a helis hover above a downed pilot

csar.csarUnits =  {}
-- SETTINGS FOR MISSION DESIGNER ^^^^^^^^^^^^^^^^^^^*

-- ***************************************************************
-- **************** Mission Editor Functions *********************
-- ***************************************************************

-----------------------------------------------------------------
-- Resets all life limits so everyone can spawn again. Usage:
-- csar.resetAllPilotLives()
--
function csar.resetAllPilotLives()

    for x, _pilot in pairs(csar.pilotLives) do

        trigger.action.setUserFlag("CSAR_PILOT" .. _pilot:gsub('%W', ''), csar.maxLives + 1)
    end

    csar.pilotLives = {}
    csar.logInfo("Pilot Lives Reset!")
end

-----------------------------------------------------------------
-- Resets all life limits so everyone can spawn again. Usage:
-- csar.resetAllPilotLives()
--
function csar.resetPilotLife(_playerName)

    csar.pilotLives[_playerName] = nil

    trigger.action.setUserFlag("CSAR_PILOT" .. _playerName:gsub('%W', ''), csar.maxLives + 1)

    csar.logInfo("Pilot life Reset!")
end


-- ***************************************************************
-- **************** BE CAREFUL BELOW HERE ************************
-- ***************************************************************

-- Sanity checks of mission designer
assert(mist ~= nil, "\n\n** HEY MISSION-DESIGNER! **\n\nMiST has not been loaded!\n\nMake sure MiST 4.0.57 or higher is running\n*before* running this script!\n")

csar.addedTo = {}

csar.downedPilotCounterRed = 0
csar.downedPilotCounterBlue = 0

csar.woundedGroups = {} -- contains the new group of units
csar.inTransitGroups = {} -- contain a table for each SAR with all units he has with the
-- original name of the killed group

csar.smokeMarkers = {} -- tracks smoke markers for groups
csar.heliVisibleMessage = {} -- tracks if the first message has been sent of the heli being visible

csar.heliCloseMessage = {} -- tracks heli close message  ie heli < 500m distance

csar.max_units = 6 --number of pilots that can be carried

csar.currentlyDisabled = {} --stored disabled aircraft

csar.hoverStatus = {} -- tracks status of a helis hover above a downed pilot

csar.pilotDisabled = {} -- tracks what aircraft a pilot is disabled for

csar.pilotLives = {} -- tracks how many lives a pilot has

csar.takenOff = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Logging methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- print an object for a debugging log
function csar.p(o, level)
    local MAX_LEVEL = 20
    if level == nil then level = 0 end
    if level > MAX_LEVEL then
        csar.logError("max depth reached in csar.p : "..tostring(MAX_LEVEL))
        return ""
    end
    local text = ""
    if (type(o) == "table") then
        text = "\n"
        for key,value in pairs(o) do
            for i=0, level do
                text = text .. " "
            end
            text = text .. ".".. key.."="..csar.p(value, level+1) .. "\n"
        end
    elseif (type(o) == "function") then
        text = "[function]"
    elseif (type(o) == "boolean") then
        if o == true then
            text = "[true]"
        else
            text = "[false]"
        end
    else
        if o == nil then
            text = "[nil]"
        else
            text = tostring(o)
        end
    end
    return text
end

function csar.logError(message)
    env.info(" E - " .. csar.Id .. " - " .. message)
end

function csar.logInfo(message)
    env.info(" I - " .. csar.Id .. " - " .. message)
end

function csar.logDebug(message)
    if message and csar.Debug then
        env.info(" D - " .. csar.Id .. " - " .. message)
    end
end

function csar.logTrace(message)
    if message and csar.Trace then
        env.info(" T - " .. csar.Id .. " - " .. message)
    end
end

function csar.tableLength(T)

    if T == nil then
        return 0
    end


    local count = 0
    for _ in pairs(T) do count = count + 1 end
    return count
end

function csar.pilotsOnboard(_heliName)
    local count = 0
    if csar.inTransitGroups[_heliName] then
        for _, _group in pairs(csar.inTransitGroups[_heliName]) do
            count = count + 1
        end
    end
    return count
end

function csar.addCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description )
      
  local _spawnedGroup = csar.spawnGroup( _coalition, _country, _point, _typeName )
  csar.addSpecialParametersToGroup(_spawnedGroup)
  
  if noMessage ~= true then
    trigger.action.outTextForCoalition(_spawnedGroup:getCoalition(), "MAYDAY MAYDAY! " .. _typeName .. " is down. ", 10)
  end
  
  if _freq == nil then
    _freq = csar.generateADFFrequency()
  end 
  
  if _freq ~= nil then
    csar.addBeaconToGroup(_spawnedGroup:getName(), _freq)
  end
  
  
  csar.handleEjectOrCrash(_playerName, false)
  
-- Generate DESCRIPTION text
  local _text = " "
  if _playerName ~= nil then
      _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName
  elseif _typeName ~= nil then
      _text = "AI Pilot of " .. _unitName .. " - " .. _typeName
  else
      _text = _description
  end
  
  csar.woundedGroups[_spawnedGroup:getName()] = { side = _spawnedGroup:getCoalition(), originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName }
 
  csar.initSARForPilot(_spawnedGroup, _freq, noMessage)
  
  if _spawnedGroup ~= nil then
     local _controller = _spawnedGroup:getController();
     Controller.setOption(_controller, AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN)
     Controller.setOption(_controller, AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
  end

end

function csar.spawnCsarAtZone( _zone, _coalition, _description, _randomPoint)
  local _country 
  local freq = csar.generateADFFrequency()
  local _triggerZone = trigger.misc.getZone(_zone) -- trigger to use as reference position
  if _triggerZone == nil then
    trigger.action.outText("Csar.lua ERROR: Cant find zone called " .. _zone, 10)
    return
  end
  
  local pos
  if _randomPoint == true then
    pos =  mist.getRandomPointInZone(_zone)
    pos.z = pos.y
    pos.y = 0
  else
    pos  = mist.utils.zoneToVec3(_zone)
  end
  if _coalition == coalition.side.BLUE then
    _country = country.id.USA
  else
    _country =  country.id.RUSSIA
  end
  csar.addCsar(_coalition, _country, pos, nil, nil, nil, freq, true, _description)

end
-- Handles all world events
csar.eventHandler = {}
function csar.eventHandler:onEvent(_event)
    local status, err = pcall(function(_event)

        if _event == nil or _event.initiator == nil then
            return false

        elseif _event.id == 3 then -- taken offf

        if _event.initiator:getName() then
            csar.takenOff[_event.initiator:getName()] = true
        end

        return true
        elseif _event.id == 15 then --player entered unit

        if _event.initiator:getName() then
            csar.takenOff[_event.initiator:getName()] = nil
        end

        -- if its a sar heli, re-add check status script
        for _, _heliName in pairs(csar.csarUnits) do

            if _heliName == _event.initiator:getName() then
                -- add back the status script
                for _woundedName, _groupInfo in pairs(csar.woundedGroups) do

                    if _groupInfo.side == _event.initiator:getCoalition() then

                        --csar.logInfo(string.format("Schedule Respawn %s %s",_heliName,_woundedName))
                        -- queue up script
                        -- Schedule timer to check when to pop smoke
                          
                          timer.scheduleFunction(csar.checkWoundedGroupStatus, { _heliName, _woundedName }, timer.getTime() + 5)

                    end
                end
            end
        end

        -- sometimes the "getPlayerName" function is not defined in the _event.initiator object (not a player unit) ; check for it.
        if _event.initiator.getName and _event.initiator:getName() and _event.initiator.getPlayerName and _event.initiator:getPlayerName() then

            csar.logInfo("Checking Unit - " .. _event.initiator:getName())
            csar.checkDisabledAircraftStatus({ _event.initiator:getName(), _event.initiator:getPlayerName() })
        end

        return true

        elseif (_event.id == 9 and csar.csarOncrash == false) then
            -- Pilot dead

            csar.logInfo("Event unit - Pilot Dead")

            local _unit = _event.initiator

            if _unit == nil then
                return -- error!
            end

            local _coalition = _unit:getCoalition()

            if _coalition == 1 and not csar.enableForRED then
                return --ignore!
            end

            if _coalition == 2 and not csar.enableForBLUE then
                return --ignore!
            end

            -- Catch multiple events here?
            if csar.takenOff[_event.initiator:getName()] == true or _unit:inAir() then

                if csar.doubleEjection(_unit) then
                    return
                end

                trigger.action.outTextForCoalition(_unit:getCoalition(), "MAYDAY MAYDAY! " .. _unit:getTypeName() .. " shot down. No Chute!", 10)
                csar.handleEjectOrCrash(_unit, true)
            else
                csar.logInfo("Pilot Hasnt taken off, ignore")
            end

            return

        elseif _event.id == 9 or world.event.S_EVENT_EJECTION == _event.id then
            if _event.id == 9 and csar.csarOncrash == false then 
                return     
            end
            csar.logInfo("Event unit - Pilot Ejected")

            local _unit = _event.initiator

            if _unit == nil then
                return -- error!
            end

            local _coalition = _unit:getCoalition()

            if _coalition == 1 and not csar.enableForRED then
                return --ignore!
            end

            if _coalition == 2 and not csar.enableForBLUE then
                return --ignore!
            end

            -- TODO catch ejection on runway?

            if csar.enableForAI == false and _unit:getPlayerName() == nil then

                return
            end

            if csar.takenOff[_event.initiator:getName()] ~= true and not _unit:inAir() then
                csar.logInfo("Pilot Hasnt taken off, ignore")
                return -- give up, pilot hasnt taken off
            end

            if csar.doubleEjection(_unit) then
                return
            end



            local _freq = csar.generateADFFrequency()
             csar.addCsar(_coalition, _unit:getCountry(), _unit:getPoint()  , _unit:getTypeName(),  _unit:getName(), _unit:getPlayerName(), _freq, false, 0)
             
            return true

        elseif world.event.S_EVENT_LAND == _event.id then

            if _event.initiator:getName() then
                csar.takenOff[_event.initiator:getName()] = nil
            end

            if csar.allowFARPRescue then

                --csar.logInfo("Landing")

                local _unit = _event.initiator

                if _unit == nil then
                    csar.logInfo("Unit Nil on Landing")
                    return -- error!
                end

                csar.takenOff[_event.initiator:getName()] = nil

                local _place = _event.place

                if _place == nil then
                    csar.logInfo("Landing Place Nil")
                    return -- error!
                end
                -- Coalition == 3 seems to be a bug... unless it means contested?!
                if _place:getCoalition() == _unit:getCoalition() or _place:getCoalition() == 0 or _place:getCoalition() == 3 then
                    csar.rescuePilots(_unit)
                    --csar.logInfo("Rescued")
                    --   csar.logInfo("Rescued by Landing")

                else
                    --    csar.logInfo("Cant Rescue ")

                    csar.logInfo(string.format("airfield %d, unit %d", _place:getCoalition(), _unit:getCoalition()))
                end
            end

            return true
        end
    end, _event)
    if (not status) then
        env.error(string.format("Error while handling event %s", err), false)
    end
end

csar.lastCrash = {}

function csar.doubleEjection(_unit)

    if csar.lastCrash[_unit:getName()] then
        local _time = csar.lastCrash[_unit:getName()]

        if timer.getTime() - _time < 10 then
            csar.logInfo("Caught double ejection!")
            return true
        end
    end

    csar.lastCrash[_unit:getName()] = timer.getTime()

    return false
end

function csar.handleEjectOrCrash(_unit, _crashed)

    -- disable aircraft for ALL pilots
    if csar.csarMode == 1 then

        if csar.currentlyDisabled[_unit:getName()] ~= nil then
            return --already ejected once!
        end

        --                --mark plane as broken and unflyable
        if _unit:getPlayerName() ~= nil and csar.currentlyDisabled[_unit:getName()] == nil then

            if csar.countCSARCrash == false then
                for _, _heliName in pairs(csar.csarUnits) do

                    if _unit:getName() == _heliName then
                        -- IGNORE Crashed CSAR
                        return
                    end
                end
            end

            csar.currentlyDisabled[_unit:getName()] = { timeout = (csar.disableTimeoutTime * 60) + timer.getTime(), desc = "", noPilot = _crashed, unitId = _unit:getID(), name = _unit:getName() }

            -- disable aircraft

            trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _unit:getID(), 100)

            csar.logInfo("Unit Disabled: " .. _unit:getName() .. " ID:" .. _unit:getID())
        end

    elseif csar.csarMode == 2 then -- disable aircraft for pilot

    --csar.pilotDisabled
    if _unit:getPlayerName() ~= nil and csar.pilotDisabled[_unit:getPlayerName() .. "_" .. _unit:getName()] == nil then

        if csar.countCSARCrash == false then
            for _, _heliName in pairs(csar.csarUnits) do

                if _unit:getName() == _heliName then
                    -- IGNORE Crashed CSAR
                    return
                end
            end
        end

        csar.pilotDisabled[_unit:getPlayerName() .. "_" .. _unit:getName()] = { timeout = (csar.disableTimeoutTime * 60) + timer.getTime(), desc = "", noPilot = true, unitId = _unit:getID(), player = _unit:getPlayerName(), name = _unit:getName() }

        -- disable aircraft

        -- strip special characters from name gsub('%W','')
        trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _unit:getPlayerName():gsub('%W', '') .. "_" .. _unit:getID(), 100)

        csar.logInfo("Unit Disabled for player : " .. _unit:getName())
    end

    elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives

    --csar.pilotDisabled
    if _unit:getPlayerName() ~= nil then

        if csar.countCSARCrash == false then
            for _, _heliName in pairs(csar.csarUnits) do

                if _unit:getName() == _heliName then
                    -- IGNORE Crashed CSAR
                    return
                end
            end
        end

        local _lives = csar.pilotLives[_unit:getPlayerName()]

        if _lives == nil then
            _lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES
        end

        csar.pilotLives[_unit:getPlayerName()] = _lives - 1

        trigger.action.setUserFlag("CSAR_PILOT" .. _unit:getPlayerName():gsub('%W', ''), _lives - 1)
    end
    end
end

function csar.enableAircraft(_name, _playerName)


    -- enable aircraft for ALL pilots
    if csar.csarMode == 1 then

        local _details = csar.currentlyDisabled[_name]

        if _details ~= nil then
            csar.currentlyDisabled[_name] = nil -- {timeout =  (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = _crashed,unitId=_unit:getID() }

            --use flag to reenable
            trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _details.unitId, 0)
        end

    elseif csar.csarMode == 2 and _playerName ~= nil then -- enable aircraft for pilot

    local _details = csar.pilotDisabled[_playerName .. "_" .. _name]

    if _details ~= nil then
        csar.pilotDisabled[_playerName .. "_" .. _name] = nil

        trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _playerName:gsub('%W', '') .. "_" .. _details.unitId, 0)
    end

    elseif csar.csarMode == 3 and _playerName ~= nil then -- No Disable - Just reduce player lives

    -- give back life

    local _lives = csar.pilotLives[_playerName]

    if _lives == nil then
        _lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES
    else
        _lives = _lives + 1 -- give back live!

        if csar.maxLives + 1 <= _lives then
            _lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES
        end
    end

    csar.pilotLives[_playerName] = _lives

    trigger.action.setUserFlag("CSAR_PILOT" .. _playerName:gsub('%W', ''), _lives)
    end
end



function csar.reactivateAircraft()

    timer.scheduleFunction(csar.reactivateAircraft, nil, timer.getTime() + 5)

    -- disable aircraft for ALL pilots
    if csar.csarMode == 1 then

        for _unitName, _details in pairs(csar.currentlyDisabled) do

            if timer.getTime() >= _details.timeout then

                csar.enableAircraft(_unitName)
            end
        end

    elseif csar.csarMode == 2 then -- disable aircraft for pilot

    for _key, _details in pairs(csar.pilotDisabled) do

        if timer.getTime() >= _details.timeout then

            csar.enableAircraft(_details.name, _details.player)
        end
    end

    elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives
    end
end

function csar.checkDisabledAircraftStatus(_args)

    local _name = _args[1]
    local _playerName = _args[2]

    local _unit = Unit.getByName(_name)

    --if its not the same user anymore, stop checking
    if _unit ~= nil and _unit:getPlayerName() ~= nil and _playerName == _unit:getPlayerName() then
        -- disable aircraft for ALL pilots
        if csar.csarMode == 1 then

            local _details = csar.currentlyDisabled[_unit:getName()]

            if _details ~= nil then

                local _time = _details.timeout - timer.getTime()

                if _details.noPilot then

                    if csar.disableAircraftTimeout then

                        local _text = string.format("This aircraft cannot be flow as the pilot was killed in a crash. Reinforcements in %.2dM,%.2dS\n\nIt will be DESTROYED on takeoff!", (_time / 60), _time % 60)

                        --display message,
                        csar.displayMessageToSAR(_unit, _text, 10, true)
                    else
                        --display message,
                        csar.displayMessageToSAR(_unit, "This aircraft cannot be flown again as the pilot was killed in a crash\n\nIt will be DESTROYED on takeoff!", 10, true)
                    end
                else
                    if csar.disableAircraftTimeout then
                        --display message,
                        csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued or reinforcements arrive before this aircraft can be flown again! Reinforcements in " .. string.format("%.2dM,%.2d", (_time / 60), _time % 60) .. "\n\nIt will be DESTROYED on takeoff!", 10, true)
                    else
                        --display message,
                        csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued before this aircraft can be flown again!\n\nIt will be DESTROYED on takeoff!", 10, true)
                    end
                end

                if csar.destroyUnit(_unit) then
                    return --plane destroyed
                else
                    --check again in 10 seconds
                    timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10)
                end
            end



        elseif csar.csarMode == 2 then -- disable aircraft for pilot

        local _details = csar.pilotDisabled[_unit:getPlayerName() .. "_" .. _unit:getName()]

        if _details ~= nil then

            local _time = _details.timeout - timer.getTime()

            if _details.noPilot then

                if csar.disableAircraftTimeout then

                    local _text = string.format("This aircraft cannot be flow as the pilot was killed in a crash. Reinforcements in %.2dM,%.2dS\n\nIt will be DESTROYED on takeoff!", (_time / 60), _time % 60)

                    --display message,
                    csar.displayMessageToSAR(_unit, _text, 10, true)
                else
                    --display message,
                    csar.displayMessageToSAR(_unit, "This aircraft cannot be flown again as the pilot was killed in a crash\n\nIt will be DESTROYED on takeoff!", 10, true)
                end
            else
                if csar.disableAircraftTimeout then
                    --display message,
                    csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued or reinforcements arrive before this aircraft can be flown again! Reinforcements in " .. string.format("%.2dM,%.2d", (_time / 60), _time % 60) .. "\n\nIt will be DESTROYED on takeoff!", 10, true)
                else
                    --display message,
                    csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued before this aircraft can be flown again!\n\nIt will be DESTROYED on takeoff!", 10, true)
                end
            end

            if csar.destroyUnit(_unit) then
                return --plane destroyed
            else
                --check again in 10 seconds
                timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10)
            end
        end


        elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives

        local _lives = csar.pilotLives[_unit:getPlayerName()]

        if _lives == nil or _lives > 1 then

            if _lives == nil then
                _lives = csar.maxLives + 1
            end

            -- -1 for lives as we use 1 to indicate out of lives!
            local _text = string.format("CSAR ACTIVE! \n\nYou have " .. (_lives - 1) .. " lives remaining. Make sure you eject!")

            csar.displayMessageToSAR(_unit, _text, 20, true)

            return

        else

            local _text = string.format("You have run out of LIVES! Lives will be reset on mission restart or when your pilot is rescued.\n\nThis aircraft will be DESTROYED on takeoff!")

            --display message,
            csar.displayMessageToSAR(_unit, _text, 10, true)

            if csar.destroyUnit(_unit) then
                return --plane destroyed
            else
                --check again in 10 seconds
                timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10)
            end
        end
        end
    end
end

function csar.destroyUnit(_unit)

    --destroy if the SAME player is still in the aircraft
    -- if a new player got in it'll be destroyed in a bit anyways
    if _unit ~= nil and _unit:getPlayerName() ~= nil then

        if csar.heightDiff(_unit) > csar.destructionHeight then

            csar.displayMessageToSAR(_unit, "**** Aircraft Destroyed as the pilot needs to be rescued or you have no lives! ****", 10, true)
            --if we're off the ground then explode
            trigger.action.explosion(_unit:getPoint(), 100);

            return true
        end
        --_unit:destroy() destroy doesnt work for playes who arent the host in multiplayer
    end

    return false
end

function csar.heightDiff(_unit)

    local _point = _unit:getPoint()

    return _point.y - land.getHeight({ x = _point.x, y = _point.z })
end

function csar.stopBeaconIfNeeded(parameters)
    csar.logDebug(string.format("csar.stopBeaconIfNeeded(parameters=%s)", csar.p(parameters)))
    local _woundedGroupName, _freq = unpack(parameters)
    csar.logDebug(string.format("csar.stopBeaconIfNeeded(_woundedGroupName=%s, _freq=%s)", csar.p(_woundedGroupName), csar.p(_freq)))

    local _group = Group.getByName(_woundedGroupName)

    
    if _group == nil then
        
        --return frequency to pool of available
        for _i, _current in ipairs(csar.usedVHFFrequencies) do
            if _current == _freq then
                table.insert(csar.freeVHFFrequencies, _freq)
                table.remove(csar.usedVHFFrequencies, _i)
            end
        end
        
        --compute radio beacon names
        local _vhfBeaconName = _woundedGroupName .. "-CSAR-VHF"

        -- stop the transmission 
        trigger.action.stopRadioTransmission(_vhfBeaconName)

        return
    end

    -- schedule each minute to check if the beacon should stop transmitting
    timer.scheduleFunction(csar.stopBeaconIfNeeded, { _woundedGroupName, _freq }, timer.getTime() + 60)
end

csar.addBeaconToGroup = function(_woundedGroupName, _freq)
    csar.logDebug(string.format("csar.addBeaconToGroup(_woundedGroupName=%s, _freq=%s)", csar.p(_woundedGroupName), csar.p(_freq)))

    local _group = Group.getByName(_woundedGroupName)

    --compute radio beacon names
    local _vhfBeaconName = _woundedGroupName .. "-CSAR-VHF"

    if _group ~= nil then
        -- start the transmission; it is set to loop and has the name of the transmitting DCS group with the suffix "-CSAR-VHF"
        local _sound = "l10n/DEFAULT/" .. csar.radioSound
        trigger.action.radioTransmission(_sound, _group:getUnit(1):getPoint(), 0, true, _freq, 1000, _vhfBeaconName)

        -- call the function that will stop the beacon when the group is destroyed or rescued; it'll reschedule itself
        csar.stopBeaconIfNeeded({_woundedGroupName, _freq})
    end

end

csar.addSpecialParametersToGroup = function(_spawnedGroup)

    -- Immortal code for alexej21
    local _setImmortal = {
        id = 'SetImmortal',
        params = {
            value = true
        }
    }
    -- invisible to AI, Shagrat
    local _setInvisible = {
        id = 'SetInvisible',
        params = {
            value = true
        }
    }

    local _controller = _spawnedGroup:getController()

    if (csar.immortalcrew) then
        Controller.setCommand(_controller, _setImmortal)
    end

    if (csar.invisiblecrew) then
        Controller.setCommand(_controller, _setInvisible)
    end
end

function csar.spawnGroup( _coalition, _country, _point, _typeName )

    local _id = mist.getNextGroupId()

    local _groupName = "Downed Pilot #" .. _id

   local _side = _coalition

     local _pos = _point

    local _group = {
        ["visible"] = false,
        ["groupId"] = _id,
        ["hidden"] = false,
        ["units"] = {},
        ["name"] = _groupName,
        ["task"] = {},
    }

    if _side == 2 then
        _group.units[1] = csar.createUnit(_pos.x + 50, _pos.z + 50, 120, "Soldier M4")
    else
        _group.units[1] = csar.createUnit(_pos.x + 50, _pos.z + 50, 120, "Infantry AK")
    end

    _group.category = Group.Category.GROUND;
    _group.country = _country;

    local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name)

    -- Turn off AI
    if csar.allowDownedPilotCAcontrol == false then
      trigger.action.setGroupAIOff(_spawnedGroup)
    end
    return _spawnedGroup
end


function csar.createUnit(_x, _y, _heading, _type)

    local _id = mist.getNextUnitId();

    local _name = string.format("Wounded Pilot #%s", _id)

    local _newUnit = {
        ["y"] = _y,
        ["type"] = _type,
        ["name"] = _name,
        ["unitId"] = _id,
        ["heading"] = _heading,
        ["playerCanDrive"] = false,
        ["skill"] = "Excellent",
        ["x"] = _x,
    }

    return _newUnit
end

function csar.initSARForPilot(_downedGroup, _freq)

    local _leader = _downedGroup:getUnit(1)

    local _coordinatesText = csar.getPositionOfWounded(_downedGroup)

    local
    _text = string.format("%s requests SAR at %s, beacon at %.2f KHz",
        _leader:getName(), _coordinatesText, _freq / 1000)

    local _randPercent = math.random(1, 100)

    -- Loop through all the medevac units
    for x, _heliName in pairs(csar.csarUnits) do
        local _status, _err = pcall(function(_args)
            local _unitName = _args[1]
            local _woundedSide = _args[2]
            local _medevacText = _args[3]
            local _leaderPos = _args[4]
            local _groupName = _args[5]
            local _group = _args[6]

            local _heli = csar.getSARHeli(_unitName)

            -- queue up for all SAR, alive or dead, we dont know the side if they're dead or not spawned so check
            --coalition in scheduled smoke

            if _heli ~= nil then

                -- Check coalition side
                if (_woundedSide == _heli:getCoalition()) then
                    -- Display a delayed message
                    timer.scheduleFunction(csar.delayedHelpMessage, { _unitName, _medevacText, _groupName }, timer.getTime() + csar.requestdelay)

                    -- Schedule timer to check when to pop smoke
                    timer.scheduleFunction(csar.checkWoundedGroupStatus, { _unitName, _groupName }, timer.getTime() + 1)
                end
            else
                --env.warning(string.format("Medevac unit %s not active", _heliName), false)

                -- Schedule timer for Dead unit so when the unit respawns he can still pickup units
                --timer.scheduleFunction(medevac.checkStatus, {_unitName,_groupName}, timer.getTime() + 5)
            end
        end, { _heliName, _leader:getCoalition(), _text, _leader:getPoint(), _downedGroup:getName(), _downedGroup })

        if (not _status) then
            env.warning(string.format("Error while checking with medevac-units %s", _err))
        end
    end
end

function csar.checkWoundedGroupStatus(_argument)
    local _status, _err = pcall(function(_args)
        local _heliName = _args[1]
        local _woundedGroupName = _args[2]

        local _woundedGroup = csar.getWoundedGroup(_woundedGroupName)
        local _heliUnit = csar.getSARHeli(_heliName)

        -- if wounded group is not here then message alread been sent to SARs
        -- stop processing any further
        if csar.woundedGroups[_woundedGroupName] == nil then
            return
        end
        
        local _woundedLeader = _woundedGroup[1]
        local _lookupKeyHeli = _heliName .. "_" .. _woundedLeader:getID() --lookup key for message state tracking
                
        if _heliUnit == nil then
            -- stop wounded moving, head back to smoke as target heli is DEAD

            -- in transit cleanup
            --  csar.inTransitGroups[_heliName] = nil
  
            csar.heliVisibleMessage[_lookupKeyHeli] = nil
            csar.heliCloseMessage[_lookupKeyHeli] = nil
            csar.landedStatus[_lookupKeyHeli] = nil
            
            return
        end
        

        

        -- double check that this function hasnt been queued for the wrong side

        if csar.woundedGroups[_woundedGroupName].side ~= _heliUnit:getCoalition() then
            return --wrong side!
        end

        if csar.checkGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then

            local _woundedLeader = _woundedGroup[1]
            local _lookupKeyHeli = _heliUnit:getName() .. "_" .. _woundedLeader:getID() --lookup key for message state tracking

            local _distance = csar.getDistance(_heliUnit:getPoint(), _woundedLeader:getPoint())

            if _distance < 3000 then

                if csar.checkCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then
                    -- we're close, reschedule
                    timer.scheduleFunction(csar.checkWoundedGroupStatus, _args, timer.getTime() + 1)
                end

            else
                csar.heliVisibleMessage[_lookupKeyHeli] = nil

                --reschedule as units arent dead yet , schedule for a bit slower though as we're far away
                timer.scheduleFunction(csar.checkWoundedGroupStatus, _args, timer.getTime() + 5)
            end
        end
    end, _argument)

    if not _status then

        env.error(string.format("error checkWoundedGroupStatus %s", _err))
    end
end

function csar.popSmokeForGroup(_woundedGroupName, _woundedLeader)
    -- have we popped smoke already in the last 5 mins
    local _lastSmoke = csar.smokeMarkers[_woundedGroupName]
    if _lastSmoke == nil or timer.getTime() > _lastSmoke then

        local _smokecolor
        if (_woundedLeader:getCoalition() == 2) then
            _smokecolor = csar.bluesmokecolor
        else
            _smokecolor = csar.redsmokecolor
        end
        trigger.action.smoke(_woundedLeader:getPoint(), _smokecolor)

        csar.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time
    end
end

function csar.pickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)

    local _woundedLeader = _woundedGroup[1]

    -- GET IN!
    local _heliName = _heliUnit:getName()
    local _groups = csar.inTransitGroups[_heliName]
    local _unitsInHelicopter = csar.pilotsOnboard(_heliName)

    -- init table if there is none for this helicopter
    if not _groups then
        csar.inTransitGroups[_heliName] = {}
        _groups = csar.inTransitGroups[_heliName]
    end

    -- if the heli can't pick them up, show a message and return
    local _maxUnits = csar.aircraftType[_heliUnit:getTypeName()]
    if _maxUnits == nil then
      _maxUnits = csar.max_units
    end
    if _unitsInHelicopter + 1 > _maxUnits then
        csar.displayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!",
            _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), 10)
        return true
    end

    csar.inTransitGroups[_heliName][_woundedGroupName] =
    {
        originalUnit = csar.woundedGroups[_woundedGroupName].originalUnit,
        woundedGroup = _woundedGroupName,
        side = _heliUnit:getCoalition(),
        desc = csar.woundedGroups[_woundedGroupName].desc,
        player = csar.woundedGroups[_woundedGroupName].player,
    }

    Group.destroy(_woundedLeader:getGroup())

    csar.displayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10)

    timer.scheduleFunction(csar.scheduledSARFlight,
        {
            heliName = _heliUnit:getName(),
            groupName = _woundedGroupName
        },
        timer.getTime() + 1)

    return true
end
function csar.getAliveGroup(_groupName)

    local _group = Group.getByName(_groupName)

    if _group and _group:isExist() == true and #_group:getUnits() > 0 then
        return _group
    end

    return nil
end
function csar.orderGroupToMoveToPoint(_leader, _destination)

    local _group = _leader:getGroup()

    local _path = {}
    table.insert(_path, mist.ground.buildWP(_leader:getPoint(), 'Off Road', 50))
    table.insert(_path, mist.ground.buildWP(_destination, 'Off Road', 50))

    local _mission = {
        id = 'Mission',
        params = {
            route = {
                points =_path
            },
        },
    }

    -- delayed 2 second to work around bug
    timer.scheduleFunction(function(_arg)
        local _grp = csar.getAliveGroup(_arg[1])

        if _grp ~= nil then
            local _controller = _grp:getController();
            Controller.setOption(_controller, AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN)
            Controller.setOption(_controller, AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
            _controller:setTask(_arg[2])
        end
    end
        , {_group:getName(), _mission}, timer.getTime() + 2)

end

-- Helicopter is within 3km
function csar.checkCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName)

    local _woundedLeader = _woundedGroup[1]
    local _lookupKeyHeli = _heliUnit:getName() .. "_" .. _woundedLeader:getID() --lookup key for message state tracking

    local _pilotName = csar.woundedGroups[_woundedGroupName].desc

    local _woundedCount = 1

    local _reset = true
    
    if csar.autosmoke == true then
        csar.popSmokeForGroup(_woundedGroupName, _woundedLeader)
    end
    
    if csar.heliVisibleMessage[_lookupKeyHeli] == nil then
        if csar.autosmoke == true then
          csar.displayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), 30)
        else
          csar.displayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), 30)
        end
        --mark as shown for THIS heli and THIS group
        csar.heliVisibleMessage[_lookupKeyHeli] = true
    end

    if (_distance < 500) then

        if csar.heliCloseMessage[_lookupKeyHeli] == nil then
            if csar.autosmoke == true then
              csar.displayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), 10)
            else
              csar.displayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, i will go there ", _heliName, _pilotName), 10)
            end
            --mark as shown for THIS heli and THIS group
            csar.heliCloseMessage[_lookupKeyHeli] = true
        end

        -- have we landed close enough?
        if csar.inAir(_heliUnit) == false then

            -- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :)
          if csar.pilotRuntoExtractPoint == true then
              if (_distance < csar.extractDistance) then
                local _time = csar.landedStatus[_lookupKeyHeli]
                if _time == nil then
                    --csar.displayMessageToSAR(_heliUnit, "Landed at " .. _distance, 10, true)
                    csar.landedStatus[_lookupKeyHeli] = math.floor( (_distance * csar.loadtimemax ) / csar.extractDistance )   
                    _time = csar.landedStatus[_lookupKeyHeli] 
                    csar.orderGroupToMoveToPoint(_woundedLeader, _heliUnit:getPoint())
                    csar.displayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. ". Gets in \n" .. _time .. " more seconds.", 10, true)
                else
                    _time = csar.landedStatus[_lookupKeyHeli] - 1
                    csar.landedStatus[_lookupKeyHeli] = _time

                end
                if _time <= 0 then
                   csar.landedStatus[_lookupKeyHeli] = nil
                   return csar.pickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
                end
              end
          else
            if (_distance < csar.loadDistance) then
                return csar.pickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
            end
          end
        else

            local _unitsInHelicopter = csar.pilotsOnboard(_heliName)
            local _maxUnits = csar.aircraftType[_heliUnit:getTypeName()]
            if _maxUnits == nil then
              _maxUnits = csar.max_units
            end
            
            if csar.inAir(_heliUnit) and _unitsInHelicopter + 1 <= _maxUnits then

                if _distance < 8.0 then

                    --check height!
                    local _height = _heliUnit:getPoint().y - _woundedLeader:getPoint().y

                    if _height <= 20.0 then

                        local _time = csar.hoverStatus[_lookupKeyHeli]

                        if _time == nil then
                            csar.hoverStatus[_lookupKeyHeli] = 10
                            _time = 10
                        else
                            _time = csar.hoverStatus[_lookupKeyHeli] - 1
                            csar.hoverStatus[_lookupKeyHeli] = _time
                        end

                        if _time > 0 then
                            csar.displayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true)
                        else
                            csar.hoverStatus[_lookupKeyHeli] = nil
                            return csar.pickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
                        end
                        _reset = false
                    else
                        csar.displayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true)
                    end
                end
            
            end
        end
    end

    if _reset then
        csar.hoverStatus[_lookupKeyHeli] = nil
    end

    return true
end



function csar.checkGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName)

    -- check if unit has died or been picked up
    if #_woundedGroup == 0 and _heliUnit ~= nil then

        local inTransit = false

        for _currentHeli, _groups in pairs(csar.inTransitGroups) do

            if _groups[_woundedGroupName] then
                local _group = _groups[_woundedGroupName]
                if _group.side == _heliUnit:getCoalition() then
                    inTransit = true

                    csar.displayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), _heliUnit:getCoalition(), _heliName)

                    break
                end
            end
        end


        --display to all sar
        if inTransit == false then
            --DEAD

            csar.displayToAllSAR(string.format("%s is KIA ", _woundedGroupName), _heliUnit:getCoalition(), _heliName)
        end

        --     medevac.displayMessageToSAR(_heliUnit, string.format("%s: %s is dead", _heliName,_woundedGroupName ),10)

        --stops the message being displayed again
        csar.woundedGroups[_woundedGroupName] = nil

        return false
    end

    --continue
    return true
end


function csar.scheduledSARFlight(_args)

    local _status, _err = pcall(function(_args)

        local _heliUnit = csar.getSARHeli(_args.heliName)
        local _woundedGroupName = _args.groupName

        if (_heliUnit == nil) then

            --helicopter crashed?
            -- Put intransit pilots back
            --TODO possibly respawn the guys
            if csar.reenableIfCSARCrashes then
                local _rescuedGroups = csar.inTransitGroups[_args.heliName]

                if _rescuedGroups ~= nil then

                    -- enable pilots again
                    for _, _rescueGroup in pairs(_rescuedGroups) do

                        csar.enableAircraft(_rescueGroup.originalUnit, _rescuedGroups.player)
                    end
                end
            end

            csar.inTransitGroups[_args.heliName] = nil

            return
        end

        if csar.inTransitGroups[_heliUnit:getName()] == nil or csar.inTransitGroups[_heliUnit:getName()][_woundedGroupName] == nil then
            -- Groups already rescued
            return
        end


        local _dist = csar.getClosetMASH(_heliUnit)

        if _dist == -1 then
            -- Can now rescue to FARP
            -- Mash Dead
            --  csar.inTransitGroups[_heliUnit:getName()][_woundedGroupName] = nil

            --  csar.displayMessageToSAR(_heliUnit, string.format("%s: NO MASH! The pilot died of despair!", _heliUnit:getName()), 10)

            return
        end

        if _dist < 200 and _heliUnit:inAir() == false then

            csar.rescuePilots(_heliUnit)

            return
        end

        -- end
        --queue up
        timer.scheduleFunction(csar.scheduledSARFlight,
            {
                heliName = _heliUnit:getName(),
                groupName = _woundedGroupName
            },
            timer.getTime() + 1)
    end, _args)
    if (not _status) then
        env.error(string.format("Error in scheduledSARFlight\n\n%s", _err))
    end
end

function csar.rescuePilots(_heliUnit)
    local _rescuedGroups = csar.inTransitGroups[_heliUnit:getName()]

    if _rescuedGroups == nil then
        -- Groups already rescued
        return
    end

    csar.inTransitGroups[_heliUnit:getName()] = nil

    local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliUnit:getName())

    -- enable pilots again
    for _, _rescueGroup in pairs(_rescuedGroups) do

        csar.enableAircraft(_rescueGroup.originalUnit, _rescueGroup.player)
    end

    csar.displayMessageToSAR(_heliUnit, _txt, 10)

    -- csar.logInfo("Rescued")
end


function csar.getSARHeli(_unitName)

    local _heli = Unit.getByName(_unitName)

    if _heli ~= nil and _heli:isActive() and _heli:getLife() > 0 then

        return _heli
    end

    return nil
end


-- Displays a request for medivac
function csar.delayedHelpMessage(_args)
    local status, err = pcall(function(_args)
        local _heliName = _args[1]
        local _text = _args[2]
        local _injuredGroupName = _args[3]

        local _heli = csar.getSARHeli(_heliName)

        if _heli ~= nil and #csar.getWoundedGroup(_injuredGroupName) > 0 then
            csar.displayMessageToSAR(_heli, _text, csar.messageTime)


            local _groupId = csar.getGroupId(_heli)

            if _groupId then
                trigger.action.outSoundForGroup(_groupId, "l10n/DEFAULT/CSAR.ogg")
            end

        else
            csar.logInfo("No Active Heli or Group DEAD")
        end
    end, _args)

    if (not status) then
        env.error(string.format("Error in delayedHelpMessage "))
    end

    return nil
end

function csar.displayMessageToSAR(_unit, _text, _time, _clear)

    local _groupId = csar.getGroupId(_unit)

    if _groupId then
        if _clear == true then
            trigger.action.outTextForGroup(_groupId, _text, _time, _clear)
        else
            trigger.action.outTextForGroup(_groupId, _text, _time)
        end
    end
end

function csar.getWoundedGroup(_groupName)
    local _status, _result = pcall(function(_groupName)

        local _woundedGroup = {}
        local _units = Group.getByName(_groupName):getUnits()

        for _, _unit in pairs(_units) do

            if _unit ~= nil and _unit:isActive() and _unit:getLife() > 0 then
                table.insert(_woundedGroup, _unit)
            end
        end

        return _woundedGroup
    end, _groupName)

    if (_status) then
        return _result
    else
        --env.warning(string.format("getWoundedGroup failed! Returning 0.%s",_result), false)
        return {} --return empty table
    end
end


function csar.convertGroupToTable(_group)

    local _unitTable = {}

    for _, _unit in pairs(_group:getUnits()) do

        if _unit ~= nil and _unit:getLife() > 0 then
            table.insert(_unitTable, _unit:getName())
        end
    end

    return _unitTable
end

function csar.getPositionOfWounded(_woundedGroup)

    local _woundedTable = csar.convertGroupToTable(_woundedGroup)

    local _coordinatesText = ""
    if csar.coordtype == 0 then -- Lat/Long DMTM
    _coordinatesText = string.format("%s", mist.getLLString({ units = _woundedTable, acc = csar.coordaccuracy, DMS = 0 }))

    elseif csar.coordtype == 1 then -- Lat/Long DMS
    _coordinatesText = string.format("%s", mist.getLLString({ units = _woundedTable, acc = csar.coordaccuracy, DMS = 1 }))

    elseif csar.coordtype == 2 then -- MGRS
    _coordinatesText = string.format("%s", mist.getMGRSString({ units = _woundedTable, acc = csar.coordaccuracy }))

    elseif csar.coordtype == 3 then -- Bullseye Imperial
    _coordinatesText = string.format("bullseye %s", mist.getBRString({ units = _woundedTable, ref = coalition.getMainRefPoint(_woundedGroup:getCoalition()), alt = 0 }))

    else -- Bullseye Metric --(medevac.coordtype == 4)
    _coordinatesText = string.format("bullseye %s", mist.getBRString({ units = _woundedTable, ref = coalition.getMainRefPoint(_woundedGroup:getCoalition()), alt = 0, metric = 1 }))
    end

    return _coordinatesText
end

-- Displays all active MEDEVACS/SAR
function csar.displayActiveSAR(_unitName)
    local _msg = "Active MEDEVAC/SAR:"

    local _heli = csar.getSARHeli(_unitName)

    if _heli == nil then
        return
    end

    local _heliSide = _heli:getCoalition()

    local _csarList = {}

    for _groupName, _value in pairs(csar.woundedGroups) do

        local _woundedGroup = csar.getWoundedGroup(_groupName)

        if #_woundedGroup > 0 and (_woundedGroup[1]:getCoalition() == _heliSide) then

            local _coordinatesText = csar.getPositionOfWounded(_woundedGroup[1]:getGroup())

            local _distance = csar.getDistance(_heli:getPoint(), _woundedGroup[1]:getPoint())

            table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) })
        end
    end

    local function sortDistance(a, b)
        return a.dist < b.dist
    end

    table.sort(_csarList, sortDistance)

    for _, _line in pairs(_csarList) do
        _msg = _msg .. "\n" .. _line.msg
    end

    csar.displayMessageToSAR(_heli, _msg, 20)
end


function csar.getClosetDownedPilot(_heli)

    local _side = _heli:getCoalition()

    local _closetGroup = nil
    local _shortestDistance = -1
    local _distance = 0
    local _closetGroupInfo = nil

    for _woundedName, _groupInfo in pairs(csar.woundedGroups) do

        local _tempWounded = csar.getWoundedGroup(_woundedName)

        -- check group exists and not moving to someone else
        if #_tempWounded > 0 and (_tempWounded[1]:getCoalition() == _side) then

            _distance = csar.getDistance(_heli:getPoint(), _tempWounded[1]:getPoint())

            if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then


                _shortestDistance = _distance
                _closetGroup = _tempWounded[1]
                _closetGroupInfo = _groupInfo
            end
        end
    end

    return { pilot = _closetGroup, distance = _shortestDistance, groupInfo = _closetGroupInfo }
end

function csar.signalFlare(_unitName)

    local _heli = csar.getSARHeli(_unitName)

    if _heli == nil then
        return
    end

    local _closet = csar.getClosetDownedPilot(_heli)

    if _closet ~= nil and _closet.pilot ~= nil and _closet.distance < 8000.0 then

        local _clockDir = csar.getClockDirection(_heli, _closet.pilot)

        local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s ", _closet.groupInfo.desc, _closet.groupInfo.frequency / 1000, _closet.distance, _clockDir)
        csar.displayMessageToSAR(_heli, _msg, 20)

        trigger.action.signalFlare(_closet.pilot:getPoint(), 1, 0)
    else
        csar.displayMessageToSAR(_heli, "No Pilots within 8KM", 20)
    end
end

function csar.displayToAllSAR(_message, _side, _ignore)

    for _, _unitName in pairs(csar.csarUnits) do

        local _unit = csar.getSARHeli(_unitName)

        if _unit ~= nil and _unit:getCoalition() == _side then

            if _ignore == nil or _ignore ~= _unitName then
                csar.displayMessageToSAR(_unit, _message, 10)
            end
        else
            -- csar.logInfo(string.format("unit nil %s",_unitName))
        end
    end
end
function csar.reqsmoke( _unitName )

    local _heli = csar.getSARHeli(_unitName)
    if _heli == nil then
        return
    end

    local _closet = csar.getClosetDownedPilot(_heli)

    if _closet ~= nil and _closet.pilot ~= nil and _closet.distance < 8000.0 then

        local _clockDir = csar.getClockDirection(_heli, _closet.pilot)

        local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Blue smoke at your %s ", _closet.groupInfo.desc, _closet.groupInfo.frequency / 1000, _closet.distance, _clockDir)
        csar.displayMessageToSAR(_heli, _msg, 20)
        
       local _smokecolor
        if (_closet.pilot:getCoalition() == 2) then
            _smokecolor = csar.bluesmokecolor
        else
            _smokecolor = csar.redsmokecolor
        end

         trigger.action.smoke(_closet.pilot:getPoint(), _smokecolor)
  
    else
        csar.displayMessageToSAR(_heli, "No Pilots within 8KM", 20)
    end

end

function csar.getClosetMASH(_heli)

    local _mashes = csar.bluemash

    if (_heli:getCoalition() == 1) then
        _mashes = csar.redmash
    end

    local _shortestDistance = -1
    local _distance = 0

    for _, _mashName in pairs(_mashes) do

        local _mashUnit = Unit.getByName(_mashName)

        if _mashUnit ~= nil and _mashUnit:isActive() and _mashUnit:getLife() > 0 then

            _distance = csar.getDistance(_heli:getPoint(), _mashUnit:getPoint())

            if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then

                _shortestDistance = _distance
            end
        end
    end

    if _shortestDistance ~= -1 then
        return _shortestDistance
    else
        return -1
    end
end

function csar.checkOnboard(_unitName)
    local _unit = csar.getSARHeli(_unitName)

    if _unit == nil then
        return
    end

    --list onboard pilots

    local _inTransit = csar.inTransitGroups[_unitName]

    if _inTransit == nil or csar.tableLength(_inTransit) == 0 then
        csar.displayMessageToSAR(_unit, "No Rescued Pilots onboard", 30)
    else

        local _text = "Onboard - RTB to FARP/Airfield or MASH: "

        for _, _onboard in pairs(csar.inTransitGroups[_unitName]) do
            _text = _text .. "\n" .. _onboard.desc
        end

        csar.displayMessageToSAR(_unit, _text, 30)
    end
end

function csar.addweight( _heli )
  local cargoWeight = 0
  
  local _heliName =  _heli:getName()
  if ctld ~= nil and ctld.troopWeight ~= nil then
      -- TODO Count CTLD troops
          
  end
  ctld.troopWeight = 100
  if csar.inTransitGroups[_heliName] then
    local csarcount = 0
    for _, _group in pairs(csar.inTransitGroups[_heliName]) do
        csarcount = csarcount + 1
    end
    cargoWeight = cargoWeight + csar.weight * csarcount
  end
  
  trigger.action.setUnitInternalCargo(_heli:getName(),0 ) -- Set To  to recalculate 
  trigger.action.setUnitInternalCargo(_heli:getName(), cargoWeight)
  

end
-- Adds menuitem to all medevac units that are active
function csar.addMedevacMenuItem()
    -- Loop through all Medevac units

    timer.scheduleFunction(csar.addMedevacMenuItem, nil, timer.getTime() + 5)

    local _allHeliGroups = coalition.getGroups(coalition.side.BLUE, Group.Category.HELICOPTER)
    
    for key, val in pairs (coalition.getGroups(coalition.side.RED, Group.Category.HELICOPTER)) do
      table.insert(_allHeliGroups, val)    
    end
    
    for _key, _group in pairs (_allHeliGroups) do
      
      local _unit = _group:getUnit(1) -- Asume that there is only one unit in the flight for players
      if _unit ~= nil then 
        if _unit:isExist() == true then         
          local unitName = _unit:getName()
          if csar.enableAllslots == true then
          -- Enable all helicopters
            local _type = _unit:getTypeName()
            if csar.aircraftType[_type] ~= nil then
              if csar.csarUnits[_unit:getName()] == nil then
                csar.csarUnits[_unit:getName()] = _unit:getName()
                
                  for _woundedName, _groupInfo in pairs(csar.woundedGroups) do
                    if _groupInfo.side == _group:getCoalition() then
                    
                      -- Schedule timer to check when to pop smoke
                      timer.scheduleFunction(csar.checkWoundedGroupStatus, { _unit:getName() , _woundedName }, timer.getTime() + 5)
                    end
                  end
              end
            end
            
          elseif csar.useprefix == true then
          --use prefix 
            local upperCaseUnitname = string.upper(unitName)
            
            for key, prefix in pairs (csar.csarPrefix) do
              local upperCasePrefix = string.upper(prefix)
              
              if string.match(upperCaseUnitname, upperCasePrefix) then
                
                if csar.csarUnits[_unit:getName()] == nil then
                  csar.csarUnits[_unit:getName()] = _unit:getName() 
                    for _woundedName, _groupInfo in pairs(csar.woundedGroups) do
                      if _groupInfo.side == _group:getCoalition() then
                        -- Schedule timer to check when to pop smoke
                        timer.scheduleFunction(csar.checkWoundedGroupStatus, { _unit:getName() , _woundedName }, timer.getTime() + 5)
                      end
                    end
                end            
                break
              end
            end
                  
          end
        end
      end
    end

    for key, unitName in pairs(csar.csarFixedUnits) do
      if csar.csarUnits[unitName] == nil then
        csar.csarUnits[unitName] = unitName
        for _woundedName, _groupInfo in pairs(csar.woundedGroups) do
          if false --[[ _group is nil here, can't ever work (see my comment https://github.com/ciribob/DCS-CSAR/commit/907524d1e97930061d5f32832523bc4f7455192d#commitcomment-126043845) _groupInfo.side == _group:getCoalition() ]] then
            -- Schedule timer to check when to pop smoke
              timer.scheduleFunction(csar.checkWoundedGroupStatus, { unitName , _woundedName }, timer.getTime() + 5)
           end
        end
      end
    end

    for _, _unitName in pairs(csar.csarUnits) do

        local _unit = csar.getSARHeli(_unitName)

        if _unit ~= nil then

            local _groupId = csar.getGroupId(_unit)

            if _groupId then

                if csar.addedTo[tostring(_groupId)] == nil then

                    csar.addedTo[tostring(_groupId)] = true

                    local _rootPath = missionCommands.addSubMenuForGroup(_groupId, "CSAR")

                    missionCommands.addCommandForGroup(_groupId, "List Active CSAR", _rootPath, csar.displayActiveSAR,
                        _unitName)

                    missionCommands.addCommandForGroup(_groupId, "Check Onboard", _rootPath, csar.checkOnboard, _unitName)

                    missionCommands.addCommandForGroup(_groupId, "Request Signal Flare", _rootPath, csar.signalFlare, _unitName)
                    missionCommands.addCommandForGroup(_groupId, "Request Smoke", _rootPath, csar.reqsmoke, _unitName)
                    
                end
            end
        else
            -- csar.logInfo(string.format("unit nil %s",_unitName))
        end
    end

    return
end

--get distance in meters assuming a Flat world
function csar.getDistance(_point1, _point2)

    local xUnit = _point1.x
    local yUnit = _point1.z
    local xZone = _point2.x
    local yZone = _point2.z

    local xDiff = xUnit - xZone
    local yDiff = yUnit - yZone

    return math.sqrt(xDiff * xDiff + yDiff * yDiff)
end

-- 200 - 400 in 10KHz
-- 400 - 850 in 10 KHz
-- 850 - 1250 in 50 KHz
function csar.generateVHFrequencies()

    --ignore list
    --list of all frequencies in KHZ that could conflict with
    -- 191 - 1290 KHz, beacon range
    local _skipFrequencies = {
        745, --Astrahan
        381,
        384,
        300.50,
        312.5,
        1175,
        342,
        735,
        300.50,
        353.00,
        440,
        795,
        525,
        520,
        690,
        625,
        291.5,
        300.50,
        435,
        309.50,
        920,
        1065,
        274,
        312.50,
        580,
        602,
        297.50,
        750,
        485,
        950,
        214,
        1025, 730, 995, 455, 307, 670, 329, 395, 770,
        380, 705, 300.5, 507, 740, 1030, 515,
        330, 309.5,
        348, 462, 905, 352, 1210, 942, 435,
        324,
        320, 420, 311, 389, 396, 862, 680, 297.5,
        920, 662,
        866, 907, 309.5, 822, 515, 470, 342, 1182, 309.5, 720, 528,
        337, 312.5, 830, 740, 309.5, 641, 312, 722, 682, 1050,
        1116, 935, 1000, 430, 577
    }

    csar.freeVHFFrequencies = {}
    csar.usedVHFFrequencies = {}

    local _start = 200000

    -- first range
    while _start < 400000 do

        -- skip existing NDB frequencies
        local _found = false
        for _, value in pairs(_skipFrequencies) do
            if value * 1000 == _start then
                _found = true
                break
            end
        end


        if _found == false then
            table.insert(csar.freeVHFFrequencies, _start)
        end

        _start = _start + 10000
    end

    _start = 400000
    -- second range
    while _start < 850000 do

        -- skip existing NDB frequencies
        local _found = false
        for _, value in pairs(_skipFrequencies) do
            if value * 1000 == _start then
                _found = true
                break
            end
        end

        if _found == false then
            table.insert(csar.freeVHFFrequencies, _start)
        end

        _start = _start + 10000
    end

    _start = 850000
    -- third range
    while _start <= 1250000 do

        -- skip existing NDB frequencies
        local _found = false
        for _, value in pairs(_skipFrequencies) do
            if value * 1000 == _start then
                _found = true
                break
            end
        end

        if _found == false then
            table.insert(csar.freeVHFFrequencies, _start)
        end

        _start = _start + 50000
    end
end

function csar.generateADFFrequency()

    if #csar.freeVHFFrequencies <= 3 then
        csar.freeVHFFrequencies = csar.usedVHFFrequencies
        csar.usedVHFFrequencies = {}
    end

    local _vhf = table.remove(csar.freeVHFFrequencies, math.random(#csar.freeVHFFrequencies))

    return _vhf
    --- return {uhf=_uhf,vhf=_vhf}
end

function csar.inAir(_heli)

    if _heli:inAir() == false then
        return false
    end

    -- less than 5 cm/s a second so landed
    -- BUT AI can hold a perfect hover so ignore AI
    if mist.vec.mag(_heli:getVelocity()) < 0.05 and _heli:getPlayerName() ~= nil then
        return false
    end
    return true
end

function csar.getClockDirection(_heli, _crate)

    -- Source: Helicopter Script - Thanks!

    local _position = _crate:getPosition().p -- get position of crate
    local _playerPosition = _heli:getPosition().p -- get position of helicopter
    local _relativePosition = mist.vec.sub(_position, _playerPosition)

    local _playerHeading = mist.getHeading(_heli) -- the rest of the code determines the 'o'clock' bearing of the missile relative to the helicopter

    local _headingVector = { x = math.cos(_playerHeading), y = 0, z = math.sin(_playerHeading) }

    local _headingVectorPerpendicular = { x = math.cos(_playerHeading + math.pi / 2), y = 0, z = math.sin(_playerHeading + math.pi / 2) }

    local _forwardDistance = mist.vec.dp(_relativePosition, _headingVector)

    local _rightDistance = mist.vec.dp(_relativePosition, _headingVectorPerpendicular)

    local _angle = math.atan2(_rightDistance, _forwardDistance) * 180 / math.pi

    if _angle < 0 then
        _angle = 360 + _angle
    end
    _angle = math.floor(_angle * 12 / 360 + 0.5)
    if _angle == 0 then
        _angle = 12
    end

    return _angle
end

function csar.getGroupId(_unit)

    local _unitDB = mist.DBs.unitsById[tonumber(_unit:getID())]
    if _unitDB ~= nil and _unitDB.groupId then
        return _unitDB.groupId
    end

    return nil
end

function csar.initialize(force)
    csar.logInfo(string.format("Initializing version %s", csar.Version))
    csar.logTrace(string.format("csar.alreadyInitialized=%s", csar.p(csar.alreadyInitialized)))
    csar.logTrace(string.format("force=%s", csar.p(force)))

    if csar.alreadyInitialized and not force then
        csar.logInfo(string.format("Bypassing initialization because csar.alreadyInitialized = true"))
        return
    end

    csar.generateVHFrequencies()

    -- Schedule timer to add radio item
    timer.scheduleFunction(csar.addMedevacMenuItem, nil, timer.getTime() + 5)

    if csar.disableAircraftTimeout then
        -- Schedule timer to reactivate things
        timer.scheduleFunction(csar.reactivateAircraft, nil, timer.getTime() + 5)
    end

    world.addEventHandler(csar.eventHandler)

    csar.logInfo("CSAR event handler added")

    --save CSAR MODE
    trigger.action.setUserFlag("CSAR_MODE", csar.csarMode)

    -- disable aircraft
    if csar.enableSlotBlocking then

        trigger.action.setUserFlag("CSAR_SLOTBLOCK", 100)

        csar.logInfo("CSAR Slot block enabled")
    end
end

-- initialize the random number generator to make it almost random
math.random(); math.random(); math.random()

--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)

-- initialize CSAR in 2 seconds, so other scripts (namely the veaf.lua script, loaded after) have a chance to modify the configuration before initialization
csar.logInfo(string.format("Loading version %s in 2 seconds", csar.Version))
timer.scheduleFunction(csar.initialize, nil, timer.getTime() + 2)
