-- ==========================================================================
-- This file is part of Briefing Room for DCS World, a mission
-- generator for DCS World, by @akaAgar (https://github.com/akaAgar/briefing-room-for-dcs)

-- Briefing Room for DCS World is free software: you can redistribute it
-- and/or modify it under the terms of the GNU General Public License
-- as published by the Free Software Foundation, either version 3 of
-- the License, or (at your option) any later version.

-- Briefing Room for DCS World is distributed in the hope that it will
-- be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
-- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.

-- You should have received a copy of the GNU General Public License
-- along with Briefing Room for DCS World. If not, see https://www.gnu.org/licenses/
-- ==========================================================================

-- ===================================================================================
-- SUMMARY
-- ===================================================================================
-- 1 - Core functions
--   1.1 - Constants and initialization
--   1.2 - Lua extensions
--     1.2.1 - Converters
--     1.2.2 - Math extensions
--     1.2.3 - String extensions
--     1.2.4 - Table extensions
--   1.3 - DCS World extensions
--   1.4 - MIST
-- 2 - Tools
--   2.1 - Radio manager
--   2.2 - Aircraft activator
--   2.3 - Event handler
--   2.4 - Transport Handler
-- 3 - Mission
--   3.1 - Main BriefingRoom table and core functions
--   3.2 - Common F10 menu
--   3.3 - Objectives tables (generated by BriefingRoom)
--   3.4 - Objectives triggers (generated by BriefingRoom)
--   3.5 - Script singletons (generated by BriefingRoom)
--   3.6 - Objectives features (generated by BriefingRoom)
--   3.7 - Mission features (generated by BriefingRoom)
--   3.8 - Startup

-- ***********************************************************************************
-- * 1 - CORE FUNCTIONS                                                              *
-- ***********************************************************************************

-- ===================================================================================
-- 1.1 - CONSTANTS AND INITIALIZATION
-- ===================================================================================

DEGREES_TO_RADIANS = 0.0174533 -- multiply by this constant to convert degrees to radians
LASER_CODE = 1688 -- laser code to use for AI target designation
METERS_TO_NM = 0.000539957 -- number of nautical miles in a meter
NM_TO_METERS = 1852.0 -- number of meters in a nautical mile
SMOKE_DURATION = 300 -- smoke markers last for 5 minutes (300 seconds) in DCS World
TWO_PI = math.pi * 2 -- two times Pi

briefingRoom = {} -- Main BriefingRoom table
briefingRoom.playerPilotNames = { $SCRIPTCLIENTPILOTNAMES$ }

-- Debug logging function
briefingRoom.printDebugMessages = false -- Disable debug messages logging, can be enabled later through mission features
function briefingRoom.debugPrint(message, duration)
  if not briefingRoom.printDebugMessages then return end -- Do not print debug messages if not in debug mode

  message = message or ""
  message = "BRIEFINGROOM: "..tostring(message)
  duration = duration or 3

  trigger.action.outText(message, duration, false)
  env.info(message, false)
end

-- ===================================================================================
-- 1.2 - LUA EXTENSIONS: Provides additional core functions to Lua
-- ===================================================================================

-- ==================
-- 1.2.1 - CONVERTERS
-- ==================

-- Converts a value to a boolean
function toboolean(val)
  if val == nil or val == 0 or val == false then return false end
  if type(val) == "string" and string.lower(val) == "false" then return false end
  return true
end

-- Like the built-in "tonumber" functions, but returns 0 instead of nil in case of an error
function tonumber0(val)
  local numVal = tonumber(val)
  if numVal == nil then return 0 end
  return numVal
end

-- =======================
-- 1.2.2 - MATH EXTENSIONS
-- =======================

-- Makes sure the value is between min and max and returns the clamped value
function math.clamp(val, min, max)
  return math.min(math.max(val, min), max)
end

-- Returns a random floating-point number between min and max
function math.randomFloat(min, max)
  if min >= max then return a end
  return min + math.random() * (max - min)
end

-- Returns a random floating point number between t[1] and t[2]
function math.randomFloatTable(t)
  return math.randomFloat(t[1], t[2])
end

-- Returns a random value from numerically-indexed table t
function math.randomFromTable(t)
  return t[math.random(#t)]
end

-- Returns a random value from hash table t
function math.randomFromHashTable(t)
  local keyset = {}
  for k in pairs(t) do
      table.insert(keyset, k)
  end
  -- now you can reliably return a random key
  return t[keyset[math.random(#keyset)]]
end

-- Returns a random point in circle of center center and of radius radius
function math.randomPointInCircle(center, radius)
  local dist = math.random() * radius
  local angle = math.random() * TWO_PI

  local x = center.x + math.cos(angle) * dist
  local y = center.y + math.sin(angle) * dist

  return { ["x"] = x, ["y"] = y }
end

-- =========================
-- 1.2.3 - STRING EXTENSIONS
-- =========================

-- Returns true if string str ends with needle
function string.endsWith(str, needle)
  return needle == "" or str:sub(-#needle) == needle
end

-- Search a string for all keys in a table and replace them with the matching value
function string.replace(str, repTable)
  for k,v in pairs(repTable) do
    str = string.gsub(str, k, v)
  end
  return str
end

-- Split string str in an array of substring, using the provided separator
function string.split(str, separator)
  separator = separator or "%s"

  local t = { }
  for s in string.gmatch(str, "([^"..separator.."]+)") do
    table.insert(t, s)
  end

  return t
end

-- Returns true if string str starts with needle
function string.startsWith(str, needle)
  return str:sub(1, #needle) == needle
end

-- Returns the value matching the case-insensitive key in enumTable
function string.toEnum(str, enumTable, defaultVal)
  local cleanStr = string.trim(string.lower(str))

  for key,val in pairs(enumTable) do
    if key:lower() == cleanStr then return val end
  end

  return defaultVal
end

-- Returns string str withtout leading and closing spaces
function string.trim(str)
  return str:match "^%s*(.-)%s*$"
end

-- ========================
-- 1.2.4 - TABLE EXTENSIONS
-- ========================

-- Returns true if table t contains value val
function table.contains(t, val)
  for _,v in pairs(t) do
    if v == val then return true end
  end
  return false
end


-- Returns Key count
function table.count(t)
  local count = 0
  for _ in pairs(t) do count = count + 1 end
  return count
end

-- Returns true if table t contains key key
function table.containsKey(t, key)
  for k,_v in pairs(t) do
    if k == key then return true end
  end
  return false
end

function table.merge(t1,t2)
  for i=1,#t2 do
      t1[#t1+1] = t2[i]
  end
  return t1
end

-- Creates a new table which countains count elements from table valTable
function table.createFromRandomElements(valTable, count)
  local t = { }
  for i=1,count do table.insert(t, math.randomFromTable(valTable)) end
  return t
end

-- Creates a new table which countains count times the value val
function table.createFromSameElement(val, count)
  local t = { }
  for i=1,count do table.insert(t, val) end
  return t
end

-- Returns a deep copy of the table, doesn't work with recursive tables (code from http://lua-users.org/wiki/CopyTable)
function table.deepCopy(orig)
  if type(orig) ~= 'table' then return orig end

  local copy = {}
  for orig_key, orig_value in next, orig, nil do
    copy[table.deepCopy(orig_key)] = table.deepCopy(orig_value)
  end
  setmetatable(copy, table.deepCopy(getmetatable(orig)))

  return copy
end

-- Returns the key associated to a value in a table, or nil if not found
function table.getKeyFromValue(t, val)
  for k,v in pairs(t) do
    if v == val then return k end
  end
  return nil
end

-- Removes one instance of a value from a table
function table.removeValue(t, val)
  for k,v in pairs(t)do
    if v == val then
      table.remove(t, k)
      return
    end
  end
end

-- Shuffles a table
function table.shuffle(t)
  local len, random = #t, math.random
  for i = len, 2, -1 do
    local j = random( 1, i )
    t[i], t[j] = t[j], t[i]
  end
  return t
end

function table.removeNils(t)
  local ans = {}
  for _,v in pairs(t) do
    ans[ #ans+1 ] = v
  end
  return ans
end

-- THIS FUNCTION IS NOT SUITABLE for counting filtered output
function table.filter(t, filterIter)
  local out = {}

  for k, v in pairs(t) do
    if filterIter(v, k, t) then out[k] = v end
  end

  return out
end

function table.find(t, filterIter)
  for k, v in pairs(t) do
    if filterIter(v, k, t) then return v end
  end

  return nil
end

-- ===================================================================================
-- 1.3 - DCS WORLD EXTENSIONS: Provides additional functions to DCS World scripting
-- ===================================================================================

dcsExtensions = { } -- main dcsExtensions table

-- Returns an angle in degrees to the nearest cardinal direction, as a string
function dcsExtensions.degreesToCardinalDirection(angle)
  angle = math.clamp(angle % 360, 0, 359)
  local val = math.floor((angle / 22.5) + 0.5)
  local directions = { "north", "north-north-east", "north-east", "east-north-east", "east", "east-south-east", "south-east", "south-south-east", "south", "south-south-west", "south-west", "west-south-west", "west", "west-north-west", "north-west", "north-north-west" }
  return directions[(val % 16) + 1]
end

-- Returns a table with all units controlled by a player
function dcsExtensions.getAllPlayers()
  local players = { }
  
  for coaName, i in pairs(coalition.side) do
    for _,g in pairs(coalition.getGroups(i)) do
      for __,u in pairs(g:getUnits()) do
        if u:getPlayerName() ~= nil then
          table.insert(players, u)
        end
      end
    end
  end

  return players
end

-- Returns the distance between two vec2s
function dcsExtensions.getDistance(vec2a, vec2b)
  return math.sqrt(math.pow(vec2a.x - vec2b.x, 2) + math.pow(vec2a.y - vec2b.y, 2))
end

-- Is an unit alive?
function dcsExtensions.isUnitAlive(name)
  if name == nil then return false end
  local unit = Unit.getByName(name)
  if unit == nil then return false end
  if unit:isActive() == false then return false end
  if unit:getLife() < 1 then return false end

  return true
end

function dcsExtensions.getUnitOrStatic(name)
  local unit = Unit.getByName(name)
  if unit == nil then -- no unit found with the ID, try searching for a static
    unit = StaticObject.getByName(name)
  end
  return unit
end


-- Returns the first unit alive in group with ID groupID, or nil if group doesn't exist or is completely destroyed
function dcsExtensions.getAliveUnitInGroup(groupName)
  local g = Group.getByName(groupName)
  if g == nil then return nil end

  for __,u in ipairs(g:getUnits()) do
    if u:getLife() >= 1 and u:isActive() then
      return u
    end
  end

  return nil
end

-- Returns all units belonging to the given coalition
function dcsExtensions.getCoalitionUnits(coalID)
  local units = { }
  for _,g in pairs(coalition.getGroups(coalID)) do
    for __,u in pairs(g:getUnits()) do
      if u:isActive() then
        if u:getLife() >= 1 then
          table.insert(units, u)
        end
      end
    end
  end

  return units
end


function dcsExtensions.getCoalitionStaticObjects(coalID)
  local units = { }
  for _,u in pairs(coalition.getStaticObjects(coalID)) do
    if u:getLife() >= 1 then
      table.insert(units, u)
    end
  end

  return units
end


function dcsExtensions.getGroupNamesContaining(search)
  local groups = { }
  for coaName, i in pairs(coalition.side) do
    for _,g in pairs(coalition.getGroups(i)) do
        if string.match(g:getName(), search) then
          table.insert(groups, g:getName())
      end
    end
  end

  return groups
end

function dcsExtensions.getUnitNamesByGroupNameSuffix(suffix)
  local unitNames = {}
  for coaName, i in pairs(coalition.side) do
    for _,g in pairs(coalition.getGroups(i)) do
        if string.endsWith(g:getName(), suffix) then
          for _,u in pairs(g:getUnits()) do
            table.insert(unitNames, u:getName())
          end
      end
    end
    for _,u in pairs(coalition.getStaticObjects(i)) do
      if string.endsWith(u:getName(), suffix) then
          table.insert(unitNames, u:getName())
      end
    end
  end
  return unitNames
end

-- Converts a timecode (in seconds since midnight) in a hh:mm:ss string
function dcsExtensions.timeToHMS(timecode)
  local h = math.floor(timecode / 3600)
  timecode = timecode - h * 3600
  local m = math.floor(timecode / 60)
  timecode = timecode - m * 60
  local s = timecode

  return string.format("%.2i:%.2i:%.2i", h, m, s)
end

-- Converts a pair of x, y coordinates or a vec3 to a vec2
function dcsExtensions.toVec2(xOrVector, y)
  if y == nil then
    if xOrVector.z then return { ["x"] = xOrVector.x, ["y"] = xOrVector.z } end
    return { ["x"] = xOrVector.x, ["y"] = xOrVector.y } -- return xOrVector if it was already a vec2
  else
    return { ["x"] = xOrVector, ["y"] = y }
  end
end

-- Converts a triplet of x, y, z coordinates or a vec2 to a vec3
function dcsExtensions.toVec3(xOrVector, y, z)
  if y == nil or z == nil then
    if xOrVector.z then return { ["x"] = xOrVector.x, ["y"] = xOrVector.y, ["z"] = xOrVector.z } end  -- return xOrVector if it was already a vec3
    return { ["x"] = xOrVector.x, ["y"] = 0, ["z"] = xOrVector.y }
  else
    return { ["x"] = xOrVector, ["y"] = y, ["z"] = z }
  end
end

-- Converts a vec2 or ver3 into a human-readable string
function dcsExtensions.vectorToString(vec)
  if vec.z == nil then -- no Z coordinate, vec is a Vec2
    return tostring(vec.x)..","..tostring(vec.y)
  else
    return tostring(vec.x)..","..tostring(vec.y)..","..tostring(vec.z)
  end
end

-- Turns a vec2 to a string with LL/MGRS coordinates
-- Based on code by Bushmanni - https://forums.eagle.ru/showthread.php?t=99480
function dcsExtensions.vec2ToStringCoordinates(vec2)
  local pos = { x = vec2.x, y = land.getHeight({x = vec2.x, y = vec2.y}), z = vec2.y }
  local cooString = ""

  local LLposN, LLposE, alt = coord.LOtoLL(pos)
  local LLposfixN, LLposdegN = math.modf(LLposN)
  LLposdegN = LLposdegN * 60
  local LLposdegN2, LLposdegN3 = math.modf(LLposdegN)
  local LLposdegN3Decimal = LLposdegN3 * 1000
  LLposdegN3 = LLposdegN3 * 60

  local LLposfixE, LLposdegE = math.modf(LLposE)
  LLposdegE = LLposdegE * 60
  local LLposdegE2, LLposdegE3 = math.modf(LLposdegE)
  local LLposdegE3Decimal = LLposdegE3 * 1000
  LLposdegE3 = LLposdegE3 * 60

  local LLns = "N"
  if LLposfixN < 0 then LLns = "S" end
  local LLew = "E"
  if LLposfixE < 0 then LLew = "W" end

  local LLposNstring = LLns.." "..string.format("%.2i°%.2i'%.2i''", LLposfixN, LLposdegN2, LLposdegN3)
  local LLposEstring = LLew.." "..string.format("%.3i°%.2i'%.2i''", LLposfixE, LLposdegE2, LLposdegE3)
  cooString = "L/L: "..LLposNstring.." "..LLposEstring

  local LLposNstring = LLns.." "..string.format("%.2i°%.2i.%.3i", LLposfixN, LLposdegN2, LLposdegN3Decimal)
  local LLposEstring = LLew.." "..string.format("%.3i°%.2i.%.3i", LLposfixE, LLposdegE2, LLposdegE3Decimal)
  cooString = cooString.."\nL/L: "..LLposNstring.." "..LLposEstring

  local mgrs = coord.LLtoMGRS(LLposN, LLposE)
  local mgrsString = mgrs.MGRSDigraph.." "..mgrs.UTMZone.." "..tostring(mgrs.Easting).." "..tostring(mgrs.Northing)
  cooString = cooString.."\nMGRS: "..mgrsString
  cooString = cooString.."\n$LANG_ALTITUDE$: "..math.floor(alt * 3.281).."ft"

  return cooString
end

function dcsExtensions.lerp(value1, value2, linearInterpolation)
  return value1 * (1 - linearInterpolation) + value2 * linearInterpolation;
end
-- ===================================================================================
-- 1.4 - MIST: https://github.com/mrSkortch/MissionScriptingTools
-- ===================================================================================
$SCRIPTMIST$

-- ***********************************************************************************
-- * 2 - TOOLS                                                                       *
-- ***********************************************************************************
briefingRoom.playerCoalition = $LUAPLAYERCOALITION$
briefingRoom.enemyCoalition = $LUAENEMYCOALITION$
-- ===================================================================================
-- 2.1 - RADIO MANAGER : plays radio messages (text and audio)
-- ===================================================================================

briefingRoom.radioManager = { } -- Main radio manager table
briefingRoom.radioManager.ANSWER_DELAY = { 4, 6 } -- Min/max time to get a answer to a radio message, in seconds
briefingRoom.radioManager.enableAudioMessages = $ENABLEAUDIORADIOMESSAGES$ -- Should audio radio messages be played?

function briefingRoom.radioManager.getAnswerDelay()
  return math.randomFloat(briefingRoom.radioManager.ANSWER_DELAY[1], briefingRoom.radioManager.ANSWER_DELAY[2])
end

-- Estimates the time (in seconds) required for the player to read a message
function briefingRoom.radioManager.getReadingTime(message)
  message = message or ""
  messsage = tostring(message)

  return math.max(5.0, #message / 8.7) -- 10.7 letters per second, minimum length 3.0 seconds
end

function briefingRoom.radioManager.play(message, oggFile, delay, functionToRun, functionParameters)
  delay = delay or 0
  local argsTable = { ["message"] = message, ["oggFile"] = oggFile, ["functionToRun"] = functionToRun, ["functionParameters"] = functionParameters }

  if delay > 0 then -- a delay was provided, schedule the radio message
    timer.scheduleFunction(briefingRoom.radioManager.doRadioMessage, argsTable, timer.getTime() + delay)
  else -- no delay, play the message at once
    briefingRoom.radioManager.doRadioMessage(argsTable, nil)
  end
end

function briefingRoom.radioManager.doRadioMessage(args, time)
  if args.message ~= nil then -- a message was provided, print it
    args.message = tostring(args.message)
    local duration = briefingRoom.radioManager.getReadingTime(args.message)
    trigger.action.outTextForCoalition(briefingRoom.playerCoalition, args.message, duration, false)
  end

  if args.oggFile ~= nil and briefingRoom.radioManager.enableAudioMessages then -- a sound was provided and radio sounds are enabled, play it
    trigger.action.outSoundForCoalition(briefingRoom.playerCoalition, args.oggFile..".ogg")
  else -- else play the default sound
    trigger.action.outSoundForCoalition(briefingRoom.playerCoalition, "Radio0.ogg")
  end

  if args.functionToRun ~= nil then -- a function was provided, run it
    args.functionToRun(args.functionParameters)
  end

  return nil -- disable scheduling, if any
end

-- ===================================================================================
-- 2.2 - AIRCRAFT ACTIVATOR: activates aircraft flight groups gradually during the mission
-- ===================================================================================

briefingRoom.aircraftActivator = { }
briefingRoom.aircraftActivator.INTERVAL = { 10, 20 } -- min/max interval (in seconds) between two updates
briefingRoom.aircraftActivator.currentQueue = dcsExtensions.getGroupNamesContaining("%-IQ%-") -- current queue of aircraft group IDs to spawn every INTERVAL seconds
briefingRoom.aircraftActivator.reserveQueue = dcsExtensions.getGroupNamesContaining("%-RQ%-")
briefingRoom.aircraftActivator.timeQueue = dcsExtensions.getGroupNamesContaining("%-TQ%-") -- additional aircraft group IDs to be added to the queue later
briefingRoom.aircraftActivator.responsiveMode = false


function briefingRoom.aircraftActivator.getAircraftTime(str)
  local tempa, tempb = string.find(str, "%-TQ%-%d+")
  local mid = string.sub(str, tempa,tempb)
  local mida, midb = string.find(mid, "%d+")
  return tonumber(string.sub(mid, mida,midb))
end

function briefingRoom.aircraftActivator.getRandomInterval()
  return math.random(briefingRoom.aircraftActivator.INTERVAL[1], briefingRoom.aircraftActivator.INTERVAL[2])
end

function briefingRoom.aircraftActivator.pushFromReserveQueue()
  if table.count(briefingRoom.aircraftActivator.reserveQueue) == 0 then -- no extra queues available
    briefingRoom.debugPrint("Tried to push extra aircraft to the activation queue, but found none")
    return
  end

  -- add aircraft groups from the reserve queue to the current queue
  local numberOfGroupsToAdd = math.max(1, math.min(briefingRoom.aircraftActivator.reserveQueueInitialCount / (table.count(briefingRoom.mission.objectives) + 1), table.count(briefingRoom.aircraftActivator.reserveQueue)))

  for i=0,numberOfGroupsToAdd do
    briefingRoom.debugPrint("Pushed aircraft group #"..tostring(briefingRoom.aircraftActivator.reserveQueue[1]).." into the activation queue")
    table.insert(briefingRoom.aircraftActivator.currentQueue, briefingRoom.aircraftActivator.reserveQueue[1])
    table.remove(briefingRoom.aircraftActivator.reserveQueue, 1)
  end
end

-- Every INTERVAL seconds, check for aircraft groups to activate in the queue
function briefingRoom.aircraftActivator.update(args, time)
  local minsPassed = math.floor((timer.getAbsTime() - timer.getTime0())/60)
  for k, name in pairs(briefingRoom.aircraftActivator.timeQueue) do
    local actTime = briefingRoom.aircraftActivator.getAircraftTime(name)
    briefingRoom.debugPrint("Looking for aircraft groups to activate, "..name.." ActTime:"..tostring(actTime).." Time:"..tostring(minsPassed), 1)
    for k,objective in pairs(briefingRoom.mission.objectives) do
      if string.match(name, objective.name) then 
        if objective.startMinutes > -1 then
          actTime = actTime + objective.startMinutes
          briefingRoom.debugPrint("Adjusted ActTime for objective "..objective.name.." to "..tostring(actTime), 1)
        else 
          actTime = 999999999999999999999 -- objective is not active, do not spawn aircraft
          briefingRoom.debugPrint("Objective "..objective.name.." not active ignoring "..name, 1)
        end
      end
    end
    if actTime <= minsPassed then
      table.insert(briefingRoom.aircraftActivator.currentQueue, name)
      table.removeValue(briefingRoom.aircraftActivator.timeQueue, name)
      briefingRoom.debugPrint(name.." Pushed to current queue", 1)
    end
  end
  briefingRoom.debugPrint("Looking for aircraft groups to activate, found "..tostring(table.count(briefingRoom.aircraftActivator.currentQueue)).." Time:"..tostring(minsPassed), 1)
  if table.count(briefingRoom.aircraftActivator.currentQueue) == 0 then -- no aircraft in the queue at the moment
    return time + briefingRoom.aircraftActivator.getRandomInterval() -- schedule next update and return
  end
  
  local acGroup = Group.getByName(briefingRoom.aircraftActivator.currentQueue[1]) -- get the group
  if acGroup ~= nil then -- activate the group, if it exists
    acGroup:activate()
    local Start = {
      id = 'Start',
      params = {
      }
    }
    acGroup:getController():setCommand(Start)
    briefingRoom.debugPrint("Activating aircraft group "..acGroup:getName())
  else
    briefingRoom.debugPrint("Failed to activate aircraft group "..tostring(briefingRoom.aircraftActivator.currentQueue[1]))
  end
  table.remove(briefingRoom.aircraftActivator.currentQueue, 1) -- remove the ID from the queue
  if acGroup == nil or string.match(acGroup:getName(), "%-IQ%-") then
    return time + 1
  end
  return time + briefingRoom.aircraftActivator.getRandomInterval() -- schedule next update
end

function briefingRoom.aircraftActivator.possibleResponsiveSpawn()
  if briefingRoom.aircraftActivator.responsiveMode and briefingRoom.mission.hasStarted then
    local roll = math.random(1, 100)
    briefingRoom.debugPrint("Possible Responsive Spawn rolled: "..tostring(roll))
    if roll < 25 then -- aprox 25% chance of spawn
      briefingRoom.aircraftActivator.pushFromReserveQueue()
    end
  end
end

function briefingRoom.aircraftActivator.convertToStatic()
  local queue = dcsExtensions.getGroupNamesContaining("%-STATIC%-") -- aircraft to be converted to static version on mission load for performance
  for k, name in pairs(queue) do
    local acGroup = Group.getByName(name) -- get the group
    local mistGroupData = mist.getGroupData(name)
    for l, unit in pairs(acGroup:getUnits()) do
      local unitpos = unit:getPosition()
      local unitPoint = dcsExtensions.toVec2(unit:getPoint())
      local vars = 
      {
        type = unit:getTypeName(),
        country = unit:getCountry(),
        category = unit:getCategory(),
        x = unitPoint['x'],
        y = unitPoint['y'],
        heading =  mist.getHeading(unit),
        livery_id = mistGroupData.units[l].livery_id
      }
      unit:destroy()
      mist.dynAddStatic(vars)
    end
    acGroup:destroy()
  end
end

briefingRoom.aircraftActivator.convertToStatic()

briefingRoom.aircraftActivator.reserveQueueInitialCount = table.count(briefingRoom.aircraftActivator.reserveQueue)

-- ===================================================================================
-- 2.3 - EVENT HANDLER: common event handler used during the mission
-- ===================================================================================

briefingRoom.eventHandler = {}
briefingRoom.eventHandler.BDASetting = "$BDASETTING$"

function briefingRoom.handleGeneralPlayerKill(event)
  local playerName = event.initiator:getPlayerName()
  if playerName == nil or event.target.getCoalition == nil then return end
  if event.target:getCoalition() ~= briefingRoom.playerCoalition then -- unit is an enemy, radio some variation of a "enemy destroyed" message
    local soundName = "UnitDestroyed"
    local messages = { "$LANG_DESTROY1$", "$LANG_DESTROY2$", "$LANG_SHOOTDOWN1$", "$LANG_SHOOTDOWN2$" }
    local messageIndex = math.random(1, 2)
    local messageIndexOffset = 0



    local targetType = "Ground"
    if event.id == world.event.S_EVENT_CRASH then
      messageIndexOffset = 2
      if event.target:inAir() then
        targetType = "Air"
        messageIndexOffset = 2
      elseif unitWasAMissionTarget then
        return -- No "target splashed" message when destroying a target aircraft on the ground (mostly for OCA missions)
      end
    end
    if briefingRoom.eventHandler.BDASetting == "ALL" then
      briefingRoom.radioManager.play("$LANG_COMMAND$: "..playerName.." "..messages[messageIndex + messageIndexOffset], "RadioHQ"..soundName..targetType..tostring(messageIndex), math.random(1, 3))
    end
    briefingRoom.aircraftActivator.possibleResponsiveSpawn()
  else
    briefingRoom.radioManager.play("$LANG_COMMAND$: "..playerName.." $LANG_TEAMKILL$", "RadioHQTeamkill", math.random(1, 3))
  end 
end

function briefingRoom.handleGeneralPlayerKilled(event)
  local playerName = event.target:getPlayerName()
  if playerName == nil then return end
    briefingRoom.handleTroopsInAircraft(event)
end

function briefingRoom.handleTroopsInAircraft(event)
  local unitName = event.initiator:getName()
  -- TODO see if we can detect units in aircraft that embarked using DCS radio
  if table.containsKey(briefingRoom.transportManager.transportRoster, unitName) then
    local troopNames={}
    local n=0
    for k,v in pairs(briefingRoom.transportManager.transportRoster[unitName].troops) do
      n=n+1
      troopNames[n]=k
    end
    briefingRoom.debugPrint("unpacking troops "..table.count(troopNames))
    -- Assume all troops are main characters and survive the crash no issues
    briefingRoom.transportManager.removeTroopCargo(unitName, troopNames, event.initiator:getPoint())
  end
end

function briefingRoom.handleGeneralKill(event)
  if event.id == world.event.S_EVENT_KILL then 
    if event.initiator == nil or event.target == nil or (event.initiator.getPlayerName == nil and event.target.getPlayerName == nil) then return end -- Incomplete event or player not involved
    if event.initiator.getPlayerName ~= nil then
      briefingRoom.handleGeneralPlayerKill(event)
    end
    if event.target.getPlayerName ~= nil then
      briefingRoom.handleGeneralPlayerKilled(event)
    end
  end

  if event.id == world.event.S_EVENT_DEAD or event.id == world.event.S_EVENT_CRASH or event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT then
    if event.initiator == nil or event.initiator.getCategory == nil then  return end -- no initiator
    if event.initiator:getCategory() ~= Object.Category.UNIT and event.initiator:getCategory() ~= Object.Category.STATIC then return end -- initiator was not an unit or static
    if event.initiator:getCoalition() == briefingRoom.playerCoalition then
      local unitName = event.initiator:getName()
      briefingRoom.debugPrint("Friendly Crash "..unitName)
      briefingRoom.handleTroopsInAircraft(event)
    end
  end
end

function briefingRoom.eventHandler:onEvent(event)
  if event.id == world.event.S_EVENT_TAKEOFF and -- unit took off
    event.initiator:getPlayerName() ~= nil then -- unit is a pleyr
      briefingRoom.mission.coreFunctions.beginMission() -- first player to take off triggers the mission start
  end

  local eventHandled = false
  -- Pass the event to the completion trigger of all objectives that have one
  for k, func in pairs(briefingRoom.mission.objectiveTriggers) do
    if func ~= nil then
      local didHandle = func(event)
      if didHandle then
        eventHandled = true
      end
    end
  end 
  if eventHandled == false then
    briefingRoom.handleGeneralKill(event)
  end
end

-- ===================================================================================
-- 2.4 - TRANSPORT MANAGER: common event handler used during the mission
-- ===================================================================================
briefingRoom.transportManager = {}
briefingRoom.transportManager.transportRoster = {}
briefingRoom.transportManager.maxTroops = 10
briefingRoom.transportManager.maxTroopsByType = {
  SA342Mistral=2,
  SA342Minigun=2,
  SA342L=2,
  SA342M=2,
  ["UH-1H"]=8,
  ["Mi-8MT"]=24,
  ["UH-60L"]=11,
  ["Mi-24P"]=8,
  ["AH-64D_BLK_II"]=2,
  ["CH-47Fbl1"]=31,
  OH58D=2,
  ["Ka-50"]=1,
  ["Ka-50_3"]=1,
  ["OH-6A"]=4,
}

function briefingRoom.transportManager.initTransport(transportUnitName)
  briefingRoom.transportManager.transportRoster[transportUnitName] = {
    troops = {}
  }
end

function briefingRoom.transportManager.troopsMoveToGetIn(transportUnitName, unitNames)
  local _leader = Unit.getByName(unitNames[1])
  local _helo = Unit.getByName(transportUnitName)
  local _group = _leader:getGroup()
  local _destination = dcsExtensions.toVec2(_helo:getPoint())
  local _distance = dcsExtensions.getDistance(_destination, dcsExtensions.toVec2(_leader:getPoint()))
  local _time = math.floor((_distance * 135) / 500)
  -- BLOCK TAKEN FROM MIST CSAR

    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 = Group.getByName(_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)
    -- BLOCK TAKEN FROM MIST CSAR
    local groupUnitNames = {}
    for index, data in pairs(_group:getUnits()) do
      table.insert(groupUnitNames, data:getName())
    end
    timer.scheduleFunction(function(_arg)
      briefingRoom.transportManager.addTroopCargo(_arg[1], _arg[2])
    end, { transportUnitName, groupUnitNames }, timer.getTime() + _time)
end

function briefingRoom.transportManager.addTroopCargo(transportUnitName, unitNames)
  if not table.containsKey(briefingRoom.transportManager.transportRoster, transportUnitName) then
    briefingRoom.transportManager.initTransport(transportUnitName)
  end
  local addedCount = 0
  local transportUnit = Unit.getByName(transportUnitName)
  local maxUnitTroops = briefingRoom.transportManager.maxTroops
  local transportUnitType = transportUnit:getTypeName()
  briefingRoom.debugPrint("Transport Unit Type: "..transportUnitType)
  if table.containsKey(briefingRoom.transportManager.maxTroopsByType, transportUnitType) then
    maxUnitTroops = briefingRoom.transportManager.maxTroopsByType[transportUnitType]
  end
  briefingRoom.debugPrint("Max Troops: "..maxUnitTroops)
  for index, unitName in ipairs(unitNames) do
    local unitCount = table.count(briefingRoom.transportManager.transportRoster[transportUnitName].troops)
    briefingRoom.debugPrint("Troop Count Troops: "..unitCount)
    if unitCount == maxUnitTroops then
      briefingRoom.radioManager.play("$LANG_TROOP$: $LANG_TRANSPORTFULL$ ($LANG_TOTALTROOPS$: "..maxUnitTroops..")", "RadioTroopFull")
      return true
    end

    local isUnitAlreadyInHelo = false
    for k,v in pairs(briefingRoom.transportManager.transportRoster) do
      if table.containsKey(v.troops, unitName) then
        isUnitAlreadyInHelo = true
      end
    end

    local unit = Unit.getByName(unitName)
    if unit ~= nil and not isUnitAlreadyInHelo then
      briefingRoom.transportManager.transportRoster[transportUnitName].troops[unitName] = {
        ["type"] = unit:getTypeName(),
        ["name"] = unit:getName(),
        ["country"] = unit: getCountry()
      }
      unit:destroy()
      addedCount = addedCount + 1
    end
  end
  if addedCount > 0 then
    briefingRoom.radioManager.play("$LANG_TROOP$: $LANG_TRANSPORTALLIN$ ($LANG_TOTALTROOPS$: "..table.count(briefingRoom.transportManager.transportRoster[transportUnitName].troops)..")", "RadioTroopAllIn")
  end
end

function briefingRoom.transportManager.removeTroopCargo(transportUnitName, unitNames, unitPos)
  local transportUnit = Unit.getByName(transportUnitName)
  if not table.containsKey(briefingRoom.transportManager.transportRoster, transportUnitName) then
    briefingRoom.debugPrint("transport unload bailing no roster")
    return {}
  end
  local transportUnitPoint = unitPos or transportUnit:getPoint()
  if transportUnitPoint == nil then
    briefingRoom.debugPrint("transport unload bailing no pos")
    return {}
  end
  local removed = {}
  local spawnUnits = {}
  local country = nil
  for index, unitName in ipairs(unitNames) do
    if table.containsKey(briefingRoom.transportManager.transportRoster[transportUnitName].troops, unitName) then
      local unitDeets = briefingRoom.transportManager.transportRoster[transportUnitName].troops[unitName]
      country = unitDeets.country
      briefingRoom.transportManager.transportRoster[transportUnitName].troops[unitName] = nil
      table.insert(removed, unitName)
      table.insert(spawnUnits, {
        ["y"] = transportUnitPoint.z + math.random(-30, 30),
        ["type"] = unitDeets.type,
        ["name"] = unitDeets.name,
        ["heading"] = 0,
        ["playerCanDrive"] = true,
        ["skill"] = "Excellent",
        ["x"] = transportUnitPoint.x + math.random(-30, 30),
      })
    end
  end
  if table.count(spawnUnits) > 0 then
    mist.dynAdd({
      units = spawnUnits,
      country = country,
      category = Group.Category.GROUND
    })
  end

  if unitPos == nil then
    briefingRoom.radioManager.play("$LANG_TROOP$: $LANG_TRANSPORTEVERYONEOUT$ ($LANG_REMAININGTROOPS$: "..table.count(briefingRoom.transportManager.transportRoster[transportUnitName].troops)..")", "RadioTroopTakeoff")
  end
  return removed
end



-- ***********************************************************************************
-- * 3 - MISSION                                                                     *
-- ***********************************************************************************

-- ===================================================================================
-- 3.1 - MAIN BRIEFINGROOM TABLE AND CORE FUNCTIONS
-- ===================================================================================

briefingRoom.mission = {} -- Main BriefingRoom mission table
briefingRoom.mission.complete = false -- Is the mission complete?
briefingRoom.mission.coreFunctions = { }
briefingRoom.mission.hasStarted = false -- has at least one player taken off?
briefingRoom.mission.autoEnd = $ENDMISSIONAUTOMATICALLY$
briefingRoom.mission.commandEnd = $ENDMISSIONONCOMMAND$
briefingRoom.mission.MapMarkers = $SHOWMAPMARKERS$
briefingRoom.mission.objectiveDropDistanceMeters = $DROPOFFDISTANCEMETERS$

-- Marks objective with index index as complete, and completes the mission itself if all objectives are complete
function briefingRoom.mission.coreFunctions.completeObjective(index, failed)
  failed = failed or false
  local revealObjective = 0
  if briefingRoom.mission.complete then return end -- mission already complete
  if briefingRoom.mission.objectives[index].complete and briefingRoom.mission.objectives[index].failed == failed then return end -- objective already complete with same fail state

  local objName = briefingRoom.mission.objectives[index].name
  briefingRoom.debugPrint("Objective "..objName.." marked as "..(failed and "failed" or"complete"))
  briefingRoom.mission.objectives[index].complete = true
  briefingRoom.mission.objectives[index].failed = failed
  briefingRoom.aircraftActivator.pushFromReserveQueue() -- activate next batch of aircraft (so more CAP will pop up)
  for k,objective in pairs(briefingRoom.mission.objectives) do
    if objective ~= nil and objective.progressionHidden and briefingRoom.mission.coreFunctions.assesCondition(objective.progressionCondition) then
      local minsPassed = math.floor((timer.getAbsTime() - timer.getTime0())/60)
      objective.startMinutes = minsPassed
      local acGroup = Group.getByName(objective.groupName) -- get the group
      if acGroup ~= nil then -- activate the group, if it exists
        acGroup:activate()
        local Start = {
          id = 'Start',
          params = {
          }
        }
        acGroup:getController():setCommand(Start)
        briefingRoom.debugPrint("Activating objective group "..acGroup:getName())
        objective.progressionHidden = false
      else
        briefingRoom.debugPrint("Failed to activate objective group "..objective.name)
      end
      if objective ~= nil and objective.progressionHiddenBrief then
        objective.progressionHiddenBrief = false
        briefingRoom.f10MenuCommands.activateObjective(k)
        if objective.waypoint ~= nil and briefingRoom.mission.MapMarkers then
          local vec3pos = dcsExtensions.toVec3(objective.waypoint)
          trigger.action.textToAll(briefingRoom.playerCoalition, k * 100 , vec3pos,{0, 0, 0, .53} , {1, 1, 1, .53} , 15, true , objective.name)
        end
        revealObjective = revealObjective + 1
      end
    end
  end
  

  -- Remove objective menu from the F10 menu
  if briefingRoom.f10Menu.objectives[index] ~= nil then
    missionCommands.removeItemForCoalition(briefingRoom.playerCoalition, briefingRoom.f10Menu.objectives[index])
    briefingRoom.f10Menu.objectives[index] = nil
  end

  -- Add a little delay before playing the "mission/objective complete" sounds to make sure all "target destroyed", "target photographed", etc. sounds are done playing
  local completeCount = 0
  for k,v in pairs(briefingRoom.mission.objectives) do
    if v.complete then
      completeCount = completeCount + 1
    end
  end
  local missionOver = completeCount >= table.count(briefingRoom.mission.objectives)
  -- Debug missions called complete early
  if briefingRoom.printDebugMessages then
    briefingRoom.debugPrint("Objective Completion state: "..tostring(completeCount).."/"..tostring(table.count(briefingRoom.mission.objectives)).."=".. tostring(missionOver))
  end
  -- End Debug
  if missionOver then
    briefingRoom.debugPrint("Mission marked as complete")
    briefingRoom.mission.complete = true
    local hasFailed = false

    for k,v in pairs(briefingRoom.mission.objectives) do
      if v.failed then
        hasFailed = true
      end
    end

    briefingRoom.radioManager.play("$LANG_COMMAND$: "..(hasFailed and "$LANG_MISSIONCOMPLETEWITHFAILURES$" or "$LANG_MISSIONCOMPLETE$"), (hasFailed and  "RadioHQMissionFailed" or "RadioHQMissionComplete"), math.random(6, 8))
    trigger.action.setUserFlag("BR_MISSION_COMPLETE", true) -- Mark the mission complete internally, so campaigns can move to the next mission
    if briefingRoom.mission.autoEnd then
      trigger.action.setUserFlag("BR_END_MISSION", true) -- Set off end mission trigger
    end
    if briefingRoom.mission.commandEnd then
      missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "$LANG_ENDMISSION$", nil, briefingRoom.f10MenuCommands.endMission, nil)
    end
  elseif not briefingRoom.mission.hasStarted then
    briefingRoom.radioManager.play("$LANG_AUTOCOMPLETEOBJECTIVE$", "Radio0", math.random(6, 8))
  else
    briefingRoom.radioManager.play("$LANG_COMMAND$: "..(failed and "$LANG_FAILEDOBJECTIVE$" or "$LANG_COMPLETEOBJECTIVE$"), (failed and "RadioHQObjectiveFailed" or "RadioHQObjectiveComplete"), math.random(6, 8))
    if revealObjective > 0 then 
      local time =  math.random(12, 15)
      for j = 1, revealObjective, 1 do
        if j > 1 then
          time = time + 4
        end
        briefingRoom.radioManager.play("$LANG_COMMAND$: $LANG_NEWOBJECTIVE$: "..briefingRoom.mission.objectives[index + j].task, "RadioHQNewObjective", time)
      end
    end
  end
end

function briefingRoom.mission.coreFunctions.assesCondition(condition)
    if condition == nil then return true end -- better to have a objective activate than not
    briefingRoom.debugPrint("Assessing raw condition: "..condition)
    local parsedCondition = string.gsub(condition, "(%d+)", "briefingRoom.mission.objectives[%1].complete == true")
    briefingRoom.debugPrint("Assessing parsed condition: "..parsedCondition)
    local f,err=loadstring("return "..parsedCondition)
    if f then
        return f()
    else
        briefingRoom.debugPrint("Condition Parsing Error: "..err)
        return true -- better to have a objective activate than not
    end
end

-- Begins the mission (called when the first player takes off)
function briefingRoom.mission.coreFunctions.beginMission()
  if briefingRoom.mission.hasStarted then return end -- mission has already started, do nothing

  briefingRoom.debugPrint("Mission has started")

  -- enable the aircraft activator and start spawning aircraft
  briefingRoom.mission.hasStarted = true
  timer.scheduleFunction(briefingRoom.aircraftActivator.update, nil, timer.getTime() + briefingRoom.aircraftActivator.getRandomInterval())
end

-- ===================================================================================
-- 3.2 - COMMON F10 MENU
-- ===================================================================================

-- Mission F10 menu hierarchy
briefingRoom.f10Menu = { }
briefingRoom.f10Menu.objectives = { }

 -- Mission F10 menu functions
briefingRoom.f10MenuCommands = { }
briefingRoom.f10MenuCommands.missionFeatures = { }

 -- Mission status menu
function briefingRoom.f10MenuCommands.missionStatus()
  local msnStatus = ""
  local msnSound = ""

  if briefingRoom.mission.complete then
    msnStatus = "$LANG_COMMAND$: Mission complete, you may return to base.\n\n"
    msnSound = "RadioHQMissionStatusComplete"
  else
    msnStatus = "$LANG_COMMAND$: Mission is still in progress.\n\n"
    msnSound = "RadioHQMissionStatusInProgress"
  end

  for i,o in ipairs(briefingRoom.mission.objectives) do
    if o.progressionHiddenBrief == false then
      if o.complete then
        msnStatus = msnStatus..(o.failed and "[/]" or "[X]")
      else
        msnStatus = msnStatus.."[ ]"
      end

      local objectiveProgress = ""
      if o.unitsCount > 0 and o.hideTargetCount ~= true then
        local targetsDone = math.max(0, o.unitsCount - table.count(o.unitNames))
        objectiveProgress = " ("..tostring(targetsDone).."/"..tostring(o.unitsCount)..")"
      end

      msnStatus = msnStatus.." "..o.task..objectiveProgress.."\n"
    end
  end

  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_MISSIONSTATUSREQUEST$", "RadioPilotMissionStatus")
  briefingRoom.radioManager.play(msnStatus, msnSound, briefingRoom.radioManager.getAnswerDelay())
end

function briefingRoom.f10MenuCommands.getWaypointCoordinates(index)
  local cooMessage = dcsExtensions.vec2ToStringCoordinates(briefingRoom.mission.objectives[index].waypoint)
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_WAYPOINTREQUEST$", "RadioPilotWaypointCoordinates")
  briefingRoom.radioManager.play("$LANG_COMMAND$: $LANG_WAYPOINTRESPONSE$\n\n"..cooMessage, "RadioHQWaypointCoordinates", briefingRoom.radioManager.getAnswerDelay())
  
  local idx = briefingRoom.mission.objectives[index].waypointRadioCommandIndex
  missionCommands.removeItemForCoalition(briefingRoom.playerCoalition, briefingRoom.mission.objectives[index].f10Commands[idx].commandPath)
  briefingRoom.mission.objectives[index].f10Commands[idx].commandPath = missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "$LANG_WAYPOINTCOORDINATES$:\n"..cooMessage, briefingRoom.f10Menu.objectives[index], briefingRoom.f10MenuCommands.getWaypointCoordinates, index)
end

function briefingRoom.f10MenuCommands.endMission ()
  trigger.action.setUserFlag("BR_END_MISSION_NOW", true)
end

function briefingRoom.f10MenuCommands.activateObjective(i)
  briefingRoom.f10Menu.objectives[i] = missionCommands.addSubMenuForCoalition(briefingRoom.playerCoalition,  briefingRoom.mission.objectives[i].f10MenuText, briefingRoom.f10Menu.objectiveMenu)
  for j=1,table.count(briefingRoom.mission.objectives[i].f10Commands) do
    briefingRoom.mission.objectives[i].f10Commands[j].commandPath = missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, briefingRoom.mission.objectives[i].f10Commands[j].text, briefingRoom.f10Menu.objectives[i] ,briefingRoom.mission.objectives[i].f10Commands[j].func, briefingRoom.mission.objectives[i].f10Commands[j].args)
  end
  if briefingRoom.printDebugMessages then
    missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "(DEBUG) Destroy target unit", briefingRoom.f10Menu.objectives[i] ,briefingRoom.f10MenuCommands.debug.destroySpecificTargetUnit, i)
  end
end

-- Common mission menu (mission status and mission features)
briefingRoom.f10Menu.missionMenu = missionCommands.addSubMenuForCoalition(briefingRoom.playerCoalition, "$LANG_MISSION$", nil)
missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "$LANG_MISSIONSTATUS$", briefingRoom.f10Menu.missionMenu, briefingRoom.f10MenuCommands.missionStatus, nil)

briefingRoom.f10Menu.objectiveMenu = missionCommands.addSubMenuForCoalition(briefingRoom.playerCoalition, "$LANG_OBJECTIVES$", nil)

-- ===================================================================================
-- 3.3 - OBJECTIVES TABLES (generated by BriefingRoom)
-- ===================================================================================

briefingRoom.mission.objectives = { } -- Main objective table
briefingRoom.mission.objectivesTriggersCommon = { }
$SCRIPTOBJECTIVES$


-- ===================================================================================
-- 3.4 - OBJECTIVES TRIGGERS (generated by BriefingRoom)
-- ===================================================================================

briefingRoom.mission.objectiveTriggers = { } -- Objective triggers (checks objective completion)
briefingRoom.mission.objectiveTimers = { } -- Objective timers (called every second)

function briefingRoom.mission.objectiveTimerSchedule(args, time)
  for i=1,table.count(briefingRoom.mission.objectives) do
    if briefingRoom.mission.objectiveTimers[i] ~= nil then
      briefingRoom.mission.objectiveTimers[i]()
    end
  end

  return time + 1
end

function briefingRoom.mission.destroyCallout(objectiveIndex, killedUnit, eventID, playerName)
  if killedUnit == nil or killedUnit.getName == nil  then return false end
  local unitName = killedUnit:getName()
  if not table.contains(briefingRoom.mission.objectives[objectiveIndex].unitNames, unitName) then return false end

  -- Remove the unit from the list of targets
  table.removeValue(briefingRoom.mission.objectives[objectiveIndex].unitNames, unitName)

  -- Play "target destroyed" radio message
  local soundName = "TargetDestroyed"
  local messages = { "$LANG_TARGETDESTROY1$", "$LANG_TARGETDESTROY2$", "$LANG_TARGETSHOOTDOWN1$", "$LANG_TARGETSHOOTDOWN2$" }
  local targetType = "Ground"
  local messageIndex = math.random(1, 2)
  local messageIndexOffset = 0
  if eventID == world.event.S_EVENT_CRASH and killedUnit:inAir() then
    targetType = "Air"
    messageIndexOffset = 2
  end
  if briefingRoom.eventHandler.BDASetting == "ALL" or briefingRoom.eventHandler.BDASetting == "TARGETONLY" then
    briefingRoom.radioManager.play("$LANG_COMMAND$: "..(playerName or "").." "..messages[messageIndex + messageIndexOffset], "RadioHQ"..soundName..targetType..tostring(messageIndex), math.random(1, 3))
  end

  -- Mark the objective as complete if all targets have been destroyed
  if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) < 1 then -- all target units destroyed, objective complete
    briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
  else
    briefingRoom.aircraftActivator.possibleResponsiveSpawn()
  end
  return true
end

function briefingRoom.mission.isSoftKillEvent(eventID)
  return eventID == world.event.S_EVENT_DEAD or
 eventID == world.event.S_EVENT_CRASH or
 eventID == world.event.S_EVENT_AI_ABORT_MISSION
end

function briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex)
  return briefingRoom.mission.complete or briefingRoom.mission.objectives[objectiveIndex].complete
end

function briefingRoom.mission.objectivesTriggersCommon.filterTargets(objectiveIndex, attribute, inverse)
  inverse = inverse or false
  do
    local newTargetTable = { }
    
    for _,i in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
      local unit = Unit.getByName(i)
      if unit ~= nil then
        if inverse and not unit:hasAttribute(attribute) then
          table.insert(newTargetTable, i)
        end
        if not inverse and unit:hasAttribute(attribute) then
          table.insert(newTargetTable, i)
        end 
      end
    end
  
    if table.count(newTargetTable) > 0 then
      briefingRoom.mission.objectives[objectiveIndex].unitNames = newTargetTable
      briefingRoom.mission.objectives[objectiveIndex].unitsCount = table.count(newTargetTable)
    end
  end
end

------START OF TRIGGERS TODO MOVE TO INDEPENDENT FILES---------------


function briefingRoom.mission.objectivesTriggersCommon.registerDestroyTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectiveTriggers, function(event)

    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end

    -- Check if event is a "destruction" event
    local destructionEvent = false
    local killedUnit = event.initiator
    local playerName = nil
    if event.id == world.event.S_EVENT_KILL then
      destructionEvent = true
      killedUnit = event.target
      playerName = event.initiator.getPlayerName and event.initiator:getPlayerName() or nil
    elseif 
      briefingRoom.mission.isSoftKillEvent(event.id) or
      (event.id == world.event.S_EVENT_LAND and briefingRoom.mission.objectives[objectiveIndex].targetCategory == Unit.Category.HELICOPTER) then -- Check if parked AI Aircraft are damaged enough to be considered dead
      destructionEvent = true
    end
    -- "Landing" events are considered kills for helicopter targets

    if 
      not destructionEvent or
      killedUnit == nil or
      Object.getCategory(killedUnit) ~= Object.Category.UNIT and Object.getCategory(killedUnit) ~= Object.Category.STATIC
    then return false end

    return briefingRoom.mission.destroyCallout(objectiveIndex, killedUnit, event.id, playerName)
  end)
end

function briefingRoom.mission.objectivesTriggersCommon.registerDisableTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectiveTriggers, function(event)
    -- Mission complete, nothing to do
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end

    local killedUnit = event.initiator
    local playerName = nil
    if event.id == world.event.S_EVENT_KILL then
        if event.target == nil then return false end
        killedUnit = event.target
        playerName = event.initiator.getPlayerName and event.initiator:getPlayerName() or nil
    elseif  event.id == world.event.S_EVENT_HIT then -- unit was hit but not destroyed, check anyway because destroying a parked aircraft in DCS is HARD, and any aircraft with less than 90% hp left is not airworthy
        if event.target == nil then return false end
        if event.target:getCategory() ~= Object.Category.UNIT then return false end -- target was not a unit
        local life = event.target:getLife() / event.target:getLife0()
        if life > .9 then return false end -- not damaged enough
        killedUnit = event.target
    elseif briefingRoom.mission.isSoftKillEvent(event.id) then -- unit destroyed
        if event.initiator == nil then return false end
    else return false end
    return  briefingRoom.mission.destroyCallout(objectiveIndex, killedUnit, event.id, playerName)
  end)
end

function briefingRoom.mission.objectivesTriggersCommon.registerCargoNearTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectiveTimers,  function ()
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end

    for __,u in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
      local unit = dcsExtensions.getUnitOrStatic(u)
      if unit ~= nil then
        local vec2p = briefingRoom.mission.objectives[objectiveIndex].waypoint
        local vec2u = dcsExtensions.toVec2(unit:getPoint())
        local distance = dcsExtensions.getDistance(vec2p, vec2u);
        if distance < briefingRoom.mission.objectiveDropDistanceMeters and not unit:inAir() then
          briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_CARGODELIVERED$", "RadioPilotCargoDelivered")
          table.removeValue(briefingRoom.mission.objectives[objectiveIndex].unitNames, u)
          if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) < 1 then -- all target units destroyed, objective complete
            briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
          end
        end
      end
    end
  end)
end

function briefingRoom.mission.objectivesTriggersCommon.registerKeepAliveTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectiveTriggers, function(event)
    -- Mission complete, nothing to do
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end

  
    -- Check if event is a "destruction" event
    local destructionEvent = false
    if event.id == world.event.S_EVENT_DEAD or event.id == world.event.S_EVENT_CRASH then destructionEvent = true end
    -- "Landing" events are considered kills for helicopter targets
    if briefingRoom.mission.objectives[objectiveIndex].targetCategory == Unit.Category.HELICOPTER and event.id == world.event.S_EVENT_LAND then destructionEvent = true end
    if not destructionEvent then return false end
  
    -- Initiator was nil
    if event.initiator == nil then return false end
    if Object.getCategory(event.initiator) ~= Object.Category.UNIT and Object.getCategory(event.initiator) ~= Object.Category.STATIC then return false end
  
    local unitName = event.initiator:getName()
    -- Destroyed unit wasn't a target
    if not table.contains(briefingRoom.mission.objectives[objectiveIndex].unitNames, unitName) then return false end
  
    -- Remove the unit from the list of targets
    table.removeValue(briefingRoom.mission.objectives[objectiveIndex].unitNames, unitName)
  
    -- Play "target destroyed" radio message
    local messages = { "$LANG_COMMAND$: $LANG_TARGETLOST1$", "$LANG_COMMAND$: $LANG_TARGETLOST2$" }
    local messageIndex = math.random(1, 2)
    local messageIndexOffset = 0
  
    if briefingRoom.eventHandler.BDASetting == "ALL" or briefingRoom.eventHandler.BDASetting == "TARGETONLY" then
      briefingRoom.radioManager.play(messages[messageIndex + messageIndexOffset], "RadioHQTargetLost", math.random(1, 3))
    end
  
    -- Mark the objective as complete if all targets have been destroyed
    if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) < 1 then -- all target units destroyed, objective failed
      briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex, true)
    end
  
    return true
  end)
end

function briefingRoom.mission.objectivesTriggersCommon.registerLandNearTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectiveTriggers, function(event)
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end
    if event.id ~= world.event.S_EVENT_LAND then return false end -- Not a "land" event, nothing to do
  
    if event.initiator == nil then return false end -- Initiator was nil
    if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return false end -- Initiator was not an unit
    if event.initiator:getCoalition() ~= briefingRoom.playerCoalition then return false end -- Initiator was not a friendy unit

    local position = dcsExtensions.toVec2(event.initiator:getPoint()) -- get the landing unit position

    -- check if any target unit is close enough from the landing unit
    -- if so, clean the target unit ID table and mark the objective as completed
    for _,id in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
      local targetUnit = Unit.getByName(id)
      if targetUnit ~= nil then
        local targetPosition = dcsExtensions.toVec2(targetUnit:getPoint())
        if dcsExtensions.getDistance(position, targetPosition) < 650 then
          briefingRoom.mission.objectives[objectiveIndex].unitNames = { }
          briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
          return true
        end
      end
    end
end)

briefingRoom.mission.objectives[objectiveIndex].hideTargetCount = true
end

function briefingRoom.mission.objectivesTriggersCommon.registerFlyNearTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectiveTimers,  function ()
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end

  
    local players = dcsExtensions.getAllPlayers()
  
    for _,p in ipairs(players) do
      for __,u in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
        local unit = Unit.getByName(u)
        if unit ~= nil then
          local vec2p = dcsExtensions.toVec2(p:getPoint())
          local vec2u = dcsExtensions.toVec2(unit:getPoint())
          local distance = dcsExtensions.getDistance(vec2p, vec2u);
          
          if distance < 3704 and math.abs(vec2p.y - vec2u.y) < 609.6 and p:inAir() then -- less than 2nm away on the X/Z axis, less than 2000 feet of altitude difference
            local playername = p.getPlayerName and p:getPlayerName() or nil
            if math.random(1, 2) == 1 then
              briefingRoom.radioManager.play((playername or"$LANG_PILOT$")..": $LANG_FLYNEAR1$", "RadioPilotTargetReconned1")
            else
              briefingRoom.radioManager.play((playername or"$LANG_PILOT$")..": $LANG_FLYNEAR2$", "RadioPilotTargetReconned2")
            end
            briefingRoom.mission.objectives[objectiveIndex].unitNames = { }
            briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
            return nil
          end
        end
      end
    end
  end)
  
  briefingRoom.mission.objectives[objectiveIndex].hideTargetCount = true
end


function briefingRoom.mission.objectivesTriggersCommon.registerHoldTrigger(objectiveIndex, distanceInMeters, timeRequiredSeconds, superiorityRequired)
  superiorityRequired = superiorityRequired or false
  briefingRoom.mission.objectives[objectiveIndex].superiortyTimer = 0
  table.insert(briefingRoom.mission.objectiveTimers,  function ()
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end
    local players = dcsExtensions.getAllPlayers()
  
    for _,p in ipairs(players) do
      local vec2p = dcsExtensions.toVec2(p:getPoint())
      local vec2u = briefingRoom.mission.objectives[objectiveIndex].waypoint
      local distance = dcsExtensions.getDistance(vec2p, vec2u);
      if distance < distanceInMeters then -- less than 2nm
        if superiorityRequired then
          for __,eu in ipairs(dcsExtensions.getCoalitionUnits(briefingRoom.enemyCoalition)) do
            local evec2u = dcsExtensions.toVec2(eu:getPoint())
            local edistance = dcsExtensions.getDistance(vec2p, evec2u);
            if edistance < distanceInMeters then
              return false
            end
          end
        end
        briefingRoom.mission.objectives[objectiveIndex].superiortyTimer = briefingRoom.mission.objectives[objectiveIndex].superiortyTimer + 1
        briefingRoom.debugPrint("Player in zone "..tostring(objectiveIndex).." for "..tostring(briefingRoom.mission.objectives[objectiveIndex].superiortyTimer).." seconds")
        if briefingRoom.mission.objectives[objectiveIndex].superiortyTimer > timeRequiredSeconds then
          local playername = p.getPlayerName and p:getPlayerName() or nil
          briefingRoom.mission.objectives[objectiveIndex].unitNames = { }
          briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
          return nil
        end
        end
    end
  end)
  
  briefingRoom.mission.objectives[objectiveIndex].hideTargetCount = true
end

function briefingRoom.mission.objectivesTriggersCommon.escortNearTriggerlaunchMission (args)
  local objectiveIndex = args[1]
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_ESCORTSTARTREQUEST$", "RadioPilotBeginEscort")
  local unit = Unit.getByName(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  if unit == nil then
    unit = StaticObject.getByName(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  end
  if unit ~= nil then
    local group = unit:getGroup()
    if group ~= nil then
      group:activate()
      briefingRoom.radioManager.play("$LANG_ESCORT$ "..objective.name..": $LANG_ESCORTAFFIRM$", "RadioEscortMoving", briefingRoom.radioManager.getAnswerDelay(), nil, nil)
    end
  end
end

function briefingRoom.mission.objectivesTriggersCommon.registerEscortNearTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectiveTimers,  function ()
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end
    for __,u in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
      local unit = Unit.getByName(u)
      if unit == nil then
        unit = StaticObject.getByName(u)
      end
      if unit ~= nil then
        local vec2p = briefingRoom.mission.objectives[objectiveIndex].waypoint
        local vec2u = dcsExtensions.toVec2(unit:getPoint())
        local distance = dcsExtensions.getDistance(vec2p, vec2u);
        local trigDistanceMeters = briefingRoom.mission.objectiveDropDistanceMeters;
        if unit:inAir() then
          trigDistanceMeters = briefingRoom.mission.objectiveDropDistanceMeters * 10
        end
        if distance < trigDistanceMeters then
          table.removeValue(briefingRoom.mission.objectives[objectiveIndex].unitNames, u)
          if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) < 1 then -- all target units destroyed, objective complete
            briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_ESCORTCOMPLETE$", "RadioPilotEscortComplete")
            briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
          end
        end
      end
    end
  end)
  
  local unit = Unit.getByName(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  if unit == nil then
    unit = StaticObject.getByName(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  end
  if unit ~= nil and not unit:isActive() then
    table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_ESCORTMENU$", func = briefingRoom.mission.objectivesTriggersCommon.escortNearTriggerlaunchMission, args =  {objectiveIndex}})
  end
end

function briefingRoom.mission.objectivesTriggersCommon.flyNearAndReportComplete(args)
  local objectiveIndex = args[1]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_PILOTREPORTCOMPLETE$", "RadioPilotReportComplete", math.random(1, 3))
  briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
  missionCommands.removeItemForCoalition(briefingRoom.playerCoalition, briefingRoom.mission.objectives[objectiveIndex].completeCommand)
end

function briefingRoom.mission.objectivesTriggersCommon.registerFlyNearAndReportTrigger(objectiveIndex)
  briefingRoom.mission.objectives[objectiveIndex].completeCommand = nil


  table.insert(briefingRoom.mission.objectiveTimers,  function ()
    if briefingRoom.mission.objectives[objectiveIndex].flownOver then return false end -- Objective complete, nothing to do
  
    local players = dcsExtensions.getAllPlayers()
  
    for _,p in ipairs(players) do
      for __,u in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
        local unit = Unit.getByName(u)
        if unit ~= nil then
          local vec2p = dcsExtensions.toVec2(p:getPoint())
          local vec2u = dcsExtensions.toVec2(unit:getPoint())
          local distance = dcsExtensions.getDistance(vec2p, vec2u);
  
          if distance < 9260 and math.abs(vec2p.y - vec2u.y) < 2438 and briefingRoom.mission.objectives[objectiveIndex].completeCommand == nil then
            local playername = p.getPlayerName and p:getPlayerName() or nil
            if math.random(1, 2) == 1 then
                briefingRoom.radioManager.play((playername or"$LANG_PILOT$").." $LANG_FLYNEAR1$", "RadioPilotTargetReconned1")
            else
                briefingRoom.radioManager.play((playername or"$LANG_PILOT$").." $LANG_FLYNEAR2$", "RadioPilotTargetReconned2")
            end
            briefingRoom.mission.objectives[objectiveIndex].unitNames = { }
            briefingRoom.mission.objectives[objectiveIndex].completeCommand = missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "$LANG_REPORTCOMPLETE$", briefingRoom.f10Menu.objectives[objectiveIndex],  briefingRoom.mission.objectivesTriggersCommon.flyNearAndReportComplete, {objectiveIndex})
          end
        end
      end
    end
  end)
end


function briefingRoom.mission.objectivesTriggersCommon.transportTroopsForcePickup(args)
  local objectiveIndex = args[1]
  if briefingRoom.mission.objectives[objectiveIndex].complete then return end
  local players = dcsExtensions.getAllPlayers()
    for _,p in ipairs(players) do
      if not p:inAir() then
      briefingRoom.debugPrint("Player on ground")
      local position = dcsExtensions.toVec2(p:getPoint())
      local collect = {}
      -- Pickup
      for _,id in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
        local targetUnit = Unit.getByName(id)
        if targetUnit ~= nil then
          local targetPosition = dcsExtensions.toVec2(targetUnit:getPoint())
          briefingRoom.debugPrint("Player distance"..dcsExtensions.getDistance(position, targetPosition))
          if dcsExtensions.getDistance(position, targetPosition) < briefingRoom.mission.objectiveDropDistanceMeters then
            table.insert(collect, id)
          end
        end
      end


      if table.count(collect) > 0 then
        briefingRoom.debugPrint("Loading "..table.count(collect).." troops")
        briefingRoom.transportManager.troopsMoveToGetIn(p:getName(), collect)
      end
    end
  end
end

function briefingRoom.mission.objectivesTriggersCommon.transportTroopsForceDrop(args)
  local objectiveIndex = args[1]
  local objectiveIndex = objectiveIndex
  if briefingRoom.mission.objectives[objectiveIndex].complete then return end
  local players = dcsExtensions.getAllPlayers()
    for _,p in ipairs(players) do
      if not p:inAir() then
        briefingRoom.debugPrint("Player on ground")
        briefingRoom.transportManager.removeTroopCargo(p:getName(), briefingRoom.mission.objectives[objectiveIndex].unitNames)
    end
  end
end

function briefingRoom.mission.objectivesTriggersCommon.registerTransportTroopsTrigger(objectiveIndex)
  table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_FORCEPICKUP$", func = briefingRoom.mission.objectivesTriggersCommon.transportTroopsForcePickup, args = {objectiveIndex}})
  table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_FORCEDROP$", func =  briefingRoom.mission.objectivesTriggersCommon.transportTroopsForceDrop, args =  {objectiveIndex}})

  table.insert(briefingRoom.mission.objectiveTimers,  function ()
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end

    for __,u in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
      local unit = Unit.getByName(u)
      if unit == nil then
        unit = StaticObject.getByName(u)
      end
      if unit ~= nil then
        local vec2p = briefingRoom.mission.objectives[objectiveIndex].waypoint
        local vec2u = dcsExtensions.toVec2(unit:getPoint())
        local distance = dcsExtensions.getDistance(vec2p, vec2u);
        if distance < briefingRoom.mission.objectiveDropDistanceMeters then
          table.removeValue(briefingRoom.mission.objectives[objectiveIndex].unitNames, u)
          if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) < 1 then -- all target units destroyed, objective complete
            briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_TROOPSDELIVERED$", "RadioPilotTroopsDelivered")
            briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
          end
        end
      end
    end
  end)
  

  table.insert(briefingRoom.mission.objectiveTriggers, function(event)
    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end

    if event.id ~= world.event.S_EVENT_LAND then return false end -- Not a "land" event, nothing to do
  
    if event.initiator == nil then return false end -- Initiator was nil
    if Object.getCategory(event.initiator) ~= Object.Category.UNIT then return false end -- Initiator was not an unit
    if event.initiator:getCoalition() ~= briefingRoom.playerCoalition then return false end -- Initiator was not a friendy unit
  
    local position = dcsExtensions.toVec2(event.initiator:getPoint()) -- get the landing unit position
   
    -- Drop off
    local distanceToObjective = dcsExtensions.getDistance(briefingRoom.mission.objectives[objectiveIndex].waypoint, position);
    if distanceToObjective < briefingRoom.mission.objectiveDropDistanceMeters then
      local removed = briefingRoom.transportManager.removeTroopCargo(event.initiator:getName(), briefingRoom.mission.objectives[objectiveIndex].unitNames)
      for index, value in ipairs(removed) do
        table.removeValue(briefingRoom.mission.objectives[objectiveIndex].unitNames, value)
      end
      if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) < 1 then -- all target units moved or dead, objective complete
        local playername = event.initiator.getPlayerName and event.initiator:getPlayerName() or nil
        briefingRoom.radioManager.play((playername or"$LANG_PILOT$")..": $LANG_TROOPSDELIVERED$", "RadioPilotTroopsDelivered")
        briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
      end
      return true
    end
  
    local collect = {}
    -- Pickup
    for _,id in ipairs(briefingRoom.mission.objectives[objectiveIndex].unitNames) do
      local targetUnit = Unit.getByName(id)
      if targetUnit ~= nil then
        local targetPosition = dcsExtensions.toVec2(targetUnit:getPoint())
        if dcsExtensions.getDistance(position, targetPosition) < briefingRoom.mission.objectiveDropDistanceMeters then
          table.insert(collect, id)
        end
      end
    end
  
    local nonNativeTransportingAircraft = { "UH-60L" }
    if table.count(collect) > 0 and table.contains(nonNativeTransportingAircraft, event.initiator:getTypeName()) then
      briefingRoom.transportManager.troopsMoveToGetIn(event.initiator:getName(), collect)
    end
  end)
end

function briefingRoom.mission.objectivesTriggersCommon.registerCaptureLocation(objectiveIndex, objectiveLocationId)
  table.insert(briefingRoom.mission.objectiveTriggers, function(event)

    if briefingRoom.mission.objectivesTriggersCommon.isMissionOrObjectiveComplete(objectiveIndex) then return false end
    if event.id == world.event.S_EVENT_BASE_CAPTURED then

      if event.place:getID() == objectiveLocationId and event.place:getCoalition() == briefingRoom.playerCoalition then
        briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_PILOTREPORTCOMPLETE$", "RadioPilotReportComplete", math.random(1, 3))
        briefingRoom.mission.coreFunctions.completeObjective(objectiveIndex)
        return true
      end
    else return false end
  end)
end

------END OF TRIGGERS TODO MOVE TO INDEPENDENT FILES---------------

for objIndex,obj in ipairs(briefingRoom.mission.objectives) do
  if obj.unitsCount > 0 then
    obj.unitNames = table.filter(obj.unitNames, function(o, k, i)
      local u = Unit.getByName(o)
      if u == nil then
        u = StaticObject.getByName(o)
      end
      if u == nil then
        return false
      end
      return u:isExist()
    end)
  end
  if(next(obj.unitNames) == nil) then
    briefingRoom.mission.coreFunctions.completeObjective(objIndex)
  end
end

timer.scheduleFunction(briefingRoom.mission.objectiveTimerSchedule, nil, timer.getTime() + 1)
$SCRIPTOBJECTIVESTRIGGERS$

-- ===================================================================================
-- 3.5 - SCRIPT SINGLETONS (generated by BriefingRoom)
-- ===================================================================================
-- Files in here are marked with "-- BR SINGLETON FLAG" as first line
-- They should only appear once in generated scripts
$SCRIPTSINGLETONS$

-- ===================================================================================
-- 3.6 - OBJECTIVES FEATURES (generated by BriefingRoom)
-- ===================================================================================

briefingRoom.mission.objectiveFeatures = { } -- Objective features
briefingRoom.mission.objectiveFeaturesCommon = { } -- Common objective features functions
--- START OF OBJECTIVE REGISTER FEATURES-----
function briefingRoom.mission.objectiveFeaturesCommon.resgiterEmitRadioTransmisison(objectiveIndex)
    local unit = dcsExtensions.getUnitOrStatic(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
    trigger.action.radioTransmission('l10n/DEFAULT/FXRadioSignal.ogg', unit:getPoint(), 0, true, 124000000, 100, "-- Morse Code --")
end

function briefingRoom.mission.objectiveFeaturesCommon.resgiterFireNearby(objectiveIndex)
  local unit = dcsExtensions.getUnitOrStatic(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  if unit == nil then -- no unit nor static found with the ID
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end
  local unitVec3 = unit:getPoint()
  local unitVec2 = { x = unitVec3.x, y = unitVec3.z }
  unitVec2.x = unitVec2.x + math.random(-3000, 3000)
  unitVec2.y = unitVec2.y + math.random(-3000, 3000)
  local spawnPoint = { x = unitVec2.x, y = land.getHeight(unitVec2), z = unitVec2.y }
  trigger.action.effectSmokeBig(spawnPoint, math.random(2, 3), math.random(7, 10) * 0.1)
end

function briefingRoom.mission.objectivesTriggersCommon.startAttack(args)
  local objectiveIndex = args[1]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_LAUNCHATTACKREQUEST$", "RadioPilotBeginYourAttack")
  local groupNames = dcsExtensions.getGroupNamesContaining("%-STGT%-$OBJECTIVENAME$")
  briefingRoom.debugPrint("Activating Attack group objectiveIndex: "..table.count(groupNames), 1)
  for _, value in pairs(groupNames) do
      local acGroup = Group.getByName(value) -- get the group
      if acGroup ~= nil then -- activate the group, if it exists
          acGroup:activate()
          local Start = {
              id = 'Start',
              params = {}
          }
          acGroup:getController():setCommand(Start)
          briefingRoom.debugPrint("Activating Attack group objectiveIndex: "..value, 1)
      end
  end
  briefingRoom.radioManager.play("$LANG_TROOP$: $LANG_BEGINATTACK$", "RadioOtherPilotBeginAttack")
  local idx = briefingRoom.mission.objectiveFeatures[objectiveIndex].startAttackCommandIndex
  missionCommands.removeItemForCoalition(briefingRoom.playerCoalition, briefingRoom.mission.objectives[objectiveIndex].f10Commands[idx].commandPath)
end

function briefingRoom.mission.objectiveFeaturesCommon.registerStartAttack(objectiveIndex)
  

table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_LAUNCHATTACK$", func = briefingRoom.mission.objectivesTriggersCommon.startAttack, args = {objectiveIndex}})
briefingRoom.mission.objectiveFeatures[objectiveIndex].startAttackCommandIndex = table.count(briefingRoom.mission.objectives[objectiveIndex].f10Commands)
end

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationCoordinates(args)
  local objectiveIndex = args[1]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_TARGETCOORDSREQUEST$", "RadioPilotTargetCoordinates")
  local objective = briefingRoom.mission.objectives[objectiveIndex]
    
  if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) == 0 then -- no target units left
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end
    
  local unit = dcsExtensions.getUnitOrStatic(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  if unit == nil then -- no unit nor static found with the ID
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end
    
  local unitVec3 = unit:getPoint()
  local unitVec2 = { x = unitVec3.x, y = unitVec3.z }
  local cooMessage = dcsExtensions.vec2ToStringCoordinates(unitVec2)
  briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_TARGETCOORDSAFFIRM$\n"..cooMessage, "RadioSupportTargetCoordinates", briefingRoom.radioManager.getAnswerDelay())

  local idx = briefingRoom.mission.objectiveFeatures[objectiveIndex].objRadioCommandIndex

  missionCommands.removeItemForCoalition(briefingRoom.playerCoalition, briefingRoom.mission.objectives[objectiveIndex].f10Commands[idx].commandPath)
  briefingRoom.mission.objectives[objectiveIndex].f10Commands[idx].commandPath = missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "$LANG_TARGETCOORDSMENU$.\n$LANG_TARGETCOORDSMENULAST$:\n"..cooMessage, briefingRoom.f10Menu.objectives[objectiveIndex], briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationCoordinates)
end



function briefingRoom.mission.objectiveFeaturesCommon.registerTargetDesignationCoordinates(objectiveIndex)
  table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_TARGETCOORDSMENU$", func = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationCoordinates, args =  {objectiveIndex}})
  briefingRoom.mission.objectiveFeatures[objectiveIndex].objRadioCommandIndex = table.count(briefingRoom.mission.objectives[objectiveIndex].f10Commands)
end

-- Flare

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationFlareDoFlare(args)
  trigger.action.signalFlare(args.position, trigger.flareColor.Yellow, 0)
end

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationFlare(args)
  local objectiveIndex = args[1]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_FLAIRREQUEST$", "RadioPilotMarkSelfWithFlare")
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  local objectiveFeature = briefingRoom.mission.objectiveFeatures[objectiveIndex]

  if objectiveFeature.targetDesignationFlareFlaresLeft <= 0 then
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_FLAIRNOFLAIRS$", "RadioSupportShootingFlareOut", briefingRoom.radioManager.getAnswerDelay())
    return
  end

  if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) == 0 then -- no target units left
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$:$LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end

  local unit = dcsExtensions.getUnitOrStatic(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  if unit == nil then -- no unit nor static found with the ID
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$:$LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end
  
  objectiveFeature.targetDesignationFlareFlaresLeft = objectiveFeature.targetDesignationFlareFlaresLeft - 1

  local args = { ["position"] = unit:getPoint() }

  if briefingRoom.mission.objectiveFeatures[objectiveIndex].flareInnacurate then 
    local heading = math.random(0, 359)
    local distance = math.floor(math.random(5, 20)) * 100
    args.position.x = args.position.x - math.cos(heading * DEGREES_TO_RADIANS) * distance;
    args.position.z = args.position.z - math.sin(heading * DEGREES_TO_RADIANS) * distance;
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_FLAIRAFFIRM$ "..dcsExtensions.degreesToCardinalDirection(heading).." of the Flare.", "RadioSupportShootingFlare", briefingRoom.radioManager.getAnswerDelay(), briefingRoom.mission.objectiveFeaturesCommon.targetDesignationFlareDoFlare, args)
  else
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_FLAIRAFFIRM$", "RadioSupportShootingFlare", briefingRoom.radioManager.getAnswerDelay(), briefingRoom.mission.objectiveFeaturesCommon.targetDesignationFlareDoFlare, args)
  end
end

function briefingRoom.mission.objectiveFeaturesCommon.registerTargetDesignationFlare(objectiveIndex, innacurate)
  briefingRoom.mission.objectiveFeatures[objectiveIndex].flareInnacurate = innacurate or false 
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationFlareFlaresLeft = 5
  table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_FLAIRMENU$", func = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationFlare, args =  {objectiveIndex}})
end

-- IlluminationBomb

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationIlluminationBombDoBomb(args)
  args.position.y = args.position.y + 1250 + math.random(0, 500)
  trigger.action.illuminationBomb(args.position, 100000)
end


function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationIlluminationBomb(args)
  local objectiveIndex = args[1]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_ILLUMINATIONREQUEST$", "RadioPilotDropIlluminationBomb")
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  local objectiveFeature = briefingRoom.mission.objectiveFeatures[objectiveIndex]
  -- out of bombs
  if objectiveFeature.targetDesignationIlluminationBombBombsLeft <= 0 then
    briefingRoom.radioManager.play(objective.name.." $LANG_RECON$: $LANG_ILLUMINATIONREJECT$", "RadioSupportIlluminationBombOut", briefingRoom.radioManager.getAnswerDelay())
    return
  end
  
  if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) == 0 then -- no target units left
    briefingRoom.radioManager.play(objective.name.." $LANG_RECON$:$LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end

  local unit = dcsExtensions.getUnitOrStatic(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  if unit == nil then -- no unit nor static found with the ID
    briefingRoom.radioManager.play(objective.name.." $LANG_RECON$:$LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end

  objectiveFeature.targetDesignationIlluminationBombBombsLeft = objectiveFeature.targetDesignationIlluminationBombBombsLeft - 1

  local args = { ["position"] = unit:getPoint() }
  if briefingRoom.mission.objectiveFeatures[objectiveIndex].illuminationBombInnacurate then 
    local heading = math.random(0, 359)
    local distance = math.floor(math.random(5, 20)) * 100
    args.position.x = args.position.x - math.cos(heading * DEGREES_TO_RADIANS) * distance;
    args.position.z = args.position.z - math.sin(heading * DEGREES_TO_RADIANS) * distance;
    briefingRoom.radioManager.play(objective.name.." $LANG_RECON$: $LANG_ILLUMINATIONAFFIRM$ "..dcsExtensions.degreesToCardinalDirection(heading).." of the Illumination.", "RadioSupportIlluminationBomb", briefingRoom.radioManager.getAnswerDelay(), briefingRoom.mission.objectiveFeaturesCommon.targetDesignationIlluminationBombDoBomb, args)
  else
    briefingRoom.radioManager.play(objective.name.." $LANG_RECON$: $LANG_ILLUMINATIONAFFIRM$", "RadioSupportIlluminationBomb", briefingRoom.radioManager.getAnswerDelay(), briefingRoom.mission.objectiveFeaturesCommon.targetDesignationIlluminationBombDoBomb, args)
  end
end

function briefingRoom.mission.objectiveFeaturesCommon.registerTargetDesignationIlluminationBomb(objectiveIndex, innacurate)
  briefingRoom.mission.objectiveFeatures[objectiveIndex].illuminationBombInnacurate = innacurate or false 
  -- Number of bombs available
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationIlluminationBombBombsLeft = 4
   
-- Add the command to the F10 menu
table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_ILLUMINATIONMENU$", func = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationIlluminationBomb, args =  {objectiveIndex}})

end

-- Laser

briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser = {}

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.laserWatch(args, time)
  local objectiveIndex = args[1]
  local objFeature = briefingRoom.mission.objectiveFeatures[objectiveIndex]
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  -- if lasing target is set...
  if objFeature.targetDesignationLaser.laserTarget == nil then
    return time + 1 -- next update in one second
  end

  if not objFeature.targetDesignationLaser.laserTarget:isExist() or not table.contains(objective.unitNames, objFeature.targetDesignationLaser.laserTarget:getName()) then -- target is considered complete
    briefingRoom.debugPrint("JTAC objectiveIndex: $LANG_LASERTARGETDESTROYED$", 1)
    local unit = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.setRandomTarget(objectiveIndex)
    if unit == nil then
      briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.deleteLaser(objectiveIndex)
      briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASERNOTARGET$", "RadioSupportLasingNoMoreTargets", briefingRoom.radioManager.getAnswerDelay())
      return
    end
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASERNEXTTARGET$", "RadioSupportLasingNextTarget", briefingRoom.radioManager.getAnswerDelay())
  end

  briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.updateLaserPos(objectiveIndex)
  return time + 1
end

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.turnOn(args)
  local objectiveIndex = args[1]
  local objFeature = briefingRoom.mission.objectiveFeatures[objectiveIndex]
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_LASERREQUEST$", "RadioPilotLaseTarget")

  -- already lasing something
  if objFeature.targetDesignationLaser.laserTarget ~= nil then
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASERALREADYPAINTING$ "..tostring(objFeature.targetDesignationLaser.laserCode)..".", "RadioSupportTargetLasingAlready", briefingRoom.radioManager.getAnswerDelay())
    return
  end

  -- no target units left
  local unit = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.setRandomTarget(objectiveIndex)
  if unit == nil then
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASERNOTARGETREMAINING$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end
  briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASERAFFIRM$ "..tostring(objFeature.targetDesignationLaser.laserCode)..".", "RadioSupportLasingOk", briefingRoom.radioManager.getAnswerDelay())
  missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "$LANG_LASERMENUNEWTARGET$", briefingRoom.f10Menu.objectives[objectiveIndex], briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.newTarget, {objectiveIndex})
  missionCommands.addCommandForCoalition(briefingRoom.playerCoalition, "$LANG_LASERMENUSTOP$", briefingRoom.f10Menu.objectives[objectiveIndex], briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.turnOff, {objectiveIndex})
end

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.turnOff(args)
  local objectiveIndex = args[1]
  local objFeature = briefingRoom.mission.objectiveFeatures[objectiveIndex]
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  briefingRoom.radioManager.play("$LANG_PILOT$: Terminate. Laser off.", "RadioPilotLaseTargetStop")
  -- not lasing anything
  if objFeature.targetDesignationLaser.laserTarget == nil then
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASERALREADYOFF$", "RadioSupportLasingNotLasing", briefingRoom.radioManager.getAnswerDelay())
    return
  end

  briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.deleteLaser(objectiveIndex)
  briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASEROFF$", "RadioSupportLasingStopped", briefingRoom.radioManager.getAnswerDelay())
end

-- Get new target
function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.newTarget(args)
  local objectiveIndex = args[1]
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_LASERNEWTARGET$", "RadioPilotLaseDiffrentTarget")

  -- no target units left
  local unit = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.setRandomTarget(objectiveIndex)
  if unit == nil then
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$:$LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end
  briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_LASERNEXTTARGET$", "RadioSupportLasingNextTarget", briefingRoom.radioManager.getAnswerDelay())
end
function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.updateLaserPos(objectiveIndex)
  local objFeature = briefingRoom.mission.objectiveFeatures[objectiveIndex]
  local targetPos = objFeature.targetDesignationLaser.laserTarget:getPoint()
  local targetSpeed = objFeature.targetDesignationLaser.laserTarget:getVelocity()
  -- adds a small offset so that the laser is always where the (moving) target will be, not where it is
  targetPos.x = targetPos.x + targetSpeed.x
  targetPos.y = targetPos.y + 2.0
  targetPos.z = targetPos.z + targetSpeed.z
  if objFeature.targetDesignationLaser.laserSpot == nil then
    objFeature.targetDesignationLaser.laserIRSpot = Spot.createInfraRed(objFeature.targetDesignationLaser.laserTarget, { x = math.random(-1000,1000), y = 2000, z = math.random(-1000,1000) }, targetPos)
    objFeature.targetDesignationLaser.laserSpot = Spot.createLaser(objFeature.targetDesignationLaser.laserTarget, { x = math.random(-1000,1000), y = 2000, z = math.random(-1000,1000) }, targetPos, objFeature.targetDesignationLaser.laserCode)
    briefingRoom.debugPrint("JTAC objectiveIndex: Created Laser "..objFeature.targetDesignationLaser.laserSpot:getCode()..":"..tostring(targetPos.x)..","..tostring(targetPos.y)..","..tostring(targetPos.z), 1)
  else -- spot already exists, update its position
    objFeature.targetDesignationLaser.laserIRSpot:setPoint(targetPos)
    objFeature.targetDesignationLaser.laserSpot:setPoint(targetPos)
    briefingRoom.debugPrint("JTAC objectiveIndex: Update Laser Pos "..objFeature.targetDesignationLaser.laserSpot:getCode()..":"..tostring(targetPos.x)..","..tostring(targetPos.y)..","..tostring(targetPos.z), 1)
  end

end

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.deleteLaser(objectiveIndex)
  local objFeature = briefingRoom.mission.objectiveFeatures[objectiveIndex]
  if objFeature.targetDesignationLaser.laserSpot ~= nil then
    Spot.destroy(objFeature.targetDesignationLaser.laserSpot)
    objFeature.targetDesignationLaser.laserSpot = nil
    Spot.destroy(objFeature.targetDesignationLaser.laserIRSpot)
    objFeature.targetDesignationLaser.laserIRSpot = nil
  end

  -- unset target and play radio message
  objFeature.targetDesignationLaser.laserTarget = nil
  briefingRoom.debugPrint("JTAC objectiveIndex: Deleted Laser", 1)
end

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.setRandomTarget(objectiveIndex)
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  local randomUnitName = math.randomFromHashTable(table.filter(objective.unitNames, function(o, k, i)
    local u = Unit.getByName(o)
    if u == nil then
      u = StaticObject.getByName(o)
    end
    if u == nil then
      return false
    end
    return u:isExist()
  end))
  if randomUnitName == "" or randomUnitName == nil then
    return nil
  end
  local unit = dcsExtensions.getUnitOrStatic(randomUnitName)
    if unit == nil then -- no unit nor static found with the ID
      return nil
  end
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationLaser.laserTarget = unit
  briefingRoom.debugPrint("JTAC objectiveIndex: Assigned Laser Target:"..randomUnitName, 1)
  return unit
end

function briefingRoom.mission.objectiveFeaturesCommon.registerTargetDesignationLaser(objectiveIndex, laserCode)
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationLaser = { }
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationLaser.laserSpot = nil -- current laser spot
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationLaser.laserIRSpot = nil -- current laser spot
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationLaser.laserTarget = nil -- current lased target
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationLaser.laserCode = laserCode -- current lased target


-- Begin updating laser designation
timer.scheduleFunction(briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.laserWatch, {objectiveIndex}, timer.getTime() + 1)

table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_LASERMENUNEW$", func = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationLaser.turnOn, args =  {objectiveIndex}})
end


---- SMOKE

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationSmokeMarker(args)
  local objectiveIndex = args[1]
  local objective = briefingRoom.mission.objectives[objectiveIndex]
  briefingRoom.radioManager.play("$LANG_PILOT$: $LANG_SMOKEREQUEST$", "RadioPilotMarkTargetWithSmoke")

  if table.count(briefingRoom.mission.objectives[objectiveIndex].unitNames) == 0 then -- no target units left
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$:$LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
    return
  end
  
  local unit = dcsExtensions.getUnitOrStatic(briefingRoom.mission.objectives[objectiveIndex].unitNames[1])
  if unit == nil then -- no unit nor static found with the ID
      briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$:$LANG_NOTARGET$", "RadioSupportNoTarget", briefingRoom.radioManager.getAnswerDelay())
      return
  end
  
  local timeNow = timer.getAbsTime()
  
  if timeNow < briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationSmokeMarkerNextSmoke then -- Smoke not ready
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_SMOKEALREADY$", "RadioSupportTargetAlreadyMarkedWithSmoke", briefingRoom.radioManager.getAnswerDelay())
    return
  end
  
  -- Set cooldown for next smoke
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationSmokeMarkerNextSmoke = timeNow + SMOKE_DURATION
  
  -- Play radio message and setup smoke creating function
  local args = { position = unit:getPoint(), color = trigger.smokeColor.Red }
  if unit:getCoalition() == briefingRoom.playerCoalition then args.color = trigger.smokeColor.Green end
  if briefingRoom.mission.objectiveFeatures[objectiveIndex].smokeInnacurate then 
    local heading = math.random(0, 359)
    local distance = math.floor(math.random(5, 20)) * 100
    args.position.x = args.position.x - math.cos(heading * DEGREES_TO_RADIANS) * distance;
    args.position.z = args.position.z - math.sin(heading * DEGREES_TO_RADIANS) * distance;
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_SMOKEAFFIRM$ "..dcsExtensions.degreesToCardinalDirection(heading).." of the smoke.", "RadioSupportTargetMarkedWithSmoke", briefingRoom.radioManager.getAnswerDelay(), briefingRoom.mission.objectiveFeaturesCommon.targetDesignationSmokeMarkerDoSmoke, args)
  else
    briefingRoom.radioManager.play(objective.name.." $LANG_JTAC$: $LANG_SMOKEAFFIRM$", "RadioSupportTargetMarkedWithSmoke", briefingRoom.radioManager.getAnswerDelay(), briefingRoom.mission.objectiveFeaturesCommon.targetDesignationSmokeMarkerDoSmoke, args)
  end
end

function briefingRoom.mission.objectiveFeaturesCommon.targetDesignationSmokeMarkerDoSmoke(args)
  local smokePosition = args.position
  trigger.action.smoke(smokePosition, args.color)
  return nil
end

function briefingRoom.mission.objectiveFeaturesCommon.registerTargetDesignationSmoke(objectiveIndex, innacurate)
  briefingRoom.mission.objectiveFeatures[objectiveIndex].smokeInnacurate = innacurate or false 
  briefingRoom.mission.objectiveFeatures[objectiveIndex].targetDesignationSmokeMarkerNextSmoke = -999999
  -- Add the command to the F10 menu
  table.insert(briefingRoom.mission.objectives[objectiveIndex].f10Commands, {text = "$LANG_SMOKEMENU$", func = briefingRoom.mission.objectiveFeaturesCommon.targetDesignationSmokeMarker, args = {objectiveIndex}})
end


--- END OF OBJECTIVE REGISTER FEATURES-----

for i=1,table.count(briefingRoom.mission.objectives) do briefingRoom.mission.objectiveFeatures[i] = {} end
$SCRIPTOBJECTIVESFEATURES$

-- ===================================================================================
-- 3.7 - MISSION FEATURES (generated by BriefingRoom)
-- ===================================================================================

briefingRoom.mission.missionFeatures = { } -- Mission features
briefingRoom.mission.missionFeatures.groupNames = { } -- Mission features group ID
briefingRoom.mission.missionFeatures.unitNames = { } -- Mission features units ID
$SCRIPTMISSIONFEATURES$

-- ===================================================================================
-- 3.8 - STARTUP
-- ===================================================================================

-- All done, enable event handler so the mission can begin
world.addEventHandler(briefingRoom.eventHandler)

for i=1,table.count(briefingRoom.mission.objectives) do
  table.insert(briefingRoom.mission.objectives[i].f10Commands, {text = "$LANG_WAYPOINTCOORDINATESREQUEST$", func = briefingRoom.f10MenuCommands.getWaypointCoordinates, args =  i})
  briefingRoom.mission.objectives[i].waypointRadioCommandIndex = table.count(briefingRoom.mission.objectives[i].f10Commands)

  if briefingRoom.mission.objectives[i].progressionHiddenBrief == false then
    briefingRoom.f10MenuCommands.activateObjective(i)
  end
end

if $INSTANTSTART$ then
  briefingRoom.mission.coreFunctions.beginMission()
end
